pax_global_header00006660000000000000000000000064150153311660014513gustar00rootroot0000000000000052 comment=994bc96bb1744cb153392fc96bdba43eae56e17f redis-8.0.2/000077500000000000000000000000001501533116600126305ustar00rootroot00000000000000redis-8.0.2/.codespell/000077500000000000000000000000001501533116600146605ustar00rootroot00000000000000redis-8.0.2/.codespell/.codespellrc000066400000000000000000000002131501533116600171540ustar00rootroot00000000000000[codespell] quiet-level = 2 count = skip = ./deps,./src/crc16_slottable.h,tmp*,./.git,./lcov-html ignore-words = ./.codespell/wordlist.txt redis-8.0.2/.codespell/requirements.txt000066400000000000000000000000211501533116600201350ustar00rootroot00000000000000codespell==2.2.5 redis-8.0.2/.codespell/wordlist.txt000066400000000000000000000001571501533116600172730ustar00rootroot00000000000000ake bale fle fo gameboy mutli nd nees oll optin ot smove te tre cancelability ist statics filetest ro exat clenredis-8.0.2/.gitattributes000066400000000000000000000006251501533116600155260ustar00rootroot00000000000000# We set commands.c's merge driver to `binary` so when it conflicts during a # merge git will leave the local version unmodified. This way our Makefile # will rebuild it based on src/commands/*.json before trying to compile it. # Otherwise the file gets modified and gets the same timestamp as the .json # files. So the Makefile doesn't attempt to rebuild it before compiling. src/commands.c merge=binary redis-8.0.2/.github/000077500000000000000000000000001501533116600141705ustar00rootroot00000000000000redis-8.0.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001501533116600163535ustar00rootroot00000000000000redis-8.0.2/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000006311501533116600210450ustar00rootroot00000000000000--- name: Bug report about: Help us improve Redis by reporting a bug title: '[BUG]' labels: '' assignees: '' --- **Describe the bug** A short description of the bug. **To reproduce** Steps to reproduce the behavior and/or a minimal code sample. **Expected behavior** A description of what you expected to happen. **Additional information** Any additional information that is relevant to the problem. redis-8.0.2/.github/ISSUE_TEMPLATE/crash_report.md000066400000000000000000000011041501533116600213640ustar00rootroot00000000000000--- name: Crash report about: Submit a crash report title: '[CRASH] ' labels: '' assignees: '' --- Notice! - If a Redis module was involved, please open an issue in the module's repo instead! - If you're using docker on Apple M1, please make sure the image you're using was compiled for ARM! **Crash report** Paste the complete crash log between the quotes below. Please include a few lines from the log preceding the crash report to provide some context. ``` ``` **Additional information** 1. OS distribution and version 2. Steps to reproduce (if any) redis-8.0.2/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011571501533116600221040ustar00rootroot00000000000000--- name: Feature request about: Suggest a feature for Redis title: '[NEW]' labels: '' assignees: '' --- **The problem/use-case that the feature addresses** A description of the problem that the feature will solve, or the use-case with which the feature will be used. **Description of the feature** A description of what you want to happen. **Alternatives you've considered** Any alternative solutions or features you've considered, including references to existing open and closed feature requests in this repository. **Additional information** Any additional information that is relevant to the feature request. redis-8.0.2/.github/ISSUE_TEMPLATE/other_stuff.md000066400000000000000000000001561501533116600212270ustar00rootroot00000000000000--- name: Other about: Can't find the right issue type? Use this one! title: '' labels: '' assignees: '' --- redis-8.0.2/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000016551501533116600205530ustar00rootroot00000000000000--- name: Question about: Ask the Redis developers title: '[QUESTION]' labels: '' assignees: '' --- Please keep in mind that this issue tracker should be used for reporting bugs or proposing improvements to the Redis server. Generally, questions about using Redis should be directed to the [community](https://redis.io/community): * [the mailing list](https://groups.google.com/forum/#!forum/redis-db) * [the `redis` tag at StackOverflow](http://stackoverflow.com/questions/tagged/redis) * [/r/redis subreddit](http://www.reddit.com/r/redis) * [github discussions](https://github.com/redis/redis/discussions) It is also possible that your question was already asked here, so please do a quick issues search before submitting. Lastly, if your question is about one of Redis' [clients](https://redis.io/clients), you may to contact your client's developers for help. That said, please feel free to replace all this with your question :) redis-8.0.2/.github/dependabot.yml000066400000000000000000000010161501533116600170160ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly - package-ecosystem: pip directory: /.codespell schedule: interval: weekly redis-8.0.2/.github/workflows/000077500000000000000000000000001501533116600162255ustar00rootroot00000000000000redis-8.0.2/.github/workflows/ci.yml000066400000000000000000000061051501533116600173450ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: test-ubuntu-latest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: make # Fail build if there are warnings # build with TLS just for compilation coverage run: make REDIS_CFLAGS='-Werror' BUILD_TLS=yes - name: test run: | sudo apt-get install tcl8.6 tclx ./runtest --verbose --tags -slow --dump-logs - name: module api test run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs - name: validate commands.def up to date run: | touch src/commands/ping.json make commands.def dirty=$(git diff) if [[ ! -z $dirty ]]; then echo $dirty; exit 1; fi test-sanitizer-address: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: make # build with TLS module just for compilation coverage run: make SANITIZER=address REDIS_CFLAGS='-Werror -DDEBUG_ASSERTIONS' BUILD_TLS=module - name: testprep run: sudo apt-get install tcl8.6 tclx -y - name: test run: ./runtest --verbose --tags -slow --dump-logs - name: module api test run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs build-debian-old: runs-on: ubuntu-latest container: debian:buster steps: - uses: actions/checkout@v4 - name: make run: | apt-get update && apt-get install -y build-essential make REDIS_CFLAGS='-Werror' build-macos-latest: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: make run: make REDIS_CFLAGS='-Werror' build-32bit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: make run: | sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib g++-multilib make REDIS_CFLAGS='-Werror' 32bit build-libc-malloc: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: make run: make REDIS_CFLAGS='-Werror' MALLOC=libc build-centos-jemalloc: runs-on: ubuntu-latest container: quay.io/centos/centos:stream9 steps: - uses: actions/checkout@v4 - name: make run: | dnf -y install which gcc gcc-c++ make make REDIS_CFLAGS='-Werror' build-old-chain-jemalloc: runs-on: ubuntu-latest container: ubuntu:20.04 steps: - uses: actions/checkout@v4 - name: make run: | apt-get update apt-get install -y gnupg2 echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list 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 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' redis-8.0.2/.github/workflows/codecov.yml000066400000000000000000000011231501533116600203670ustar00rootroot00000000000000name: "Codecov" # Enabling on each push is to display the coverage changes in every PR, # where each PR needs to be compared against the coverage of the head commit on: [push, pull_request] jobs: code-coverage: runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install lcov and run test run: | sudo apt-get install lcov make lcov - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./src/redis.info redis-8.0.2/.github/workflows/codeql-analysis.yml000066400000000000000000000013021501533116600220340ustar00rootroot00000000000000name: "CodeQL" on: pull_request: schedule: # run weekly new vulnerability was added to the database - cron: '0 0 * * 0' jobs: analyze: name: Analyze runs-on: ubuntu-latest if: github.event_name != 'schedule' || github.repository == 'redis/redis' strategy: fail-fast: false matrix: language: [ 'cpp' ] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 redis-8.0.2/.github/workflows/coverity.yml000066400000000000000000000024521501533116600206170ustar00rootroot00000000000000# Creates and uploads a Coverity build on a schedule name: Coverity Scan on: schedule: # Run once daily, since below 500k LOC can have 21 builds per week, per https://scan.coverity.com/faq#frequency - cron: '0 0 * * *' # Support manual execution workflow_dispatch: jobs: coverity: if: github.repository == 'redis/redis' runs-on: ubuntu-latest steps: - uses: actions/checkout@main - 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 mkdir cov-analysis-linux64 tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64 - name: Install Redis dependencies run: sudo apt install -y gcc tcl8.6 tclx procps libssl-dev - name: Build with cov-build run: cov-analysis-linux64/bin/cov-build --dir cov-int make - name: Upload the result run: | 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 file=@cov-int.tgz \ https://scan.coverity.com/builds redis-8.0.2/.github/workflows/daily.yml000066400000000000000000001570351501533116600200650ustar00rootroot00000000000000name: Daily on: pull_request: branches: # any PR to a release branch. - '[0-9].[0-9]' schedule: - cron: '0 0 * * *' workflow_dispatch: 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' skiptests: description: 'tests to skip (delete the ones you wanna keep, do not leave empty)' default: 'redis,modules,sentinel,cluster,unittest' test_args: description: 'extra test arguments' default: '' cluster_test_args: description: 'extra cluster / sentinel test arguments' default: '' use_repo: description: 'repo owner and name' default: 'redis/redis' use_git_ref: description: 'git branch or sha to use' default: 'unstable' jobs: test-ubuntu-jemalloc: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'ubuntu') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 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 - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/redis-server test all --accurate 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: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 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' - name: testprep run: sudo apt-get install -y tcl8.6 tclx procps - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/redis-server test all --accurate test-ubuntu-libc-malloc: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'malloc') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make MALLOC=libc REDIS_CFLAGS='-Werror' - name: testprep run: sudo apt-get install 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: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-ubuntu-no-malloc-usable-size: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'malloc') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make MALLOC=libc CFLAGS=-DNO_MALLOC_USABLE_SIZE REDIS_CFLAGS='-Werror' - name: testprep run: sudo apt-get install 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: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-ubuntu-32bit: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, '32bit') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 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 make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST' - name: testprep run: sudo apt-get install 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: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | make -C tests/modules 32bit # the script below doesn't have an argument, we must build manually ahead of time CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/redis-server test all --accurate test-ubuntu-tls: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'tls') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | make BUILD_TLS=yes REDIS_CFLAGS='-Werror' - name: testprep run: | sudo apt-get install tcl8.6 tclx tcl-tls ./utils/gen-test-certs.sh - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: | ./runtest --accurate --verbose --dump-logs --tls --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --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-ubuntu-tls-no-tls: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'tls') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | make BUILD_TLS=yes REDIS_CFLAGS='-Werror' - name: testprep run: | sudo apt-get install tcl8.6 tclx tcl-tls ./utils/gen-test-certs.sh - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: | ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: | ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: | ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-ubuntu-io-threads: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'iothreads') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | make REDIS_CFLAGS='-Werror' - name: testprep 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}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster --config io-threads 4 ${{github.event.inputs.cluster_test_args}} test-ubuntu-reclaim-cache: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'specific') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | make REDIS_CFLAGS='-Werror' - name: testprep run: | sudo apt-get install vmtouch mkdir /tmp/master mkdir /tmp/slave - name: warm up run: | ./src/redis-server --daemonize yes --logfile /dev/null ./src/redis-benchmark -n 1 > /dev/null ./src/redis-cli save | grep OK > /dev/null vmtouch -v ./dump.rdb > /dev/null - name: test run: | echo "test SAVE doesn't increase cache" CACHE0=$(grep -w file /sys/fs/cgroup/memory.stat | awk '{print $2}') echo "$CACHE0" ./src/redis-server --daemonize yes --logfile /dev/null --dir /tmp/master --port 8080 --repl-diskless-sync no --pidfile /tmp/master/redis.pid --rdbcompression no --enable-debug-command yes ./src/redis-cli -p 8080 debug populate 10000 k 102400 ./src/redis-server --daemonize yes --logfile /dev/null --dir /tmp/slave --port 8081 --repl-diskless-load disabled --rdbcompression no ./src/redis-cli -p 8080 save > /dev/null VMOUT=$(vmtouch -v /tmp/master/dump.rdb) echo $VMOUT grep -q " 0%" <<< $VMOUT CACHE=$(grep -w file /sys/fs/cgroup/memory.stat | awk '{print $2}') echo "$CACHE" if [ "$(( $CACHE-$CACHE0 ))" -gt "8000000" ]; then exit 1; fi echo "test replication doesn't increase cache" ./src/redis-cli -p 8081 REPLICAOF 127.0.0.1 8080 > /dev/null while [ $(./src/redis-cli -p 8081 info replication | grep "master_link_status:down") ]; do sleep 1; done; sleep 1 # wait for the completion of cache reclaim bio VMOUT=$(vmtouch -v /tmp/master/dump.rdb) echo $VMOUT grep -q " 0%" <<< $VMOUT VMOUT=$(vmtouch -v /tmp/slave/dump.rdb) echo $VMOUT grep -q " 0%" <<< $VMOUT CACHE=$(grep -w file /sys/fs/cgroup/memory.stat | awk '{print $2}') echo "$CACHE" if [ "$(( $CACHE-$CACHE0 ))" -gt "8000000" ]; then exit 1; fi echo "test reboot doesn't increase cache" PID=$(cat /tmp/master/redis.pid) kill -15 $PID while [ -x /proc/${PID} ]; do sleep 1; done ./src/redis-server --daemonize yes --logfile /dev/null --dir /tmp/master --port 8080 while [ $(./src/redis-cli -p 8080 info persistence | grep "loading:1") ]; do sleep 1; done; sleep 1 # wait for the completion of cache reclaim bio VMOUT=$(vmtouch -v /tmp/master/dump.rdb) echo $VMOUT grep -q " 0%" <<< $VMOUT CACHE=$(grep -w file /sys/fs/cgroup/memory.stat | awk '{print $2}') echo "$CACHE" if [ "$(( $CACHE-$CACHE0 ))" -gt "8000000" ]; then exit 1; fi test-valgrind-test: 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, 'redis') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make valgrind REDIS_CFLAGS='-Werror -DREDIS_TEST' - name: testprep run: | sudo apt-get update sudo apt-get install tcl8.6 tclx valgrind g++ -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}} 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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make valgrind REDIS_CFLAGS='-Werror -DREDIS_TEST' - name: testprep run: | sudo apt-get update sudo apt-get install tcl8.6 tclx valgrind -y - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: | valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/redis-server test all --valgrind if grep -q 0x err.txt; then cat err.txt; exit 1; fi test-valgrind-no-malloc-usable-size-test: 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, 'redis') timeout-minutes: 14400 steps: - name: prep if: github.event_name == 'workflow_dispatch' run: | echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV echo "skipjobs: ${{github.event.inputs.skipjobs}}" echo "skiptests: ${{github.event.inputs.skiptests}}" echo "test_args: ${{github.event.inputs.test_args}}" echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}" - uses: actions/checkout@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make valgrind CFLAGS="-DNO_MALLOC_USABLE_SIZE -DREDIS_TEST" REDIS_CFLAGS='-Werror' - name: testprep run: | sudo apt-get update sudo apt-get install tcl8.6 tclx valgrind g++ -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}} 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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make valgrind CFLAGS="-DNO_MALLOC_USABLE_SIZE -DREDIS_TEST" REDIS_CFLAGS='-Werror' - name: testprep run: | sudo apt-get update sudo apt-get install tcl8.6 tclx valgrind -y - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: | valgrind --track-origins=yes --suppressions=./src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full --log-file=err.txt ./src/redis-server test all --valgrind if grep -q 0x err.txt; then cat err.txt; exit 1; fi test-sanitizer-address: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'sanitizer') timeout-minutes: 14400 strategy: matrix: compiler: [ gcc, clang ] env: CC: ${{ matrix.compiler }} 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make SANITIZER=address REDIS_CFLAGS='-DREDIS_TEST -Werror -DDEBUG_ASSERTIONS' - name: testprep run: | sudo apt-get update sudo apt-get install tcl8.6 tclx -y - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/redis-server test all test-sanitizer-undefined: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'sanitizer') timeout-minutes: 14400 strategy: matrix: compiler: [ gcc, clang ] env: CC: ${{ matrix.compiler }} 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make SANITIZER=undefined REDIS_CFLAGS='-DREDIS_TEST -Werror' SKIP_VEC_SETS=yes LUA_DEBUG=yes # we (ab)use this flow to also check Lua C API violations - name: testprep run: | sudo apt-get update sudo apt-get install tcl8.6 tclx -y - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} - name: unittest if: true && !contains(github.event.inputs.skiptests, 'unittest') run: ./src/redis-server test all --accurate test-centos-jemalloc: runs-on: ubuntu-latest if: | (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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | dnf -y install which gcc make g++ make REDIS_CFLAGS='-Werror' - name: testprep run: | dnf -y install epel-release dnf -y install tcl tcltls procps-ng /usr/bin/kill - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-centos-tls-module: runs-on: ubuntu-latest if: | (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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | dnf -y install which gcc make openssl-devel openssl g++ make BUILD_TLS=module REDIS_CFLAGS='-Werror' - name: testprep run: | dnf -y install epel-release dnf -y install tcl tcltls procps-ng /usr/bin/kill ./utils/gen-test-certs.sh - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: | ./runtest --accurate --verbose --dump-logs --tls-module --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --tls-module --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 --tls-module ${{github.event.inputs.cluster_test_args}} test-centos-tls-module-no-tls: runs-on: ubuntu-latest if: | (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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | dnf -y install which gcc make openssl-devel openssl g++ make BUILD_TLS=module REDIS_CFLAGS='-Werror' - name: testprep run: | dnf -y install epel-release dnf -y install tcl tcltls procps-ng /usr/bin/kill ./utils/gen-test-certs.sh - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: | ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: | ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: | ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-macos-latest: runs-on: macos-latest 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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make REDIS_CFLAGS='-Werror' - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --clients 1 --no-latency --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --clients 1 --no-latency --dump-logs ${{github.event.inputs.test_args}} test-macos-latest-sentinel: runs-on: macos-latest 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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make REDIS_CFLAGS='-Werror' - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} test-macos-latest-cluster: runs-on: macos-latest 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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make REDIS_CFLAGS='-Werror' - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} build-macos: strategy: matrix: os: [macos-13, macos-15] 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 steps: - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest - 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make REDIS_CFLAGS='-Werror -DREDIS_TEST' test-freebsd: runs-on: macos-13 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++ 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 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: test uses: cross-platform-actions/action@v0.22.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 gmake ./runtest --single unit/keyspace --single unit/auth --single unit/networking --single unit/protocol test-alpine-jemalloc: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'alpine') container: alpine:latest 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | apk add build-base make REDIS_CFLAGS='-Werror' - name: testprep run: apk add tcl procps tclx - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-alpine-libc-malloc: runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'alpine') container: alpine:latest 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | apk add build-base make REDIS_CFLAGS='-Werror' USE_JEMALLOC=no CFLAGS=-DUSE_MALLOC_USABLE_SIZE - name: testprep run: apk add tcl procps tclx - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} reply-schemas-validator: runs-on: ubuntu-latest timeout-minutes: 14400 if: | (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) && !contains(github.event.inputs.skipjobs, 'reply-schema') 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: make REDIS_CFLAGS='-Werror -DLOG_REQ_RES' - name: testprep run: sudo apt-get install tcl8.6 tclx - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --log-req-res --no-latency --dont-clean --force-resp3 --tags -slow --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --log-req-res --no-latency --dont-clean --force-resp3 --dont-pre-clean --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel --log-req-res --dont-clean --force-resp3 ${{github.event.inputs.cluster_test_args}} - 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 with: path: "./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' || '' }} test-old-chain-jemalloc: runs-on: ubuntu-latest if: | (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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | apt-get update apt-get install -y gnupg2 echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list 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 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 - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}} test-old-chain-tls-module: runs-on: ubuntu-latest if: | (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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | apt-get update apt-get install -y gnupg2 echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list 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 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' - name: testprep run: | apt-get install -y tcl tcltls tclx ./utils/gen-test-certs.sh - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: | ./runtest --accurate --verbose --dump-logs --tls-module --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --tls-module --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 --tls-module ${{github.event.inputs.cluster_test_args}} test-old-chain-tls-module-no-tls: runs-on: ubuntu-latest if: | (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 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@v4 with: repository: ${{ env.GITHUB_REPOSITORY }} ref: ${{ env.GITHUB_HEAD_REF }} - name: make run: | apt-get update apt-get install -y gnupg2 echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list 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 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: | apt-get install -y tcl tcltls tclx ./utils/gen-test-certs.sh - name: test if: true && !contains(github.event.inputs.skiptests, 'redis') run: | ./runtest --accurate --verbose --dump-logs ${{github.event.inputs.test_args}} - name: module api test if: true && !contains(github.event.inputs.skiptests, 'modules') run: | CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}} - name: sentinel tests if: true && !contains(github.event.inputs.skiptests, 'sentinel') run: | ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} - name: cluster tests if: true && !contains(github.event.inputs.skiptests, 'cluster') run: | ./runtest-cluster ${{github.event.inputs.cluster_test_args}} redis-8.0.2/.github/workflows/external.yml000066400000000000000000000052611501533116600205760ustar00rootroot00000000000000name: External Server Tests on: pull_request: push: schedule: - cron: '0 0 * * *' jobs: test-external-standalone: runs-on: ubuntu-latest if: github.event_name != 'schedule' || github.repository == 'redis/redis' timeout-minutes: 14400 steps: - uses: actions/checkout@v4 - name: Build run: make REDIS_CFLAGS=-Werror - name: Start redis-server run: | ./src/redis-server --daemonize yes --save "" --logfile external-redis.log \ --enable-protected-configs yes --enable-debug-command yes --enable-module-command yes - name: Run external test run: | ./runtest \ --host 127.0.0.1 --port 6379 \ --verbose \ --tags -slow - name: Archive redis log if: ${{ failure() }} uses: actions/upload-artifact@v4 with: name: test-external-redis-log path: external-redis.log test-external-cluster: runs-on: ubuntu-latest if: github.event_name != 'schedule' || github.repository == 'redis/redis' timeout-minutes: 14400 steps: - uses: actions/checkout@v4 - name: Build run: make REDIS_CFLAGS=-Werror - name: Start redis-server run: | ./src/redis-server --cluster-enabled yes --daemonize yes --save "" --logfile external-redis-cluster.log \ --enable-protected-configs yes --enable-debug-command yes --enable-module-command yes - name: Create a single node cluster run: ./src/redis-cli cluster addslots $(for slot in {0..16383}; do echo $slot; done); sleep 5 - name: Run external test run: | ./runtest \ --host 127.0.0.1 --port 6379 \ --verbose \ --cluster-mode \ --tags -slow - name: Archive redis log if: ${{ failure() }} uses: actions/upload-artifact@v4 with: name: test-external-cluster-log path: external-redis-cluster.log test-external-nodebug: runs-on: ubuntu-latest if: github.event_name != 'schedule' || github.repository == 'redis/redis' timeout-minutes: 14400 steps: - uses: actions/checkout@v4 - name: Build run: make REDIS_CFLAGS=-Werror - name: Start redis-server run: | ./src/redis-server --daemonize yes --save "" --logfile external-redis-nodebug.log - name: Run external test run: | ./runtest \ --host 127.0.0.1 --port 6379 \ --verbose \ --tags "-slow -needs:debug" - name: Archive redis log if: ${{ failure() }} uses: actions/upload-artifact@v4 with: name: test-external-redis-nodebug-log path: external-redis-nodebug.log redis-8.0.2/.github/workflows/redis_docs_sync.yaml000066400000000000000000000021131501533116600222600ustar00rootroot00000000000000name: redis_docs_sync on: release: types: [published] jobs: redis_docs_sync: if: github.repository == 'redis/redis' runs-on: ubuntu-latest steps: - name: Generate a token id: generate-token uses: actions/create-github-app-token@v1 with: app-id: ${{ secrets.DOCS_APP_ID }} private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }} - name: Invoke workflow on redis/docs env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} RELEASE_NAME: ${{ github.event.release.tag_name }} run: | LATEST_RELEASE=$( curl -Ls \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${GH_TOKEN}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/redis/redis/releases/latest \ | jq -r '.tag_name' ) if [[ "${LATEST_RELEASE}" == "${RELEASE_NAME}" ]]; then gh workflow run -R redis/docs redis_docs_sync.yaml -f release="${RELEASE_NAME}" fi redis-8.0.2/.github/workflows/reply-schemas-linter.yml000066400000000000000000000006631501533116600230240ustar00rootroot00000000000000name: Reply-schemas linter on: push: paths: - 'src/commands/*.json' pull_request: paths: - 'src/commands/*.json' jobs: reply-schemas-linter: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup nodejs uses: actions/setup-node@v4 - name: Install packages run: npm install ajv - name: linter run: node ./utils/reply_schema_linter.js redis-8.0.2/.github/workflows/spell-check.yml000066400000000000000000000015311501533116600211420ustar00rootroot00000000000000# A CI action that using codespell to check spell. # .github/.codespellrc is a config file. # .github/wordlist.txt is a list of words that will ignore word checks. # More details please check the following link: # https://github.com/codespell-project/codespell name: Spellcheck on: push: pull_request: jobs: build: name: Spellcheck runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: pip cache uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} restore-keys: ${{ runner.os }}-pip- - name: Install prerequisites run: sudo pip install -r ./.codespell/requirements.txt - name: Spell check run: codespell --config=./.codespell/.codespellrc redis-8.0.2/.gitignore000066400000000000000000000011171501533116600146200ustar00rootroot00000000000000.*.swp *.o *.xo *.so *.d *.log dump.rdb redis-benchmark redis-check-aof redis-check-rdb redis-check-dump redis-cli redis-sentinel redis-server doc-tools release misc/* src/release.h appendonly.aof* appendonlydir SHORT_TERM_TODO release.h src/transfer.sh src/configs redis.ds src/redis.conf src/nodes.conf deps/lua/src/lua deps/lua/src/luac deps/lua/src/liblua.a deps/hdr_histogram/libhdrhistogram.a deps/fpconv/libfpconv.a deps/fast_float/libfast_float.a tests/tls/* .make-* .prerequisites *.dSYM Makefile.dep .vscode/* .idea/* .ccls .ccls-cache/* compile_commands.json redis.code-workspace redis-8.0.2/00-RELEASENOTES000066400000000000000000000450221501533116600146640ustar00rootroot00000000000000Redis Open Source 8.0 release notes =================================== -------------------------------------------------------------------------------- Upgrade urgency levels: LOW: No need to upgrade unless there are new features you want to use. MODERATE: Program an upgrade of the server, but it's not urgent. HIGH: There is a critical bug that may affect a subset of users. Upgrade! CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP. SECURITY: There are security fixes in the release. -------------------------------------------------------------------------------- Starting with v8.0.1, the release notes contain PRs from multiple repositories: #n - Redis (https://github.com/redis/redis) #QEn = Query Engine (https://github.com/RediSearch/RediSearch) #JSn = JSON (https://github.com/RedisJSON/RedisJSON) #TSn = Time Series (https://github.com/RedisTimeSeries/RedisTimeSeries) #PRn = Probabilistic (https://github.com/RedisBloom/RedisBloom) ================================================================================ Redis 8.0.2 Released Tue 27 May 2025 12:00:00 IST ================================================================================ Update urgency: `SECURITY`: There are security fixes in the release. ### Security fixes - (CVE-2025-27151) redis-check-aof may lead to stack overflow and potential RCE ### Other general improvements - #14048 `LOLWUT` for Redis 8 ================================================================================ Redis 8.0.1 Released Sun 13 May 2025 16:00:00 IST ================================================================================ Update urgency: `MODERATE`: No need to upgrade unless there are new features you want to use. ### Performance and resource utilization improvements - #13959 Vector set - faster `VSIM` `FILTER` parsing ### Bug fixes - #QE6083 Query Engine - revert default policy `search-on-timeout` to `RETURN` - #QE6050 Query Engine - `@__key` on `FT.AGGREGATE` used as reserved field name preventing access to Redis keyspace - #QE6077 Query Engine - crash when calling `FT.CURSOR DEL` while retrieving from the CURSOR ### Notes - Fixed wrong text in the license files ======================================================= 8.0 GA (v8.0.0) Released Fri 2 May 2025 12:00:00 IST ======================================================= This is the General Availability release of Redis Open Source 8.0. Redis 8.0 deprecates previous Redis and Redis Stack versions. Stand alone RediSearch, RedisJSON, RedisTimeSeries, and RedisBloom are no longer needed as they are now part of Redis. ### Major changes compared to 7.4.2 - Name change: Redis Community Edition is now Redis Open Source - License change: 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 (AGPLv3) - Redis Query engine and 8 new data structures are now an integral part of Redis 8 - (1) Redis Query Engine, which now supports both horizontal and vertical scaling for search, query and vector workloads - (2) JSON - a queryable JSON document - (3) Time series - (4-8) Five probabilistic data structures: Bloom filter, Cuckoo filter, Count-min sketch, Top-k, and t-digest - (9) Vector set [beta] - a data structure designed for Vector Similarity Search, inspired by Sorted set - These nine components are included in all binary distributions - See instructions in the README.md file on how to build from source with all these components - New configuration file: redis-full.conf - loads Redis with all these components, and contains new configuration parameters for Redis Query engine and the new data structures - New ACL categories: @search, @json, @timeseries, @bloom, @cuckoo, @cms, @topk, @tdigest - Commands are also included in the existing ACL categories (@read, @write, etc.) - More than 30 performance and resource utilization improvements - A new I/O threading implementation which enables throughput increase on multi-core environments (set with `io-threads` configuration parameter) - An improved replication mechanism which is more performant and robust - New hash commands - `HGETDEL`, `HGETEX`, `HSETEX` For more details, see the release notes of 8.0-M01, 8.0-M02, 8.0-M03,8.0-M04, and 8.0-RC1 ### Binary distributions - Alpine and Debian Docker images - https://hub.docker.com/_/redis - Install using snap - see https://github.com/redis/redis-snap - Install using brew - see https://github.com/redis/homebrew-redis - Install using RPM - see https://github.com/redis/redis-rpm - Install using Debian APT - see https://github.com/redis/redis-debian ### Operating systems we test Redis 8.0 on - Ubuntu 20.04 (Focal Fossa), 22.04 (Jammy Jellyfish), 24.04 (Noble Numbat) - Rocky Linux 8.10, 9.5 - AlmaLinux 8.10, 9.5 - Debian 11 (Bullseye), 12 (Bookworm) - macOS 13 (Ventura), 14 (Sonoma), 15 (Sequoia) ### Supported upgrade paths (by replication or persistence) - From previous Redis versions, without modules - From previous Redis versions with modules (RediSearch, RedisJSON, RedisTimeSeries, RedisBloom) - From Redis Stack 7.2 or 7.4 ### Security fixes (compared to 8.0-RC1) * (CVE-2025-21605) An unauthenticated client can cause an unlimited growth of output buffers ### Bug fixes (compared to 8.0-RC1) - #13966, #13932 `CLUSTER SLOTS` - TLS port update not reflected in CLUSTER SLOTS - #13958 `XTRIM`, `XADD` - incorrect lag due to trimming stream - #13931 `HGETEX` - wrong order of keyspace notifications ========================================================== 8.0-RC1 (v7.9.240) Released Mon 7 Apr 2025 10:00:00 IST ========================================================== This is the first Release Candidate of Redis Community Edition 8.0. Release Candidates are feature-complete pre-releases. Pre-releases are not suitable for production use. ### Headlines 8.0-RC1 includes a new beta data structure - vector set. ### Distributions - Alpine and Debian Docker images - https://hub.docker.com/_/redis - Install using snap - see https://github.com/redis/redis-snap - Install using brew - see https://github.com/redis/homebrew-redis - Install using RPM and Debian APT - will be added on the GA release ### New Features - #13915 Vector set - a new data structure [beta]: Vector set extends the concept of sorted sets to allow the storage and querying of high-dimensional vector embeddings, enhancing Redis for AI use cases that involve semantic search and recommendation systems. Vector sets complement the existing vector search capability in the Redis Query Engine. The vector set data type is available in beta. We may change, or even break, the features and the API in future versions. We are open to your feedback as you try out this new data type. - #13846 Allow detecting incompatibility risks before switching to cluster mode ### Bug fixes - #13895 RDB Channel replication - replica is online after BGSAVE is done - #13877 Inconsistency for ShardID in case both master and replica support it - #13883 Defrag scan may return nothing when type/encoding changes during it - #13863 `RANDOMKEY` - infinite loop during client pause - #13853 `SLAVEOF` - crash when clients are blocked on lazy free - #13632 `XREAD` returns nil while stream is not empty ### Metrics - #13846 `INFO`: `cluster_incompatible_ops` - number of cluster-incompatible commands ### Configuration parameters - #13846 `cluster-compatibility-sample-ratio` - sampling ratio (0-100) for checking command compatibility with cluster mode ============================================================ 8.0-M04 (v7.9.227) Committed Sun 16 Mar 2025 11:00:00 IST ============================================================ This is the fourth Milestone of Redis Community Edition 8.0. Milestones are non-feature-complete pre-releases. Pre-releases are not suitable for production use. Once we reach feature-completeness we will release RC1. ### Headlines 8.0-M04 includes 3 new hash commands, performance improvements, and memory defragmentation improvements. ### Distributions - Alpine and Debian Docker images - https://hub.docker.com/_/redis - Install using snap - see https://github.com/redis/redis-snap - Install using brew - see https://github.com/redis/homebrew-redis - Install using RPM and Debian APT - will be added on the GA release ### New Features - #13798 Hash - new commands: - `HGETDEL` Get and delete the value of one or more fields of a given hash key - `HGETEX` Get the value of one or more fields of a given hash key, and optionally set their expiration - `HSETEX` Set the value of one or more fields of a given hash key, and optionally set their expiration - #13773 Add replication offset to AOF, allowing more robust way to determine which AOF has a more up-to-date data during recovery - #13740, #13763 shared secret - new mechanism to allow sending internal commands between nodes ### Bug fixes - #13804 Overflow on 32-bit systems when calculating idle time for eviction - #13793 `WAITAOF` returns prematurely - #13800 Remove `DENYOOM` from `HEXPIRE`, `HEXPIREAT`, `HPEXPIRE`, and `HPEXPIREAT` - #13632 Streams - wrong behavior of `XREAD +` after last entry ### Modules API - #13788 `RedisModule_LoadDefaultConfigs` - load module configuration values from redis.conf - #13815 `RM_RegisterDefragFunc2` - support for incremental defragmentation of global module data - #13816 `RM_DefragRedisModuleDict` - allow modules to defrag `RedisModuleDict` - #13774 `RM_GetContextFlags` - add a `REDISMODULE_CTX_FLAGS_DEBUG_ENABLED` flag to execute debug commands ### Performance and resource utilization improvements - #13752 Reduce defrag CPU usage when defragmentation is ineffective - #13764 Reduce latency when a command is called consecutively - #13787 Optimize parsing data from clients, specifically multi-bulk (array) data - #13792 Optimize dictionary lookup by avoiding duplicate key length calculation during comparisons - #13796 Optimize expiration checks ============================================================ 8.0-M03 (v7.9.226) Committed Mon 20 Jan 2025 15:00:00 IST ============================================================ This is the third Milestone of Redis Community Edition 8.0. Milestones are non-feature-complete pre-releases. Pre-releases are not suitable for production use. Once we reach feature-completeness we will release RC1. ### Headlines: 8.0-M03 introduces an improved replication mechanism which is more performant and robust, a new I/O threading implementation which enables throughput increase on multi-core environments, and many additional performance improvements. Both Alpine and Debian Docker images are now available on [Docker Hub](https://hub.docker.com/_/redis). A snap and Homebrew distributions are available as well. ### Security fixes - (CVE-2024-46981) Lua script may lead to remote code execution - (CVE-2024-51741) Denial-of-service due to malformed ACL selectors ### New Features - #13695 New I/O threading implementation - #13732 New replication mechanism ### Bug fixes - #13653 `MODULE LOADEX` - crash on nonexistent parameter name - #13661 `FUNCTION FLUSH` - memory leak when using jemalloc - #13626 Memory leak on failed RDB loading ### Other general improvements - #13639 When `hide-user-data-from-log` is enabled - also print command tokens on crash - #13660 Add the Lua VM memory to memory overhead ### New metrics - #13592 `INFO` - new `KEYSIZES` section includes key size distributions for basic data types - #13695 `INFO` - new `Threads` section includes I/O threading metrics ### Modules API - #13666 `RedisModule_ACLCheckKeyPrefixPermissions` - check access permissions to any key matching a given prefix - #13676 `RedisModule_HashFieldMinExpire` - query the minimum expiration time over all the hash’s fields - #13676 `RedisModule_HashGet` - new `REDISMODULE_HASH_EXPIRE_TIME` flag - query the field expiration time - #13656 `RedisModule_RegisterXXXConfig` - allow registering unprefixed configuration parameters ### Configuration parameters - `replica-full-sync-buffer-limit` - maximum size of accumulated replication stream data on the replica side - `io-threads-do-reads` is no longer effective. The new I/O threading implementation always use threads for both reads and writes ### Performance and resource utilization improvements - #13638 Optimize CRC64 performance - #13521 Optimize commands with large argument count - reuse c->argv after command execution - #13558 Optimize `PFCOUNT` and `PFMERGE` - SIMD acceleration - #13644 Optimize `GET` on high pipeline use-cases - #13646 Optimize `EXISTS` - prefetching and branch prediction hints - #13652 Optimize `LRANGE` - improve listpack handling and decoding efficiency - #13655 Optimize `HSET` - avoid unnecessary hash field creation or deletion - #13721 Optimize `LRANGE` and `HGETALL` - refactor client write preparation and handling ============================================================ 8.0-M02 (v7.9.225) Committed Mon 28 Oct 2024 14:00:00 IST ============================================================ This is the second Milestone of Redis Community Edition 8.0. Milestones are non-feature-complete pre-releases. Pre-releases are not suitable for production use. Once we reach feature-completeness we will release RC1. ### Headlines: 8.0-M02 introduces significant performance improvements. Both Alpine and Debian Docker images are now available on [Docker Hub](https://hub.docker.com/_/redis). Additional distributions will be introduced in upcoming pre-releases. ### Supported upgrade paths (by replication or persistence) to 8.0-M02 - From previous Redis versions, without modules The following upgrade paths (by replication or persistence) to 8.0-M02 are not yet tested and will be introduced in upcoming pre-releases: - From previous Redis versions with modules (RediSearch, RedisJSON, RedisTimeSeries, RedisBloom) - From Redis Stack 7.2 or 7.4 ### Security fixes - (CVE-2024-31449) Lua library commands may lead to stack overflow and potential RCE. - (CVE-2024-31227) Potential Denial-of-service due to malformed ACL selectors. - (CVE-2024-31228) Potential Denial-of-service due to unbounded pattern matching. ### Bug fixes - #13539 Hash: Fix key ref for a hash that no longer has fields with expiration on `RENAME`/`MOVE`/`SWAPDB`/`RESTORE` - #13512 Fix `TOUCH` command from a script in no-touch mode - #13468 Cluster: Fix cluster node config corruption caused by mixing shard-id and non-shard-id versions - #13608 Cluster: Fix `GET #` option in `SORT` command ### Modules API - #13526 Extend `RedisModule_OpenKey` to read also expired keys and subkeys ### Performance and resource utilization improvements - #11884 Optimize `ZADD` and `ZRANGE*` commands - #13530 Optimize `SSCAN` command in case of listpack or intset encoding - #13531 Optimize `HSCAN`/`ZSCAN` command in case of listpack encoding - #13520 Optimize commands that heavily rely on bulk/mbulk replies (example of `LRANGE`) - #13566 Optimize `ZUNION[STORE]` by avoiding redundant temporary dict usage - #13567 Optimize `SUNION`/`SDIFF` commands by avoiding redundant temporary dict usage - #11533 Avoid redundant `lpGet` to boost `quicklistCompare` - #13412 Reduce redundant call of `prepareClientToWrite` when call `addReply*` continuously =========================================================== 8.0-M01 (v7.9.224) Released Thu 12 Sep 2024 10:00:00 IST =========================================================== This is the first Milestone of Redis Community Edition 8.0. Milestones are non-feature-complete pre-releases. Pre-releases are not suitable for production use. Once we reach feature-completeness we will release RC1. ### Headlines: Redis 8.0 introduces new data structures: JSON, time series, and 5 probabilistic data structures (previously available as separate Redis modules) and incorporates the enhanced Redis Query Enginer (with vector search). 8.0-M01 is available as a Docker image and can be downloaded from [Docker Hub](https://hub.docker.com/_/redis). Additional distributions will be introduced in upcoming pre-releases. ### Supported upgrade paths (by replication or persistence) to 8.0-M01 - From previous Redis versions, without modules The following upgrade paths (by replication or persistence) to 8.0-M01 are not yet tested and will be introduced in upcoming pre-releases: - From previous Redis versions with modules (RediSearch, RedisJSON, RedisTimeSeries, RedisBloom) - From Redis Stack 7.2 or 7.4 ### New Features in binary distributions - 7 new data structures: JSON, Time series, Bloom filter, Cuckoo filter, Count-min sketch, Top-k, t-digest - The enhanced Redis Query Engine (with vector search) ### Potentially breaking changes - #12272 `GETRANGE` returns an empty bulk when the negative end index is out of range - #12395 Optimize `SCAN` command when matching data type ### Bug fixes - #13510 Fix `RM_RdbLoad` to enable AOF after RDB loading is completed - #13489 `ACL CAT` - return module commands - #13476 Fix a race condition in the `cache_memory` of `functionsLibCtx` - #13473 Fix incorrect lag due to trimming stream via `XTRIM` command - #13338 Fix incorrect lag field in `XINFO` when tombstone is after the `last_id` of the consume group - #13470 On `HDEL` of last field - update the global hash field expiration data structure - #13465 Cluster: Pass extensions to node if extension processing is handled by it - #13443 Cluster: Ensure validity of myself when loading cluster config - #13422 Cluster: Fix `CLUSTER SHARDS` command returns empty array ### Modules API - #13509 New API calls: `RM_DefragAllocRaw`, `RM_DefragFreeRaw`, and `RM_RegisterDefragCallbacks` - defrag API to allocate and free raw memory ### Performance and resource utilization improvements - #13503 Avoid overhead of comparison function pointer calls in listpack `lpFind` - #13505 Optimize `STRING` datatype write commands - #13499 Optimize `SMEMBERS` command - #13494 Optimize `GEO*` commands reply - #13490 Optimize `HELLO` command - #13488 Optimize client query buffer - #12395 Optimize `SCAN` command when matching data type - #13529 Optimize `LREM`, `LPOS`, `LINSERT`, and `LINDEX` commands - #13516 Optimize `LRANGE` and other commands that perform several writes to client buffers per call - #13431 Avoid `used_memory` contention when updating from multiple threads ### Other general improvements - #13495 Reply `-LOADING` on replica while flushing the db ### CLI tools - #13411 redis-cli: Fix wrong `dbnum` showed after the client reconnected ### Notes - No backward compatibility for replication or persistence. - Additional distributions, upgrade paths, features, and improvements will be introduced in upcoming pre-releases. - With the GA release of 8.0 we will deprecate Redis Stack. redis-8.0.2/BUGS000066400000000000000000000000631501533116600133120ustar00rootroot00000000000000Please check https://github.com/redis/redis/issues redis-8.0.2/CODE_OF_CONDUCT.md000066400000000000000000000116371501533116600154370ustar00rootroot00000000000000Contributor Covenant Code of Conduct Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at this email address: redis@redis.io. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 1. Correction Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 2. Warning Community Impact: A violation through a single incident or series of actions. Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 3. Temporary Ban Community Impact: A serious violation of community standards, including sustained inappropriate behavior. Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 4. Permanent Ban Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. Consequence: A permanent ban from any sort of public interaction within the community. Attribution This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder. For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. redis-8.0.2/CONTRIBUTING.md000066400000000000000000000160621501533116600150660ustar00rootroot00000000000000By contributing code to the Redis project in any form you agree to the Redis Software Grant and Contributor License Agreement attached below. Only contributions made under the Redis Software Grant and Contributor License Agreement may be accepted by Redis, and any contribution is subject to the terms of the Redis tri-license under RSALv2/SSPLv1/AGPLv3 as described in the LICENSE.txt file included in the Redis source distribution. # REDIS SOFTWARE GRANT AND CONTRIBUTOR LICENSE AGREEMENT To specify the intellectual property license granted in any Contribution, Redis Ltd., ("**Redis**") requires a Software Grant and Contributor License Agreement ("**Agreement**"). This Agreement is for your protection as a contributor as well as the protection of Redis and its users; it does not change your rights to use your own Contribution for any other purpose permitted by this Agreement. By making any Contribution, You accept and agree to the following terms and conditions for the Contribution. Except for the license granted in this Agreement to Redis and the recipients of the software distributed by Redis, You reserve all right, title, and interest in and to Your Contribution. 1. **Definitions** 1.1. "**You**" (or "**Your**") means the copyright owner or legal entity authorized by the copyright owner that is entering into this Agreement with Redis. For legal entities, the entity making a Contribution and all other entities that Control, are Controlled by, or are under common Control with that entity are considered to be a single contributor. For the purposes of this definition, "**Control**" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 1.2. "**Contribution**" means the code, documentation, or any original work of authorship, including any modifications or additions to an existing work described above. 2. "**Work**" means any software project stewarded by Redis. 3. **Grant of Copyright License**. Subject to the terms and conditions of this Agreement, You grant to Redis and to the recipients of the software distributed by Redis a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contribution and such derivative works. 4. **Grant of Patent License**. Subject to the terms and conditions of this Agreement, You grant to Redis and to the recipients of the software distributed by Redis a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution alone or by a combination of Your Contribution with the Work to which such Contribution was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes a direct or contributory patent infringement, then any patent licenses granted to the claimant entity under this Agreement for that Contribution or Work terminate as of the date such litigation is filed. 5. **Representations and Warranties**. You represent and warrant that: (i) You are legally entitled to grant the above licenses; and (ii) if You are an entity, each employee or agent designated by You is authorized to submit the Contribution on behalf of You; and (iii) your Contribution is Your original work, and that it will not infringe on any third party's intellectual property right(s). 6. **Disclaimer**. You are not expected to provide support for Your Contribution, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contribution on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 7. **Enforceability**. Nothing in this Agreement will be construed as creating any joint venture, employment relationship, or partnership between You and Redis. If any provision of this Agreement is held to be unenforceable, the remaining provisions of this Agreement will not be affected. This represents the entire agreement between You and Redis relating to the Contribution. # IMPORTANT: HOW TO USE REDIS GITHUB ISSUES GitHub issues SHOULD ONLY BE USED to report bugs and for DETAILED feature requests. Everything else should be asked on Discord: https://discord.com/invite/redis PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected bugs in the GitHub issues system. We'll be delighted to help you and provide all the support on Discord. There is also an active community of Redis users at Stack Overflow: https://stackoverflow.com/questions/tagged/redis Issues and pull requests for documentation belong on the redis-doc repo: https://github.com/redis/redis-doc If you are reporting a security bug or vulnerability, see SECURITY.md. # How to provide a patch for a new feature 1. If it is a major feature or a semantical change, please don't start coding straight away: if your feature is not a conceptual fit you'll lose a lot of time writing the code without any reason. Start by posting in the mailing list and creating an issue at Github with the description of, exactly, what you want to accomplish and why. Use cases are important for features to be accepted. Here you can see if there is consensus about your idea. 2. If in step 1 you get an acknowledgment from the project leaders, use the following procedure to submit a patch: a. Fork Redis on GitHub ( https://docs.github.com/en/github/getting-started-with-github/fork-a-repo ) b. Create a topic branch (git checkout -b my_branch) c. Push to your branch (git push origin my_branch) d. Initiate a pull request on GitHub ( https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request ) e. Done :) 3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait for a *very* long time. However this is not a lack of interest, as the project gets more and more users, we find ourselves in a constant need to prioritize certain issues/PRs over others. If you think your issue/PR is very important try to popularize it, have other users commenting and sharing their point of view, and so forth. This helps. 4. For minor fixes - open a pull request on GitHub. Additional information on the RSALv2/SSPLv1/AGPLv3 tri-license is also found in the LICENSE.txt file. redis-8.0.2/INSTALL000066400000000000000000000000131501533116600136530ustar00rootroot00000000000000See README redis-8.0.2/LICENSE.txt000066400000000000000000002151121501533116600144550ustar00rootroot00000000000000Starting with Redis 8, Redis Open Source is moving to a tri-licensing model with all new Redis code contributions governed by the updated Redis Software Grant and Contributor License Agreement. After this release, contributions are subject to your choice of: (a) the Redis Source Available License v2 (RSALv2);or (b) the Server Side Public License v1 (SSPLv1); or (c) the GNU Affero General Public License v3 (AGPLv3). Redis Open Source 7.2 and prior releases remain subject to the BSDv3 clause license as referenced in the REDISCONTRIBUTIONS.txt file. The licensing structure for Redis 8.0 and subsequent releases is as follows: 1. Redis Source Available License 2.0 (RSALv2) Agreement ======================================================== Last Update: December 30, 2023 Acceptance ---------- This Agreement sets forth the terms and conditions on which the Licensor makes available the Software. By installing, downloading, accessing, Using, or distributing any of the Software, You agree to all of the terms and conditions of this Agreement. If You are receiving the Software on behalf of Your Company, You represent and warrant that You have the authority to agree to this Agreement on behalf of such entity. The Licensor reserves the right to update this Agreement from time to time. The terms below have the meanings set forth below for purposes of this Agreement: Definitions ----------- Agreement: this Redis Source Available License 2.0 Agreement. Control: ownership, directly or indirectly, of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. License: the License as described in the License paragraph below. Licensor: the entity offering these terms, which includes Redis Ltd. on behalf of itself and its subsidiaries and affiliates worldwide. Modify, Modified, or Modification: copy from or adapt all or part of the work in a fashion requiring copyright permission other than making an exact copy. The resulting work is called a Modified version of the earlier work. Redis: the Redis software as described in redis.com redis.io. Software: certain Software components designed to work with Redis and provided to You under this Agreement. Trademark: the trademarks, service marks, and any other similar rights. Use: anything You do with the Software requiring one of Your Licenses. You: the recipient of the Software, the individual or entity on whose behalf You are agreeing to this Agreement. Your Company: any legal entity, sole proprietorship, or other kind of organization that You work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Your Licenses: means all the Licenses granted to You for the Software under this Agreement. License ------- The Licensor grants You a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the Software, in each case subject to the limitations and conditions below. Limitations ----------- You may not make the functionality of the Software or a Modified version available to third parties as a service or distribute the Software or a Modified version in a manner that makes the functionality of the Software available to third parties. Making the functionality of the Software or Modified version available to third parties includes, without limitation, enabling third parties to interact with the functionality of the Software or Modified version in distributed form or remotely through a computer network, offering a product or service, the value of which entirely or primarily derives from the value of the Software or Modified version, or offering a product or service that accomplishes for users the primary purpose of the Software or Modified version. You may not alter, remove, or obscure any licensing, copyright, or other notices of the Licensor in the Software. Any use of the Licensor's Trademarks is subject to applicable law. Patents ------- The Licensor grants You a License, under any patent claims the Licensor can License, or becomes able to License, to make, have made, use, sell, offer for sale, import and have imported the Software, in each case subject to the limitations and conditions in this License. This License does not cover any patent claims that You cause to be infringed by Modifications or additions to the Software. If You or Your Company make any written claim that the Software infringes or contributes to infringement of any patent, your patent License for the Software granted under this Agreement ends immediately. If Your Company makes such a claim, your patent License ends immediately for work on behalf of Your Company. Notices ------- You must ensure that anyone who gets a copy of any part of the Software from You also gets a copy of the terms and conditions in this Agreement. If You modify the Software, You must include in any Modified copies of the Software prominent notices stating that You have Modified the Software. No Other Rights --------------- The terms and conditions of this Agreement do not imply any Licenses other than those expressly granted in this Agreement. Termination ----------- If You Use the Software in violation of this Agreement, such Use is not Licensed, and Your Licenses will automatically terminate. If the Licensor provides You with a notice of your violation, and You cease all violations of this License no later than 30 days after You receive that notice, Your Licenses will be reinstated retroactively. However, if You violate this Agreement after such reinstatement, any additional violation of this Agreement will cause your Licenses to terminate automatically and permanently. No Liability ------------ As far as the law allows, the Software comes as is, without any warranty or condition, and the Licensor will not be liable to You for any damages arising out of this Agreement or the Use or nature of the Software, under any kind of legal claim. Governing Law and Jurisdiction ------------------------------ If You are located in Asia, Pacific, Americas, or other jurisdictions not listed below, the Agreement will be construed and enforced in all respects in accordance with the laws of the State of California, U.S.A., without reference to its choice of law rules. The courts located in the County of Santa Clara, California, have exclusive jurisdiction for all purposes relating to this Agreement. If You are located in Israel, the Agreement will be construed and enforced in all respects in accordance with the laws of the State of Israel without reference to its choice of law rules. The courts located in the Central District of the State of Israel have exclusive jurisdiction for all purposes relating to this Agreement. If You are located in Europe, United Kingdom, Middle East or Africa, the Agreement will be construed and enforced in all respects in accordance with the laws of England and Wales without reference to its choice of law rules. The competent courts located in London, England, have exclusive jurisdiction for all purposes relating to this Agreement. 2. Server Side Public License (SSPL) ==================================== Server Side Public License VERSION 1, OCTOBER 16, 2018 Copyright (c) 2018 MongoDB, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. TERMS AND CONDITIONS 0. Definitions. "This License" refers to Server Side Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program, subject to section 13. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. Subject to section 13, you may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot use, propagate or convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not use, propagate or convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Offering the Program as a Service. If you make the functionality of the Program or a modified version available to third parties as a service, you must make the Service Source Code available via network download to everyone at no charge, under the terms of this License. Making the functionality of the Program or modified version available to third parties as a service includes, without limitation, enabling third parties to interact with the functionality of the Program or modified version remotely through a computer network, offering a service the value of which entirely or primarily derives from the value of the Program or modified version, or offering a service that accomplishes for users the primary purpose of the Program or modified version. "Service Source Code" means the Corresponding Source for the Program or the modified version, and the Corresponding Source for all programs that you use to make the Program or modified version available as a service, including, without limitation, management software, user interfaces, application program interfaces, automation software, monitoring software, backup software, storage software and hosting software, all such that a user could run an instance of the service using the Service Source Code you make available. 14. Revised Versions of this License. MongoDB, Inc. may publish revised and/or new versions of the Server Side Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the Server Side Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by MongoDB, Inc. If the Program does not specify a version number of the Server Side Public License, you may choose any version ever published by MongoDB, Inc. If the Program specifies that a proxy can decide which future versions of the Server Side Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS 3. GNU AFFERO GENERAL PUBLIC LICENSE, Version 3, 19 Nov 2007 ======================================================== Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . redis-8.0.2/MANIFESTO000066400000000000000000000153501501533116600141040ustar00rootroot00000000000000[Note: this is the Redis manifesto, for general information about installing and running Redis read the README file instead.] Redis Manifesto =============== 1 - A DSL for Abstract Data Types. Redis is a DSL (Domain Specific Language) that manipulates abstract data types and implemented as a TCP daemon. Commands manipulate a key space where keys are binary-safe strings and values are different kinds of abstract data types. Every data type represents an abstract version of a fundamental data structure. For instance Redis Lists are an abstract representation of linked lists. In Redis, the essence of a data type isn't just the kind of operations that the data types support, but also the space and time complexity of the data type and the operations performed upon it. 2 - Memory storage is #1. The Redis data set, composed of defined key-value pairs, is primarily stored in the computer's memory. The amount of memory in all kinds of computers, including entry-level servers, is increasing significantly each year. Memory is fast, and allows Redis to have very predictable performance. Datasets composed of 10k or 40 millions keys will perform similarly. Complex data types like Redis Sorted Sets are easy to implement and manipulate in memory with good performance, making Redis very simple. Redis will continue to explore alternative options (where data can be optionally stored on disk, say) but the main goal of the project remains the development of an in-memory database. 3 - Fundamental data structures for a fundamental API. The Redis API is a direct consequence of fundamental data structures. APIs can often be arbitrary but not an API that resembles the nature of fundamental data structures. If we ever meet intelligent life forms from another part of the universe, they'll likely know, understand and recognize the same basic data structures we have in our computer science books. Redis will avoid intermediate layers in API, so that the complexity is obvious and more complex operations can be performed as the sum of the basic operations. 4 - We believe in code efficiency. Computers get faster and faster, yet we believe that abusing computing capabilities is not wise: the amount of operations you can do for a given amount of energy remains anyway a significant parameter: it allows to do more with less computers and, at the same time, having a smaller environmental impact. Similarly Redis is able to "scale down" to smaller devices. It is perfectly usable in a Raspberry Pi and other small ARM based computers. Faster code having just the layers of abstractions that are really needed will also result, often, in more predictable performances. We think likewise about memory usage, one of the fundamental goals of the Redis project is to incrementally build more and more memory efficient data structures, so that problems that were not approachable in RAM in the past will be perfectly fine to handle in the future. 5 - Code is like a poem; it's not just something we write to reach some practical result. Sometimes people that are far from the Redis philosophy suggest using other code written by other authors (frequently in other languages) in order to implement something Redis currently lacks. But to us this is like if Shakespeare decided to end Enrico IV using the Paradiso from the Divina Commedia. Is using any external code a bad idea? Not at all. Like in "One Thousand and One Nights" smaller self contained stories are embedded in a bigger story, we'll be happy to use beautiful self contained libraries when needed. At the same time, when writing the Redis story we're trying to write smaller stories that will fit in to other code. 6 - We're against complexity. We believe designing systems is a fight against complexity. We'll accept to fight the complexity when it's worthwhile but we'll try hard to recognize when a small feature is not worth 1000s of lines of code. Most of the time the best way to fight complexity is by not creating it at all. Complexity is also a form of lock-in: code that is very hard to understand cannot be modified by users in an independent way regardless of the license. One of the main Redis goals is to remain understandable, enough for a single programmer to have a clear idea of how it works in detail just reading the source code for a couple of weeks. 7 - Threading is not a silver bullet. Instead of making Redis threaded we believe on the idea of an efficient (mostly) single threaded Redis core. Multiple of such cores, that may run in the same computer or may run in multiple computers, are abstracted away as a single big system by higher order protocols and features: Redis Cluster and the upcoming Redis Proxy are our main goals. A shared nothing approach is not just much simpler (see the previous point in this document), is also optimal in NUMA systems. In the specific case of Redis it allows for each instance to have a more limited amount of data, making the Redis persist-by-fork approach more sounding. In the future we may explore parallelism only for I/O, which is the low hanging fruit: minimal complexity could provide an improved single process experience. 8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits naturally into a distributed version of Redis and 2) a more complex API that supports multi-key operations. Both are useful if used judiciously but there's no way to make the more complex multi-keys API distributed in an opaque way without violating our other principles. We don't want to provide the illusion of something that will work magically when actually it can't in all cases. Instead we'll provide commands to quickly migrate keys from one instance to another to perform multi-key operations and expose the trade-offs to the user. 9 - We optimize for joy. We believe writing code is a lot of hard work, and the only way it can be worth is by enjoying it. When there is no longer joy in writing code, the best thing to do is stop. To prevent this, we'll avoid taking paths that will make Redis less of a joy to develop. 10 - All the above points are put together in what we call opportunistic programming: trying to get the most for the user with minimal increases in complexity (hanging fruits). Solve 95% of the problem with 5% of the code when it is acceptable. Avoid a fixed schedule but follow the flow of user requests, inspiration, Redis internal readiness for certain features (sometimes many past changes reach a critical point making a previously complex feature very easy to obtain). redis-8.0.2/Makefile000066400000000000000000000004751501533116600142760ustar00rootroot00000000000000# Top level makefile, the real stuff is at ./src/Makefile and in ./modules/Makefile SUBDIRS = src ifeq ($(BUILD_WITH_MODULES), yes) SUBDIRS += modules endif default: all .DEFAULT: for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done install: for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done .PHONY: install redis-8.0.2/README.md000066400000000000000000000760071501533116600141210ustar00rootroot00000000000000[![codecov](https://codecov.io/github/redis/redis/graph/badge.svg?token=6bVHb5fRuz)](https://codecov.io/github/redis/redis) This README is just a fast *quick start* document. You can find more detailed documentation at [redis.io](https://redis.io). ### What is Redis? For developers, who are building real-time data-driven applications, Redis is the preferred, fastest, and most feature-rich cache, data structure server, and document and vector query engine. Redis covers a wide range of use cases across a wide range of industries and projects, serving as - **A cache**, supporting multiple key eviction policies, key expiration, and hash-field expiration - **A distributed session store**, supporting multiple session data modeling options (string, JSON, hash). - **A data structure server**: low-level data structures (string, JSON, list, hash, set, sorted set, bitmap, bitfield, and more - see full list below) with high-level semantics (counter, stack, queue, priority queue, rate limiter, leaderboard, ...), and with transactions and scripting support. - **A NoSQL data store**: key-value, document, and time series. - **A secondary index and a search and query engine**: with Redis Query Engine users can define indexes for hash and JSON documents, and use a rich query language for vector search, full-text search, geospatial queries, ranking, and aggregations. - **A distributed event store, stream-processing platform, and message broker**: queue (list), priority queue (sorted set), event deduplication (set), streams, and pub/sub, with probabilistic stream processing capabilities. - **A vector store**, integrated with GenAI applications and ecosystems (e.g., LangGraph, mem0), providing short-term memory (checkpointers), long-term memory (store), LLM response caching (LLM semantic cache), and context retrieval (RAG applications). - **Real time analytics**, including personalization, recommendations, fraud and anomaly detection, and risk assessment. Redis can be relied upon (it is robust and has a well-defined behavior), it comes with a long-term commitment (we keep maintaining Redis, avoid introducing breaking changes, and keep it innovative and competitive). Redis is fast and has a low memory footprint (with the right tradeoffs), easy to understand, learn, and use, and easy to adopt across a wide range of development environments and languages. If you want to know more, here is a list of starting points: - Introduction to Redis data types - https://redis.io/docs/latest/develop/data-types/ - The full list of Redis commands - https://redis.io/commands - Redis for AI - https://redis.io/docs/latest/develop/ai/ - and much more - https://redis.io/documentation ### What is Redis Open Source? Redis Community Edition (Redis CE) was renamed Redis Open Source with the v8.0 release. Redis Ltd. also offers [Redis Software](https://redis.io/enterprise/), a self-managed software with additional compliance, reliability, and resiliency for enterprise scaling, and [Redis Cloud](https://redis.io/cloud/), a fully managed service integrated with Google Cloud, Azure, and AWS for production-ready apps. Read more about the differences between Redis Open Source and Redis [here](https://redis.io/technology/advantages/). ### Redis Open Source - binary distributions The fastest way to deploy Redis is using one the binary distributions: - Alpine and Debian Docker images - https://hub.docker.com/_/redis - Install using snap - see https://github.com/redis/redis-snap - Install using brew - see https://github.com/redis/homebrew-redis - Install using RPM - see https://github.com/redis/redis-rpm - Install using Debian APT - see https://github.com/redis/redis-debian If you prefer to build Redis from source - see instructions below. ### Using Redis with redis-cli `redis-cli` is Redis' command line interface. It is available as part of all the binary distributions and when you build Redis from source. See https://redis.io/docs/latest/develop/tools/cli/ You can start a redis-server instance, and then, in another terminal try the following: ``` % cd src % ./redis-cli redis> ping PONG redis> set foo bar OK redis> get foo "bar" redis> incr mycounter (integer) 1 redis> incr mycounter (integer) 2 redis> ``` ### Using Redis with Redis Insight For a more visual and user-friendly experience, use Redis Insight - a tool that lets you explore data, design, develop, and optimize your applications while also serving as a platform for Redis education and onboarding. Redis Insight integrates Redis Copilot, a natural language AI assistant that improves the experience when working with data and commands. See https://redis.io/docs/latest/develop/tools/insight/ and https://github.com/RedisInsight/RedisInsight. ### Using Redis with client libraries To connect your application to Redis, you will need a client library. The list of client libraries is available in https://redis.io/docs/latest/develop/clients/. ### Redis Data types, processing engines, and capabilities Redis provides a variety of data types, processing engines, and capabilities to support a wide range of use cases: 1. **String** - Strings store sequences of bytes, including text, serialized objects, and binary arrays. As such, strings are the simplest type of value you can associate with a Redis key. - [Documentation: Strings](https://redis.io/docs/latest/develop/data-types/strings) 1. **JSON** (*) - The JSON data type provides JavaScript Object Notation (JSON) support for Redis. It lets you store, retrieve, and update JSON documents. A JSON document can be queried and manipulated using JSONPath expressions. JSON also works seamlessly with the Redis Query Engine to let you index and query JSON documents. - [Documentation: JSON quick start](https://redis.io/docs/latest/develop/data-types/json/#use-redisjson) 1. **Hash** - A hash is a collection of fields, each field is a name-value string pair. You can use hashes to represent flat objects and to store groupings of counters, among other things. Expiration time or a time-to-live can be set for each hash field. - [Documentation: Hashes](https://redis.io/docs/latest/develop/data-types/hashes) 1. **Redis Query Engine** (*) - The Redis Query Engine allows you to use Redis as a document database, a vector database, a secondary index, and a search engine. With Redis Query Engine, users can define indexes for hash and JSON documents, and use a rich query language for vector search, full-text search, geospatial queries, and aggregations. - [Documentation: Redis Query Engine](https://redis.io/docs/latest/develop/interact/search-and-query/) 1. **List** - A list is a list of strings, sorted by insertion order. They are great for stacks, queues, and for queue management and worker systems. - [Documentation: Lists](https://redis.io/docs/latest/develop/data-types/lists) 1. **Set** - A set is an unordered collection of unique strings (members). Sets can be used to track unique items (e.g., track all unique IP addresses accessing a given blog post), represent relations (e.g., the set of all users with a given role). Redis also supports common set operations such as intersection, unions, and differences. - [Documentation: Sets](https://redis.io/docs/latest/develop/data-types/sets) 1. **Sorted Set** - A sorted set is similar to a set, but each member is associated with a score. When more than one member has the same score, the members are ordered lexicographically. Some use cases for sorted sets include leaderboards and sliding-window rate limiters. - [Documentation: Sorted Sets](https://redis.io/docs/latest/develop/data-types/sorted-sets) 1. **Vector Set** (beta) - Vector set is a data type similar to a sorted set, but instead of a score, each member is associated with a vector embedding. You can add items to a vector set, and then retrieve the members that are the most similar to a specified vector embedding, or to the vector embedding of an existing member. - [Documentation: Vector sets](https://redis.io/docs/latest/develop/data-types/vector-sets) 1. **Geospatial Index** - Geospatial indexes let you store coordinates and search for them. This data structure is useful for finding nearby points within a given radius or bounding box. - [Documentation: Geo](https://redis.io/docs/latest/develop/data-types/geospatial/) 1. **Bitmap** - Bitmaps provide bit-oriented operations on a bit vector. You can get bits, set bits, and perform bitwise operations between bitmaps. Bitmaps are often used for efficient management of memberships or permissions, where each bit represents a particular member. - [Documentation: Bitmap](https://redis.io/docs/latest/develop/data-types/bitmaps/) 1. **Bitfield** - Bitfields let you set, increment, and get integer values of arbitrary bit length - from unsigned 1-bit integers to signed 63-bit integers. Bitfields are often used for efficient management of arrays of limited-range counters or numerical values. - [Documentation: Bitfield](https://redis.io/docs/latest/develop/data-types/bitfields/) 1. **HyperLogLog** - HyperLogLog is a probabilistic data structure used for approximating the cardinality of a stream (i.e., the number of unique elements). - [Documentation: HyperLogLog](https://redis.io/docs/latest/develop/data-types/hyperloglogs) 1. **Bloom filter** (*) - Bloom filter is a probabilistic data structure used for checking if a given value is present in a stream. - [Documentation: Bloom filter](https://redis.io/docs/latest/develop/data-types/probabilistic/bloom-filter/) 1. **Cuckoo filter** (*) - Cuckoo filter, just like Bloom filter, is a probabilistic data structure for checking if a given value is present in a stream, while also allowing limited counting and deletions. - [Documentation: Cuckoo filter](https://redis.io/docs/latest/develop/data-types/probabilistic/cuckoo-filter/) 1. **Top-k** (*) - Top-k is a probabilistic data structure used for tracking the most frequent values in a data stream. - [Documentation: Top-k](https://redis.io/docs/latest/develop/data-types/probabilistic/top-k/) 1. **Count-min sketch** (*) - Count-Min Sketch is a probabilistic data structure used for estimating how many times a given value appears in the data stream. - [Documentation: Count-min sketch](https://redis.io/docs/latest/develop/data-types/probabilistic/count-min-sketch/) 1. **t-digest** (*) - t-digest is a probabilistic data structure used for estimating which fraction / how many values in a data stream are smaller than a given value, which value is smaller than p percent of the values in a data stream, or what are the smallest/largest values in the data stream. - [Documentation: t-digeset](https://redis.io/docs/latest/develop/data-types/probabilistic/t-digest/) 1. **Time series** (*) - a time series allows storing and querying timetagged data points (samples). - [Documentation: Time series quick start](https://redis.io/docs/latest/develop/data-types/timeseries/quickstart/) 1. **Pub/Sub** - Pub/Sub (short for publish/subscribe) is a lightweight messaging capability. Publishers send messages to a channel, and subscribers receive messages from that channel. - [Documentation: Redis streams](https://redis.io/docs/latest/develop/interact/pubsub/) 1. **Stream** - A stream is a data structure that acts like an append-only log. Each stream entry consists of name-value string pairs, similar to a hash. Streams support multiple consumption strategies, where consumers can pop entries, consume entries by range, or listen to entries. Consumer groups allow multiple workers to retrieve different stream entries in order to scale message processing. - [Documentation: Redis streams](https://redis.io/docs/latest/develop/data-types/streams/) 1. **Transactions** - A transaction 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. - [Documentation: Transactions](https://redis.io/docs/latest/develop/interact/transactions/) 1. **Programmability** - Users can 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. Functions provide the same core functionality as scripts but are first-class software artifacts of the database. Redis manages functions as an integral part of the database and ensures their availability via data persistence and replication. - [Documentation: Scripting with Lua](https://redis.io/docs/latest/develop/interact/programmability/eval-intro/) - [Documentation: Functions](https://redis.io/docs/latest/develop/interact/programmability/functions-intro/) Items marked with (*) require building with `BUILD_WITH_MODULES=yes`. ### Build and run Redis with all data structures - Ubuntu 20.04 (Focal) Tested with the following Docker images: - ubuntu:20.04 1. Install required dependencies Update your package lists and install the necessary development tools and libraries: ``` apt-get update apt-get install -y sudo sudo apt-get install -y --no-install-recommends ca-certificates wget dpkg-dev gcc g++ libc6-dev libssl-dev make git python3 python3-pip python3-venv python3-dev unzip rsync clang automake autoconf gcc-10 g++-10 libtool ``` 2. Use GCC 10 as the default compiler Update the system's default compiler to GCC 10: ``` sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 ``` 3. Install CMake Install CMake using `pip3` and link it for system-wide access: ``` pip3 install cmake==3.31.6 sudo ln -sf /usr/local/bin/cmake /usr/bin/cmake cmake --version ``` Note: CMake version 3.31.6 is the latest supported version. Newer versions cannot be used. 4. Download the Redis source Download a specific version of the Redis source code archive from GitHub. Replace `` with the Redis version, for example: `8.0.0`. ``` cd /usr/src wget -O redis-.tar.gz https://github.com/redis/redis/archive/refs/tags/.tar.gz ``` 5. Extract the source archive Create a directory for the source code and extract the contents into it: ``` cd /usr/src tar xvf redis-.tar.gz rm redis-.tar.gz ``` 6. Build Redis Set the necessary environment variables and compile Redis: ``` cd /usr/src/redis- export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes make -j "$(nproc)" all ``` 7. Run Redis ``` cd /usr/src/redis- ./src/redis-server redis-full.conf ``` ### Build and run Redis with all data structures - Ubuntu 22.04 (Jammy) / 24.04 (Noble) Tested with the following Docker image: - ubuntu:22.04 - ubuntu:24.04 1. Install required dependencies Update your package lists and install the necessary development tools and libraries: ``` apt-get update apt-get install -y sudo sudo apt-get install -y --no-install-recommends ca-certificates wget dpkg-dev gcc g++ libc6-dev libssl-dev make git cmake python3 python3-pip python3-venv python3-dev unzip rsync clang automake autoconf libtool ``` 2. Download the Redis source Download a specific version of the Redis source code archive from GitHub. Replace `` with the Redis version, for example: `8.0.0`. ``` cd /usr/src wget -O redis-.tar.gz https://github.com/redis/redis/archive/refs/tags/.tar.gz ``` 3. Extract the source archive Create a directory for the source code and extract the contents into it: ``` cd /usr/src tar xvf redis-.tar.gz rm redis-.tar.gz ``` 4. Build Redis Set the necessary environment variables and build Redis: ``` cd /usr/src/redis- export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes make -j "$(nproc)" all ``` 5. Run Redis ``` cd /usr/src/redis- ./src/redis-server redis-full.conf ``` ### Build and run Redis with all data structures - Debian 11 (Bullseye) / 12 (Bookworm) Tested with the following Docker images: - debian:bullseye - debian:bullseye-slim - debian:bookworm - debian:bookworm-slim 1. Install required dependencies Update your package lists and install the necessary development tools and libraries: ``` apt-get update apt-get install -y sudo sudo apt-get install -y --no-install-recommends ca-certificates wget dpkg-dev gcc g++ libc6-dev libssl-dev make git cmake python3 python3-pip python3-venv python3-dev unzip rsync clang automake autoconf libtool ``` 2. Download the Redis source Download a specific version of the Redis source code archive from GitHub. Replace `` with the Redis version, for example: `8.0.0`. ``` cd /usr/src wget -O redis-.tar.gz https://github.com/redis/redis/archive/refs/tags/.tar.gz ``` 3. Extract the source archive Create a directory for the source code and extract the contents into it: ``` cd /usr/src tar xvf redis-.tar.gz rm redis-.tar.gz ``` 4. Build Redis Set the necessary environment variables and build Redis: ``` cd /usr/src/redis- export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes make -j "$(nproc)" all ``` 5. Run Redis ``` cd /usr/src/redis- ./src/redis-server redis-full.conf ``` ### Build and run Redis with all data structures - AlmaLinux 8.10 / Rocky Linux 8.10 Tested with the following Docker images: - almalinux:8.10 - almalinux:8.10-minimal - rockylinux/rockylinux:8.10 - rockylinux/rockylinux:8.10-minimal 1. Prepare the system For 8.10-minimal, install `sudo` and `dnf` as follows: ``` microdnf install dnf sudo -y ``` For 8.10 (regular), install sudo as follows: ``` dnf install sudo -y ``` Clean the package metadata, enable required repositories, and install development tools: ``` sudo dnf clean all sudo tee /etc/yum.repos.d/goreleaser.repo > /dev/null <` with the Redis version, for example: `8.0.0`. ``` cd /usr/src wget -O redis-.tar.gz https://github.com/redis/redis/archive/refs/tags/.tar.gz ``` 5. Extract the source archive Create a directory for the source code and extract the contents into it: ``` cd /usr/src tar xvf redis-.tar.gz rm redis-.tar.gz ``` 6. Build Redis Enable the GCC toolset, set the necessary environment variables, and build Redis: ``` source /etc/profile.d/gcc-toolset-13.sh cd /usr/src/redis- export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes make -j "$(nproc)" all ``` 7. Run Redis ``` cd /usr/src/redis- ./src/redis-server redis-full.conf ``` ### Build and run Redis with all data structures - AlmaLinux 9.5 / Rocky Linux 9.5 Tested with the following Docker images: - almalinux:9.5 - almalinux:9.5-minimal - rockylinux/rockylinux:9.5 - rockylinux/rockylinux:9.5-minimal 1. Prepare the system For 9.5-minimal, install `sudo` and `dnf` as follows: ``` microdnf install dnf sudo -y ``` For 9.5 (regular), install sudo as follows: ``` dnf install sudo -y ``` Clean the package metadata, enable required repositories, and install development tools: ``` sudo tee /etc/yum.repos.d/goreleaser.repo > /dev/null <` with the Redis version, for example: `8.0.0`. ``` cd /usr/src wget -O redis-.tar.gz https://github.com/redis/redis/archive/refs/tags/.tar.gz ``` 5. Extract the source archive Create a directory for the source code and extract the contents into it: ``` cd /usr/src tar xvf redis-.tar.gz rm redis-.tar.gz ``` 6. Build Redis Enable the GCC toolset, set the necessary environment variables, and build Redis: ``` source /etc/profile.d/gcc-toolset-13.sh cd /usr/src/redis- export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes make -j "$(nproc)" all ``` 7. Run Redis ``` cd /usr/src/redis- ./src/redis-server redis-full.conf ``` ### Build and run Redis with all data structures - macOS 13 (Ventura) and macOS 14 (Sonoma) 1. Install Homebrew If Homebrew is not already installed, follow the installation instructions on the [Homebrew home page](https://brew.sh/). 2. Install required packages ``` export HOMEBREW_NO_AUTO_UPDATE=1 brew update brew install coreutils brew install make brew install openssl brew install llvm@18 brew install cmake brew install gnu-sed brew install automake brew install libtool brew install wget ``` 3. Install Rust Rust is required to build the JSON package. ``` RUST_INSTALLER=rust-1.80.1-$(if [ "$(uname -m)" = "arm64" ]; then echo "aarch64"; else echo "x86_64"; fi)-apple-darwin wget --quiet -O ${RUST_INSTALLER}.tar.xz https://static.rust-lang.org/dist/${RUST_INSTALLER}.tar.xz tar -xf ${RUST_INSTALLER}.tar.xz (cd ${RUST_INSTALLER} && sudo ./install.sh) ``` 4. Download the Redis source Download a specific version of the Redis source code archive from GitHub. Replace `` with the Redis version, for example: `8.0.0`. ``` cd ~/src wget -O redis-.tar.gz https://github.com/redis/redis/archive/refs/tags/.tar.gz ``` 5. Extract the source archive Create a directory for the source code and extract the contents into it: ``` cd ~/src tar xvf redis-.tar.gz rm redis-.tar.gz ``` 6. Build Redis ``` cd ~/src/redis- 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" mkdir -p build_dir/etc make -C redis-8.0 -j "$(nproc)" all OS=macos make -C redis-8.0 install PREFIX=$(pwd)/build_dir OS=macos ``` 7. Run Redis ``` export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 build_dir/bin/redis-server redis-full.conf ``` ### Build and run Redis with all data structures - macOS 15 (Sequoia) Support and instructions will be provided at a later date. ### Building Redis - flags and general notes Redis can be compiled and used on Linux, OSX, OpenBSD, NetBSD, FreeBSD. We support big endian and little endian architectures, and both 32 bit and 64 bit systems. It may compile on Solaris derived systems (for instance SmartOS) but our support for this platform is *best effort* and Redis is not guaranteed to work as well as in Linux, OSX, and \*BSD. To build Redis with all the data structures (including JSON, time series, Bloom filter, cuckoo filter, count-min sketch, top-k, and t-digest) and with Redis Query Engine, make sure first that all the prerequisites are installed (see build instructions above, per operating system). You need to use the following flag in the make command: ``` make BUILD_WITH_MODULES=yes ``` To build Redis with just the core data structures, use: ``` make ``` To build with TLS support, you need OpenSSL development libraries (e.g. libssl-dev on Debian/Ubuntu) and the following flag in the make command: ``` make BUILD_TLS=yes ``` To build with systemd support, you need systemd development libraries (such as libsystemd-dev on Debian/Ubuntu or systemd-devel on CentOS), and the following flag: ``` make USE_SYSTEMD=yes ``` To append a suffix to Redis program names, add the following flag: ``` make PROG_SUFFIX="-alt" ``` You can build a 32 bit Redis binary using: ``` make 32bit ``` After building Redis, it is a good idea to test it using: ``` make test ``` If TLS is built, running the tests with TLS enabled (you will need `tcl-tls` installed): ``` ./utils/gen-test-certs.sh ./runtest --tls ``` ### Fixing build problems with dependencies or cached build options Redis has some dependencies which are included in the `deps` directory. `make` does not automatically rebuild dependencies even if something in the source code of dependencies changes. When you update the source code with `git pull` or when code inside the dependencies tree is modified in any other way, make sure to use the following command in order to really clean everything and rebuild from scratch: ``` make distclean ``` This will clean: jemalloc, lua, hiredis, linenoise and other dependencies. Also if you force certain build options like 32bit target, no C compiler optimizations (for debugging purposes), and other similar build time options, those options are cached indefinitely until you issue a `make distclean` command. ### Fixing problems building 32 bit binaries If after building Redis with a 32 bit target you need to rebuild it with a 64 bit target, or the other way around, you need to perform a `make distclean` in the root directory of the Redis distribution. In case of build errors when trying to build a 32 bit binary of Redis, try the following steps: * Install the package libc6-dev-i386 (also try g++-multilib). * Try using the following command line instead of `make 32bit`: `make CFLAGS="-m32 -march=native" LDFLAGS="-m32"` ### Allocator Selecting a non-default memory allocator when building Redis is done by setting the `MALLOC` environment variable. Redis is compiled and linked against libc malloc by default, with the exception of jemalloc being the default on Linux systems. This default was picked because jemalloc has proven to have fewer fragmentation problems than libc malloc. To force compiling against libc malloc, use: ``` make MALLOC=libc ``` To compile against jemalloc on Mac OS X systems, use: ``` make MALLOC=jemalloc ``` ### Monotonic clock 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: ``` make CFLAGS="-DUSE_PROCESSOR_CLOCK" ``` ### Verbose build Redis will build with a user-friendly colorized output by default. If you want to see a more verbose output, use the following: ``` make V=1 ``` ### Running Redis with TLS Please consult the [TLS.md](TLS.md) file for more information on how to use Redis with TLS. ### Code contributions By contributing code to the Redis project in any form, including sending a pull request via GitHub, a code fragment or patch via private email or public discussion groups, you agree to release your code under the terms of the Redis Software Grant and Contributor License Agreement. Please see the CONTRIBUTING.md file in this source distribution for more information. For security bugs and vulnerabilities, please see SECURITY.md. Open Source Redis releases are subject to the following licenses: 1. Version 7.2.x and prior releases are subject to BSDv3. These contributions to the original Redis core project are owned by their contributors and licensed under the 3BSDv3 license as referenced in the REDISCONTRIBUTIONS.txt file. Any copy of that license in this repository applies only to those contributions; 2. Versions 7.4.x to 7.8.x are subject to your choice of RSALv2 or SSPLv1; and 3. Version 8.0.x and subsequent releases are subject to the tri-license RSALv2/SSPLv1/AGPLv3 at your option as referenced in the LICENSE.txt file. ### Redis Trademarks The purpose of a trademark is to identify the goods and services of a person or company without causing confusion. As the registered owner of its name and logo, Redis accepts certain limited uses of its trademarks but it has requirements that must be followed as described in its Trademark Guidelines available at: https://redis.io/legal/trademark-policy/. redis-8.0.2/REDISCONTRIBUTIONS.txt000066400000000000000000000034751501533116600162330ustar00rootroot00000000000000Copyright (c) 2006-Present, Redis Ltd. and Contributors All rights reserved. Note: Continued Applicability of the BSD-3-Clause License Despite the shift to the dual-licensing model with version 7.4 (RSALv2 or SSPLv1) and the shift to a tri-license option with version 8.0 (RSALv2/SSPLv1/AGPLv3), portions of Redis Open Source remain available subject to the BSD-3-Clause License (BSD). See below for the full BSD license: 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. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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. redis-8.0.2/SECURITY.md000066400000000000000000000034611501533116600144250ustar00rootroot00000000000000# Security Policy ## Supported Versions Redis is generally backward compatible with very few exceptions, so we recommend users to always use the latest version to experience stability, performance and security. We generally backport security issues to a single previous major version, unless this is not possible or feasible with a reasonable effort. | Version | Supported | |---------|-------------------------------------------------------------| | 8.0.x | :white_check_mark: | | 7.4.x | :white_check_mark: | | 7.2.x | :white_check_mark: | | < 7.2.x | :x: | | 6.2.x | :white_check_mark: Support may be removed after end of 2025 | | < 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. ## Responsible Disclosure In some cases, we may apply a responsible disclosure process to reported or otherwise discovered vulnerabilities. We will usually do that for a critical vulnerability, and only if we have a good reason to believe information about it is not yet public. This process involves providing an early notification about the vulnerability, its impact and mitigations to a short list of vendors under a time-limited 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. redis-8.0.2/TLS.md000066400000000000000000000070541501533116600136220ustar00rootroot00000000000000TLS Support =========== Getting Started --------------- ### Building To build with TLS support you'll need OpenSSL development libraries (e.g. libssl-dev on Debian/Ubuntu). To build TLS support as Redis built-in: Run `make BUILD_TLS=yes`. Or to build TLS as Redis module: Run `make BUILD_TLS=module`. Note that sentinel mode does not support TLS module. ### Tests To run Redis test suite with TLS, you'll need TLS support for TCL (i.e. `tcl-tls` package on Debian/Ubuntu). 1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server certificate. 2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis Cluster tests in TLS mode. 3. Run `./runtest --tls-module` or `./runtest-cluster --tls-module` to run Redis and Redis cluster tests in TLS mode with Redis module. ### Running manually To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was invoked so sample certificates/keys are available): For TLS built-in mode: ./src/redis-server --tls-port 6379 --port 0 \ --tls-cert-file ./tests/tls/redis.crt \ --tls-key-file ./tests/tls/redis.key \ --tls-ca-cert-file ./tests/tls/ca.crt For TLS module mode: ./src/redis-server --tls-port 6379 --port 0 \ --tls-cert-file ./tests/tls/redis.crt \ --tls-key-file ./tests/tls/redis.key \ --tls-ca-cert-file ./tests/tls/ca.crt \ --loadmodule src/redis-tls.so To connect to this Redis server with `redis-cli`: ./src/redis-cli --tls \ --cert ./tests/tls/redis.crt \ --key ./tests/tls/redis.key \ --cacert ./tests/tls/ca.crt This will disable TCP and enable TLS on port 6379. It's also possible to have both TCP and TLS available, but you'll need to assign different ports. To make a Replica connect to the master using TLS, use `--tls-replication yes`, and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`. Connections ----------- All socket operations now go through a connection abstraction layer that hides I/O and read/write event handling from the caller. **Multi-threading I/O is not currently supported for TLS**, as a TLS connection needs to do its own manipulation of AE events which is not thread safe. The solution is probably to manage independent AE loops for I/O threads and longer term association of connections with threads. This may potentially improve overall performance as well. Sync IO for TLS is currently implemented in a hackish way, i.e. making the socket blocking and configuring socket-level timeout. This means the timeout value may not be so accurate, and there would be a lot of syscall overhead. However I believe that getting rid of syncio completely in favor of pure async work is probably a better move than trying to fix that. For replication it would probably not be so hard. For cluster keys migration it might be more difficult, but there are probably other good reasons to improve that part anyway. To-Do List ---------- - [ ] redis-benchmark support. The current implementation is a mix of using hiredis for parsing and basic networking (establishing connections), but directly manipulating sockets for most actions. This will need to be cleaned up for proper TLS support. The best approach is probably to migrate to hiredis async mode. - [ ] redis-cli `--slave` and `--rdb` support. Multi-port ---------- Consider the implications of allowing TLS to be configured on a separate port, making Redis listening on multiple ports: 1. Startup banner port notification 2. Proctitle 3. How slaves announce themselves 4. Cluster bus port calculation redis-8.0.2/codecov.yml000066400000000000000000000005231501533116600147750ustar00rootroot00000000000000coverage: status: patch: default: informational: true project: default: informational: true comment: require_changes: false require_head: false require_base: false layout: "condensed_header, diff, files" hide_project_coverage: false behavior: default github_checks: annotations: false redis-8.0.2/deps/000077500000000000000000000000001501533116600135635ustar00rootroot00000000000000redis-8.0.2/deps/Makefile000066400000000000000000000067721501533116600152370ustar00rootroot00000000000000# Redis dependency Makefile uname_S:= $(shell sh -c 'uname -s 2>/dev/null || echo not') LUA_DEBUG?=no LUA_COVERAGE?=no CCCOLOR="\033[34m" LINKCOLOR="\033[34;1m" SRCCOLOR="\033[33m" BINCOLOR="\033[37;1m" MAKECOLOR="\033[32;1m" ENDCOLOR="\033[0m" default: @echo "Explicit target required" .PHONY: default # Prerequisites target .make-prerequisites: @touch $@ # Clean everything when CFLAGS is different ifneq ($(shell sh -c '[ -f .make-cflags ] && cat .make-cflags || echo none'), $(CFLAGS)) .make-cflags: distclean -(echo "$(CFLAGS)" > .make-cflags) .make-prerequisites: .make-cflags endif # Clean everything when LDFLAGS is different ifneq ($(shell sh -c '[ -f .make-ldflags ] && cat .make-ldflags || echo none'), $(LDFLAGS)) .make-ldflags: distclean -(echo "$(LDFLAGS)" > .make-ldflags) .make-prerequisites: .make-ldflags endif distclean: -(cd hiredis && $(MAKE) clean) > /dev/null || true -(cd linenoise && $(MAKE) clean) > /dev/null || true -(cd lua && $(MAKE) clean) > /dev/null || true -(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 -(rm -f .make-*) .PHONY: distclean ifneq (,$(filter $(BUILD_TLS),yes module)) HIREDIS_MAKE_FLAGS = USE_SSL=1 endif hiredis: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS) .PHONY: hiredis linenoise: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd linenoise && $(MAKE) .PHONY: linenoise hdr_histogram: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd hdr_histogram && $(MAKE) .PHONY: hdr_histogram fpconv: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd fpconv && $(MAKE) .PHONY: fpconv fast_float: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd fast_float && $(MAKE) libfast_float .PHONY: fast_float ifeq ($(uname_S),SunOS) # Make isinf() available LUA_CFLAGS= -D__C99FEATURES__=1 endif LUA_CFLAGS+= -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' -DLUA_USE_MKSTEMP $(CFLAGS) LUA_LDFLAGS+= $(LDFLAGS) ifeq ($(LUA_DEBUG),yes) LUA_CFLAGS+= -O0 -g -DLUA_USE_APICHECK else LUA_CFLAGS+= -O2 endif ifeq ($(LUA_COVERAGE),yes) LUA_CFLAGS += -fprofile-arcs -ftest-coverage LUA_LDFLAGS += -fprofile-arcs -ftest-coverage endif # lua's Makefile defines AR="ar rcu", which is unusual, and makes it more # challenging to cross-compile lua (and redis). These defines make it easier # to fit redis into cross-compilation environments, which typically set AR. AR=ar ARFLAGS=rc lua: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd lua/src && $(MAKE) all CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(LUA_LDFLAGS)" AR="$(AR) $(ARFLAGS)" .PHONY: lua JEMALLOC_CFLAGS=$(CFLAGS) JEMALLOC_LDFLAGS=$(LDFLAGS) ifneq ($(DEB_HOST_GNU_TYPE),) JEMALLOC_CONFIGURE_OPTS += --host=$(DEB_HOST_GNU_TYPE) endif jemalloc: .make-prerequisites @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) cd jemalloc && ./configure --disable-cxx --with-version=5.3.0-0-g0 --with-lg-quantum=3 --disable-cache-oblivious --with-jemalloc-prefix=je_ CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" $(JEMALLOC_CONFIGURE_OPTS) cd jemalloc && $(MAKE) lib/libjemalloc.a .PHONY: jemalloc redis-8.0.2/deps/README.md000066400000000000000000000127161501533116600150510ustar00rootroot00000000000000This directory contains all Redis dependencies, except for the libc that should be provided by the operating system. * **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time. * **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed. * **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed. * **lua** is Lua 5.1 with minor changes for security and additional libraries. * **hdr_histogram** Used for per-command latency tracking histograms. How to upgrade the above dependencies === Jemalloc --- Jemalloc is modified with changes that allow us to implement the Redis active defragmentation logic. However this feature of Redis is not mandatory and Redis is able to understand if the Jemalloc version it is compiled against supports such Redis-specific modifications. So in theory, if you are not interested in the active defragmentation, you can replace Jemalloc just following these steps: 1. Remove the jemalloc directory. 2. Substitute it with the new jemalloc source tree. 3. Edit the Makefile located in the same directory as the README you are reading, and change the --with-version in the Jemalloc configure script options with the version you are using. This is required because otherwise Jemalloc configuration script is broken and will not work nested in another git repository. However note that we change Jemalloc settings via the `configure` script of Jemalloc using the `--with-lg-quantum` option, setting it to the value of 3 instead of 4. This provides us with more size classes that better suit the Redis data structures, in order to gain memory efficiency. If you want to upgrade Jemalloc while also providing support for active defragmentation, in addition to the above steps you need to perform the following additional steps: 5. In Jemalloc tree, file `include/jemalloc/jemalloc_macros.h.in`, make sure to add `#define JEMALLOC_FRAG_HINT`. 6. Implement the function `je_get_defrag_hint()` inside `src/jemalloc.c`. You can see how it is implemented in the current Jemalloc source tree shipped with Redis, and rewrite it according to the new Jemalloc internals, if they changed, otherwise you could just copy the old implementation if you are upgrading just to a similar version of Jemalloc. #### Updating/upgrading jemalloc The jemalloc directory is pulled as a subtree from the upstream jemalloc github repo. To update it you should run from the project root: 1. `git subtree pull --prefix deps/jemalloc https://github.com/jemalloc/jemalloc.git --squash`
This should hopefully merge the local changes into the new version. 2. In case any conflicts arise (due to our changes) you'll need to resolve them and commit. 3. Reconfigure jemalloc:
```sh rm deps/jemalloc/VERSION deps/jemalloc/configure cd deps/jemalloc ./autogen.sh --with-version=-0-g0 ``` 4. Update jemalloc's version in `deps/Makefile`: search for "`--with-version=-0-g0`" and update it accordingly. 5. Commit the changes (VERSION,configure,Makefile). Hiredis --- Hiredis is used by Sentinel, `redis-cli` and `redis-benchmark`. Like Redis, uses the SDS string library, but not necessarily the same version. In order to avoid conflicts, this version has all SDS identifiers prefixed by `hi`. 1. `git subtree pull --prefix deps/hiredis https://github.com/redis/hiredis.git --squash`
This should hopefully merge the local changes into the new version. 2. Conflicts will arise (due to our changes) you'll need to resolve them and commit. Linenoise --- Linenoise is rarely upgraded as needed. The upgrade process is trivial since Redis uses a non modified version of linenoise, so to upgrade just do the following: 1. Remove the linenoise directory. 2. Substitute it with the new linenoise source tree. Lua --- We use Lua 5.1 and no upgrade is planned currently, since we don't want to break Lua scripts for new Lua features: in the context of Redis Lua scripts the capabilities of 5.1 are usually more than enough, the release is rock solid, and we definitely don't want to break old scripts. So upgrading of Lua is up to the Redis project maintainers and should be a manual procedure performed by taking a diff between the different versions. Currently we have at least the following differences between official Lua 5.1 and our version: 1. Makefile is modified to allow a different compiler than GCC. 2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`. 3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order to avoid direct bytecode execution. Hdr_Histogram --- Updated source can be found here: https://github.com/HdrHistogram/HdrHistogram_c We use a customized version based on master branch commit e4448cf6d1cd08fff519812d3b1e58bd5a94ac42. 1. Compare all changes under /hdr_histogram directory to upstream master commit e4448cf6d1cd08fff519812d3b1e58bd5a94ac42 2. Copy updated files from newer version onto files in /hdr_histogram. 3. Apply the changes from 1 above to the updated files. redis-8.0.2/deps/fast_float/000077500000000000000000000000001501533116600157055ustar00rootroot00000000000000redis-8.0.2/deps/fast_float/Makefile000066400000000000000000000011271501533116600173460ustar00rootroot00000000000000# Fallback to gcc/g++ when $CC or $CXX is not in $PATH. CC ?= gcc CXX ?= g++ CFLAGS=-Wall -O3 # This avoids loosing the fastfloat specific compile flags when we override the CFLAGS via the main project FASTFLOAT_CFLAGS=-std=c++11 -DFASTFLOAT_ALLOWS_LEADING_PLUS LDFLAGS= libfast_float: fast_float_strtod.o $(AR) -r libfast_float.a fast_float_strtod.o 32bit: CFLAGS += -m32 32bit: LDFLAGS += -m32 32bit: libfast_float fast_float_strtod.o: fast_float_strtod.cpp $(CXX) $(CFLAGS) $(FASTFLOAT_CFLAGS) -c fast_float_strtod.cpp $(LDFLAGS) clean: rm -f *.o rm -f *.a rm -f *.h.gch rm -rf *.dSYM redis-8.0.2/deps/fast_float/README.md000066400000000000000000000012151501533116600171630ustar00rootroot00000000000000README 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 redis-8.0.2/deps/fast_float/fast_float.h000066400000000000000000004123151501533116600202060ustar00rootroot00000000000000// fast_float by Daniel Lemire // fast_float by João Paulo Magalhaes // // // with contributions from Eugene Golushkov // with contributions from Maksim Kita // with contributions from Marcin Wojdyr // with contributions from Neal Richardson // with contributions from Tim Paine // with contributions from Fabio Pellacini // with contributions from Lénárd Szolnoki // with contributions from Jan Pharago // with contributions from Maya Warrier // with contributions from Taha Khokhar // // // MIT License Notice // // MIT License // // Copyright (c) 2021 The fast_float authors // // Permission is hereby granted, free of charge, to any // person obtaining a copy of this software and associated // documentation files (the "Software"), to deal in the // Software without restriction, including without // limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software // is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice // shall be included in all copies or substantial portions // of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // #ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #ifdef __has_include #if __has_include() #include #endif #endif // Testing for https://wg21.link/N3652, adopted in C++14 #if __cpp_constexpr >= 201304 #define FASTFLOAT_CONSTEXPR14 constexpr #else #define FASTFLOAT_CONSTEXPR14 #endif #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L #define FASTFLOAT_HAS_BIT_CAST 1 #else #define FASTFLOAT_HAS_BIT_CAST 0 #endif #if defined(__cpp_lib_is_constant_evaluated) && \ __cpp_lib_is_constant_evaluated >= 201811L #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 #else #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 #endif // Testing for relevant C++20 constexpr library features #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ #define FASTFLOAT_CONSTEXPR20 constexpr #define FASTFLOAT_IS_CONSTEXPR 1 #else #define FASTFLOAT_CONSTEXPR20 #define FASTFLOAT_IS_CONSTEXPR 0 #endif #endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H #ifndef FASTFLOAT_FLOAT_COMMON_H #define FASTFLOAT_FLOAT_COMMON_H #include #include #include #include #include #include #ifdef __has_include #if __has_include() && (__cplusplus > 202002L || _MSVC_LANG > 202002L) #include #endif #endif namespace fast_float { #define FASTFLOAT_JSONFMT (1 << 5) #define FASTFLOAT_FORTRANFMT (1 << 6) enum chars_format { scientific = 1 << 0, fixed = 1 << 2, hex = 1 << 3, no_infnan = 1 << 4, // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 json = FASTFLOAT_JSONFMT | fixed | scientific | no_infnan, // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. json_or_infnan = FASTFLOAT_JSONFMT | fixed | scientific, fortran = FASTFLOAT_FORTRANFMT | fixed | scientific, general = fixed | scientific }; template struct from_chars_result_t { UC const *ptr; std::errc ec; }; using from_chars_result = from_chars_result_t; template struct parse_options_t { constexpr explicit parse_options_t(chars_format fmt = chars_format::general, UC dot = UC('.')) : format(fmt), decimal_point(dot) {} /** Which number formats are accepted */ chars_format format; /** The character used as decimal point */ UC decimal_point; }; using parse_options = parse_options_t; } // namespace fast_float #if FASTFLOAT_HAS_BIT_CAST #include #endif #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ defined(__MINGW64__) || defined(__s390x__) || \ (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ defined(__PPC64LE__)) || \ defined(__loongarch64)) #define FASTFLOAT_64BIT 1 #elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ defined(__MINGW32__) || defined(__EMSCRIPTEN__)) #define FASTFLOAT_32BIT 1 #else // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. // We can never tell the register width, but the SIZE_MAX is a good // approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max // portability. #if SIZE_MAX == 0xffff #error Unknown platform (16-bit, unsupported) #elif SIZE_MAX == 0xffffffff #define FASTFLOAT_32BIT 1 #elif SIZE_MAX == 0xffffffffffffffff #define FASTFLOAT_64BIT 1 #else #error Unknown platform (not 32-bit, not 64-bit?) #endif #endif #if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ (defined(_M_ARM64) && !defined(__MINGW32__)) #include #endif #if defined(_MSC_VER) && !defined(__clang__) #define FASTFLOAT_VISUAL_STUDIO 1 #endif #if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ #define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #elif defined _WIN32 #define FASTFLOAT_IS_BIG_ENDIAN 0 #else #if defined(__APPLE__) || defined(__FreeBSD__) #include #elif defined(sun) || defined(__sun) #include #elif defined(__MVS__) #include #else #ifdef __has_include #if __has_include() #include #endif //__has_include() #endif //__has_include #endif # #ifndef __BYTE_ORDER__ // safe choice #define FASTFLOAT_IS_BIG_ENDIAN 0 #endif # #ifndef __ORDER_LITTLE_ENDIAN__ // safe choice #define FASTFLOAT_IS_BIG_ENDIAN 0 #endif # #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define FASTFLOAT_IS_BIG_ENDIAN 0 #else #define FASTFLOAT_IS_BIG_ENDIAN 1 #endif #endif #if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ (defined(_M_AMD64) || defined(_M_X64) || \ (defined(_M_IX86_FP) && _M_IX86_FP == 2))) #define FASTFLOAT_SSE2 1 #endif #if defined(__aarch64__) || defined(_M_ARM64) #define FASTFLOAT_NEON 1 #endif #if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) #define FASTFLOAT_HAS_SIMD 1 #endif #if defined(__GNUC__) // disable -Wcast-align=strict (GCC only) #define FASTFLOAT_SIMD_DISABLE_WARNINGS \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wcast-align\"") #else #define FASTFLOAT_SIMD_DISABLE_WARNINGS #endif #if defined(__GNUC__) #define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") #else #define FASTFLOAT_SIMD_RESTORE_WARNINGS #endif #ifdef FASTFLOAT_VISUAL_STUDIO #define fastfloat_really_inline __forceinline #else #define fastfloat_really_inline inline __attribute__((always_inline)) #endif #ifndef FASTFLOAT_ASSERT #define FASTFLOAT_ASSERT(x) \ { ((void)(x)); } #endif #ifndef FASTFLOAT_DEBUG_ASSERT #define FASTFLOAT_DEBUG_ASSERT(x) \ { ((void)(x)); } #endif // rust style `try!()` macro, or `?` operator #define FASTFLOAT_TRY(x) \ { \ if (!(x)) \ return false; \ } #define FASTFLOAT_ENABLE_IF(...) \ typename std::enable_if<(__VA_ARGS__), int>::type namespace fast_float { fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED return std::is_constant_evaluated(); #else return false; #endif } template fastfloat_really_inline constexpr bool is_supported_float_type() { return std::is_same::value || std::is_same::value #if __STDCPP_FLOAT32_T__ || std::is_same::value #endif #if __STDCPP_FLOAT64_T__ || std::is_same::value #endif ; } template fastfloat_really_inline constexpr bool is_supported_char_type() { return std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value; } // Compares two ASCII strings in a case insensitive manner. template inline FASTFLOAT_CONSTEXPR14 bool fastfloat_strncasecmp(UC const *input1, UC const *input2, size_t length) { char running_diff{0}; for (size_t i = 0; i < length; ++i) { running_diff |= (char(input1[i]) ^ char(input2[i])); } return (running_diff == 0) || (running_diff == 32); } #ifndef FLT_EVAL_METHOD #error "FLT_EVAL_METHOD should be defined, please include cfloat." #endif // a pointer and a length to a contiguous block of memory template struct span { const T *ptr; size_t length; constexpr span(const T *_ptr, size_t _length) : ptr(_ptr), length(_length) {} constexpr span() : ptr(nullptr), length(0) {} constexpr size_t len() const noexcept { return length; } FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return ptr[index]; } }; struct value128 { uint64_t low; uint64_t high; constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} constexpr value128() : low(0), high(0) {} }; /* Helper C++14 constexpr generic implementation of leading_zeroes */ fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { if (input_num & uint64_t(0xffffffff00000000)) { input_num >>= 32; last_bit |= 32; } if (input_num & uint64_t(0xffff0000)) { input_num >>= 16; last_bit |= 16; } if (input_num & uint64_t(0xff00)) { input_num >>= 8; last_bit |= 8; } if (input_num & uint64_t(0xf0)) { input_num >>= 4; last_bit |= 4; } if (input_num & uint64_t(0xc)) { input_num >>= 2; last_bit |= 2; } if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ last_bit |= 1; } return 63 - last_bit; } /* result might be undefined when input_num is zero */ fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int leading_zeroes(uint64_t input_num) { assert(input_num > 0); if (cpp20_and_in_constexpr()) { return leading_zeroes_generic(input_num); } #ifdef FASTFLOAT_VISUAL_STUDIO #if defined(_M_X64) || defined(_M_ARM64) unsigned long leading_zero = 0; // Search the mask data from most significant bit (MSB) // to least significant bit (LSB) for a set bit (1). _BitScanReverse64(&leading_zero, input_num); return (int)(63 - leading_zero); #else return leading_zeroes_generic(input_num); #endif #else return __builtin_clzll(input_num); #endif } // slow emulation routine for 32-bit fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { return x * (uint64_t)y; } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); uint64_t adbc_carry = (uint64_t)(adbc < ad); uint64_t lo = bd + (adbc << 32); *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + (adbc_carry << 32) + (uint64_t)(lo < bd); return lo; } #ifdef FASTFLOAT_32BIT // slow emulation routine for 32-bit #if !defined(__MINGW64__) fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { return umul128_generic(ab, cd, hi); } #endif // !__MINGW64__ #endif // FASTFLOAT_32BIT // compute 64-bit a*b fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 full_multiplication(uint64_t a, uint64_t b) { if (cpp20_and_in_constexpr()) { value128 answer; answer.low = umul128_generic(a, b, &answer.high); return answer; } value128 answer; #if defined(_M_ARM64) && !defined(__MINGW32__) // ARM64 has native support for 64-bit multiplications, no need to emulate // But MinGW on ARM64 doesn't have native support for 64-bit multiplications answer.high = __umulh(a, b); answer.low = a * b; #elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 #elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) __uint128_t r = ((__uint128_t)a) * b; answer.low = uint64_t(r); answer.high = uint64_t(r >> 64); #else answer.low = umul128_generic(a, b, &answer.high); #endif return answer; } struct adjusted_mantissa { uint64_t mantissa{0}; int32_t power2{0}; // a negative value indicates an invalid result adjusted_mantissa() = default; constexpr bool operator==(const adjusted_mantissa &o) const { return mantissa == o.mantissa && power2 == o.power2; } constexpr bool operator!=(const adjusted_mantissa &o) const { return mantissa != o.mantissa || power2 != o.power2; } }; // Bias so we can get the real exponent with an invalid adjusted_mantissa. constexpr static int32_t invalid_am_bias = -0x8000; // used for binary_format_lookup_tables::max_mantissa constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; template struct binary_format_lookup_tables; template struct binary_format : binary_format_lookup_tables { using equiv_uint = typename std::conditional::type; static inline constexpr int mantissa_explicit_bits(); static inline constexpr int minimum_exponent(); static inline constexpr int infinite_power(); static inline constexpr int sign_index(); static inline constexpr int min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST static inline constexpr int max_exponent_fast_path(); static inline constexpr int max_exponent_round_to_even(); static inline constexpr int min_exponent_round_to_even(); static inline constexpr uint64_t max_mantissa_fast_path(int64_t power); static inline constexpr uint64_t max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST static inline constexpr int largest_power_of_ten(); static inline constexpr int smallest_power_of_ten(); static inline constexpr T exact_power_of_ten(int64_t power); static inline constexpr size_t max_digits(); static inline constexpr equiv_uint exponent_mask(); static inline constexpr equiv_uint mantissa_mask(); static inline constexpr equiv_uint hidden_bit_mask(); }; template struct binary_format_lookup_tables { static constexpr double powers_of_ten[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; // Largest integer value v so that (5**index * v) <= 1<<53. // 0x20000000000000 == 1 << 53 static constexpr uint64_t max_mantissa[] = { 0x20000000000000, 0x20000000000000 / 5, 0x20000000000000 / (5 * 5), 0x20000000000000 / (5 * 5 * 5), 0x20000000000000 / (5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555), 0x20000000000000 / (constant_55555 * 5), 0x20000000000000 / (constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5)}; }; template constexpr double binary_format_lookup_tables::powers_of_ten[]; template constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; template struct binary_format_lookup_tables { static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; // Largest integer value v so that (5**index * v) <= 1<<24. // 0x1000000 == 1<<24 static constexpr uint64_t max_mantissa[] = { 0x1000000, 0x1000000 / 5, 0x1000000 / (5 * 5), 0x1000000 / (5 * 5 * 5), 0x1000000 / (5 * 5 * 5 * 5), 0x1000000 / (constant_55555), 0x1000000 / (constant_55555 * 5), 0x1000000 / (constant_55555 * 5 * 5), 0x1000000 / (constant_55555 * 5 * 5 * 5), 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), 0x1000000 / (constant_55555 * constant_55555), 0x1000000 / (constant_55555 * constant_55555 * 5)}; }; template constexpr float binary_format_lookup_tables::powers_of_ten[]; template constexpr uint64_t binary_format_lookup_tables::max_mantissa[]; template <> inline constexpr int binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else return -22; #endif } template <> inline constexpr int binary_format::min_exponent_fast_path() { #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return 0; #else return -10; #endif } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 52; } template <> inline constexpr int binary_format::mantissa_explicit_bits() { return 23; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 23; } template <> inline constexpr int binary_format::max_exponent_round_to_even() { return 10; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -4; } template <> inline constexpr int binary_format::min_exponent_round_to_even() { return -17; } template <> inline constexpr int binary_format::minimum_exponent() { return -1023; } template <> inline constexpr int binary_format::minimum_exponent() { return -127; } template <> inline constexpr int binary_format::infinite_power() { return 0x7FF; } template <> inline constexpr int binary_format::infinite_power() { return 0xFF; } template <> inline constexpr int binary_format::sign_index() { return 63; } template <> inline constexpr int binary_format::sign_index() { return 31; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 22; } template <> inline constexpr int binary_format::max_exponent_fast_path() { return 10; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that // power >= 0 && power <= 22 // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { return uint64_t(2) << mantissa_explicit_bits(); } template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path(int64_t power) { // caller is responsible to ensure that // power >= 0 && power <= 10 // // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)max_mantissa[0], max_mantissa[power]; } template <> inline constexpr double binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr float binary_format::exact_power_of_ten(int64_t power) { // Work around clang bug https://godbolt.org/z/zedh7rrhc return (void)powers_of_ten[0], powers_of_ten[power]; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 308; } template <> inline constexpr int binary_format::largest_power_of_ten() { return 38; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -342; } template <> inline constexpr int binary_format::smallest_power_of_ten() { return -64; } template <> inline constexpr size_t binary_format::max_digits() { return 769; } template <> inline constexpr size_t binary_format::max_digits() { return 114; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7F800000; } template <> inline constexpr binary_format::equiv_uint binary_format::exponent_mask() { return 0x7FF0000000000000; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x007FFFFF; } template <> inline constexpr binary_format::equiv_uint binary_format::mantissa_mask() { return 0x000FFFFFFFFFFFFF; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x00800000; } template <> inline constexpr binary_format::equiv_uint binary_format::hidden_bit_mask() { return 0x0010000000000000; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void to_float(bool negative, adjusted_mantissa am, T &value) { using fastfloat_uint = typename binary_format::equiv_uint; fastfloat_uint word = (fastfloat_uint)am.mantissa; word |= fastfloat_uint(am.power2) << binary_format::mantissa_explicit_bits(); word |= fastfloat_uint(negative) << binary_format::sign_index(); #if FASTFLOAT_HAS_BIT_CAST value = std::bit_cast(word); #else ::memcpy(&value, &word, sizeof(T)); #endif } #ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default template struct space_lut { static constexpr bool value[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; }; template constexpr bool space_lut::value[]; inline constexpr bool is_space(uint8_t c) { return space_lut<>::value[c]; } #endif template static constexpr uint64_t int_cmp_zeros() { static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), "Unsupported character size"); return (sizeof(UC) == 1) ? 0x3030303030303030 : (sizeof(UC) == 2) ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | uint64_t(UC('0')) << 16 | UC('0')) : (uint64_t(UC('0')) << 32 | UC('0')); } template static constexpr int int_cmp_len() { return sizeof(uint64_t) / sizeof(UC); } template static constexpr UC const *str_const_nan() { return nullptr; } template <> constexpr char const *str_const_nan() { return "nan"; } template <> constexpr wchar_t const *str_const_nan() { return L"nan"; } template <> constexpr char16_t const *str_const_nan() { return u"nan"; } template <> constexpr char32_t const *str_const_nan() { return U"nan"; } template static constexpr UC const *str_const_inf() { return nullptr; } template <> constexpr char const *str_const_inf() { return "infinity"; } template <> constexpr wchar_t const *str_const_inf() { return L"infinity"; } template <> constexpr char16_t const *str_const_inf() { return u"infinity"; } template <> constexpr char32_t const *str_const_inf() { return U"infinity"; } template struct int_luts { static constexpr uint8_t chdigit[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; static constexpr size_t maxdigits_u64[] = { 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; static constexpr uint64_t min_safe_u64[] = { 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, 7450580596923828125, 4738381338321616896, 3909821048582988049, 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, 5559917313492231481, 2218611106740436992, 8650415919381337933, 2177953337809371136, 6568408355712890625, 1152921504606846976, 2862423051509815793, 6746640616477458432, 15181127029874798299ull, 1638400000000000000, 3243919932521508681, 6221821273427820544, 11592836324538749809ull, 876488338465357824, 1490116119384765625, 2481152873203736576, 4052555153018976267, 6502111422497947648, 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, 1152921504606846976, 1667889514952984961, 2386420683693101056, 3379220508056640625, 4738381338321616896}; }; template constexpr uint8_t int_luts::chdigit[]; template constexpr size_t int_luts::maxdigits_u64[]; template constexpr uint64_t int_luts::min_safe_u64[]; template fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { return int_luts<>::chdigit[static_cast(c)]; } fastfloat_really_inline constexpr size_t max_digits_u64(int base) { return int_luts<>::maxdigits_u64[base - 2]; } // If a u64 is exactly max_digits_u64() in length, this is // the value below which it has definitely overflowed. fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { return int_luts<>::min_safe_u64[base - 2]; } } // namespace fast_float #endif #ifndef FASTFLOAT_FAST_FLOAT_H #define FASTFLOAT_FAST_FLOAT_H namespace fast_float { /** * This function parses the character sequence [first,last) for a number. It * parses floating-point numbers expecting a locale-indepent format equivalent * to what is used by std::strtod in the default ("C") locale. The resulting * floating-point value is the closest floating-point values (using either float * or double), using the "round to even" convention for values that would * otherwise fall right in-between two values. That is, we provide exact parsing * according to the IEEE standard. * * Given a successful parse, the pointer (`ptr`) in the returned value is set to * point right after the parsed number, and the `value` referenced is set to the * parsed value. In case of error, the returned `ec` contains a representative * error, otherwise the default (`std::errc()`) value is stored. * * The implementation does not throw and does not allocate memory (e.g., with * `new` or `malloc`). * * Like the C++17 standard, the `fast_float::from_chars` functions take an * optional last argument of the type `fast_float::chars_format`. It is a bitset * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & * fast_float::chars_format::scientific` are set to determine whether we allow * the fixed point and scientific notation respectively. The default is * `fast_float::chars_format::general` which allows both `fixed` and * `scientific`. */ template ())> FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, chars_format fmt = chars_format::general) noexcept; /** * Like from_chars, but accepts an `options` argument to govern number parsing. */ template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept; /** * from_chars for integer types. */ template ())> FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; } // namespace fast_float #endif // FASTFLOAT_FAST_FLOAT_H #ifndef FASTFLOAT_ASCII_NUMBER_H #define FASTFLOAT_ASCII_NUMBER_H #include #include #include #include #include #include #ifdef FASTFLOAT_SSE2 #include #endif #ifdef FASTFLOAT_NEON #include #endif namespace fast_float { template fastfloat_really_inline constexpr bool has_simd_opt() { #ifdef FASTFLOAT_HAS_SIMD return std::is_same::value; #else return false; #endif } // Next function can be micro-optimized, but compilers are entirely // able to optimize it well. template fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { return !(c > UC('9') || c < UC('0')); } fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; } // Read 8 UC into a u64. Truncates UC if not char. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t read8_to_u64(const UC *chars) { if (cpp20_and_in_constexpr() || !std::is_same::value) { uint64_t val = 0; for (int i = 0; i < 8; ++i) { val |= uint64_t(uint8_t(*chars)) << (i * 8); ++chars; } return val; } uint64_t val; ::memcpy(&val, chars, sizeof(uint64_t)); #if FASTFLOAT_IS_BIG_ENDIAN == 1 // Need to read as-if the number was in little-endian order. val = byteswap(val); #endif return val; } #ifdef FASTFLOAT_SSE2 fastfloat_really_inline uint64_t simd_read8_to_u64(const __m128i data) { FASTFLOAT_SIMD_DISABLE_WARNINGS const __m128i packed = _mm_packus_epi16(data, data); #ifdef FASTFLOAT_64BIT return uint64_t(_mm_cvtsi128_si64(packed)); #else uint64_t value; // Visual Studio + older versions of GCC don't support _mm_storeu_si64 _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); return value; #endif FASTFLOAT_SIMD_RESTORE_WARNINGS } fastfloat_really_inline uint64_t simd_read8_to_u64(const char16_t *chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS return simd_read8_to_u64( _mm_loadu_si128(reinterpret_cast(chars))); FASTFLOAT_SIMD_RESTORE_WARNINGS } #elif defined(FASTFLOAT_NEON) fastfloat_really_inline uint64_t simd_read8_to_u64(const uint16x8_t data) { FASTFLOAT_SIMD_DISABLE_WARNINGS uint8x8_t utf8_packed = vmovn_u16(data); return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); FASTFLOAT_SIMD_RESTORE_WARNINGS } fastfloat_really_inline uint64_t simd_read8_to_u64(const char16_t *chars) { FASTFLOAT_SIMD_DISABLE_WARNINGS return simd_read8_to_u64( vld1q_u16(reinterpret_cast(chars))); FASTFLOAT_SIMD_RESTORE_WARNINGS } #endif // FASTFLOAT_SSE2 // MSVC SFINAE is broken pre-VS2017 #if defined(_MSC_VER) && _MSC_VER <= 1900 template #else template ()) = 0> #endif // dummy for compile uint64_t simd_read8_to_u64(UC const *) { return 0; } // credit @aqrit fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t parse_eight_digits_unrolled(uint64_t val) { const uint64_t mask = 0x000000FF000000FF; const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) val -= 0x3030303030303030; val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; return uint32_t(val); } // Call this if chars are definitely 8 digits. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t parse_eight_digits_unrolled(UC const *chars) noexcept { if (cpp20_and_in_constexpr() || !has_simd_opt()) { return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay } return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); } // credit @aqrit fastfloat_really_inline constexpr bool is_made_of_eight_digits_fast(uint64_t val) noexcept { return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & 0x8080808080808080)); } #ifdef FASTFLOAT_HAS_SIMD // Call this if chars might not be 8 digits. // Using this style (instead of is_made_of_eight_digits_fast() then // parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool simd_parse_if_eight_digits_unrolled(const char16_t *chars, uint64_t &i) noexcept { if (cpp20_and_in_constexpr()) { return false; } #ifdef FASTFLOAT_SSE2 FASTFLOAT_SIMD_DISABLE_WARNINGS const __m128i data = _mm_loadu_si128(reinterpret_cast(chars)); // (x - '0') <= 9 // http://0x80.pl/articles/simd-parsing-int-sequences.html const __m128i t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); const __m128i t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); if (_mm_movemask_epi8(t1) == 0) { i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); return true; } else return false; FASTFLOAT_SIMD_RESTORE_WARNINGS #elif defined(FASTFLOAT_NEON) FASTFLOAT_SIMD_DISABLE_WARNINGS const uint16x8_t data = vld1q_u16(reinterpret_cast(chars)); // (x - '0') <= 9 // http://0x80.pl/articles/simd-parsing-int-sequences.html const uint16x8_t t0 = vsubq_u16(data, vmovq_n_u16('0')); const uint16x8_t mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); if (vminvq_u16(mask) == 0xFFFF) { i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); return true; } else return false; FASTFLOAT_SIMD_RESTORE_WARNINGS #else (void)chars; (void)i; return false; #endif // FASTFLOAT_SSE2 } #endif // FASTFLOAT_HAS_SIMD // MSVC SFINAE is broken pre-VS2017 #if defined(_MSC_VER) && _MSC_VER <= 1900 template #else template ()) = 0> #endif // dummy for compile bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { return 0; } template ::value) = 0> fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void loop_parse_if_eight_digits(const UC *&p, const UC *const pend, uint64_t &i) { if (!has_simd_opt()) { return; } while ((std::distance(p, pend) >= 8) && simd_parse_if_eight_digits_unrolled( p, i)) { // in rare cases, this will overflow, but that's ok p += 8; } } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void loop_parse_if_eight_digits(const char *&p, const char *const pend, uint64_t &i) { // optimizes better than parse_if_eight_digits_unrolled() for UC = char. while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(read8_to_u64(p))) { i = i * 100000000 + parse_eight_digits_unrolled(read8_to_u64( p)); // in rare cases, this will overflow, but that's ok p += 8; } } enum class parse_error { no_error, // [JSON-only] The minus sign must be followed by an integer. missing_integer_after_sign, // A sign must be followed by an integer or dot. missing_integer_or_dot_after_sign, // [JSON-only] The integer part must not have leading zeros. leading_zeros_in_integer_part, // [JSON-only] The integer part must have at least one digit. no_digits_in_integer_part, // [JSON-only] If there is a decimal point, there must be digits in the // fractional part. no_digits_in_fractional_part, // The mantissa must have at least one digit. no_digits_in_mantissa, // Scientific notation requires an exponential part. missing_exponential_part, }; template struct parsed_number_string_t { int64_t exponent{0}; uint64_t mantissa{0}; UC const *lastmatch{nullptr}; bool negative{false}; bool valid{false}; bool too_many_digits{false}; // contains the range of the significant digits span integer{}; // non-nullable span fraction{}; // nullable parse_error error{parse_error::no_error}; }; using byte_span = span; using parsed_number_string = parsed_number_string_t; template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t report_parse_error(UC const *p, parse_error error) { parsed_number_string_t answer; answer.valid = false; answer.lastmatch = p; answer.error = error; return answer; } // Assuming that you use no more than 19 digits, this will // parse an ASCII string. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t parse_number_string(UC const *p, UC const *pend, parse_options_t options) noexcept { chars_format const fmt = options.format; UC const decimal_point = options.decimal_point; parsed_number_string_t answer; answer.valid = false; answer.too_many_digits = false; answer.negative = (*p == UC('-')); #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default if ((*p == UC('-')) || (!(fmt & FASTFLOAT_JSONFMT) && *p == UC('+'))) { #else if (*p == UC('-')) { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here #endif ++p; if (p == pend) { return report_parse_error( p, parse_error::missing_integer_or_dot_after_sign); } if (fmt & FASTFLOAT_JSONFMT) { if (!is_integer(*p)) { // a sign must be followed by an integer return report_parse_error(p, parse_error::missing_integer_after_sign); } } else { if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot return report_parse_error( p, parse_error::missing_integer_or_dot_after_sign); } } } UC const *const start_digits = p; uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) while ((p != pend) && is_integer(*p)) { // a multiplication by 10 is cheaper than an arbitrary integer // multiplication i = 10 * i + uint64_t(*p - UC('0')); // might overflow, we will handle the overflow later ++p; } UC const *const end_of_integer_part = p; int64_t digit_count = int64_t(end_of_integer_part - start_digits); answer.integer = span(start_digits, size_t(digit_count)); if (fmt & FASTFLOAT_JSONFMT) { // at least 1 digit in integer part, without leading zeros if (digit_count == 0) { return report_parse_error(p, parse_error::no_digits_in_integer_part); } if ((start_digits[0] == UC('0') && digit_count > 1)) { return report_parse_error(start_digits, parse_error::leading_zeros_in_integer_part); } } int64_t exponent = 0; const bool has_decimal_point = (p != pend) && (*p == decimal_point); if (has_decimal_point) { ++p; UC const *before = p; // can occur at most twice without overflowing, but let it occur more, since // for integers with many digits, digit parsing is the primary bottleneck. loop_parse_if_eight_digits(p, pend, i); while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - UC('0')); ++p; i = i * 10 + digit; // in rare cases, this will overflow, but that's ok } exponent = before - p; answer.fraction = span(before, size_t(p - before)); digit_count -= exponent; } if (fmt & FASTFLOAT_JSONFMT) { // at least 1 digit in fractional part if (has_decimal_point && exponent == 0) { return report_parse_error(p, parse_error::no_digits_in_fractional_part); } } else if (digit_count == 0) { // we must have encountered at least one integer! return report_parse_error(p, parse_error::no_digits_in_mantissa); } int64_t exp_number = 0; // explicit exponential part if (((fmt & chars_format::scientific) && (p != pend) && ((UC('e') == *p) || (UC('E') == *p))) || ((fmt & FASTFLOAT_FORTRANFMT) && (p != pend) && ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || (UC('D') == *p)))) { UC const *location_of_e = p; if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || (UC('D') == *p)) { ++p; } bool neg_exp = false; if ((p != pend) && (UC('-') == *p)) { neg_exp = true; ++p; } else if ((p != pend) && (UC('+') == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) ++p; } if ((p == pend) || !is_integer(*p)) { if (!(fmt & chars_format::fixed)) { // The exponential part is invalid for scientific notation, so it must // be a trailing token for fixed notation. However, fixed notation is // disabled, so report a scientific notation error. return report_parse_error(p, parse_error::missing_exponential_part); } // Otherwise, we will be ignoring the 'e'. p = location_of_e; } else { while ((p != pend) && is_integer(*p)) { uint8_t digit = uint8_t(*p - UC('0')); if (exp_number < 0x10000000) { exp_number = 10 * exp_number + digit; } ++p; } if (neg_exp) { exp_number = -exp_number; } exponent += exp_number; } } else { // If it scientific and not fixed, we have to bail out. if ((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return report_parse_error(p, parse_error::missing_exponential_part); } } answer.lastmatch = p; answer.valid = true; // If we frequently had to deal with long strings of digits, // we could extend our code by using a 128-bit integer instead // of a 64-bit integer. However, this is uncommon. // // We can deal with up to 19 digits. if (digit_count > 19) { // this is uncommon // It is possible that the integer had an overflow. // We have to handle the case where we have 0.0000somenumber. // We need to be mindful of the case where we only have zeroes... // E.g., 0.000000000...000. UC const *start = start_digits; while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { if (*start == UC('0')) { digit_count--; } start++; } if (digit_count > 19) { answer.too_many_digits = true; // Let us start again, this time, avoiding overflows. // We don't need to check if is_integer, since we use the // pre-tokenized spans from above. i = 0; p = answer.integer.ptr; UC const *int_end = p + answer.integer.len(); const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { i = i * 10 + uint64_t(*p - UC('0')); ++p; } if (i >= minimal_nineteen_digit_integer) { // We have a big integers exponent = end_of_integer_part - p + exp_number; } else { // We have a value with a fractional component. p = answer.fraction.ptr; UC const *frac_end = p + answer.fraction.len(); while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { i = i * 10 + uint64_t(*p - UC('0')); ++p; } exponent = answer.fraction.ptr - p + exp_number; } // We have now corrected both exponent and i, to a truncated value } } answer.exponent = exponent; answer.mantissa = i; return answer; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t parse_int_string(UC const *p, UC const *pend, T &value, int base) { from_chars_result_t answer; UC const *const first = p; bool negative = (*p == UC('-')); if (!std::is_signed::value && negative) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default if ((*p == UC('-')) || (*p == UC('+'))) { #else if (*p == UC('-')) { #endif ++p; } UC const *const start_num = p; while (p != pend && *p == UC('0')) { ++p; } const bool has_leading_zeros = p > start_num; UC const *const start_digits = p; uint64_t i = 0; if (base == 10) { loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible } while (p != pend) { uint8_t digit = ch_to_digit(*p); if (digit >= base) { break; } i = uint64_t(base) * i + digit; // might overflow, check this later p++; } size_t digit_count = size_t(p - start_digits); if (digit_count == 0) { if (has_leading_zeros) { value = 0; answer.ec = std::errc(); answer.ptr = p; } else { answer.ec = std::errc::invalid_argument; answer.ptr = first; } return answer; } answer.ptr = p; // check u64 overflow size_t max_digits = max_digits_u64(base); if (digit_count > max_digits) { answer.ec = std::errc::result_out_of_range; return answer; } // this check can be eliminated for all other types, but they will all require // a max_digits(base) equivalent if (digit_count == max_digits && i < min_safe_u64(base)) { answer.ec = std::errc::result_out_of_range; return answer; } // check other types overflow if (!std::is_same::value) { if (i > uint64_t(std::numeric_limits::max()) + uint64_t(negative)) { answer.ec = std::errc::result_out_of_range; return answer; } } if (negative) { #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) #pragma warning(disable : 4146) #endif // this weird workaround is required because: // - converting unsigned to signed when its value is greater than signed max // is UB pre-C++23. // - reinterpret_casting (~i + 1) would work, but it is not constexpr // this is always optimized into a neg instruction (note: T is an integer // type) value = T(-std::numeric_limits::max() - T(i - uint64_t(std::numeric_limits::max()))); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(pop) #endif } else { value = T(i); } answer.ec = std::errc(); return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_FAST_TABLE_H #define FASTFLOAT_FAST_TABLE_H #include namespace fast_float { /** * When mapping numbers from decimal to binary, * we go from w * 10^q to m * 2^p but we have * 10^q = 5^q * 2^q, so effectively * we are trying to match * w * 2^q * 5^q to m * 2^p. Thus the powers of two * are not a concern since they can be represented * exactly using the binary notation, only the powers of five * affect the binary significand. */ /** * The smallest non-zero float (binary64) is 2^-1074. * We take as input numbers of the form w x 10^q where w < 2^64. * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. * However, we have that * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. * Thus it is possible for a number of the form w * 10^-342 where * w is a 64-bit value to be a non-zero floating-point number. ********* * Any number of form w * 10^309 where w>= 1 is going to be * infinite in binary64 so we never need to worry about powers * of 5 greater than 308. */ template struct powers_template { constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); // Powers of five from 5^-342 all the way to 5^308 rounded toward one. constexpr static uint64_t power_of_five_128[number_of_entries] = { 0xeef453d6923bd65a, 0x113faa2906a13b3f, 0x9558b4661b6565f8, 0x4ac7ca59a424c507, 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, 0x91d8a02bb6c10594, 0x79071b9b8a4be869, 0xb64ec836a47146f9, 0x9748e2826cdee284, 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, 0xb208ef855c969f4f, 0xbdbd2d335e51a935, 0xde8b2b66b3bc4723, 0xad2c788035e61382, 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, 0xa9c98d8ccb009506, 0x680efdaf511f18c2, 0xd43bf0effdc0ba48, 0x212bd1b2566def2, 0x84a57695fe98746d, 0x14bb630f7604b57, 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, 0xcf42894a5dce35ea, 0x52064cac828675b9, 0x818995ce7aa0e1b2, 0x7343efebd1940993, 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, 0xca66fa129f9b60a6, 0xd41a26e077774ef6, 0xfd00b897478238d0, 0x8920b098955522b4, 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, 0xc5a890362fddbc62, 0xeb2189f734aa831d, 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, 0x9a6bb0aa55653b2d, 0x47b233c92125366e, 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, 0xf148440a256e2c76, 0xc00670ea43ca250d, 0x96cd2a865764dbca, 0x380406926a5e5728, 0xbc807527ed3e12bc, 0xc605083704f5ecf2, 0xeba09271e88d976b, 0xf7864a44c633682e, 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, 0xb8157268fdae9e4c, 0x5960ea05bad82964, 0xe61acf033d1a45df, 0x6fb92487298e33bd, 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, 0xe0b62e2929aba83c, 0x331acdabfe94de87, 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, 0x892731ac9faf056e, 0xbe311c083a225cd2, 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, 0xd64d3d9db981787d, 0x92cbbccdad5b108, 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, 0xa76c582338ed2621, 0xaf2af2b80af6f24e, 0xd1476e2c07286faa, 0x1af5af660db4aee1, 0x82cca4db847945ca, 0x50d98d9fc890ed4d, 0xa37fce126597973c, 0xe50ff107bab528a0, 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, 0x9faacf3df73609b1, 0x77b191618c54e9ac, 0xc795830d75038c1d, 0xd59df5b9ef6a2417, 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, 0x9becce62836ac577, 0x4ee367f9430aec32, 0xc2e801fb244576d5, 0x229c41f793cda73f, 0xf3a20279ed56d48a, 0x6b43527578c1110f, 0x9845418c345644d6, 0x830a13896b78aaa9, 0xbe5691ef416bd60c, 0x23cc986bc656d553, 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, 0x91376c36d99995be, 0x23100809b9c21fa1, 0xb58547448ffffb2d, 0xabd40a0c2832a78a, 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, 0xdd95317f31c7fa1d, 0x40405643d711d583, 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, 0xad1c8eab5ee43b66, 0xda3243650005eecf, 0xd863b256369d4a40, 0x90bed43e40076a82, 0x873e4f75e2224e68, 0x5a7744a6e804a291, 0xa90de3535aaae202, 0x711515d0a205cb36, 0xd3515c2831559a83, 0xd5a5b44ca873e03, 0x8412d9991ed58091, 0xe858790afe9486c2, 0xa5178fff668ae0b6, 0x626e974dbe39a872, 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, 0xc987434744ac874e, 0xa327ffb266b56220, 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, 0xc4ce17b399107c22, 0xcb550fb4384d21d3, 0xf6019da07f549b2b, 0x7e2a53a146606a48, 0x99c102844f94e0fb, 0x2eda7444cbfc426d, 0xc0314325637a1939, 0xfa911155fefb5308, 0xf03d93eebc589f88, 0x793555ab7eba27ca, 0x96267c7535b763b5, 0x4bc1558b2f3458de, 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, 0xea9c227723ee8bcb, 0x465e15a979c1cadc, 0x92a1958a7675175f, 0xbfacd89ec191ec9, 0xb749faed14125d36, 0xcef980ec671f667b, 0xe51c79a85916f484, 0x82b7e12780e7401a, 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, 0xdfbdcece67006ac9, 0x67a791e093e1d49a, 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, 0xaecc49914078536d, 0x58fae9f773886e18, 0xda7f5bf590966848, 0xaf39a475506a899e, 0x888f99797a5e012d, 0x6d8406c952429603, 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, 0xd0601d8efc57b08b, 0xf13b94daf124da26, 0x823c12795db6ce57, 0x76c53d08d6b70858, 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, 0xf867241c8cc6d4c0, 0xc30163d203c94b62, 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, 0xc21094364dfb5636, 0x985915fc12f542e4, 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, 0xbd8430bd08277231, 0x50c6ff782a838353, 0xece53cec4a314ebd, 0xa4f8bf5635246428, 0x940f4613ae5ed136, 0x871b7795e136be99, 0xb913179899f68584, 0x28e2557b59846e3f, 0xe757dd7ec07426e5, 0x331aeada2fe589cf, 0x9096ea6f3848984f, 0x3ff0d2c85def7621, 0xb4bca50b065abe63, 0xfed077a756b53a9, 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, 0xb080392cc4349dec, 0xbd8d794d96aacfb3, 0xdca04777f541c567, 0xecf0d7a0fc5583a0, 0x89e42caaf9491b60, 0xf41686c49db57244, 0xac5d37d5b79b6239, 0x311c2875c522ced5, 0xd77485cb25823ac7, 0x7d633293366b828b, 0x86a8d39ef77164bc, 0xae5dff9c02033197, 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, 0xd267caa862a12d66, 0xd072df63c324fd7b, 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, 0xa46116538d0deb78, 0x52d9be85f074e608, 0xcd795be870516656, 0x67902e276c921f8b, 0x806bd9714632dff6, 0xba1cd8a3db53b6, 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, 0xfad2a4b13d1b5d6c, 0x796b805720085f81, 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, 0xf4f1b4d515acb93b, 0xee92fb5515482d44, 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, 0xef340a98172aace4, 0x86fb897116c87c34, 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, 0xbae0a846d2195712, 0x8974836059cca109, 0xe998d258869facd7, 0x2bd1a438703fc94b, 0x91ff83775423cc06, 0x7b6306a34627ddcf, 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, 0x8e938662882af53e, 0x547eb47b7282ee9c, 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, 0xae0b158b4738705e, 0x9624ab50b148d445, 0xd98ddaee19068c76, 0x3badd624dd9b0957, 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, 0xd47487cc8470652b, 0x7647c3200069671f, 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, 0xa5fb0a17c777cf09, 0xf468107100525890, 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, 0x81ac1fe293d599bf, 0xc6f14cd848405530, 0xa21727db38cb002f, 0xb8ada00e5a506a7c, 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, 0xfd442e4688bd304a, 0x908f4a166d1da663, 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, 0xf7549530e188c128, 0xd12bee59e68ef47c, 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, 0xc13a148e3032d6e7, 0xe36a52363c1faf01, 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, 0xebdf661791d60f56, 0x111b495b3464ad21, 0x936b9fcebb25c995, 0xcab10dd900beec34, 0xb84687c269ef3bfb, 0x3d5d514f40eea742, 0xe65829b3046b0afa, 0xcb4a5a3112a5112, 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, 0xb3f4e093db73a093, 0x59ed216765690f56, 0xe0f218b8d25088b8, 0x306869c13ec3532c, 0x8c974f7383725573, 0x1e414218c73a13fb, 0xafbd2350644eeacf, 0xe5d1929ef90898fa, 0xdbac6c247d62a583, 0xdf45f746b74abf39, 0x894bc396ce5da772, 0x6b8bba8c328eb783, 0xab9eb47c81f5114f, 0x66ea92f3f326564, 0xd686619ba27255a2, 0xc80a537b0efefebd, 0x8613fd0145877585, 0xbd06742ce95f5f36, 0xa798fc4196e952e7, 0x2c48113823b73704, 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, 0x82ef85133de648c4, 0x9a984d73dbe722fb, 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, 0xcc963fee10b7d1b3, 0x318df905079926a8, 0xffbbcfe994e5c61f, 0xfdf17746497f7052, 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, 0x9c1661a651213e2d, 0x6bea10ca65c084e, 0xc31bfa0fe5698db8, 0x486e494fcff30a62, 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, 0xbe89523386091465, 0xf6bbb397f1135823, 0xee2ba6c0678b597f, 0x746aa07ded582e2c, 0x94db483840b717ef, 0xa8c2a44eb4571cdc, 0xba121a4650e4ddeb, 0x92f34d62616ce413, 0xe896a0d7e51e1566, 0x77b020baf9c81d17, 0x915e2486ef32cd60, 0xace1474dc1d122e, 0xb5b5ada8aaff80b8, 0xd819992132456ba, 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, 0xd89d64d57a607744, 0xe871c7bf077ba8b7, 0x87625f056c7c4a8b, 0x11471cd764ad4972, 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, 0xd389b47879823479, 0x4aff1d108d4ec2c3, 0x843610cb4bf160cb, 0xcedf722a585139ba, 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, 0xce947a3da6a9273e, 0x733d226229feea32, 0x811ccc668829b887, 0x806357d5a3f525f, 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, 0xc5029163f384a931, 0xa9e795e65d4df11, 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, 0x99ea0196163fa42e, 0x504bced1bf8e4e45, 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, 0xf07da27a82c37088, 0x5d767327bb4e5a4c, 0x964e858c91ba2655, 0x3a6a07f8d510f86f, 0xbbe226efb628afea, 0x890489f70a55368b, 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, 0xe55990879ddcaabd, 0xcc420a6a101d0515, 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, 0xb32df8e9f3546564, 0x47939822dc96abf9, 0xdff9772470297ebd, 0x59787e2b93bc56f7, 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, 0xaefae51477a06b03, 0xede622920b6b23f1, 0xdab99e59958885c4, 0xe95fab368e45eced, 0x88b402f7fd75539b, 0x11dbcb0218ebb414, 0xaae103b5fcd2a881, 0xd652bdc29f26a119, 0xd59944a37c0752a2, 0x4be76d3346f0495f, 0x857fcae62d8493a5, 0x6f70a4400c562ddb, 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, 0x825ecc24c873782f, 0x8ed400668c0c28c8, 0xa2f67f2dfa90563b, 0x728900802f0f32fa, 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, 0xfea126b7d78186bc, 0xe2f610c84987bfa8, 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, 0xc6ede63fa05d3143, 0x91503d1c79720dbb, 0xf8a95fcf88747d94, 0x75a44c6397ce912a, 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, 0xc24452da229b021b, 0xfbe85badce996168, 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, 0x97c560ba6b0919a5, 0xdccd879fc967d41a, 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, 0xed246723473e3813, 0x290123e9aab23b68, 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, 0xb94470938fa89bce, 0xf808e40e8d5b3e69, 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, 0x8d590723948a535f, 0x579c487e5a38ad0e, 0xb0af48ec79ace837, 0x2d835a9df0c6d851, 0xdcdb1b2798182244, 0xf8e431456cf88e65, 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, 0xa87fea27a539e9a5, 0x3f2398d747b36224, 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, 0x83a3eeeef9153e89, 0x1953cf68300424ac, 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, 0xcdb02555653131b6, 0x3792f412cb06794d, 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, 0xc8de047564d20a8b, 0xf245825a5a445275, 0xfb158592be068d2e, 0xeed6e2f0f0d56712, 0x9ced737bb6c4183d, 0x55464dd69685606b, 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, 0xf53304714d9265df, 0xd53dd99f4b3066a8, 0x993fe2c6d07b7fab, 0xe546a8038efe4029, 0xbf8fdb78849a5f96, 0xde98520472bdd033, 0xef73d256a5c0f77c, 0x963e66858f6d4440, 0x95a8637627989aad, 0xdde7001379a44aa8, 0xbb127c53b17ec159, 0x5560c018580d5d52, 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, 0x9226712162ab070d, 0xcab3961304ca70e8, 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, 0xb267ed1940f1c61c, 0x55f038b237591ed3, 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, 0xae397d8aa96c1b77, 0xabec975e0a0d081a, 0xd9c7dced53c72255, 0x96e7bd358c904a21, 0x881cea14545c7575, 0x7e50d64177da2e54, 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, 0xcfb11ead453994ba, 0x67de18eda5814af2, 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, 0xa2425ff75e14fc31, 0xa1258379a94d028d, 0xcad2f7f5359a3b3e, 0x96ee45813a04330, 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, 0x9e74d1b791e07e48, 0x775ea264cf55347e, 0xc612062576589dda, 0x95364afe032a819e, 0xf79687aed3eec551, 0x3a83ddbd83f52205, 0x9abe14cd44753b52, 0xc4926a9672793543, 0xc16d9a0095928a27, 0x75b7053c0f178294, 0xf1c90080baf72cb1, 0x5324c68b12dd6339, 0x971da05074da7bee, 0xd3f6fc16ebca5e04, 0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, 0x9392ee8e921d5d07, 0x3aff322e62439fd0, 0xb877aa3236a4b449, 0x9befeb9fad487c3, 0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, 0xb424dc35095cd80f, 0x538484c19ef38c95, 0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749, 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597, 0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0xfcf80dc33721d54, 0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b, 0x645a1cac083126ea, 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, 0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000, 0x0, 0xa000000000000000, 0x0, 0xc800000000000000, 0x0, 0xfa00000000000000, 0x0, 0x9c40000000000000, 0x0, 0xc350000000000000, 0x0, 0xf424000000000000, 0x0, 0x9896800000000000, 0x0, 0xbebc200000000000, 0x0, 0xee6b280000000000, 0x0, 0x9502f90000000000, 0x0, 0xba43b74000000000, 0x0, 0xe8d4a51000000000, 0x0, 0x9184e72a00000000, 0x0, 0xb5e620f480000000, 0x0, 0xe35fa931a0000000, 0x0, 0x8e1bc9bf04000000, 0x0, 0xb1a2bc2ec5000000, 0x0, 0xde0b6b3a76400000, 0x0, 0x8ac7230489e80000, 0x0, 0xad78ebc5ac620000, 0x0, 0xd8d726b7177a8000, 0x0, 0x878678326eac9000, 0x0, 0xa968163f0a57b400, 0x0, 0xd3c21bcecceda100, 0x0, 0x84595161401484a0, 0x0, 0xa56fa5b99019a5c8, 0x0, 0xcecb8f27f4200f3a, 0x0, 0x813f3978f8940984, 0x4000000000000000, 0xa18f07d736b90be5, 0x5000000000000000, 0xc9f2c9cd04674ede, 0xa400000000000000, 0xfc6f7c4045812296, 0x4d00000000000000, 0x9dc5ada82b70b59d, 0xf020000000000000, 0xc5371912364ce305, 0x6c28000000000000, 0xf684df56c3e01bc6, 0xc732000000000000, 0x9a130b963a6c115c, 0x3c7f400000000000, 0xc097ce7bc90715b3, 0x4b9f100000000000, 0xf0bdc21abb48db20, 0x1e86d40000000000, 0x96769950b50d88f4, 0x1314448000000000, 0xbc143fa4e250eb31, 0x17d955a000000000, 0xeb194f8e1ae525fd, 0x5dcfab0800000000, 0x92efd1b8d0cf37be, 0x5aa1cae500000000, 0xb7abc627050305ad, 0xf14a3d9e40000000, 0xe596b7b0c643c719, 0x6d9ccd05d0000000, 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xb35dbf821ae4f38b, 0xdda2802c8a800000, 0xe0352f62a19e306e, 0xd50b2037ad200000, 0x8c213d9da502de45, 0x4526f422cc340000, 0xaf298d050e4395d6, 0x9670b12b7f410000, 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, 0xab0e93b6efee0053, 0x8eea0d047a457a00, 0xd5d238a4abe98068, 0x72a4904598d6d880, 0x85a36366eb71f041, 0x47a6da2b7f864750, 0xa70c3c40a64e6c51, 0x999090b65f67d924, 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, 0x82818f1281ed449f, 0xbff8f10e7a8921a4, 0xa321f2d7226895c7, 0xaff72d52192b6a0d, 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, 0xfee50b7025c36a08, 0x2f236d04753d5b4, 0x9f4f2726179a2245, 0x1d762422c946590, 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, 0xf316271c7fc3908a, 0x8bef464e3945ef7a, 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, 0xe7d34c64a9c85d44, 0x60dbbca87196b616, 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, 0xb51d13aea4a488dd, 0x6babab6398bdbe41, 0xe264589a4dcdab14, 0xc696963c7eed2dd1, 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, 0xdd15fe86affad912, 0x49ef0eb713f39ebe, 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, 0xacb92ed9397bf996, 0x49c2c37f07965404, 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, 0xd2d80db02aabd62b, 0xf50a3fa490c30190, 0x83c7088e1aab65db, 0x792667c6da79e0fa, 0xa4b8cab1a1563f52, 0x577001b891185938, 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, 0x80b05e5ac60b6178, 0x544f8158315b05b4, 0xa0dc75f1778e39d6, 0x696361ae3db1c721, 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, 0xfb5878494ace3a5f, 0x4ab48a04065c723, 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, 0xf5746577930d6500, 0xca8f44ec7ee36479, 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, 0x95d04aee3b80ece5, 0xbba1f1d158724a12, 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, 0xea1575143cf97226, 0xf52d09d71a3293bd, 0x924d692ca61be758, 0x593c2626705f9c56, 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, 0xe498f455c38b997a, 0xb6dfb9c0f956447, 0x8edf98b59a373fec, 0x4724bd4189bd5eac, 0xb2977ee300c50fe7, 0x58edec91ec2cb657, 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, 0x8b865b215899f46c, 0xbd79e0d20082ee74, 0xae67f1e9aec07187, 0xecd8590680a3aa11, 0xda01ee641a708de9, 0xe80e6f4820cc9495, 0x884134fe908658b2, 0x3109058d147fdcdd, 0xaa51823e34a7eede, 0xbd4b46f0599fd415, 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, 0x850fadc09923329e, 0x3e2cf6bc604ddb0, 0xa6539930bf6bff45, 0x84db8346b786151c, 0xcfe87f7cef46ff16, 0xe612641865679a63, 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, 0xa26da3999aef7749, 0xe3be5e330f38f09d, 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, 0xc646d63501a1511d, 0xb281e1fd541501b8, 0xf7d88bc24209a565, 0x1f225a7ca91a4226, 0x9ae757596946075f, 0x3375788de9b06958, 0xc1a12d2fc3978937, 0x52d6b1641c83ae, 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, 0x9745eb4d50ce6332, 0xf840b7ba963646e0, 0xbd176620a501fbff, 0xb650e5a93bc3d898, 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, 0x93ba47c980e98cdf, 0xc66f336c36b10137, 0xb8a8d9bbe123f017, 0xb80b0047445d4184, 0xe6d3102ad96cec1d, 0xa60dc059157491e5, 0x9043ea1ac7e41392, 0x87c89837ad68db2f, 0xb454e4a179dd1877, 0x29babe4598c311fb, 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, 0xdc21a1171d42645d, 0x76707543f4fa1f73, 0x899504ae72497eba, 0x6a06494a791c53a8, 0xabfa45da0edbde69, 0x487db9d17636892, 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, 0xa7f26836f282b732, 0x8e6cac7768d7141e, 0xd1ef0244af2364ff, 0x3207d795430cd926, 0x8335616aed761f1f, 0x7f44e6bd49e807b8, 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, 0xcd036837130890a1, 0x36dba887c37a8c0f, 0x802221226be55a64, 0xc2494954da2c9789, 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, 0xc83553c5c8965d3d, 0x6f92829494e5acc7, 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, 0x9c69a97284b578d7, 0xff2a760414536efb, 0xc38413cf25e2d70d, 0xfef5138519684aba, 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, 0x98bf2f79d5993802, 0xef2f773ffbd97a61, 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, 0x952ab45cfa97a0b2, 0xdd945a747bf26183, 0xba756174393d88df, 0x94f971119aeef9e4, 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, 0x91abb422ccb812ee, 0xac62e055c10ab33a, 0xb616a12b7fe617aa, 0x577b986b314d6009, 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, 0x8e41ade9fbebc27d, 0x14588f13be847307, 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, 0x8aec23d680043bee, 0x25de7bb9480d5854, 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, 0xd910f7ff28069da4, 0x1b2ba1518094da04, 0x87aa9aff79042286, 0x90fb44d2f05d0842, 0xa99541bf57452b28, 0x353a1607ac744a53, 0xd3fa922f2d1675f2, 0x42889b8997915ce8, 0x847c9b5d7c2e09b7, 0x69956135febada11, 0xa59bc234db398c25, 0x43fab9837e699095, 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, 0x8161afb94b44f57d, 0x1d1be0eebac278f5, 0xa1ba1ba79e1632dc, 0x6462d92a69731732, 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, 0xfcb2cb35e702af78, 0x5cda735244c3d43e, 0x9defbf01b061adab, 0x3a0888136afa64a7, 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, 0xf6c69a72a3989f5b, 0x8aad549e57273d45, 0x9a3c2087a63f6399, 0x36ac54e2f678864b, 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, 0x969eb7c47859e743, 0x9f644ae5a4b1b325, 0xbc4665b596706114, 0x873d5d9f0dde1fee, 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, 0x8fa475791a569d10, 0xf96e017d694487bc, 0xb38d92d760ec4455, 0x37c981dcc395a9ac, 0xe070f78d3927556a, 0x85bbe253f47b1417, 0x8c469ab843b89562, 0x93956d7478ccec8e, 0xaf58416654a6babb, 0x387ac8d1970027b2, 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, 0x88fcf317f22241e2, 0x441fece3bdf81f03, 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, 0x85c7056562757456, 0xf6872d5667844e49, 0xa738c6bebb12d16c, 0xb428f8ac016561db, 0xd106f86e69d785c7, 0xe13336d701beba52, 0x82a45b450226b39c, 0xecc0024661173473, 0xa34d721642b06084, 0x27f002d7f95d0190, 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, 0xff290242c83396ce, 0x7e67047175a15271, 0x9f79a169bd203e41, 0xf0062c6e984d386, 0xc75809c42c684dd1, 0x52c07b78a3e60868, 0xf92e0c3537826145, 0xa7709a56ccdf8a82, 0x9bbcc7a142b17ccb, 0x88a66076400bb691, 0xc2abf989935ddbfe, 0x6acff893d00ea435, 0xf356f7ebf83552fe, 0x583f6b8c4124d43, 0x98165af37b2153de, 0xc3727a337a8b704a, 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, 0xeda2ee1c7064130c, 0x1162def06f79df73, 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, 0x8da471a9de737e24, 0x5ceaecfed289e5d2, 0xb10d8e1456105dad, 0x7425a83e872c5f47, 0xdd50f1996b947518, 0xd12f124e28f77719, 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, 0x8714a775e3e95c78, 0x65acfaec34810a71, 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, 0xd31045a8341ca07c, 0x1ede48111209a050, 0x83ea2b892091e44d, 0x934aed0aab460432, 0xa4e4b66b68b65d60, 0xf81da84d5617853f, 0xce1de40642e3f4b9, 0x36251260ab9d668e, 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, 0xa1075a24e4421730, 0xb24cf65b8612f81f, 0xc94930ae1d529cfc, 0xdee033f26797b627, 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, 0x9d412e0806e88aa5, 0x8e1f289560ee864e, 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, 0xea53df5fd18d5513, 0x84c86189216dc5ed, 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, 0xe4d5e82392a40515, 0xfabaf3feaa5334a, 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, 0xb2c71d5bca9023f8, 0x743e20e9ef511012, 0xdf78e4b2bd342cf6, 0x914da9246b255416, 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, 0xae9672aba3d0c320, 0xa184ac2473b529b1, 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, 0x8865899617fb1871, 0x7e2fa67c7a658892, 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, 0xd51ea6fa85785631, 0x552a74227f3ea565, 0x8533285c936b35de, 0xd53a88958f87275f, 0xa67ff273b8460356, 0x8a892abaf368f137, 0xd01fef10a657842c, 0x2d2b7569b0432d85, 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, 0xcb3f2f7642717713, 0x241c70a936219a73, 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, 0x9ec95d1463e8a506, 0xf4363804324a40aa, 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, 0x976e41088617ca01, 0xd5be0503e085d813, 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, 0x93e1ab8252f33b45, 0xcabb90e5c942b503, 0xb8da1662e7b00a17, 0x3d6a751f3b936243, 0xe7109bfba19c0c9d, 0xcc512670a783ad4, 0x906a617d450187e2, 0x27fb2b80668b24c5, 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, 0xe1a63853bbd26451, 0x5e7873f8a0396973, 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, 0xac2820d9623bf429, 0x546345fa9fbdcd44, 0xd732290fbacaf133, 0xa97c177947ad4095, 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, 0xa81f301449ee8c70, 0x5c68f256bfff5a74, 0xd226fc195c6a2f8c, 0x73832eec6fff3111, 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, 0xa0555e361951c366, 0xd7e105bcc332621f, 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, 0xfa856334878fc150, 0xb14f98f6f0feb951, 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, 0xc3b8358109e84f07, 0xa862f80ec4700c8, 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, 0xeeea5d5004981478, 0x1858ccfce06cac74, 0x95527a5202df0ccb, 0xf37801e0c43ebc8, 0xbaa718e68396cffd, 0xd30560258f54e6ba, 0xe950df20247c83fd, 0x47c6b82ef32a2069, 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, 0xb6472e511c81471d, 0xe0133fe4adf8e952, 0xe3d8f9e563a198e5, 0x58180fddd97723a6, 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, }; }; template constexpr uint64_t powers_template::power_of_five_128[number_of_entries]; using powers = powers_template<>; } // namespace fast_float #endif #ifndef FASTFLOAT_DECIMAL_TO_BINARY_H #define FASTFLOAT_DECIMAL_TO_BINARY_H #include #include #include #include #include #include namespace fast_float { // This will compute or rather approximate w * 5**q and return a pair of 64-bit // words approximating the result, with the "high" part corresponding to the // most significant bits and the low part corresponding to the least significant // bits. // template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 compute_product_approximation(int64_t q, uint64_t w) { const int index = 2 * int(q - powers::smallest_power_of_five); // For small values of q, e.g., q in [0,27], the answer is always exact // because The line value128 firstproduct = full_multiplication(w, // power_of_five_128[index]); gives the exact answer. value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); constexpr uint64_t precision_mask = (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) : uint64_t(0xFFFFFFFFFFFFFFFF); if ((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) // regarding the second product, we only need secondproduct.high, but our // expectation is that the compiler will optimize this extra work away if // needed. value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); firstproduct.low += secondproduct.high; if (secondproduct.high > firstproduct.low) { firstproduct.high++; } } return firstproduct; } namespace detail { /** * For q in (0,350), we have that * f = (((152170 + 65536) * q ) >> 16); * is equal to * floor(p) + q * where * p = log(5**q)/log(2) = q * log(5)/log(2) * * For negative values of q in (-400,0), we have that * f = (((152170 + 65536) * q ) >> 16); * is equal to * -ceil(p) + q * where * p = log(5**-q)/log(2) = -q * log(5)/log(2) */ constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { return (((152170 + 65536) * q) >> 16) + 63; } } // namespace detail // create an adjusted mantissa, biased by the invalid power2 // for significant digits already multiplied by 10 ** q. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { int hilz = int(w >> 63) ^ 1; adjusted_mantissa answer; answer.mantissa = w << hilz; int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); return answer; } // w * 10 ** q, without rounding the representation up. // the power2 in the exponent will be adjusted by invalid_am_bias. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { int lz = leading_zeroes(w); w <<= lz; value128 product = compute_product_approximation(q, w); return compute_error_scaled(q, product.high, lz); } // w * 10 ** q // The returned value should be a valid ieee64 number that simply need to be // packed. However, in some very rare cases, the computation will fail. In such // cases, we return an adjusted_mantissa with a negative power of 2: the caller // should recompute in such cases. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { adjusted_mantissa answer; if ((w == 0) || (q < binary::smallest_power_of_ten())) { answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } if (q > binary::largest_power_of_ten()) { // we want to get infinity: answer.power2 = binary::infinite_power(); answer.mantissa = 0; return answer; } // At this point in time q is in [powers::smallest_power_of_five, // powers::largest_power_of_five]. // We want the most significant bit of i to be 1. Shift if needed. int lz = leading_zeroes(w); w <<= lz; // The required precision is binary::mantissa_explicit_bits() + 3 because // 1. We need the implicit bit // 2. We need an extra bit for rounding purposes // 3. We might lose a bit due to the "upperbit" routine (result too small, // requiring a shift) value128 product = compute_product_approximation(q, w); // The computed 'product' is always sufficient. // Mathematical proof: // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to // appear) See script/mushtak_lemire.py // The "compute_product_approximation" function can be slightly slower than a // branchless approach: value128 product = compute_product(q, w); but in // practice, we can win big with the compute_product_approximation if its // additional branch is easily predicted. Which is best is data specific. int upperbit = int(product.high >> 63); int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; answer.mantissa = product.high >> shift; answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); if (answer.power2 <= 0) { // we have a subnormal? // Here have that answer.power2 <= 0 so -answer.power2 >= 0 if (-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you // have a zero for sure. answer.power2 = 0; answer.mantissa = 0; // result should be zero return answer; } // next line is safe because -answer.power2 + 1 < 64 answer.mantissa >>= -answer.power2 + 1; // Thankfully, we can't have both "round-to-even" and subnormals because // "round-to-even" only occurs for powers close to 0. answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; // There is a weird scenario where we don't have a subnormal but just. // Suppose we start with 2.2250738585072013e-308, we end up // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer // subnormal, but we can only know this after rounding. // So we only declare a subnormal if we are smaller than the threshold. answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; return answer; } // usually, we round *up*, but if we fall right in between and and we have an // even basis, we need to round down // We are only concerned with the cases where 5**q fits in single 64-bit word. if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && ((answer.mantissa & 3) == 1)) { // we may fall between two floats! // To be in-between two floats we need that in doing // answer.mantissa = product.high >> (upperbit + 64 - // binary::mantissa_explicit_bits() - 3); // ... we dropped out only zeroes. But if this happened, then we can go // back!!! if ((answer.mantissa << shift) == product.high) { answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up } } answer.mantissa += (answer.mantissa & 1); // round up answer.mantissa >>= 1; if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); answer.power2++; // undo previous addition } answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); if (answer.power2 >= binary::infinite_power()) { // infinity answer.power2 = binary::infinite_power(); answer.mantissa = 0; } return answer; } } // namespace fast_float #endif #ifndef FASTFLOAT_BIGINT_H #define FASTFLOAT_BIGINT_H #include #include #include #include namespace fast_float { // the limb width: we want efficient multiplication of double the bits in // limb, or for 64-bit limbs, at least 64-bit multiplication where we can // extract the high and low parts efficiently. this is every 64-bit // architecture except for sparc, which emulates 128-bit multiplication. // we might have platforms where `CHAR_BIT` is not 8, so let's avoid // doing `8 * sizeof(limb)`. #if defined(FASTFLOAT_64BIT) && !defined(__sparc) #define FASTFLOAT_64BIT_LIMB 1 typedef uint64_t limb; constexpr size_t limb_bits = 64; #else #define FASTFLOAT_32BIT_LIMB typedef uint32_t limb; constexpr size_t limb_bits = 32; #endif typedef span limb_span; // number of bits in a bigint. this needs to be at least the number // of bits required to store the largest bigint, which is // `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or // ~3600 bits, so we round to 4000. constexpr size_t bigint_bits = 4000; constexpr size_t bigint_limbs = bigint_bits / limb_bits; // vector-like type that is allocated on the stack. the entire // buffer is pre-allocated, and only the length changes. template struct stackvec { limb data[size]; // we never need more than 150 limbs uint16_t length{0}; stackvec() = default; stackvec(const stackvec &) = delete; stackvec &operator=(const stackvec &) = delete; stackvec(stackvec &&) = delete; stackvec &operator=(stackvec &&other) = delete; // create stack vector from existing limb span. FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { FASTFLOAT_ASSERT(try_extend(s)); } FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); return data[index]; } // index from the end of the container FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { FASTFLOAT_DEBUG_ASSERT(index < length); size_t rindex = length - index - 1; return data[rindex]; } // set the length, without bounds checking. FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { length = uint16_t(len); } constexpr size_t len() const noexcept { return length; } constexpr bool is_empty() const noexcept { return length == 0; } constexpr size_t capacity() const noexcept { return size; } // append item to vector, without bounds checking FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { data[length] = value; length++; } // append item to vector, returning if item was added FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { if (len() < capacity()) { push_unchecked(value); return true; } else { return false; } } // add items to the vector, from a span, without bounds checking FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { limb *ptr = data + length; std::copy_n(s.ptr, s.len(), ptr); set_len(len() + s.len()); } // try to add items to the vector, returning if items were added FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { if (len() + s.len() <= capacity()) { extend_unchecked(s); return true; } else { return false; } } // resize the vector, without bounds checking // if the new size is longer than the vector, assign value to each // appended item. FASTFLOAT_CONSTEXPR20 void resize_unchecked(size_t new_len, limb value) noexcept { if (new_len > len()) { size_t count = new_len - len(); limb *first = data + len(); limb *last = first + count; ::std::fill(first, last, value); set_len(new_len); } else { set_len(new_len); } } // try to resize the vector, returning if the vector was resized. FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { if (new_len > capacity()) { return false; } else { resize_unchecked(new_len, value); return true; } } // check if any limbs are non-zero after the given index. // this needs to be done in reverse order, since the index // is relative to the most significant limbs. FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { while (index < len()) { if (rindex(index) != 0) { return true; } index++; } return false; } // normalize the big integer, so most-significant zero limbs are removed. FASTFLOAT_CONSTEXPR14 void normalize() noexcept { while (len() > 0 && rindex(0) == 0) { length--; } } }; fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t empty_hi64(bool &truncated) noexcept { truncated = false; return 0; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint64_hi64(uint64_t r0, bool &truncated) noexcept { truncated = false; int shl = leading_zeroes(r0); return r0 << shl; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { int shl = leading_zeroes(r0); if (shl == 0) { truncated = r1 != 0; return r0; } else { int shr = 64 - shl; truncated = (r1 << shl) != 0; return (r0 << shl) | (r1 >> shr); } } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint32_hi64(uint32_t r0, bool &truncated) noexcept { return uint64_hi64(r0, truncated); } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { uint64_t x0 = r0; uint64_t x1 = r1; return uint64_hi64((x0 << 32) | x1, truncated); } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { uint64_t x0 = r0; uint64_t x1 = r1; uint64_t x2 = r2; return uint64_hi64(x0, (x1 << 32) | x2, truncated); } // add two small integers, checking for overflow. // we want an efficient operation. for msvc, where // we don't have built-in intrinsics, this is still // pretty fast. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb scalar_add(limb x, limb y, bool &overflow) noexcept { limb z; // gcc and clang #if defined(__has_builtin) #if __has_builtin(__builtin_add_overflow) if (!cpp20_and_in_constexpr()) { overflow = __builtin_add_overflow(x, y, &z); return z; } #endif #endif // generic, this still optimizes correctly on MSVC. z = x + y; overflow = z < x; return z; } // multiply two small integers, getting both the high and low bits. fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb scalar_mul(limb x, limb y, limb &carry) noexcept { #ifdef FASTFLOAT_64BIT_LIMB #if defined(__SIZEOF_INT128__) // GCC and clang both define it as an extension. __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); carry = limb(z >> limb_bits); return limb(z); #else // fallback, no native 128-bit integer multiplication with carry. // on msvc, this optimizes identically, somehow. value128 z = full_multiplication(x, y); bool overflow; z.low = scalar_add(z.low, carry, overflow); z.high += uint64_t(overflow); // cannot overflow carry = z.high; return z.low; #endif #else uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); carry = limb(z >> limb_bits); return limb(z); #endif } // add scalar value to bigint starting from offset. // used in grade school multiplication template inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec &vec, limb y, size_t start) noexcept { size_t index = start; limb carry = y; bool overflow; while (carry != 0 && index < vec.len()) { vec[index] = scalar_add(vec[index], carry, overflow); carry = limb(overflow); index += 1; } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); } return true; } // add scalar value to bigint. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool small_add(stackvec &vec, limb y) noexcept { return small_add_from(vec, y, 0); } // multiply bigint by scalar value. template inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec &vec, limb y) noexcept { limb carry = 0; for (size_t index = 0; index < vec.len(); index++) { vec[index] = scalar_mul(vec[index], y, carry); } if (carry != 0) { FASTFLOAT_TRY(vec.try_push(carry)); } return true; } // add bigint to bigint starting from index. // used in grade school multiplication template FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y, size_t start) noexcept { // the effective x buffer is from `xstart..x.len()`, so exit early // if we can't get that current range. if (x.len() < start || y.len() > x.len() - start) { FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); } bool carry = false; for (size_t index = 0; index < y.len(); index++) { limb xi = x[index + start]; limb yi = y[index]; bool c1 = false; bool c2 = false; xi = scalar_add(xi, yi, c1); if (carry) { xi = scalar_add(xi, 1, c2); } x[index + start] = xi; carry = c1 | c2; } // handle overflow if (carry) { FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); } return true; } // add bigint to bigint. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec &x, limb_span y) noexcept { return large_add_from(x, y, 0); } // grade-school multiplication algorithm template FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec &x, limb_span y) noexcept { limb_span xs = limb_span(x.data, x.len()); stackvec z(xs); limb_span zs = limb_span(z.data, z.len()); if (y.len() != 0) { limb y0 = y[0]; FASTFLOAT_TRY(small_mul(x, y0)); for (size_t index = 1; index < y.len(); index++) { limb yi = y[index]; stackvec zi; if (yi != 0) { // re-use the same buffer throughout zi.set_len(0); FASTFLOAT_TRY(zi.try_extend(zs)); FASTFLOAT_TRY(small_mul(zi, yi)); limb_span zis = limb_span(zi.data, zi.len()); FASTFLOAT_TRY(large_add_from(x, zis, index)); } } } x.normalize(); return true; } // grade-school multiplication algorithm template FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec &x, limb_span y) noexcept { if (y.len() == 1) { FASTFLOAT_TRY(small_mul(x, y[0])); } else { FASTFLOAT_TRY(long_mul(x, y)); } return true; } template struct pow5_tables { static constexpr uint32_t large_step = 135; static constexpr uint64_t small_power_of_5[] = { 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, }; #ifdef FASTFLOAT_64BIT_LIMB constexpr static limb large_power_of_5[] = { 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, 10482974169319127550UL, 198276706040285095UL}; #else constexpr static limb large_power_of_5[] = { 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; #endif }; template constexpr uint32_t pow5_tables::large_step; template constexpr uint64_t pow5_tables::small_power_of_5[]; template constexpr limb pow5_tables::large_power_of_5[]; // big integer type. implements a small subset of big integer // arithmetic, using simple algorithms since asymptotically // faster algorithms are slower for a small number of limbs. // all operations assume the big-integer is normalized. struct bigint : pow5_tables<> { // storage of the limbs, in little-endian order. stackvec vec; FASTFLOAT_CONSTEXPR20 bigint() : vec() {} bigint(const bigint &) = delete; bigint &operator=(const bigint &) = delete; bigint(bigint &&) = delete; bigint &operator=(bigint &&other) = delete; FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { #ifdef FASTFLOAT_64BIT_LIMB vec.push_unchecked(value); #else vec.push_unchecked(uint32_t(value)); vec.push_unchecked(uint32_t(value >> 32)); #endif vec.normalize(); } // get the high 64 bits from the vector, and if bits were truncated. // this is to get the significant digits for the float. FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { #ifdef FASTFLOAT_64BIT_LIMB if (vec.len() == 0) { return empty_hi64(truncated); } else if (vec.len() == 1) { return uint64_hi64(vec.rindex(0), truncated); } else { uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); truncated |= vec.nonzero(2); return result; } #else if (vec.len() == 0) { return empty_hi64(truncated); } else if (vec.len() == 1) { return uint32_hi64(vec.rindex(0), truncated); } else if (vec.len() == 2) { return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); } else { uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); truncated |= vec.nonzero(3); return result; } #endif } // compare two big integers, returning the large value. // assumes both are normalized. if the return value is // negative, other is larger, if the return value is // positive, this is larger, otherwise they are equal. // the limbs are stored in little-endian order, so we // must compare the limbs in ever order. FASTFLOAT_CONSTEXPR20 int compare(const bigint &other) const noexcept { if (vec.len() > other.vec.len()) { return 1; } else if (vec.len() < other.vec.len()) { return -1; } else { for (size_t index = vec.len(); index > 0; index--) { limb xi = vec[index - 1]; limb yi = other.vec[index - 1]; if (xi > yi) { return 1; } else if (xi < yi) { return -1; } } return 0; } } // shift left each limb n bits, carrying over to the new limb // returns true if we were able to shift all the digits. FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { // Internally, for each item, we shift left by n, and add the previous // right shifted limb-bits. // For example, we transform (for u8) shifted left 2, to: // b10100100 b01000010 // b10 b10010001 b00001000 FASTFLOAT_DEBUG_ASSERT(n != 0); FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); size_t shl = n; size_t shr = limb_bits - shl; limb prev = 0; for (size_t index = 0; index < vec.len(); index++) { limb xi = vec[index]; vec[index] = (xi << shl) | (prev >> shr); prev = xi; } limb carry = prev >> shr; if (carry != 0) { return vec.try_push(carry); } return true; } // move the limbs left by `n` limbs. FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { FASTFLOAT_DEBUG_ASSERT(n != 0); if (n + vec.len() > vec.capacity()) { return false; } else if (!vec.is_empty()) { // move limbs limb *dst = vec.data + n; const limb *src = vec.data; std::copy_backward(src, src + vec.len(), dst + vec.len()); // fill in empty limbs limb *first = vec.data; limb *last = first + n; ::std::fill(first, last, 0); vec.set_len(n + vec.len()); return true; } else { return true; } } // move the limbs left by `n` bits. FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { size_t rem = n % limb_bits; size_t div = n / limb_bits; if (rem != 0) { FASTFLOAT_TRY(shl_bits(rem)); } if (div != 0) { FASTFLOAT_TRY(shl_limbs(div)); } return true; } // get the number of leading zeros in the bigint. FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { if (vec.is_empty()) { return 0; } else { #ifdef FASTFLOAT_64BIT_LIMB return leading_zeroes(vec.rindex(0)); #else // no use defining a specialized leading_zeroes for a 32-bit type. uint64_t r0 = vec.rindex(0); return leading_zeroes(r0 << 32); #endif } } // get the number of bits in the bigint. FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { int lz = ctlz(); return int(limb_bits * vec.len()) - lz; } FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } // multiply as if by 2 raised to a power. FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } // multiply as if by 5 raised to a power. FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { // multiply by a power of 5 size_t large_length = sizeof(large_power_of_5) / sizeof(limb); limb_span large = limb_span(large_power_of_5, large_length); while (exp >= large_step) { FASTFLOAT_TRY(large_mul(vec, large)); exp -= large_step; } #ifdef FASTFLOAT_64BIT_LIMB uint32_t small_step = 27; limb max_native = 7450580596923828125UL; #else uint32_t small_step = 13; limb max_native = 1220703125U; #endif while (exp >= small_step) { FASTFLOAT_TRY(small_mul(vec, max_native)); exp -= small_step; } if (exp != 0) { // Work around clang bug https://godbolt.org/z/zedh7rrhc // This is similar to https://github.com/llvm/llvm-project/issues/47746, // except the workaround described there don't work here FASTFLOAT_TRY(small_mul( vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); } return true; } // multiply as if by 10 raised to a power. FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { FASTFLOAT_TRY(pow5(exp)); return pow2(exp); } }; } // namespace fast_float #endif #ifndef FASTFLOAT_DIGIT_COMPARISON_H #define FASTFLOAT_DIGIT_COMPARISON_H #include #include #include #include namespace fast_float { // 1e0 to 1e19 constexpr static uint64_t powers_of_ten_uint64[] = {1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, 1000000000000000000UL, 10000000000000000000UL}; // calculate the exponent, in scientific notation, of the number. // this algorithm is not even close to optimized, but it has no practical // effect on performance: in order to have a faster algorithm, we'd need // to slow down performance for faster algorithms, and this is still fast. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t scientific_exponent(parsed_number_string_t &num) noexcept { uint64_t mantissa = num.mantissa; int32_t exponent = int32_t(num.exponent); while (mantissa >= 10000) { mantissa /= 10000; exponent += 4; } while (mantissa >= 100) { mantissa /= 100; exponent += 2; } while (mantissa >= 10) { mantissa /= 10; exponent += 1; } return exponent; } // this converts a native floating-point number to an extended-precision float. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa to_extended(T value) noexcept { using equiv_uint = typename binary_format::equiv_uint; constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); adjusted_mantissa am; int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); equiv_uint bits; #if FASTFLOAT_HAS_BIT_CAST bits = std::bit_cast(value); #else ::memcpy(&bits, &value, sizeof(T)); #endif if ((bits & exponent_mask) == 0) { // denormal am.power2 = 1 - bias; am.mantissa = bits & mantissa_mask; } else { // normal am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); am.power2 -= bias; am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; } return am; } // get the extended precision value of the halfway point between b and b+u. // we are given a native float that represents b, so we need to adjust it // halfway between b and b+u. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa to_extended_halfway(T value) noexcept { adjusted_mantissa am = to_extended(value); am.mantissa <<= 1; am.mantissa += 1; am.power2 -= 1; return am; } // round an extended-precision float to the nearest machine float. template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, callback cb) noexcept { int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; if (-am.power2 >= mantissa_shift) { // have a denormal float int32_t shift = -am.power2 + 1; cb(am, std::min(shift, 64)); // check for round-up: if rounding-nearest carried us to the hidden bit. am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; return; } // have a normal float, use the default shift. cb(am, mantissa_shift); // check for carry if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); am.power2++; } // check for infinite: we could have carried to an infinite power am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); if (am.power2 >= binary_format::infinite_power()) { am.power2 = binary_format::infinite_power(); am.mantissa = 0; } } template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, callback cb) noexcept { const uint64_t mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; const uint64_t halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); uint64_t truncated_bits = am.mantissa & mask; bool is_above = truncated_bits > halfway; bool is_halfway = truncated_bits == halfway; // shift digits into position if (shift == 64) { am.mantissa = 0; } else { am.mantissa >>= shift; } am.power2 += shift; bool is_odd = (am.mantissa & 1) == 1; am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); } fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round_down(adjusted_mantissa &am, int32_t shift) noexcept { if (shift == 64) { am.mantissa = 0; } else { am.mantissa >>= shift; } am.power2 += shift; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void skip_zeros(UC const *&first, UC const *last) noexcept { uint64_t val; while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != int_cmp_zeros()) { break; } first += int_cmp_len(); } while (first != last) { if (*first != UC('0')) { break; } first++; } } // determine if any non-zero digits were truncated. // all characters must be valid digits. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool is_truncated(UC const *first, UC const *last) noexcept { // do 8-bit optimizations, can just compare to 8 literal 0s. uint64_t val; while (!cpp20_and_in_constexpr() && std::distance(first, last) >= int_cmp_len()) { ::memcpy(&val, first, sizeof(uint64_t)); if (val != int_cmp_zeros()) { return true; } first += int_cmp_len(); } while (first != last) { if (*first != UC('0')) { return true; } ++first; } return false; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool is_truncated(span s) noexcept { return is_truncated(s.ptr, s.ptr + s.len()); } template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void parse_eight_digits(const UC *&p, limb &value, size_t &counter, size_t &count) noexcept { value = value * 100000000 + parse_eight_digits_unrolled(p); p += 8; counter += 8; count += 8; } template fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void parse_one_digit(UC const *&p, limb &value, size_t &counter, size_t &count) noexcept { value = value * 10 + limb(*p - UC('0')); p++; counter++; count++; } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void add_native(bigint &big, limb power, limb value) noexcept { big.mul(power); big.add(value); } fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void round_up_bigint(bigint &big, size_t &count) noexcept { // need to round-up the digits, but need to avoid rounding // ....9999 to ...10000, which could cause a false halfway point. add_native(big, 10, 1); count++; } // parse the significant digits into a big integer template inline FASTFLOAT_CONSTEXPR20 void parse_mantissa(bigint &result, parsed_number_string_t &num, size_t max_digits, size_t &digits) noexcept { // try to minimize the number of big integer and scalar multiplication. // therefore, try to parse 8 digits at a time, and multiply by the largest // scalar value (9 or 19 digits) for each step. size_t counter = 0; digits = 0; limb value = 0; #ifdef FASTFLOAT_64BIT_LIMB size_t step = 19; #else size_t step = 9; #endif // process all integer digits. UC const *p = num.integer.ptr; UC const *pend = p + num.integer.len(); skip_zeros(p, pend); // process all digits, in increments of step per loop while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits add_native(result, limb(powers_of_ten_uint64[counter]), value); bool truncated = is_truncated(p, pend); if (num.fraction.ptr != nullptr) { truncated |= is_truncated(num.fraction); } if (truncated) { round_up_bigint(result, digits); } return; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; value = 0; } } // add our fraction digits, if they're available. if (num.fraction.ptr != nullptr) { p = num.fraction.ptr; pend = p + num.fraction.len(); if (digits == 0) { skip_zeros(p, pend); } // process all digits, in increments of step per loop while (p != pend) { while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { parse_eight_digits(p, value, counter, digits); } while (counter < step && p != pend && digits < max_digits) { parse_one_digit(p, value, counter, digits); } if (digits == max_digits) { // add the temporary value, then check if we've truncated any digits add_native(result, limb(powers_of_ten_uint64[counter]), value); bool truncated = is_truncated(p, pend); if (truncated) { round_up_bigint(result, digits); } return; } else { add_native(result, limb(powers_of_ten_uint64[counter]), value); counter = 0; value = 0; } } } if (counter != 0) { add_native(result, limb(powers_of_ten_uint64[counter]), value); } } template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); adjusted_mantissa answer; bool truncated; answer.mantissa = bigmant.hi64(truncated); int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); answer.power2 = bigmant.bit_length() - 64 + bias; round(answer, [truncated](adjusted_mantissa &a, int32_t shift) { round_nearest_tie_even( a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { return is_above || (is_halfway && truncated) || (is_odd && is_halfway); }); }); return answer; } // the scaling here is quite simple: we have, for the real digits `m * 10^e`, // and for the theoretical digits `n * 2^f`. Since `e` is always negative, // to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. // we then need to scale by `2^(f- e)`, and then the two significant digits // are of the same magnitude. template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { bigint &real_digits = bigmant; int32_t real_exp = exponent; // get the value of `b`, rounded down, and get a bigint representation of b+h adjusted_mantissa am_b = am; // gcc7 buf: use a lambda to remove the noexcept qualifier bug with // -Wnoexcept-type. round(am_b, [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); T b; to_float(false, am_b, b); adjusted_mantissa theor = to_extended_halfway(b); bigint theor_digits(theor.mantissa); int32_t theor_exp = theor.power2; // scale real digits and theor digits to be same power. int32_t pow2_exp = theor_exp - real_exp; uint32_t pow5_exp = uint32_t(-real_exp); if (pow5_exp != 0) { FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); } if (pow2_exp > 0) { FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); } else if (pow2_exp < 0) { FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); } // compare digits, and use it to director rounding int ord = real_digits.compare(theor_digits); adjusted_mantissa answer = am; round(answer, [ord](adjusted_mantissa &a, int32_t shift) { round_nearest_tie_even( a, shift, [ord](bool is_odd, bool _, bool __) -> bool { (void)_; // not needed, since we've done our comparison (void)__; // not needed, since we've done our comparison if (ord > 0) { return true; } else if (ord < 0) { return false; } else { return is_odd; } }); }); return answer; } // parse the significant digits as a big integer to unambiguously round the // the significant digits. here, we are trying to determine how to round // an extended float representation close to `b+h`, halfway between `b` // (the float rounded-down) and `b+u`, the next positive float. this // algorithm is always correct, and uses one of two approaches. when // the exponent is positive relative to the significant digits (such as // 1234), we create a big-integer representation, get the high 64-bits, // determine if any lower bits are truncated, and use that to direct // rounding. in case of a negative exponent relative to the significant // digits (such as 1.2345), we create a theoretical representation of // `b` as a big-integer type, scaled to the same binary exponent as // the actual digits. we then compare the big integer representations // of both, and use that to direct rounding. template inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa digit_comp(parsed_number_string_t &num, adjusted_mantissa am) noexcept { // remove the invalid exponent bias am.power2 -= invalid_am_bias; int32_t sci_exp = scientific_exponent(num); size_t max_digits = binary_format::max_digits(); size_t digits = 0; bigint bigmant; parse_mantissa(bigmant, num, max_digits, digits); // can't underflow, since digits is at most max_digits. int32_t exponent = sci_exp + 1 - int32_t(digits); if (exponent >= 0) { return positive_digit_comp(bigmant, exponent); } else { return negative_digit_comp(bigmant, am, exponent); } } } // namespace fast_float #endif #ifndef FASTFLOAT_PARSE_NUMBER_H #define FASTFLOAT_PARSE_NUMBER_H #include #include #include #include namespace fast_float { namespace detail { /** * Special case +inf, -inf, nan, infinity, -infinity. * The case comparisons could be made much faster given that we know that the * strings a null-free and fixed. **/ template from_chars_result_t FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, T &value) noexcept { from_chars_result_t answer{}; answer.ptr = first; answer.ec = std::errc(); // be optimistic bool minusSign = false; if (*first == UC('-')) { // assume first < last, so dereference without checks; // C++17 20.19.3.(7.1) explicitly forbids '+' here minusSign = true; ++first; } #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS // disabled by default if (*first == UC('+')) { ++first; } #endif if (last - first >= 3) { if (fastfloat_strncasecmp(first, str_const_nan(), 3)) { answer.ptr = (first += 3); value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). if (first != last && *first == UC('(')) { for (UC const *ptr = first + 1; ptr != last; ++ptr) { if (*ptr == UC(')')) { answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) break; } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || (UC('A') <= *ptr && *ptr <= UC('Z')) || (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) break; // forbidden char, not nan(n-char-seq-opt) } } return answer; } if (fastfloat_strncasecmp(first, str_const_inf(), 3)) { if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, str_const_inf() + 3, 5)) { answer.ptr = first + 8; } else { answer.ptr = first + 3; } value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); return answer; } } answer.ec = std::errc::invalid_argument; return answer; } /** * Returns true if the floating-pointing rounding mode is to 'nearest'. * It is the default on most system. This function is meant to be inexpensive. * Credit : @mwalcott3 */ fastfloat_really_inline bool rounds_to_nearest() noexcept { // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) return false; #endif // See // A fast function to check your floating-point rounding mode // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ // // This function is meant to be equivalent to : // prior: #include // return fegetround() == FE_TONEAREST; // However, it is expected to be much faster than the fegetround() // function call. // // The volatile keywoard prevents the compiler from computing the function // at compile-time. // There might be other ways to prevent compile-time optimizations (e.g., // asm). The value does not need to be std::numeric_limits::min(), any // small value so that 1 + x should round to 1 would do (after accounting for // excess precision, as in 387 instructions). static volatile float fmin = std::numeric_limits::min(); float fmini = fmin; // we copy it so that it gets loaded at most once. // // Explanation: // Only when fegetround() == FE_TONEAREST do we have that // fmin + 1.0f == 1.0f - fmin. // // FE_UPWARD: // fmin + 1.0f > 1 // 1.0f - fmin == 1 // // FE_DOWNWARD or FE_TOWARDZERO: // fmin + 1.0f == 1 // 1.0f - fmin < 1 // // Note: This may fail to be accurate if fast-math has been // enabled, as rounding conventions may not apply. #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(push) // todo: is there a VS warning? // see // https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 #elif defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfloat-equal" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif return (fmini + 1.0f == 1.0f - fmini); #ifdef FASTFLOAT_VISUAL_STUDIO #pragma warning(pop) #elif defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif } } // namespace detail template struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_advanced(first, last, value, options); } }; #if __STDCPP_FLOAT32_T__ == 1 template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float32_t &value, parse_options_t options) noexcept { // if std::float32_t is defined, and we are in C++23 mode; macro set for // float32; set value to float due to equivalence between float and // float32_t float val; auto ret = from_chars_advanced(first, last, val, options); value = val; return ret; } }; #endif #if __STDCPP_FLOAT64_T__ == 1 template <> struct from_chars_caller { template FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float64_t &value, parse_options_t options) noexcept { // if std::float64_t is defined, and we are in C++23 mode; macro set for // float64; set value as double due to equivalence between double and // float64_t double val; auto ret = from_chars_advanced(first, last, val, options); value = val; return ret; } }; #endif template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, chars_format fmt /*= chars_format::general*/) noexcept { return from_chars_caller::call(first, last, value, parse_options_t(fmt)); } /** * This function overload takes parsed_number_string_t structure that is created * and populated either by from_chars_advanced function taking chars range and * parsing options or other parsing custom function implemented by user. */ template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { static_assert(is_supported_float_type(), "only some floating-point types are supported"); static_assert(is_supported_char_type(), "only char, wchar_t, char16_t and char32_t are supported"); from_chars_result_t answer; answer.ec = std::errc(); // be optimistic answer.ptr = pns.lastmatch; // The implementation of the Clinger's fast path is convoluted because // we want round-to-nearest in all cases, irrespective of the rounding mode // selected on the thread. // We proceed optimistically, assuming that detail::rounds_to_nearest() // returns true. if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && !pns.too_many_digits) { // Unfortunately, the conventional Clinger's fast path is only possible // when the system rounds to the nearest float. // // We expect the next branch to almost always be selected. // We could check it first (before the previous branch), but // there might be performance advantages at having the check // be last. if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { // We have that fegetround() == FE_TONEAREST. // Next is Clinger's fast path. if (pns.mantissa <= binary_format::max_mantissa_fast_path()) { value = T(pns.mantissa); if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } else { value = value * binary_format::exact_power_of_ten(pns.exponent); } if (pns.negative) { value = -value; } return answer; } } else { // We do not have that fegetround() == FE_TONEAREST. // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's // proposal if (pns.exponent >= 0 && pns.mantissa <= binary_format::max_mantissa_fast_path(pns.exponent)) { #if defined(__clang__) || defined(FASTFLOAT_32BIT) // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD if (pns.mantissa == 0) { value = pns.negative ? T(-0.) : T(0.); return answer; } #endif value = T(pns.mantissa) * binary_format::exact_power_of_ten(pns.exponent); if (pns.negative) { value = -value; } return answer; } } } adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); if (pns.too_many_digits && am.power2 >= 0) { if (am != compute_float>(pns.exponent, pns.mantissa + 1)) { am = compute_error>(pns.exponent, pns.mantissa); } } // If we called compute_float>(pns.exponent, pns.mantissa) // and we have an invalid power (am.power2 < 0), then we need to go the long // way around again. This is very uncommon. if (am.power2 < 0) { am = digit_comp(pns, am); } to_float(pns.negative, am, value); // Test for over/underflow. if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || am.power2 == binary_format::infinite_power()) { answer.ec = std::errc::result_out_of_range; } return answer; } template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars_advanced(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { static_assert(is_supported_float_type(), "only some floating-point types are supported"); static_assert(is_supported_char_type(), "only char, wchar_t, char16_t and char32_t are supported"); from_chars_result_t answer; #ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default while ((first != last) && fast_float::is_space(uint8_t(*first))) { first++; } #endif if (first == last) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } parsed_number_string_t pns = parse_number_string(first, last, options); if (!pns.valid) { if (options.format & chars_format::no_infnan) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } else { return detail::parse_infnan(first, last, value); } } // call overload that takes parsed_number_string_t directly. return from_chars_advanced(pns, value); } template FASTFLOAT_CONSTEXPR20 from_chars_result_t from_chars(UC const *first, UC const *last, T &value, int base) noexcept { static_assert(is_supported_char_type(), "only char, wchar_t, char16_t and char32_t are supported"); from_chars_result_t answer; #ifdef FASTFLOAT_SKIP_WHITE_SPACE // disabled by default while ((first != last) && fast_float::is_space(uint8_t(*first))) { first++; } #endif if (first == last || base < 2 || base > 36) { answer.ec = std::errc::invalid_argument; answer.ptr = first; return answer; } return parse_int_string(first, last, value, base); } } // namespace fast_float #endif redis-8.0.2/deps/fast_float/fast_float_strtod.cpp000066400000000000000000000024241501533116600221340ustar00rootroot00000000000000#include "fast_float.h" #include #include #include #include /* 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; } redis-8.0.2/deps/fast_float/fast_float_strtod.h000066400000000000000000000003621501533116600216000ustar00rootroot00000000000000 #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__ */ redis-8.0.2/deps/fpconv/000077500000000000000000000000001501533116600150565ustar00rootroot00000000000000redis-8.0.2/deps/fpconv/LICENSE.txt000066400000000000000000000024721501533116600167060ustar00rootroot00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. redis-8.0.2/deps/fpconv/Makefile000066400000000000000000000005131501533116600165150ustar00rootroot00000000000000STD= WARN= -Wall OPT= -Os R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) R_LDFLAGS= $(LDFLAGS) DEBUG= -g R_CC=$(CC) $(R_CFLAGS) R_LD=$(CC) $(R_LDFLAGS) AR= ar ARFLAGS= rcs libfpconv.a: fpconv_dtoa.o $(AR) $(ARFLAGS) $@ $+ fpconv_dtoa.o: fpconv_dtoa.h fpconv_dtoa.c .c.o: $(R_CC) -c $< clean: rm -f *.o rm -f *.a redis-8.0.2/deps/fpconv/README.md000066400000000000000000000007051501533116600163370ustar00rootroot00000000000000libfpconv ---------------------------------------------- Fast and accurate double to string conversion based on Florian Loitsch's Grisu-algorithm[1]. This port contains a subset of the 'C' version of Fast and accurate double to string conversion based on Florian Loitsch's Grisu-algorithm available at [github.com/night-shift/fpconv](https://github.com/night-shift/fpconv)). [1] https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf redis-8.0.2/deps/fpconv/fpconv_dtoa.c000066400000000000000000000226401501533116600175300ustar00rootroot00000000000000/* fpconv_dtoa.c -- floating point conversion utilities. * * Fast and accurate double to string conversion based on Florian Loitsch's * Grisu-algorithm[1]. * * [1] https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf * ---------------------------------------------------------------------------- * * Copyright (c) 2013-2019, night-shift * Copyright (c) 2009, Florian Loitsch < florian.loitsch at inria dot fr > * All rights reserved. * * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "fpconv_dtoa.h" #include "fpconv_powers.h" #include #include #define fracmask 0x000FFFFFFFFFFFFFU #define expmask 0x7FF0000000000000U #define hiddenbit 0x0010000000000000U #define signmask 0x8000000000000000U #define expbias (1023 + 52) #define absv(n) ((n) < 0 ? -(n) : (n)) #define minv(a, b) ((a) < (b) ? (a) : (b)) static uint64_t tens[] = { 10000000000000000000U, 1000000000000000000U, 100000000000000000U, 10000000000000000U, 1000000000000000U, 100000000000000U, 10000000000000U, 1000000000000U, 100000000000U, 10000000000U, 1000000000U, 100000000U, 10000000U, 1000000U, 100000U, 10000U, 1000U, 100U, 10U, 1U }; static inline uint64_t get_dbits(double d) { union { double dbl; uint64_t i; } dbl_bits = { d }; return dbl_bits.i; } static Fp build_fp(double d) { uint64_t bits = get_dbits(d); Fp fp; fp.frac = bits & fracmask; fp.exp = (bits & expmask) >> 52; if (fp.exp) { fp.frac += hiddenbit; fp.exp -= expbias; } else { fp.exp = -expbias + 1; } return fp; } static void normalize(Fp *fp) { while ((fp->frac & hiddenbit) == 0) { fp->frac <<= 1; fp->exp--; } int shift = 64 - 52 - 1; fp->frac <<= shift; fp->exp -= shift; } static void get_normalized_boundaries(Fp *fp, Fp *lower, Fp *upper) { upper->frac = (fp->frac << 1) + 1; upper->exp = fp->exp - 1; while ((upper->frac & (hiddenbit << 1)) == 0) { upper->frac <<= 1; upper->exp--; } int u_shift = 64 - 52 - 2; upper->frac <<= u_shift; upper->exp = upper->exp - u_shift; int l_shift = fp->frac == hiddenbit ? 2 : 1; lower->frac = (fp->frac << l_shift) - 1; lower->exp = fp->exp - l_shift; lower->frac <<= lower->exp - upper->exp; lower->exp = upper->exp; } static Fp multiply(Fp *a, Fp *b) { const uint64_t lomask = 0x00000000FFFFFFFF; uint64_t ah_bl = (a->frac >> 32) * (b->frac & lomask); uint64_t al_bh = (a->frac & lomask) * (b->frac >> 32); uint64_t al_bl = (a->frac & lomask) * (b->frac & lomask); uint64_t ah_bh = (a->frac >> 32) * (b->frac >> 32); uint64_t tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32); /* round up */ tmp += 1U << 31; Fp fp = { ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32), a->exp + b->exp + 64 }; return fp; } static void round_digit(char *digits, int ndigits, uint64_t delta, uint64_t rem, uint64_t kappa, uint64_t frac) { while (rem < frac && delta - rem >= kappa && (rem + kappa < frac || frac - rem > rem + kappa - frac)) { digits[ndigits - 1]--; rem += kappa; } } static int generate_digits(Fp *fp, Fp *upper, Fp *lower, char *digits, int *K) { uint64_t wfrac = upper->frac - fp->frac; uint64_t delta = upper->frac - lower->frac; Fp one; one.frac = 1ULL << -upper->exp; one.exp = upper->exp; uint64_t part1 = upper->frac >> -one.exp; uint64_t part2 = upper->frac & (one.frac - 1); int idx = 0, kappa = 10; uint64_t *divp; /* 1000000000 */ for (divp = tens + 10; kappa > 0; divp++) { uint64_t div = *divp; unsigned digit = part1 / div; if (digit || idx) { digits[idx++] = digit + '0'; } part1 -= digit * div; kappa--; uint64_t tmp = (part1 << -one.exp) + part2; if (tmp <= delta) { *K += kappa; round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac); return idx; } } /* 10 */ uint64_t *unit = tens + 18; while (true) { part2 *= 10; delta *= 10; kappa--; unsigned digit = part2 >> -one.exp; if (digit || idx) { digits[idx++] = digit + '0'; } part2 &= one.frac - 1; if (part2 < delta) { *K += kappa; round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit); return idx; } unit--; } } static int grisu2(double d, char *digits, int *K) { Fp w = build_fp(d); Fp lower, upper; get_normalized_boundaries(&w, &lower, &upper); normalize(&w); int k; Fp cp = find_cachedpow10(upper.exp, &k); w = multiply(&w, &cp); upper = multiply(&upper, &cp); lower = multiply(&lower, &cp); lower.frac++; upper.frac--; *K = -k; return generate_digits(&w, &upper, &lower, digits, K); } static int emit_digits(char *digits, int ndigits, char *dest, int K, bool neg) { int exp = absv(K + ndigits - 1); /* write plain integer */ if (K >= 0 && (exp < (ndigits + 7))) { memcpy(dest, digits, ndigits); memset(dest + ndigits, '0', K); return ndigits + K; } /* write decimal w/o scientific notation */ if (K < 0 && (K > -7 || exp < 4)) { int offset = ndigits - absv(K); /* fp < 1.0 -> write leading zero */ if (offset <= 0) { offset = -offset; dest[0] = '0'; dest[1] = '.'; memset(dest + 2, '0', offset); memcpy(dest + offset + 2, digits, ndigits); return ndigits + 2 + offset; /* fp > 1.0 */ } else { memcpy(dest, digits, offset); dest[offset] = '.'; memcpy(dest + offset + 1, digits + offset, ndigits - offset); return ndigits + 1; } } /* write decimal w/ scientific notation */ ndigits = minv(ndigits, 18 - neg); int idx = 0; dest[idx++] = digits[0]; if (ndigits > 1) { dest[idx++] = '.'; memcpy(dest + idx, digits + 1, ndigits - 1); idx += ndigits - 1; } dest[idx++] = 'e'; char sign = K + ndigits - 1 < 0 ? '-' : '+'; dest[idx++] = sign; int cent = 0; if (exp > 99) { cent = exp / 100; dest[idx++] = cent + '0'; exp -= cent * 100; } if (exp > 9) { int dec = exp / 10; dest[idx++] = dec + '0'; exp -= dec * 10; } else if (cent) { dest[idx++] = '0'; } dest[idx++] = exp % 10 + '0'; return idx; } static int filter_special(double fp, char *dest) { if (fp == 0.0) { dest[0] = '0'; return 1; } uint64_t bits = get_dbits(fp); bool nan = (bits & expmask) == expmask; if (!nan) { return 0; } if (bits & fracmask) { dest[0] = 'n'; dest[1] = 'a'; dest[2] = 'n'; } else { dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f'; } return 3; } int fpconv_dtoa(double d, char dest[24]) { char digits[18]; int str_len = 0; bool neg = false; if (get_dbits(d) & signmask) { dest[0] = '-'; str_len++; neg = true; } int spec = filter_special(d, dest + str_len); if (spec) { return str_len + spec; } int K = 0; int ndigits = grisu2(d, digits, &K); str_len += emit_digits(digits, ndigits, dest + str_len, K, neg); return str_len; } redis-8.0.2/deps/fpconv/fpconv_dtoa.h000066400000000000000000000040221501533116600175270ustar00rootroot00000000000000/* fpconv_dtoa.h -- floating point conversion utilities. * * Fast and accurate double to string conversion based on Florian Loitsch's * Grisu-algorithm[1]. * * [1] https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf * ---------------------------------------------------------------------------- * * Copyright (c) 2013-2019, night-shift * Copyright (c) 2009, Florian Loitsch < florian.loitsch at inria dot fr > * All rights reserved. * * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef FPCONV_DTOA_H #define FPCONV_DTOA_H int fpconv_dtoa(double fp, char dest[24]); #endif /* [1] http://florian.loitsch.com/publications/dtoa-pldi2010.pdf */ redis-8.0.2/deps/fpconv/fpconv_powers.h000066400000000000000000000135721501533116600201310ustar00rootroot00000000000000/* fpconv_powers.h -- floating point conversion utilities. * * Fast and accurate double to string conversion based on Florian Loitsch's * Grisu-algorithm[1]. * * [1] https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf * ---------------------------------------------------------------------------- * * Copyright (c) 2021, Redis Labs * Copyright (c) 2013-2019, night-shift * Copyright (c) 2009, Florian Loitsch < florian.loitsch at inria dot fr > * All rights reserved. * * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #define npowers 87 #define steppowers 8 #define firstpower -348 /* 10 ^ -348 */ #define expmax -32 #define expmin -60 typedef struct Fp { uint64_t frac; int exp; } Fp; static Fp powers_ten[] = { { 18054884314459144840U, -1220 }, { 13451937075301367670U, -1193 }, { 10022474136428063862U, -1166 }, { 14934650266808366570U, -1140 }, { 11127181549972568877U, -1113 }, { 16580792590934885855U, -1087 }, { 12353653155963782858U, -1060 }, { 18408377700990114895U, -1034 }, { 13715310171984221708U, -1007 }, { 10218702384817765436U, -980 }, { 15227053142812498563U, -954 }, { 11345038669416679861U, -927 }, { 16905424996341287883U, -901 }, { 12595523146049147757U, -874 }, { 9384396036005875287U, -847 }, { 13983839803942852151U, -821 }, { 10418772551374772303U, -794 }, { 15525180923007089351U, -768 }, { 11567161174868858868U, -741 }, { 17236413322193710309U, -715 }, { 12842128665889583758U, -688 }, { 9568131466127621947U, -661 }, { 14257626930069360058U, -635 }, { 10622759856335341974U, -608 }, { 15829145694278690180U, -582 }, { 11793632577567316726U, -555 }, { 17573882009934360870U, -529 }, { 13093562431584567480U, -502 }, { 9755464219737475723U, -475 }, { 14536774485912137811U, -449 }, { 10830740992659433045U, -422 }, { 16139061738043178685U, -396 }, { 12024538023802026127U, -369 }, { 17917957937422433684U, -343 }, { 13349918974505688015U, -316 }, { 9946464728195732843U, -289 }, { 14821387422376473014U, -263 }, { 11042794154864902060U, -236 }, { 16455045573212060422U, -210 }, { 12259964326927110867U, -183 }, { 18268770466636286478U, -157 }, { 13611294676837538539U, -130 }, { 10141204801825835212U, -103 }, { 15111572745182864684U, -77 }, { 11258999068426240000U, -50 }, { 16777216000000000000U, -24 }, { 12500000000000000000U, 3 }, { 9313225746154785156U, 30 }, { 13877787807814456755U, 56 }, { 10339757656912845936U, 83 }, { 15407439555097886824U, 109 }, { 11479437019748901445U, 136 }, { 17105694144590052135U, 162 }, { 12744735289059618216U, 189 }, { 9495567745759798747U, 216 }, { 14149498560666738074U, 242 }, { 10542197943230523224U, 269 }, { 15709099088952724970U, 295 }, { 11704190886730495818U, 322 }, { 17440603504673385349U, 348 }, { 12994262207056124023U, 375 }, { 9681479787123295682U, 402 }, { 14426529090290212157U, 428 }, { 10748601772107342003U, 455 }, { 16016664761464807395U, 481 }, { 11933345169920330789U, 508 }, { 17782069995880619868U, 534 }, { 13248674568444952270U, 561 }, { 9871031767461413346U, 588 }, { 14708983551653345445U, 614 }, { 10959046745042015199U, 641 }, { 16330252207878254650U, 667 }, { 12166986024289022870U, 694 }, { 18130221999122236476U, 720 }, { 13508068024458167312U, 747 }, { 10064294952495520794U, 774 }, { 14996968138956309548U, 800 }, { 11173611982879273257U, 827 }, { 16649979327439178909U, 853 }, { 12405201291620119593U, 880 }, { 9242595204427927429U, 907 }, { 13772540099066387757U, 933 }, { 10261342003245940623U, 960 }, { 15290591125556738113U, 986 }, { 11392378155556871081U, 1013 }, { 16975966327722178521U, 1039 }, { 12648080533535911531U, 1066 } }; /** * Grisu needs a cache of precomputed powers-of-ten. * This function, given an exponent and an integer k * return the normalized floating-point approximation of the power of 10. * @param exp * @param k * @return */ static Fp find_cachedpow10(int exp, int* k) { const double one_log_ten = 0.30102999566398114; const int approx = -(exp + npowers) * one_log_ten; int idx = (approx - firstpower) / steppowers; while(1) { int current = exp + powers_ten[idx].exp + 64; if(current < expmin) { idx++; continue; } if(current > expmax) { idx--; continue; } *k = (firstpower + idx * steppowers); return powers_ten[idx]; } } redis-8.0.2/deps/hdr_histogram/000077500000000000000000000000001501533116600164155ustar00rootroot00000000000000redis-8.0.2/deps/hdr_histogram/COPYING.txt000066400000000000000000000156101501533116600202710ustar00rootroot00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. redis-8.0.2/deps/hdr_histogram/LICENSE.txt000066400000000000000000000041611501533116600202420ustar00rootroot00000000000000The code in this repository code was Written by Gil Tene, Michael Barker, and Matt Warren, and released to the public domain, as explained at http://creativecommons.org/publicdomain/zero/1.0/ For users of this code who wish to consume it under the "BSD" license rather than under the public domain or CC0 contribution text mentioned above, the code found under this directory is *also* provided under the following license (commonly referred to as the BSD 2-Clause License). This license does not detract from the above stated release of the code into the public domain, and simply represents an additional license granted by the Author. ----------------------------------------------------------------------------- ** Beginning of "BSD 2-Clause License" text. ** Copyright (c) 2012, 2013, 2014 Gil Tene Copyright (c) 2014 Michael Barker Copyright (c) 2014 Matt Warren 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 HOLDERS 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. redis-8.0.2/deps/hdr_histogram/Makefile000066400000000000000000000006161501533116600200600ustar00rootroot00000000000000STD= -std=c99 WARN= -Wall OPT= -Os R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) -DHDR_MALLOC_INCLUDE=\"hdr_redis_malloc.h\" R_LDFLAGS= $(LDFLAGS) DEBUG= -g R_CC=$(CC) $(R_CFLAGS) R_LD=$(CC) $(R_LDFLAGS) AR= ar ARFLAGS= rcs libhdrhistogram.a: hdr_histogram.o $(AR) $(ARFLAGS) $@ $+ hdr_histogram.o: hdr_histogram.h hdr_histogram.c .c.o: $(R_CC) -c $< clean: rm -f *.o rm -f *.a redis-8.0.2/deps/hdr_histogram/README.md000066400000000000000000000041151501533116600176750ustar00rootroot00000000000000HdrHistogram_c: 'C' port of High Dynamic Range (HDR) Histogram HdrHistogram ---------------------------------------------- [![Gitter chat](https://badges.gitter.im/HdrHistogram/HdrHistogram.png)](https://gitter.im/HdrHistogram/HdrHistogram) This port contains a subset of the functionality supported by the Java implementation. The current supported features are: * Standard histogram with 64 bit counts (32/16 bit counts not supported) * All iterator types (all values, recorded, percentiles, linear, logarithmic) * Histogram serialisation (encoding version 1.2, decoding 1.0-1.2) * Reader/writer phaser and interval recorder Features not supported, but planned * Auto-resizing of histograms Features unlikely to be implemented * Double histograms * Atomic/Concurrent histograms * 16/32 bit histograms # Simple Tutorial ## Recording values ```C #include struct hdr_histogram* histogram; // Initialise the histogram hdr_init( 1, // Minimum value INT64_C(3600000000), // Maximum value 3, // Number of significant figures &histogram) // Pointer to initialise // Record value hdr_record_value( histogram, // Histogram to record to value) // Value to record // Record value n times hdr_record_values( histogram, // Histogram to record to value, // Value to record 10) // Record value 10 times // Record value with correction for co-ordinated omission. hdr_record_corrected_value( histogram, // Histogram to record to value, // Value to record 1000) // Record with expected interval of 1000. // Print out the values of the histogram hdr_percentiles_print( histogram, stdout, // File to write to 5, // Granularity of printed values 1.0, // Multiplier for results CLASSIC); // Format CLASSIC/CSV supported. ``` ## More examples For more detailed examples of recording and logging results look at the [hdr_decoder](examples/hdr_decoder.c) and [hiccup](examples/hiccup.c) examples. You can run hiccup and decoder and pipe the results of one into the other. ``` $ ./examples/hiccup | ./examples/hdr_decoder ``` redis-8.0.2/deps/hdr_histogram/hdr_atomic.h000066400000000000000000000074021501533116600207020ustar00rootroot00000000000000/** * hdr_atomic.h * Written by Philip Orwig and released to the public domain, * as explained at http://creativecommons.org/publicdomain/zero/1.0/ */ #ifndef HDR_ATOMIC_H__ #define HDR_ATOMIC_H__ #if defined(_MSC_VER) #include #include #include static void __inline * hdr_atomic_load_pointer(void** pointer) { _ReadBarrier(); return *pointer; } static void hdr_atomic_store_pointer(void** pointer, void* value) { _WriteBarrier(); *pointer = value; } static int64_t __inline hdr_atomic_load_64(int64_t* field) { _ReadBarrier(); return *field; } static void __inline hdr_atomic_store_64(int64_t* field, int64_t value) { _WriteBarrier(); *field = value; } static int64_t __inline hdr_atomic_exchange_64(volatile int64_t* field, int64_t value) { #if defined(_WIN64) return _InterlockedExchange64(field, value); #else int64_t comparand; int64_t initial_value = *field; do { comparand = initial_value; initial_value = _InterlockedCompareExchange64(field, value, comparand); } while (comparand != initial_value); return initial_value; #endif } static int64_t __inline hdr_atomic_add_fetch_64(volatile int64_t* field, int64_t value) { #if defined(_WIN64) return _InterlockedExchangeAdd64(field, value) + value; #else int64_t comparand; int64_t initial_value = *field; do { comparand = initial_value; initial_value = _InterlockedCompareExchange64(field, comparand + value, comparand); } while (comparand != initial_value); return initial_value + value; #endif } static bool __inline hdr_atomic_compare_exchange_64(volatile int64_t* field, int64_t* expected, int64_t desired) { return *expected == _InterlockedCompareExchange64(field, desired, *expected); } #elif defined(__ATOMIC_SEQ_CST) #define hdr_atomic_load_pointer(x) __atomic_load_n(x, __ATOMIC_SEQ_CST) #define hdr_atomic_store_pointer(f,v) __atomic_store_n(f,v, __ATOMIC_SEQ_CST) #define hdr_atomic_load_64(x) __atomic_load_n(x, __ATOMIC_SEQ_CST) #define hdr_atomic_store_64(f,v) __atomic_store_n(f,v, __ATOMIC_SEQ_CST) #define hdr_atomic_exchange_64(f,i) __atomic_exchange_n(f,i, __ATOMIC_SEQ_CST) #define hdr_atomic_add_fetch_64(field, value) __atomic_add_fetch(field, value, __ATOMIC_SEQ_CST) #define hdr_atomic_compare_exchange_64(field, expected, desired) __atomic_compare_exchange_n(field, expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) #elif defined(__x86_64__) #include #include static inline void* hdr_atomic_load_pointer(void** pointer) { void* p = *pointer; asm volatile ("" ::: "memory"); return p; } static inline void hdr_atomic_store_pointer(void** pointer, void* value) { asm volatile ("lock; xchgq %0, %1" : "+q" (value), "+m" (*pointer)); } static inline int64_t hdr_atomic_load_64(int64_t* field) { int64_t i = *field; asm volatile ("" ::: "memory"); return i; } static inline void hdr_atomic_store_64(int64_t* field, int64_t value) { asm volatile ("lock; xchgq %0, %1" : "+q" (value), "+m" (*field)); } static inline int64_t hdr_atomic_exchange_64(volatile int64_t* field, int64_t value) { int64_t result = 0; asm volatile ("lock; xchgq %1, %2" : "=r" (result), "+q" (value), "+m" (*field)); return result; } static inline int64_t hdr_atomic_add_fetch_64(volatile int64_t* field, int64_t value) { return __sync_add_and_fetch(field, value); } static inline bool hdr_atomic_compare_exchange_64(volatile int64_t* field, int64_t* expected, int64_t desired) { int64_t original; asm volatile( "lock; cmpxchgq %2, %1" : "=a"(original), "+m"(*field) : "q"(desired), "0"(*expected)); return original == *expected; } #else #error "Unable to determine atomic operations for your platform" #endif #endif /* HDR_ATOMIC_H__ */ redis-8.0.2/deps/hdr_histogram/hdr_histogram.c000066400000000000000000001110501501533116600214110ustar00rootroot00000000000000/** * hdr_histogram.c * Written by Michael Barker and released to the public domain, * as explained at http://creativecommons.org/publicdomain/zero/1.0/ */ #include #include #include #include #include #include #include #include #include "hdr_histogram.h" #include "hdr_tests.h" #include "hdr_atomic.h" #ifndef HDR_MALLOC_INCLUDE #define HDR_MALLOC_INCLUDE "hdr_malloc.h" #endif #include HDR_MALLOC_INCLUDE /* ###### ####### ## ## ## ## ######## ###### */ /* ## ## ## ## ## ## ### ## ## ## ## */ /* ## ## ## ## ## #### ## ## ## */ /* ## ## ## ## ## ## ## ## ## ###### */ /* ## ## ## ## ## ## #### ## ## */ /* ## ## ## ## ## ## ## ### ## ## ## */ /* ###### ####### ####### ## ## ## ###### */ static int32_t normalize_index(const struct hdr_histogram* h, int32_t index) { int32_t normalized_index; int32_t adjustment = 0; if (h->normalizing_index_offset == 0) { return index; } normalized_index = index - h->normalizing_index_offset; if (normalized_index < 0) { adjustment = h->counts_len; } else if (normalized_index >= h->counts_len) { adjustment = -h->counts_len; } return normalized_index + adjustment; } static int64_t counts_get_direct(const struct hdr_histogram* h, int32_t index) { return h->counts[index]; } static int64_t counts_get_normalised(const struct hdr_histogram* h, int32_t index) { return counts_get_direct(h, normalize_index(h, index)); } static void counts_inc_normalised( struct hdr_histogram* h, int32_t index, int64_t value) { int32_t normalised_index = normalize_index(h, index); h->counts[normalised_index] += value; h->total_count += value; } static void counts_inc_normalised_atomic( struct hdr_histogram* h, int32_t index, int64_t value) { int32_t normalised_index = normalize_index(h, index); hdr_atomic_add_fetch_64(&h->counts[normalised_index], value); hdr_atomic_add_fetch_64(&h->total_count, value); } static void update_min_max(struct hdr_histogram* h, int64_t value) { h->min_value = (value < h->min_value && value != 0) ? value : h->min_value; h->max_value = (value > h->max_value) ? value : h->max_value; } static void update_min_max_atomic(struct hdr_histogram* h, int64_t value) { int64_t current_min_value; int64_t current_max_value; do { current_min_value = hdr_atomic_load_64(&h->min_value); if (0 == value || current_min_value <= value) { break; } } while (!hdr_atomic_compare_exchange_64(&h->min_value, ¤t_min_value, value)); do { current_max_value = hdr_atomic_load_64(&h->max_value); if (value <= current_max_value) { break; } } while (!hdr_atomic_compare_exchange_64(&h->max_value, ¤t_max_value, value)); } /* ## ## ######## #### ## #### ######## ## ## */ /* ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## #### */ /* ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## */ /* ####### ## #### ######## #### ## ## */ static int64_t power(int64_t base, int64_t exp) { int64_t result = 1; while(exp) { result *= base; exp--; } return result; } #if defined(_MSC_VER) # if defined(_WIN64) # pragma intrinsic(_BitScanReverse64) # else # pragma intrinsic(_BitScanReverse) # endif #endif static int32_t count_leading_zeros_64(int64_t value) { #if defined(_MSC_VER) uint32_t leading_zero = 0; #if defined(_WIN64) _BitScanReverse64(&leading_zero, value); #else uint32_t high = value >> 32; if (_BitScanReverse(&leading_zero, high)) { leading_zero += 32; } else { uint32_t low = value & 0x00000000FFFFFFFF; _BitScanReverse(&leading_zero, low); } #endif return 63 - leading_zero; /* smallest power of 2 containing value */ #else return __builtin_clzll(value); /* smallest power of 2 containing value */ #endif } static int32_t get_bucket_index(const struct hdr_histogram* h, int64_t value) { int32_t pow2ceiling = 64 - count_leading_zeros_64(value | h->sub_bucket_mask); /* smallest power of 2 containing value */ return pow2ceiling - h->unit_magnitude - (h->sub_bucket_half_count_magnitude + 1); } static int32_t get_sub_bucket_index(int64_t value, int32_t bucket_index, int32_t unit_magnitude) { return (int32_t)(value >> (bucket_index + unit_magnitude)); } static int32_t counts_index(const struct hdr_histogram* h, int32_t bucket_index, int32_t sub_bucket_index) { /* Calculate the index for the first entry in the bucket: */ /* (The following is the equivalent of ((bucket_index + 1) * subBucketHalfCount) ): */ int32_t bucket_base_index = (bucket_index + 1) << h->sub_bucket_half_count_magnitude; /* Calculate the offset in the bucket: */ int32_t offset_in_bucket = sub_bucket_index - h->sub_bucket_half_count; /* The following is the equivalent of ((sub_bucket_index - subBucketHalfCount) + bucketBaseIndex; */ return bucket_base_index + offset_in_bucket; } static int64_t value_from_index(int32_t bucket_index, int32_t sub_bucket_index, int32_t unit_magnitude) { return ((int64_t) sub_bucket_index) << (bucket_index + unit_magnitude); } int32_t counts_index_for(const struct hdr_histogram* h, int64_t value) { int32_t bucket_index = get_bucket_index(h, value); int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, h->unit_magnitude); return counts_index(h, bucket_index, sub_bucket_index); } int64_t hdr_value_at_index(const struct hdr_histogram *h, int32_t index) { int32_t bucket_index = (index >> h->sub_bucket_half_count_magnitude) - 1; int32_t sub_bucket_index = (index & (h->sub_bucket_half_count - 1)) + h->sub_bucket_half_count; if (bucket_index < 0) { sub_bucket_index -= h->sub_bucket_half_count; bucket_index = 0; } return value_from_index(bucket_index, sub_bucket_index, h->unit_magnitude); } int64_t hdr_size_of_equivalent_value_range(const struct hdr_histogram* h, int64_t value) { int32_t bucket_index = get_bucket_index(h, value); int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, h->unit_magnitude); int32_t adjusted_bucket = (sub_bucket_index >= h->sub_bucket_count) ? (bucket_index + 1) : bucket_index; return INT64_C(1) << (h->unit_magnitude + adjusted_bucket); } static int64_t size_of_equivalent_value_range_given_bucket_indices( const struct hdr_histogram *h, int32_t bucket_index, int32_t sub_bucket_index) { const int32_t adjusted_bucket = (sub_bucket_index >= h->sub_bucket_count) ? (bucket_index + 1) : bucket_index; return INT64_C(1) << (h->unit_magnitude + adjusted_bucket); } static int64_t lowest_equivalent_value(const struct hdr_histogram* h, int64_t value) { int32_t bucket_index = get_bucket_index(h, value); int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, h->unit_magnitude); return value_from_index(bucket_index, sub_bucket_index, h->unit_magnitude); } static int64_t lowest_equivalent_value_given_bucket_indices( const struct hdr_histogram *h, int32_t bucket_index, int32_t sub_bucket_index) { return value_from_index(bucket_index, sub_bucket_index, h->unit_magnitude); } int64_t hdr_next_non_equivalent_value(const struct hdr_histogram *h, int64_t value) { return lowest_equivalent_value(h, value) + hdr_size_of_equivalent_value_range(h, value); } static int64_t highest_equivalent_value(const struct hdr_histogram* h, int64_t value) { return hdr_next_non_equivalent_value(h, value) - 1; } int64_t hdr_median_equivalent_value(const struct hdr_histogram *h, int64_t value) { return lowest_equivalent_value(h, value) + (hdr_size_of_equivalent_value_range(h, value) >> 1); } static int64_t non_zero_min(const struct hdr_histogram* h) { if (INT64_MAX == h->min_value) { return INT64_MAX; } return lowest_equivalent_value(h, h->min_value); } void hdr_reset_internal_counters(struct hdr_histogram* h) { int min_non_zero_index = -1; int max_index = -1; int64_t observed_total_count = 0; int i; for (i = 0; i < h->counts_len; i++) { int64_t count_at_index; if ((count_at_index = counts_get_direct(h, i)) > 0) { observed_total_count += count_at_index; max_index = i; if (min_non_zero_index == -1 && i != 0) { min_non_zero_index = i; } } } if (max_index == -1) { h->max_value = 0; } else { int64_t max_value = hdr_value_at_index(h, max_index); h->max_value = highest_equivalent_value(h, max_value); } if (min_non_zero_index == -1) { h->min_value = INT64_MAX; } else { h->min_value = hdr_value_at_index(h, min_non_zero_index); } h->total_count = observed_total_count; } static int32_t buckets_needed_to_cover_value(int64_t value, int32_t sub_bucket_count, int32_t unit_magnitude) { int64_t smallest_untrackable_value = ((int64_t) sub_bucket_count) << unit_magnitude; int32_t buckets_needed = 1; while (smallest_untrackable_value <= value) { if (smallest_untrackable_value > INT64_MAX / 2) { return buckets_needed + 1; } smallest_untrackable_value <<= 1; buckets_needed++; } return buckets_needed; } /* ## ## ######## ## ## ####### ######## ## ## */ /* ### ### ## ### ### ## ## ## ## ## ## */ /* #### #### ## #### #### ## ## ## ## #### */ /* ## ### ## ###### ## ### ## ## ## ######## ## */ /* ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## */ /* ## ## ######## ## ## ####### ## ## ## */ int hdr_calculate_bucket_config( int64_t lowest_discernible_value, int64_t highest_trackable_value, int significant_figures, struct hdr_histogram_bucket_config* cfg) { int32_t sub_bucket_count_magnitude; int64_t largest_value_with_single_unit_resolution; if (lowest_discernible_value < 1 || significant_figures < 1 || 5 < significant_figures || lowest_discernible_value * 2 > highest_trackable_value) { return EINVAL; } cfg->lowest_discernible_value = lowest_discernible_value; cfg->significant_figures = significant_figures; cfg->highest_trackable_value = highest_trackable_value; largest_value_with_single_unit_resolution = 2 * power(10, significant_figures); sub_bucket_count_magnitude = (int32_t) ceil(log((double)largest_value_with_single_unit_resolution) / log(2)); cfg->sub_bucket_half_count_magnitude = ((sub_bucket_count_magnitude > 1) ? sub_bucket_count_magnitude : 1) - 1; double unit_magnitude = log((double)lowest_discernible_value) / log(2); if (INT32_MAX < unit_magnitude) { return EINVAL; } cfg->unit_magnitude = (int32_t) unit_magnitude; cfg->sub_bucket_count = (int32_t) pow(2, (cfg->sub_bucket_half_count_magnitude + 1)); cfg->sub_bucket_half_count = cfg->sub_bucket_count / 2; cfg->sub_bucket_mask = ((int64_t) cfg->sub_bucket_count - 1) << cfg->unit_magnitude; if (cfg->unit_magnitude + cfg->sub_bucket_half_count_magnitude > 61) { return EINVAL; } cfg->bucket_count = buckets_needed_to_cover_value(highest_trackable_value, cfg->sub_bucket_count, (int32_t)cfg->unit_magnitude); cfg->counts_len = (cfg->bucket_count + 1) * (cfg->sub_bucket_count / 2); return 0; } void hdr_init_preallocated(struct hdr_histogram* h, struct hdr_histogram_bucket_config* cfg) { h->lowest_discernible_value = cfg->lowest_discernible_value; h->highest_trackable_value = cfg->highest_trackable_value; h->unit_magnitude = (int32_t)cfg->unit_magnitude; h->significant_figures = (int32_t)cfg->significant_figures; h->sub_bucket_half_count_magnitude = cfg->sub_bucket_half_count_magnitude; h->sub_bucket_half_count = cfg->sub_bucket_half_count; h->sub_bucket_mask = cfg->sub_bucket_mask; h->sub_bucket_count = cfg->sub_bucket_count; h->min_value = INT64_MAX; h->max_value = 0; h->normalizing_index_offset = 0; h->conversion_ratio = 1.0; h->bucket_count = cfg->bucket_count; h->counts_len = cfg->counts_len; h->total_count = 0; } int hdr_init( int64_t lowest_discernible_value, int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result) { int64_t* counts; struct hdr_histogram_bucket_config cfg; struct hdr_histogram* histogram; int r = hdr_calculate_bucket_config(lowest_discernible_value, highest_trackable_value, significant_figures, &cfg); if (r) { return r; } counts = (int64_t*) hdr_calloc((size_t) cfg.counts_len, sizeof(int64_t)); if (!counts) { return ENOMEM; } histogram = (struct hdr_histogram*) hdr_calloc(1, sizeof(struct hdr_histogram)); if (!histogram) { hdr_free(counts); return ENOMEM; } histogram->counts = counts; hdr_init_preallocated(histogram, &cfg); *result = histogram; return 0; } void hdr_close(struct hdr_histogram* h) { if (h) { hdr_free(h->counts); hdr_free(h); } } int hdr_alloc(int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result) { return hdr_init(1, highest_trackable_value, significant_figures, result); } /* reset a histogram to zero. */ void hdr_reset(struct hdr_histogram *h) { h->total_count=0; h->min_value = INT64_MAX; h->max_value = 0; memset(h->counts, 0, (sizeof(int64_t) * h->counts_len)); } size_t hdr_get_memory_size(struct hdr_histogram *h) { return sizeof(struct hdr_histogram) + h->counts_len * sizeof(int64_t); } /* ## ## ######## ######## ### ######## ######## ###### */ /* ## ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ######## ## ## ## ## ## ###### ###### */ /* ## ## ## ## ## ######### ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## */ /* ####### ## ######## ## ## ## ######## ###### */ bool hdr_record_value(struct hdr_histogram* h, int64_t value) { return hdr_record_values(h, value, 1); } bool hdr_record_value_atomic(struct hdr_histogram* h, int64_t value) { return hdr_record_values_atomic(h, value, 1); } bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count) { int32_t counts_index; if (value < 0) { return false; } counts_index = counts_index_for(h, value); if (counts_index < 0 || h->counts_len <= counts_index) { return false; } counts_inc_normalised(h, counts_index, count); update_min_max(h, value); return true; } bool hdr_record_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count) { int32_t counts_index; if (value < 0) { return false; } counts_index = counts_index_for(h, value); if (counts_index < 0 || h->counts_len <= counts_index) { return false; } counts_inc_normalised_atomic(h, counts_index, count); update_min_max_atomic(h, value); return true; } bool hdr_record_corrected_value(struct hdr_histogram* h, int64_t value, int64_t expected_interval) { return hdr_record_corrected_values(h, value, 1, expected_interval); } bool hdr_record_corrected_value_atomic(struct hdr_histogram* h, int64_t value, int64_t expected_interval) { return hdr_record_corrected_values_atomic(h, value, 1, expected_interval); } bool hdr_record_corrected_values(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval) { int64_t missing_value; if (!hdr_record_values(h, value, count)) { return false; } if (expected_interval <= 0 || value <= expected_interval) { return true; } missing_value = value - expected_interval; for (; missing_value >= expected_interval; missing_value -= expected_interval) { if (!hdr_record_values(h, missing_value, count)) { return false; } } return true; } bool hdr_record_corrected_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval) { int64_t missing_value; if (!hdr_record_values_atomic(h, value, count)) { return false; } if (expected_interval <= 0 || value <= expected_interval) { return true; } missing_value = value - expected_interval; for (; missing_value >= expected_interval; missing_value -= expected_interval) { if (!hdr_record_values_atomic(h, missing_value, count)) { return false; } } return true; } int64_t hdr_add(struct hdr_histogram* h, const struct hdr_histogram* from) { struct hdr_iter iter; int64_t dropped = 0; hdr_iter_recorded_init(&iter, from); while (hdr_iter_next(&iter)) { int64_t value = iter.value; int64_t count = iter.count; if (!hdr_record_values(h, value, count)) { dropped += count; } } return dropped; } int64_t hdr_add_while_correcting_for_coordinated_omission( struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval) { struct hdr_iter iter; int64_t dropped = 0; hdr_iter_recorded_init(&iter, from); while (hdr_iter_next(&iter)) { int64_t value = iter.value; int64_t count = iter.count; if (!hdr_record_corrected_values(h, value, count, expected_interval)) { dropped += count; } } return dropped; } /* ## ## ### ## ## ## ######## ###### */ /* ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ###### ###### */ /* ## ## ######### ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## */ /* ### ## ## ######## ####### ######## ###### */ int64_t hdr_max(const struct hdr_histogram* h) { if (0 == h->max_value) { return 0; } return highest_equivalent_value(h, h->max_value); } int64_t hdr_min(const struct hdr_histogram* h) { if (0 < hdr_count_at_index(h, 0)) { return 0; } return non_zero_min(h); } static int64_t get_value_from_idx_up_to_count(const struct hdr_histogram* h, int64_t count_at_percentile) { int64_t count_to_idx = 0; count_at_percentile = 0 < count_at_percentile ? count_at_percentile : 1; for (int32_t idx = 0; idx < h->counts_len; idx++) { count_to_idx += h->counts[idx]; if (count_to_idx >= count_at_percentile) { return hdr_value_at_index(h, idx); } } return 0; } int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile) { double requested_percentile = percentile < 100.0 ? percentile : 100.0; int64_t count_at_percentile = (int64_t) (((requested_percentile / 100) * h->total_count) + 0.5); int64_t value_from_idx = get_value_from_idx_up_to_count(h, count_at_percentile); if (percentile == 0.0) { return lowest_equivalent_value(h, value_from_idx); } return highest_equivalent_value(h, value_from_idx); } int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percentiles, int64_t *values, size_t length) { if (NULL == percentiles || NULL == values) { return EINVAL; } struct hdr_iter iter; const int64_t total_count = h->total_count; // to avoid allocations we use the values array for intermediate computation // i.e. to store the expected cumulative count at each percentile for (size_t i = 0; i < length; i++) { const double requested_percentile = percentiles[i] < 100.0 ? percentiles[i] : 100.0; const int64_t count_at_percentile = (int64_t) (((requested_percentile / 100) * total_count) + 0.5); values[i] = count_at_percentile > 1 ? count_at_percentile : 1; } hdr_iter_init(&iter, h); int64_t total = 0; size_t at_pos = 0; while (hdr_iter_next(&iter) && at_pos < length) { total += iter.count; while (at_pos < length && total >= values[at_pos]) { values[at_pos] = highest_equivalent_value(h, iter.value); at_pos++; } } return 0; } double hdr_mean(const struct hdr_histogram* h) { struct hdr_iter iter; int64_t total = 0; hdr_iter_init(&iter, h); while (hdr_iter_next(&iter)) { if (0 != iter.count) { total += iter.count * hdr_median_equivalent_value(h, iter.value); } } return (total * 1.0) / h->total_count; } double hdr_stddev(const struct hdr_histogram* h) { double mean = hdr_mean(h); double geometric_dev_total = 0.0; struct hdr_iter iter; hdr_iter_init(&iter, h); while (hdr_iter_next(&iter)) { if (0 != iter.count) { double dev = (hdr_median_equivalent_value(h, iter.value) * 1.0) - mean; geometric_dev_total += (dev * dev) * iter.count; } } return sqrt(geometric_dev_total / h->total_count); } bool hdr_values_are_equivalent(const struct hdr_histogram* h, int64_t a, int64_t b) { return lowest_equivalent_value(h, a) == lowest_equivalent_value(h, b); } int64_t hdr_lowest_equivalent_value(const struct hdr_histogram* h, int64_t value) { return lowest_equivalent_value(h, value); } int64_t hdr_count_at_value(const struct hdr_histogram* h, int64_t value) { return counts_get_normalised(h, counts_index_for(h, value)); } int64_t hdr_count_at_index(const struct hdr_histogram* h, int32_t index) { return counts_get_normalised(h, index); } /* #### ######## ######## ######## ### ######## ####### ######## ###### */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ###### ######## ## ## ## ## ## ######## ###### */ /* ## ## ## ## ## ######### ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* #### ## ######## ## ## ## ## ## ####### ## ## ###### */ static bool has_buckets(struct hdr_iter* iter) { return iter->counts_index < iter->h->counts_len; } static bool has_next(struct hdr_iter* iter) { return iter->cumulative_count < iter->total_count; } static bool move_next(struct hdr_iter* iter) { iter->counts_index++; if (!has_buckets(iter)) { return false; } iter->count = counts_get_normalised(iter->h, iter->counts_index); iter->cumulative_count += iter->count; const int64_t value = hdr_value_at_index(iter->h, iter->counts_index); const int32_t bucket_index = get_bucket_index(iter->h, value); const int32_t sub_bucket_index = get_sub_bucket_index(value, bucket_index, iter->h->unit_magnitude); const int64_t leq = lowest_equivalent_value_given_bucket_indices(iter->h, bucket_index, sub_bucket_index); const int64_t size_of_equivalent_value_range = size_of_equivalent_value_range_given_bucket_indices( iter->h, bucket_index, sub_bucket_index); iter->lowest_equivalent_value = leq; iter->value = value; iter->highest_equivalent_value = leq + size_of_equivalent_value_range - 1; iter->median_equivalent_value = leq + (size_of_equivalent_value_range >> 1); return true; } static int64_t peek_next_value_from_index(struct hdr_iter* iter) { return hdr_value_at_index(iter->h, iter->counts_index + 1); } static bool next_value_greater_than_reporting_level_upper_bound( struct hdr_iter *iter, int64_t reporting_level_upper_bound) { if (iter->counts_index >= iter->h->counts_len) { return false; } return peek_next_value_from_index(iter) > reporting_level_upper_bound; } static bool basic_iter_next(struct hdr_iter *iter) { if (!has_next(iter) || iter->counts_index >= iter->h->counts_len) { return false; } move_next(iter); return true; } static void update_iterated_values(struct hdr_iter* iter, int64_t new_value_iterated_to) { iter->value_iterated_from = iter->value_iterated_to; iter->value_iterated_to = new_value_iterated_to; } static bool all_values_iter_next(struct hdr_iter* iter) { bool result = move_next(iter); if (result) { update_iterated_values(iter, iter->value); } return result; } void hdr_iter_init(struct hdr_iter* iter, const struct hdr_histogram* h) { iter->h = h; iter->counts_index = -1; iter->total_count = h->total_count; iter->count = 0; iter->cumulative_count = 0; iter->value = 0; iter->highest_equivalent_value = 0; iter->value_iterated_from = 0; iter->value_iterated_to = 0; iter->_next_fp = all_values_iter_next; } bool hdr_iter_next(struct hdr_iter* iter) { return iter->_next_fp(iter); } /* ######## ######## ######## ###### ######## ## ## ######## #### ## ######## ###### */ /* ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## #### ## ## ## ## ## ## */ /* ######## ###### ######## ## ###### ## ## ## ## ## ## ###### ###### */ /* ## ## ## ## ## ## ## #### ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ### ## ## ## ## ## ## */ /* ## ######## ## ## ###### ######## ## ## ## #### ######## ######## ###### */ static bool percentile_iter_next(struct hdr_iter* iter) { int64_t temp, half_distance, percentile_reporting_ticks; struct hdr_iter_percentiles* percentiles = &iter->specifics.percentiles; if (!has_next(iter)) { if (percentiles->seen_last_value) { return false; } percentiles->seen_last_value = true; percentiles->percentile = 100.0; return true; } if (iter->counts_index == -1 && !basic_iter_next(iter)) { return false; } do { double current_percentile = (100.0 * (double) iter->cumulative_count) / iter->h->total_count; if (iter->count != 0 && percentiles->percentile_to_iterate_to <= current_percentile) { update_iterated_values(iter, highest_equivalent_value(iter->h, iter->value)); percentiles->percentile = percentiles->percentile_to_iterate_to; temp = (int64_t)(log(100 / (100.0 - (percentiles->percentile_to_iterate_to))) / log(2)) + 1; half_distance = (int64_t) pow(2, (double) temp); percentile_reporting_ticks = percentiles->ticks_per_half_distance * half_distance; percentiles->percentile_to_iterate_to += 100.0 / percentile_reporting_ticks; return true; } } while (basic_iter_next(iter)); return true; } void hdr_iter_percentile_init(struct hdr_iter* iter, const struct hdr_histogram* h, int32_t ticks_per_half_distance) { iter->h = h; hdr_iter_init(iter, h); iter->specifics.percentiles.seen_last_value = false; iter->specifics.percentiles.ticks_per_half_distance = ticks_per_half_distance; iter->specifics.percentiles.percentile_to_iterate_to = 0.0; iter->specifics.percentiles.percentile = 0.0; iter->_next_fp = percentile_iter_next; } static void format_line_string(char* str, size_t len, int significant_figures, format_type format) { #if defined(_MSC_VER) #define snprintf _snprintf #pragma warning(push) #pragma warning(disable: 4996) #endif const char* format_str = "%s%d%s"; switch (format) { case CSV: snprintf(str, len, format_str, "%.", significant_figures, "f,%f,%d,%.2f\n"); break; case CLASSIC: snprintf(str, len, format_str, "%12.", significant_figures, "f %12f %12d %12.2f\n"); break; default: snprintf(str, len, format_str, "%12.", significant_figures, "f %12f %12d %12.2f\n"); } #if defined(_MSC_VER) #undef snprintf #pragma warning(pop) #endif } /* ######## ######## ###### ####### ######## ######## ######## ######## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ######## ###### ## ## ## ######## ## ## ###### ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ## ## ######## ###### ####### ## ## ######## ######## ######## */ static bool recorded_iter_next(struct hdr_iter* iter) { while (basic_iter_next(iter)) { if (iter->count != 0) { update_iterated_values(iter, iter->value); iter->specifics.recorded.count_added_in_this_iteration_step = iter->count; return true; } } return false; } void hdr_iter_recorded_init(struct hdr_iter* iter, const struct hdr_histogram* h) { hdr_iter_init(iter, h); iter->specifics.recorded.count_added_in_this_iteration_step = 0; iter->_next_fp = recorded_iter_next; } /* ## #### ## ## ######## ### ######## */ /* ## ## ### ## ## ## ## ## ## */ /* ## ## #### ## ## ## ## ## ## */ /* ## ## ## ## ## ###### ## ## ######## */ /* ## ## ## #### ## ######### ## ## */ /* ## ## ## ### ## ## ## ## ## */ /* ######## #### ## ## ######## ## ## ## ## */ static bool iter_linear_next(struct hdr_iter* iter) { struct hdr_iter_linear* linear = &iter->specifics.linear; linear->count_added_in_this_iteration_step = 0; if (has_next(iter) || next_value_greater_than_reporting_level_upper_bound( iter, linear->next_value_reporting_level_lowest_equivalent)) { do { if (iter->value >= linear->next_value_reporting_level_lowest_equivalent) { update_iterated_values(iter, linear->next_value_reporting_level); linear->next_value_reporting_level += linear->value_units_per_bucket; linear->next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(iter->h, linear->next_value_reporting_level); return true; } if (!move_next(iter)) { return true; } linear->count_added_in_this_iteration_step += iter->count; } while (true); } return false; } void hdr_iter_linear_init(struct hdr_iter* iter, const struct hdr_histogram* h, int64_t value_units_per_bucket) { hdr_iter_init(iter, h); iter->specifics.linear.count_added_in_this_iteration_step = 0; iter->specifics.linear.value_units_per_bucket = value_units_per_bucket; iter->specifics.linear.next_value_reporting_level = value_units_per_bucket; iter->specifics.linear.next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(h, value_units_per_bucket); iter->_next_fp = iter_linear_next; } void hdr_iter_linear_set_value_units_per_bucket(struct hdr_iter* iter, int64_t value_units_per_bucket) { iter->specifics.linear.value_units_per_bucket = value_units_per_bucket; } /* ## ####### ###### ### ######## #### ######## ## ## ## ## #### ###### */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## ### ### ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## #### #### ## ## */ /* ## ## ## ## #### ## ## ######## ## ## ######### ## ### ## ## ## */ /* ## ## ## ## ## ######### ## ## ## ## ## ## ## ## ## ## */ /* ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## */ /* ######## ####### ###### ## ## ## ## #### ## ## ## ## ## #### ###### */ static bool log_iter_next(struct hdr_iter *iter) { struct hdr_iter_log* logarithmic = &iter->specifics.log; logarithmic->count_added_in_this_iteration_step = 0; if (has_next(iter) || next_value_greater_than_reporting_level_upper_bound( iter, logarithmic->next_value_reporting_level_lowest_equivalent)) { do { if (iter->value >= logarithmic->next_value_reporting_level_lowest_equivalent) { update_iterated_values(iter, logarithmic->next_value_reporting_level); logarithmic->next_value_reporting_level *= (int64_t)logarithmic->log_base; logarithmic->next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(iter->h, logarithmic->next_value_reporting_level); return true; } if (!move_next(iter)) { return true; } logarithmic->count_added_in_this_iteration_step += iter->count; } while (true); } return false; } void hdr_iter_log_init( struct hdr_iter* iter, const struct hdr_histogram* h, int64_t value_units_first_bucket, double log_base) { hdr_iter_init(iter, h); iter->specifics.log.count_added_in_this_iteration_step = 0; iter->specifics.log.log_base = log_base; iter->specifics.log.next_value_reporting_level = value_units_first_bucket; iter->specifics.log.next_value_reporting_level_lowest_equivalent = lowest_equivalent_value(h, value_units_first_bucket); iter->_next_fp = log_iter_next; } /* Printing. */ static const char* format_head_string(format_type format) { switch (format) { case CSV: return "%s,%s,%s,%s\n"; case CLASSIC: default: return "%12s %12s %12s %12s\n\n"; } } static const char CLASSIC_FOOTER[] = "#[Mean = %12.3f, StdDeviation = %12.3f]\n" "#[Max = %12.3f, Total count = %12" PRIu64 "]\n" "#[Buckets = %12d, SubBuckets = %12d]\n"; int hdr_percentiles_print( struct hdr_histogram* h, FILE* stream, int32_t ticks_per_half_distance, double value_scale, format_type format) { char line_format[25]; const char* head_format; int rc = 0; struct hdr_iter iter; struct hdr_iter_percentiles * percentiles; format_line_string(line_format, 25, h->significant_figures, format); head_format = format_head_string(format); hdr_iter_percentile_init(&iter, h, ticks_per_half_distance); if (fprintf( stream, head_format, "Value", "Percentile", "TotalCount", "1/(1-Percentile)") < 0) { rc = EIO; goto cleanup; } percentiles = &iter.specifics.percentiles; while (hdr_iter_next(&iter)) { double value = iter.highest_equivalent_value / value_scale; double percentile = percentiles->percentile / 100.0; int64_t total_count = iter.cumulative_count; double inverted_percentile = (1.0 / (1.0 - percentile)); if (fprintf( stream, line_format, value, percentile, total_count, inverted_percentile) < 0) { rc = EIO; goto cleanup; } } if (CLASSIC == format) { double mean = hdr_mean(h) / value_scale; double stddev = hdr_stddev(h) / value_scale; double max = hdr_max(h) / value_scale; if (fprintf( stream, CLASSIC_FOOTER, mean, stddev, max, h->total_count, h->bucket_count, h->sub_bucket_count) < 0) { rc = EIO; goto cleanup; } } cleanup: return rc; } redis-8.0.2/deps/hdr_histogram/hdr_histogram.h000066400000000000000000000426461501533116600214340ustar00rootroot00000000000000/** * hdr_histogram.h * Written by Michael Barker and released to the public domain, * as explained at http://creativecommons.org/publicdomain/zero/1.0/ * * The source for the hdr_histogram utilises a few C99 constructs, specifically * the use of stdint/stdbool and inline variable declaration. */ #ifndef HDR_HISTOGRAM_H #define HDR_HISTOGRAM_H 1 #include #include #include struct hdr_histogram { int64_t lowest_discernible_value; int64_t highest_trackable_value; int32_t unit_magnitude; int32_t significant_figures; int32_t sub_bucket_half_count_magnitude; int32_t sub_bucket_half_count; int64_t sub_bucket_mask; int32_t sub_bucket_count; int32_t bucket_count; int64_t min_value; int64_t max_value; int32_t normalizing_index_offset; double conversion_ratio; int32_t counts_len; int64_t total_count; int64_t* counts; }; #ifdef __cplusplus extern "C" { #endif /** * Allocate the memory and initialise the hdr_histogram. * * Due to the size of the histogram being the result of some reasonably * involved math on the input parameters this function it is tricky to stack allocate. * The histogram should be released with hdr_close * * @param lowest_discernible_value The smallest possible value that is distinguishable from 0. * Must be a positive integer that is >= 1. May be internally rounded down to nearest power of 2. * @param highest_trackable_value The largest possible value to be put into the * histogram. * @param significant_figures The level of precision for this histogram, i.e. the number * of figures in a decimal number that will be maintained. E.g. a value of 3 will mean * the results from the histogram will be accurate up to the first three digits. Must * be a value between 1 and 5 (inclusive). * @param result Output parameter to capture allocated histogram. * @return 0 on success, EINVAL if lowest_discernible_value is < 1 or the * significant_figure value is outside of the allowed range, ENOMEM if malloc * failed. */ int hdr_init( int64_t lowest_discernible_value, int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result); /** * Free the memory and close the hdr_histogram. * * @param h The histogram you want to close. */ void hdr_close(struct hdr_histogram* h); /** * Allocate the memory and initialise the hdr_histogram. This is the equivalent of calling * hdr_init(1, highest_trackable_value, significant_figures, result); * * @deprecated use hdr_init. */ int hdr_alloc(int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result); /** * Reset a histogram to zero - empty out a histogram and re-initialise it * * If you want to re-use an existing histogram, but reset everything back to zero, this * is the routine to use. * * @param h The histogram you want to reset to empty. * */ void hdr_reset(struct hdr_histogram* h); /** * Get the memory size of the hdr_histogram. * * @param h "This" pointer * @return The amount of memory used by the hdr_histogram in bytes */ size_t hdr_get_memory_size(struct hdr_histogram* h); /** * Records a value in the histogram, will round this value of to a precision at or better * than the significant_figure specified at construction time. * * @param h "This" pointer * @param value Value to add to the histogram * @return false if the value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_value(struct hdr_histogram* h, int64_t value); /** * Records a value in the histogram, will round this value of to a precision at or better * than the significant_figure specified at construction time. * * Will record this value atomically, however the whole structure may appear inconsistent * when read concurrently with this update. Do NOT mix calls to this method with calls * to non-atomic updates. * * @param h "This" pointer * @param value Value to add to the histogram * @return false if the value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_value_atomic(struct hdr_histogram* h, int64_t value); /** * Records count values in the histogram, will round this value of to a * precision at or better than the significant_figure specified at construction * time. * * @param h "This" pointer * @param value Value to add to the histogram * @param count Number of 'value's to add to the histogram * @return false if any value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count); /** * Records count values in the histogram, will round this value of to a * precision at or better than the significant_figure specified at construction * time. * * Will record this value atomically, however the whole structure may appear inconsistent * when read concurrently with this update. Do NOT mix calls to this method with calls * to non-atomic updates. * * @param h "This" pointer * @param value Value to add to the histogram * @param count Number of 'value's to add to the histogram * @return false if any value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count); /** * Record a value in the histogram and backfill based on an expected interval. * * Records a value in the histogram, will round this value of to a precision at or better * than the significant_figure specified at construction time. This is specifically used * for recording latency. If the value is larger than the expected_interval then the * latency recording system has experienced co-ordinated omission. This method fills in the * values that would have occurred had the client providing the load not been blocked. * @param h "This" pointer * @param value Value to add to the histogram * @param expected_interval The delay between recording values. * @return false if the value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_corrected_value(struct hdr_histogram* h, int64_t value, int64_t expected_interval); /** * Record a value in the histogram and backfill based on an expected interval. * * Records a value in the histogram, will round this value of to a precision at or better * than the significant_figure specified at construction time. This is specifically used * for recording latency. If the value is larger than the expected_interval then the * latency recording system has experienced co-ordinated omission. This method fills in the * values that would have occurred had the client providing the load not been blocked. * * Will record this value atomically, however the whole structure may appear inconsistent * when read concurrently with this update. Do NOT mix calls to this method with calls * to non-atomic updates. * * @param h "This" pointer * @param value Value to add to the histogram * @param expected_interval The delay between recording values. * @return false if the value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_corrected_value_atomic(struct hdr_histogram* h, int64_t value, int64_t expected_interval); /** * Record a value in the histogram 'count' times. Applies the same correcting logic * as 'hdr_record_corrected_value'. * * @param h "This" pointer * @param value Value to add to the histogram * @param count Number of 'value's to add to the histogram * @param expected_interval The delay between recording values. * @return false if the value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_corrected_values(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval); /** * Record a value in the histogram 'count' times. Applies the same correcting logic * as 'hdr_record_corrected_value'. * * Will record this value atomically, however the whole structure may appear inconsistent * when read concurrently with this update. Do NOT mix calls to this method with calls * to non-atomic updates. * * @param h "This" pointer * @param value Value to add to the histogram * @param count Number of 'value's to add to the histogram * @param expected_interval The delay between recording values. * @return false if the value is larger than the highest_trackable_value and can't be recorded, * true otherwise. */ bool hdr_record_corrected_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval); /** * Adds all of the values from 'from' to 'this' histogram. Will return the * number of values that are dropped when copying. Values will be dropped * if they around outside of h.lowest_discernible_value and * h.highest_trackable_value. * * @param h "This" pointer * @param from Histogram to copy values from. * @return The number of values dropped when copying. */ int64_t hdr_add(struct hdr_histogram* h, const struct hdr_histogram* from); /** * Adds all of the values from 'from' to 'this' histogram. Will return the * number of values that are dropped when copying. Values will be dropped * if they around outside of h.lowest_discernible_value and * h.highest_trackable_value. * * @param h "This" pointer * @param from Histogram to copy values from. * @return The number of values dropped when copying. */ int64_t hdr_add_while_correcting_for_coordinated_omission( struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval); /** * Get minimum value from the histogram. Will return 2^63-1 if the histogram * is empty. * * @param h "This" pointer */ int64_t hdr_min(const struct hdr_histogram* h); /** * Get maximum value from the histogram. Will return 0 if the histogram * is empty. * * @param h "This" pointer */ int64_t hdr_max(const struct hdr_histogram* h); /** * Get the value at a specific percentile. * * @param h "This" pointer. * @param percentile The percentile to get the value for */ int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile); /** * Get the values at the given percentiles. * * @param h "This" pointer. * @param percentiles The ordered percentiles array to get the values for. * @param length Number of elements in the arrays. * @param values Destination array containing the values at the given percentiles. * The values array should be allocated by the caller. * @return 0 on success, ENOMEM if the provided destination array is null. */ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percentiles, int64_t *values, size_t length); /** * Gets the standard deviation for the values in the histogram. * * @param h "This" pointer * @return The standard deviation */ double hdr_stddev(const struct hdr_histogram* h); /** * Gets the mean for the values in the histogram. * * @param h "This" pointer * @return The mean */ double hdr_mean(const struct hdr_histogram* h); /** * Determine if two values are equivalent with the histogram's resolution. * Where "equivalent" means that value samples recorded for any two * equivalent values are counted in a common total count. * * @param h "This" pointer * @param a first value to compare * @param b second value to compare * @return 'true' if values are equivalent with the histogram's resolution. */ bool hdr_values_are_equivalent(const struct hdr_histogram* h, int64_t a, int64_t b); /** * Get the lowest value that is equivalent to the given value within the histogram's resolution. * Where "equivalent" means that value samples recorded for any two * equivalent values are counted in a common total count. * * @param h "This" pointer * @param value The given value * @return The lowest value that is equivalent to the given value within the histogram's resolution. */ int64_t hdr_lowest_equivalent_value(const struct hdr_histogram* h, int64_t value); /** * Get the count of recorded values at a specific value * (to within the histogram resolution at the value level). * * @param h "This" pointer * @param value The value for which to provide the recorded count * @return The total count of values recorded in the histogram within the value range that is * {@literal >=} lowestEquivalentValue(value) and {@literal <=} highestEquivalentValue(value) */ int64_t hdr_count_at_value(const struct hdr_histogram* h, int64_t value); int64_t hdr_count_at_index(const struct hdr_histogram* h, int32_t index); int64_t hdr_value_at_index(const struct hdr_histogram* h, int32_t index); struct hdr_iter_percentiles { bool seen_last_value; int32_t ticks_per_half_distance; double percentile_to_iterate_to; double percentile; }; struct hdr_iter_recorded { int64_t count_added_in_this_iteration_step; }; struct hdr_iter_linear { int64_t value_units_per_bucket; int64_t count_added_in_this_iteration_step; int64_t next_value_reporting_level; int64_t next_value_reporting_level_lowest_equivalent; }; struct hdr_iter_log { double log_base; int64_t count_added_in_this_iteration_step; int64_t next_value_reporting_level; int64_t next_value_reporting_level_lowest_equivalent; }; /** * The basic iterator. This is a generic structure * that supports all of the types of iteration. Use * the appropriate initialiser to get the desired * iteration. * * @ */ struct hdr_iter { const struct hdr_histogram* h; /** raw index into the counts array */ int32_t counts_index; /** snapshot of the length at the time the iterator is created */ int64_t total_count; /** value directly from array for the current counts_index */ int64_t count; /** sum of all of the counts up to and including the count at this index */ int64_t cumulative_count; /** The current value based on counts_index */ int64_t value; int64_t highest_equivalent_value; int64_t lowest_equivalent_value; int64_t median_equivalent_value; int64_t value_iterated_from; int64_t value_iterated_to; union { struct hdr_iter_percentiles percentiles; struct hdr_iter_recorded recorded; struct hdr_iter_linear linear; struct hdr_iter_log log; } specifics; bool (* _next_fp)(struct hdr_iter* iter); }; /** * Initalises the basic iterator. * * @param itr 'This' pointer * @param h The histogram to iterate over */ void hdr_iter_init(struct hdr_iter* iter, const struct hdr_histogram* h); /** * Initialise the iterator for use with percentiles. */ void hdr_iter_percentile_init(struct hdr_iter* iter, const struct hdr_histogram* h, int32_t ticks_per_half_distance); /** * Initialise the iterator for use with recorded values. */ void hdr_iter_recorded_init(struct hdr_iter* iter, const struct hdr_histogram* h); /** * Initialise the iterator for use with linear values. */ void hdr_iter_linear_init( struct hdr_iter* iter, const struct hdr_histogram* h, int64_t value_units_per_bucket); /** * Update the iterator value units per bucket */ void hdr_iter_linear_set_value_units_per_bucket(struct hdr_iter* iter, int64_t value_units_per_bucket); /** * Initialise the iterator for use with logarithmic values */ void hdr_iter_log_init( struct hdr_iter* iter, const struct hdr_histogram* h, int64_t value_units_first_bucket, double log_base); /** * Iterate to the next value for the iterator. If there are no more values * available return faluse. * * @param itr 'This' pointer * @return 'false' if there are no values remaining for this iterator. */ bool hdr_iter_next(struct hdr_iter* iter); typedef enum { CLASSIC, CSV } format_type; /** * Print out a percentile based histogram to the supplied stream. Note that * this call will not flush the FILE, this is left up to the user. * * @param h 'This' pointer * @param stream The FILE to write the output to * @param ticks_per_half_distance The number of iteration steps per half-distance to 100% * @param value_scale Scale the output values by this amount * @param format_type Format to use, e.g. CSV. * @return 0 on success, error code on failure. EIO if an error occurs writing * the output. */ int hdr_percentiles_print( struct hdr_histogram* h, FILE* stream, int32_t ticks_per_half_distance, double value_scale, format_type format); /** * Internal allocation methods, used by hdr_dbl_histogram. */ struct hdr_histogram_bucket_config { int64_t lowest_discernible_value; int64_t highest_trackable_value; int64_t unit_magnitude; int64_t significant_figures; int32_t sub_bucket_half_count_magnitude; int32_t sub_bucket_half_count; int64_t sub_bucket_mask; int32_t sub_bucket_count; int32_t bucket_count; int32_t counts_len; }; int hdr_calculate_bucket_config( int64_t lowest_discernible_value, int64_t highest_trackable_value, int significant_figures, struct hdr_histogram_bucket_config* cfg); void hdr_init_preallocated(struct hdr_histogram* h, struct hdr_histogram_bucket_config* cfg); int64_t hdr_size_of_equivalent_value_range(const struct hdr_histogram* h, int64_t value); int64_t hdr_next_non_equivalent_value(const struct hdr_histogram* h, int64_t value); int64_t hdr_median_equivalent_value(const struct hdr_histogram* h, int64_t value); /** * Used to reset counters after importing data manually into the histogram, used by the logging code * and other custom serialisation tools. */ void hdr_reset_internal_counters(struct hdr_histogram* h); #ifdef __cplusplus } #endif #endif redis-8.0.2/deps/hdr_histogram/hdr_redis_malloc.h000066400000000000000000000004541501533116600220630ustar00rootroot00000000000000#ifndef HDR_MALLOC_H__ #define HDR_MALLOC_H__ void *zmalloc(size_t size); void *zcalloc_num(size_t num, size_t size); void *zrealloc(void *ptr, size_t size); void zfree(void *ptr); #define hdr_malloc zmalloc #define hdr_calloc zcalloc_num #define hdr_realloc zrealloc #define hdr_free zfree #endif redis-8.0.2/deps/hdr_histogram/hdr_tests.h000066400000000000000000000011671501533116600205720ustar00rootroot00000000000000#ifndef HDR_TESTS_H #define HDR_TESTS_H /* These are functions used in tests and are not intended for normal usage. */ #include "hdr_histogram.h" #ifdef __cplusplus extern "C" { #endif int32_t counts_index_for(const struct hdr_histogram* h, int64_t value); int hdr_encode_compressed(struct hdr_histogram* h, uint8_t** compressed_histogram, size_t* compressed_len); int hdr_decode_compressed(uint8_t* buffer, size_t length, struct hdr_histogram** histogram); void hdr_base64_decode_block(const char* input, uint8_t* output); void hdr_base64_encode_block(const uint8_t* input, char* output); #ifdef __cplusplus } #endif #endif redis-8.0.2/deps/hiredis/000077500000000000000000000000001501533116600152125ustar00rootroot00000000000000redis-8.0.2/deps/hiredis/.github/000077500000000000000000000000001501533116600165525ustar00rootroot00000000000000redis-8.0.2/deps/hiredis/.github/release-drafter-config.yml000066400000000000000000000016601501533116600236100ustar00rootroot00000000000000name-template: '$NEXT_MAJOR_VERSION' tag-template: 'v$NEXT_MAJOR_VERSION' autolabeler: - label: 'maintenance' files: - '*.md' - '.github/*' - label: 'bug' branch: - '/bug-.+' - label: 'maintenance' branch: - '/maintenance-.+' - label: 'feature' branch: - '/feature-.+' categories: - title: 'Breaking Changes' labels: - 'breakingchange' - title: '🧪 Experimental Features' labels: - 'experimental' - title: '🚀 New Features' labels: - 'feature' - 'enhancement' - title: '🛠Bug Fixes' labels: - 'fix' - 'bugfix' - 'bug' - 'BUG' - title: '🧰 Maintenance' label: 'maintenance' change-template: '- $TITLE @$AUTHOR (#$NUMBER)' exclude-labels: - 'skip-changelog' template: | ## Changes $CHANGES ## Contributors We'd like to thank all the contributors who worked on this release! $CONTRIBUTORS redis-8.0.2/deps/hiredis/.github/workflows/000077500000000000000000000000001501533116600206075ustar00rootroot00000000000000redis-8.0.2/deps/hiredis/.github/workflows/build.yml000066400000000000000000000120131501533116600224260ustar00rootroot00000000000000name: Build and test on: [push, pull_request] jobs: ubuntu: name: Ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install dependencies run: | curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list sudo apt-get update sudo apt-get install -y redis-server valgrind libevent-dev - name: Build using cmake env: EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON CFLAGS: -Werror CXXFLAGS: -Werror run: mkdir build && cd build && cmake .. && make - name: Build using makefile run: USE_SSL=1 TEST_ASYNC=1 make - name: Run tests env: SKIPS_AS_FAILS: 1 TEST_SSL: 1 run: $GITHUB_WORKSPACE/test.sh # - name: Run tests under valgrind # env: # SKIPS_AS_FAILS: 1 # TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full # run: $GITHUB_WORKSPACE/test.sh centos7: name: CentOS 7 runs-on: ubuntu-latest container: centos:7 steps: - uses: actions/checkout@v3 - name: Install dependencies run: | yum -y install http://rpms.remirepo.net/enterprise/remi-release-7.rpm yum -y --enablerepo=remi install redis yum -y install gcc gcc-c++ make openssl openssl-devel cmake3 valgrind libevent-devel - name: Build using cmake env: EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON CFLAGS: -Werror CXXFLAGS: -Werror run: mkdir build && cd build && cmake3 .. && make - name: Build using Makefile run: USE_SSL=1 TEST_ASYNC=1 make - name: Run tests env: SKIPS_AS_FAILS: 1 TEST_SSL: 1 run: $GITHUB_WORKSPACE/test.sh - name: Run tests under valgrind env: SKIPS_AS_FAILS: 1 TEST_SSL: 1 TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full run: $GITHUB_WORKSPACE/test.sh centos8: name: RockyLinux 8 runs-on: ubuntu-latest container: rockylinux:8 steps: - uses: actions/checkout@v3 - name: Install dependencies run: | dnf -y upgrade --refresh dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm dnf -y module install redis:remi-6.0 dnf -y group install "Development Tools" dnf -y install openssl-devel cmake valgrind libevent-devel - name: Build using cmake env: EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON CFLAGS: -Werror CXXFLAGS: -Werror run: mkdir build && cd build && cmake .. && make - name: Build using Makefile run: USE_SSL=1 TEST_ASYNC=1 make - name: Run tests env: SKIPS_AS_FAILS: 1 TEST_SSL: 1 run: $GITHUB_WORKSPACE/test.sh - name: Run tests under valgrind env: SKIPS_AS_FAILS: 1 TEST_SSL: 1 TEST_PREFIX: valgrind --error-exitcode=99 --track-origins=yes --leak-check=full run: $GITHUB_WORKSPACE/test.sh freebsd: runs-on: macos-12 name: FreeBSD steps: - uses: actions/checkout@v3 - name: Build in FreeBSD uses: vmactions/freebsd-vm@v0 with: prepare: pkg install -y gmake cmake run: | mkdir build && cd build && cmake .. && make && cd .. gmake macos: name: macOS runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Install dependencies run: | brew install openssl redis@7.0 brew link redis@7.0 --force - name: Build hiredis run: USE_SSL=1 make - name: Run tests env: TEST_SSL: 1 run: $GITHUB_WORKSPACE/test.sh windows: name: Windows runs-on: windows-latest steps: - uses: actions/checkout@v3 - name: Install dependencies run: | choco install -y ninja memurai-developer - uses: ilammy/msvc-dev-cmd@v1 - name: Build hiredis run: | mkdir build && cd build cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON ninja -v - name: Run tests run: | ./build/hiredis-test.exe - name: Install Cygwin Action uses: cygwin/cygwin-install-action@v2 with: packages: make git gcc-core - name: Build in cygwin env: HIREDIS_PATH: ${{ github.workspace }} run: | make clean && make redis-8.0.2/deps/hiredis/.github/workflows/release-drafter.yml000066400000000000000000000010621501533116600243760ustar00rootroot00000000000000name: Release Drafter on: push: # branches to consider in the event; optional, defaults to all branches: - master jobs: update_release_draft: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - uses: release-drafter/release-drafter@v5 with: # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml config-name: release-drafter-config.yml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} redis-8.0.2/deps/hiredis/.github/workflows/test.yml000066400000000000000000000057311501533116600223170ustar00rootroot00000000000000name: C/C++ CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: full-build: name: Build all, plus default examples, run tests against redis runs-on: ubuntu-latest env: # the docker image used by the test.sh REDIS_DOCKER: redis:alpine steps: - name: Install prerequisites run: sudo apt-get update && sudo apt-get install -y libev-dev libevent-dev libglib2.0-dev libssl-dev valgrind - uses: actions/checkout@v3 - name: Run make run: make all examples - name: Run unittests run: make check - name: Run tests with valgrind env: TEST_PREFIX: valgrind --error-exitcode=100 SKIPS_ARG: --skip-throughput run: make check build-32-bit: name: Build and test minimal 32 bit linux runs-on: ubuntu-latest steps: - name: Install prerequisites run: sudo apt-get update && sudo apt-get install gcc-multilib - uses: actions/checkout@v3 - name: Run make run: make all env: PLATFORM_FLAGS: -m32 - name: Run unittests env: REDIS_DOCKER: redis:alpine run: make check build-arm: name: Cross-compile and test arm linux with Qemu runs-on: ubuntu-latest strategy: matrix: include: - name: arm toolset: arm-linux-gnueabi emulator: qemu-arm - name: aarch64 toolset: aarch64-linux-gnu emulator: qemu-aarch64 steps: - name: Install qemu if: matrix.emulator run: sudo apt-get update && sudo apt-get install -y qemu-user - name: Install platform toolset if: matrix.toolset run: sudo apt-get install -y gcc-${{matrix.toolset}} - uses: actions/checkout@v3 - name: Run make run: make all env: CC: ${{matrix.toolset}}-gcc AR: ${{matrix.toolset}}-ar - name: Run unittests env: REDIS_DOCKER: redis:alpine TEST_PREFIX: ${{matrix.emulator}} -L /usr/${{matrix.toolset}}/ run: make check build-windows: name: Build and test on windows 64 bit Intel runs-on: windows-latest steps: - uses: microsoft/setup-msbuild@v1.0.2 - uses: actions/checkout@v3 - name: Run CMake (shared lib) run: cmake -Wno-dev CMakeLists.txt - name: Build hiredis (shared lib) run: MSBuild hiredis.vcxproj /p:Configuration=Debug - name: Run CMake (static lib) run: cmake -Wno-dev CMakeLists.txt -DBUILD_SHARED_LIBS=OFF - name: Build hiredis (static lib) run: MSBuild hiredis.vcxproj /p:Configuration=Debug - name: Build hiredis-test run: MSBuild hiredis-test.vcxproj /p:Configuration=Debug # use memurai, redis compatible server, since it is easy to install. Can't # install official redis containers on the windows runner - name: Install Memurai redis server run: choco install -y memurai-developer.install - name: Run tests run: Debug\hiredis-test.exe redis-8.0.2/deps/hiredis/.gitignore000066400000000000000000000001241501533116600171770ustar00rootroot00000000000000/hiredis-test /examples/hiredis-example* /*.o /*.so /*.dylib /*.a /*.pc *.dSYM tags redis-8.0.2/deps/hiredis/.travis.yml000066400000000000000000000062521501533116600173300ustar00rootroot00000000000000language: c compiler: - gcc - clang os: - linux - osx dist: bionic branches: only: - staging - trying - master - /^release\/.*$/ install: - if [ "$TRAVIS_COMPILER" != "mingw" ]; then wget https://github.com/redis/redis/archive/6.0.6.tar.gz; tar -xzvf 6.0.6.tar.gz; pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd; fi; before_script: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg; sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /; export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate; sudo port -N install openssl redis; fi; addons: apt: packages: - libc6-dbg - libc6-dev - libc6:i386 - libc6-dev-i386 - libc6-dbg:i386 - gcc-multilib - g++-multilib - libssl-dev - libssl-dev:i386 - valgrind env: - BITS="32" - BITS="64" script: - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON"; if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; EXTRA_CMAKE_OPTS=; else CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; else TEST_PREFIX="valgrind --track-origins=yes --leak-check=full"; if [ "$BITS" == "32" ]; then CFLAGS="-m32 -Werror"; CXXFLAGS="-m32 -Werror"; LDFLAGS="-m32"; EXTRA_CMAKE_OPTS=; else CFLAGS="-Werror"; CXXFLAGS="-Werror"; fi; fi; export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS - make && make clean; if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$BITS" == "64" ]; then OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make; fi; else USE_SSL=1 make; fi; - mkdir build/ && cd build/ - cmake .. ${EXTRA_CMAKE_OPTS} - make VERBOSE=1 - if [ "$BITS" == "64" ]; then TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V; else SKIPS_AS_FAILS=1 ctest -V; fi; jobs: include: # Windows MinGW cross compile on Linux - os: linux dist: xenial compiler: mingw addons: apt: packages: - ninja-build - gcc-mingw-w64-x86-64 - g++-mingw-w64-x86-64 script: - mkdir build && cd build - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on - ninja -v # Windows MSVC 2017 - os: windows compiler: msvc env: - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe" before_install: - eval "${MATRIX_EVAL}" install: - choco install ninja - choco install -y memurai-developer script: - mkdir build && cd build - cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&' cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v - ./hiredis-test.exe redis-8.0.2/deps/hiredis/CHANGELOG.md000066400000000000000000001273431501533116600170350ustar00rootroot00000000000000## [1.2.0](https://github.com/redis/hiredis/tree/v1.2.0) - (2023-06-04) Announcing Hiredis v1.2.0 with with new adapters, and a great many bug fixes. ## 🚀 New Features - Add sdevent adapter @Oipo (#1144) - Allow specifying the keepalive interval @michael-grunder (#1168) - Add RedisModule adapter @tezc (#1182) - Helper for setting TCP_USER_TIMEOUT socket option @zuiderkwast (#1188) ## 🛠Bug Fixes - Fix a typo in b6a052f. @yossigo (#1190) - Fix wincrypt symbols conflict @hudayou (#1151) - Don't attempt to set a timeout if we are in an error state. @michael-grunder (#1180) - Accept -nan per the RESP3 spec recommendation. @michael-grunder (#1178) - Fix colliding option values @zuiderkwast (#1172) - Ensure functionality without `_MSC_VER` definition @windyakin (#1194) ## 🧰 Maintenance - Add a test for the TCP_USER_TIMEOUT option. @michael-grunder (#1192) - Add -Werror as a default. @yossigo (#1193) - CI: Update homebrew Redis version. @yossigo (#1191) - Fix typo in makefile. @michael-grunder (#1179) - Write a version file for the CMake package @Neverlord (#1165) - CMakeLists.txt: respect BUILD_SHARED_LIBS @ffontaine (#1147) - Cmake static or shared @autoantwort (#1160) - fix typo @tillkruss (#1153) - Add a test ensuring we don't clobber connection error. @michael-grunder (#1181) - Search for openssl on macOS @michael-grunder (#1169) ## Contributors We'd like to thank all the contributors who worked on this release! ## [1.1.0](https://github.com/redis/hiredis/tree/v1.1.0) - (2022-11-15) Announcing Hiredis v1.1.0 GA with better SSL convenience, new async adapters and a great many bug fixes. **NOTE**: Hiredis can now return `nan` in addition to `-inf` and `inf` when returning a `REDIS_REPLY_DOUBLE`. ## 🛠Bug Fixes - Add support for nan in RESP3 double [@filipecosta90](https://github.com/filipecosta90) ([\#1133](https://github.com/redis/hiredis/pull/1133)) ## 🧰 Maintenance - Add an example that calls redisCommandArgv [@michael-grunder](https://github.com/michael-grunder) ([\#1140](https://github.com/redis/hiredis/pull/1140)) - fix flag reference [@pata00](https://github.com/pata00) ([\#1136](https://github.com/redis/hiredis/pull/1136)) - Make freeing a NULL redisAsyncContext a no op. [@michael-grunder](https://github.com/michael-grunder) ([\#1135](https://github.com/redis/hiredis/pull/1135)) - CI updates ([@bjosv](https://github.com/redis/bjosv) ([\#1139](https://github.com/redis/hiredis/pull/1139)) ## Contributors We'd like to thank all the contributors who worked on this release! ## [1.1.0-rc1](https://github.com/redis/hiredis/tree/v1.1.0-rc1) - (2022-11-06) Announcing Hiredis v1.1.0-rc1, with better SSL convenience, new async adapters, and a great many bug fixes. ## 🚀 New Features - Add possibility to prefer IPv6, IPv4 or unspecified [@zuiderkwast](https://github.com/zuiderkwast) ([\#1096](https://github.com/redis/hiredis/pull/1096)) - Add adapters/libhv [@ithewei](https://github.com/ithewei) ([\#904](https://github.com/redis/hiredis/pull/904)) - Add timeout support to libhv adapter. [@michael-grunder](https://github.com/michael-grunder) ([\#1109](https://github.com/redis/hiredis/pull/1109)) - set default SSL verification path [@adobeturchenko](https://github.com/adobeturchenko) ([\#928](https://github.com/redis/hiredis/pull/928)) - Introduce .close method for redisContextFuncs [@pizhenwei](https://github.com/pizhenwei) ([\#1094](https://github.com/redis/hiredis/pull/1094)) - Make it possible to set SSL verify mode [@stanhu](https://github.com/stanhu) ([\#1085](https://github.com/redis/hiredis/pull/1085)) - Polling adapter and example [@kristjanvalur](https://github.com/kristjanvalur) ([\#932](https://github.com/redis/hiredis/pull/932)) - Unsubscribe handling in async [@bjosv](https://github.com/bjosv) ([\#1047](https://github.com/redis/hiredis/pull/1047)) - Add timeout support for libuv adapter [@MichaelSuen-thePointer](https://github.com/@MichaelSuenthePointer) ([\#1016](https://github.com/redis/hiredis/pull/1016)) ## 🛠Bug Fixes - Update for MinGW cross compile [@bit0fun](https://github.com/bit0fun) ([\#1127](https://github.com/redis/hiredis/pull/1127)) - fixed CPP build error with adapters/libhv.h [@mtdxc](https://github.com/mtdxc) ([\#1125](https://github.com/redis/hiredis/pull/1125)) - Fix protocol error [@michael-grunder](https://github.com/michael-grunder), [@mtuleika-appcast](https://github.com/mtuleika-appcast) ([\#1106](https://github.com/redis/hiredis/pull/1106)) - Use a windows specific keepalive function. [@michael-grunder](https://github.com/michael-grunder) ([\#1104](https://github.com/redis/hiredis/pull/1104)) - Fix CMake config path on Linux. [@xkszltl](https://github.com/xkszltl) ([\#989](https://github.com/redis/hiredis/pull/989)) - Fix potential fault at createDoubleObject [@afcidk](https://github.com/afcidk) ([\#964](https://github.com/redis/hiredis/pull/964)) - Fix some undefined behavior [@jengab](https://github.com/jengab) ([\#1091](https://github.com/redis/hiredis/pull/1091)) - Copy OOM errors to redisAsyncContext when finding subscribe callback [@bjosv](https://github.com/bjosv) ([\#1090](https://github.com/redis/hiredis/pull/1090)) - Maintain backward compatibility with our onConnect callback. [@michael-grunder](https://github.com/michael-grunder) ([\#1087](https://github.com/redis/hiredis/pull/1087)) - Fix PUSH handler tests for Redis >= 7.0.5 [@michael-grunder](https://github.com/michael-grunder) ([\#1121](https://github.com/redis/hiredis/pull/1121)) - fix heap-buffer-overflow [@zhangtaoXT5](https://github.com/zhangtaoXT5) ([\#957](https://github.com/redis/hiredis/pull/957)) - Fix heap-buffer-overflow issue in redisvFormatCommad [@bjosv](https://github.com/bjosv) ([\#1097](https://github.com/redis/hiredis/pull/1097)) - Polling adapter requires sockcompat.h [@michael-grunder](https://github.com/michael-grunder) ([\#1095](https://github.com/redis/hiredis/pull/1095)) - Illumos test fixes, error message difference for bad hostname test. [@devnexen](https://github.com/devnexen) ([\#901](https://github.com/redis/hiredis/pull/901)) - Remove semicolon after do-while in \_EL\_CLEANUP [@sundb](https://github.com/sundb) ([\#905](https://github.com/redis/hiredis/pull/905)) - Stability: Support calling redisAsyncCommand and redisAsyncDisconnect from the onConnected callback [@kristjanvalur](https://github.com/kristjanvalur) ([\#931](https://github.com/redis/hiredis/pull/931)) - Fix async connect on Windows [@kristjanvalur](https://github.com/kristjanvalur) ([\#1073](https://github.com/redis/hiredis/pull/1073)) - Fix tests so they work for Redis 7.0 [@michael-grunder](https://github.com/michael-grunder) ([\#1072](https://github.com/redis/hiredis/pull/1072)) - Fix warnings on Win64 [@orgads](https://github.com/orgads) ([\#1058](https://github.com/redis/hiredis/pull/1058)) - Handle push notifications before or after reply. [@yossigo](https://github.com/yossigo) ([\#1062](https://github.com/redis/hiredis/pull/1062)) - Update hiredis sds with improvements found in redis [@bjosv](https://github.com/bjosv) ([\#1045](https://github.com/redis/hiredis/pull/1045)) - Avoid incorrect call to the previous reply's callback [@bjosv](https://github.com/bjosv) ([\#1040](https://github.com/redis/hiredis/pull/1040)) - fix building on AIX and SunOS [\#1031](https://github.com/redis/hiredis/pull/1031) ([@scddev](https://github.com/scddev)) - Allow sending commands after sending an unsubscribe [@bjosv](https://github.com/bjosv) ([\#1036](https://github.com/redis/hiredis/pull/1036)) - Correction for command timeout during pubsub [@bjosv](https://github.com/bjosv) ([\#1038](https://github.com/redis/hiredis/pull/1038)) - Fix adapters/libevent.h compilation for 64-bit Windows [@pbtummillo](https://github.com/pbtummillo) ([\#937](https://github.com/redis/hiredis/pull/937)) - Fix integer overflow when format command larger than 4GB [@sundb](https://github.com/sundb) ([\#1030](https://github.com/redis/hiredis/pull/1030)) - Handle array response during subscribe in RESP3 [@bjosv](https://github.com/bjosv) ([\#1014](https://github.com/redis/hiredis/pull/1014)) - Support PING while subscribing (RESP2) [@bjosv](https://github.com/bjosv) ([\#1027](https://github.com/redis/hiredis/pull/1027)) ## 🧰 Maintenance - CI fixes in preparation of release [@michael-grunder](https://github.com/michael-grunder) ([\#1130](https://github.com/redis/hiredis/pull/1130)) - Add do while(0) (protection for macros [@afcidk](https://github.com/afcidk) [\#959](https://github.com/redis/hiredis/pull/959)) - Fixup of PR734: Coverage of hiredis.c [@bjosv](https://github.com/bjosv) ([\#1124](https://github.com/redis/hiredis/pull/1124)) - CMake corrections for building on Windows [@bjosv](https://github.com/bjosv) ([\#1122](https://github.com/redis/hiredis/pull/1122)) - Install on windows fixes [@bjosv](https://github.com/bjosv) ([\#1117](https://github.com/redis/hiredis/pull/1117)) - Add libhv example to our standard Makefile [@michael-grunder](https://github.com/michael-grunder) ([\#1108](https://github.com/redis/hiredis/pull/1108)) - Additional include directory given by pkg-config [@bjosv](https://github.com/bjosv) ([\#1118](https://github.com/redis/hiredis/pull/1118)) - Use __attribute__ when building with Clang on Windows [@bjosv](https://github.com/bjosv) ([\#1115](https://github.com/redis/hiredis/pull/1115)) - Minor refactor [@michael-grunder](https://github.com/michael-grunder) ([\#1110](https://github.com/redis/hiredis/pull/1110)) - Fix pkgconfig result for hiredis_ssl [@bjosv](https://github.com/bjosv) ([\#1107](https://github.com/redis/hiredis/pull/1107)) - Update documentation to explain redisConnectWithOptions. [@michael-grunder](https://github.com/michael-grunder) ([\#1099](https://github.com/redis/hiredis/pull/1099)) - uvadapter: reduce number of uv_poll_start calls [@noxiouz](https://github.com/noxiouz) ([\#1098](https://github.com/redis/hiredis/pull/1098)) - Regression test for off-by-one parsing error [@bugwz](https://github.com/bugwz) ([\#1092](https://github.com/redis/hiredis/pull/1092)) - CMake: remove dict.c form hiredis_sources [@Lipraxde](https://github.com/Lipraxde) ([\#1055](https://github.com/redis/hiredis/pull/1055)) - Do store command timeout in the context for redisSetTimeout [@catterer](https://github.com/catterer) ([\#593](https://github.com/redis/hiredis/pull/593), [\#1093](https://github.com/redis/hiredis/pull/1093)) - Add GitHub Actions CI workflow for hiredis: Arm, Arm64, 386, windows. [@kristjanvalur](https://github.com/kristjanvalur) ([\#943](https://github.com/redis/hiredis/pull/943)) - CI: bump macOS runner version [@SukkaW](https://github.com/SukkaW) ([\#1079](https://github.com/redis/hiredis/pull/1079)) - Support for generating release notes [@chayim](https://github.com/chayim) ([\#1083](https://github.com/redis/hiredis/pull/1083)) - Improve example for SSL initialization in README.md [@stanhu](https://github.com/stanhu) ([\#1084](https://github.com/redis/hiredis/pull/1084)) - Fix README typos [@bjosv](https://github.com/bjosv) ([\#1080](https://github.com/redis/hiredis/pull/1080)) - fix cmake version [@smmir-cent](https://github.com/@smmircent) ([\#1050](https://github.com/redis/hiredis/pull/1050)) - Use the same name for static and shared libraries [@orgads](https://github.com/orgads) ([\#1057](https://github.com/redis/hiredis/pull/1057)) - Embed debug information in windows static .lib file [@kristjanvalur](https://github.com/kristjanvalur) ([\#1054](https://github.com/redis/hiredis/pull/1054)) - Improved async documentation [@kristjanvalur](https://github.com/kristjanvalur) ([\#1074](https://github.com/redis/hiredis/pull/1074)) - Use official repository for redis package. [@yossigo](https://github.com/yossigo) ([\#1061](https://github.com/redis/hiredis/pull/1061)) - Whitelist hiredis repo path in cygwin [@michael-grunder](https://github.com/michael-grunder) ([\#1063](https://github.com/redis/hiredis/pull/1063)) - CentOS 8 is EOL, switch to RockyLinux [@michael-grunder](https://github.com/michael-grunder) ([\#1046](https://github.com/redis/hiredis/pull/1046)) - CMakeLists.txt: allow building without a C++ compiler [@ffontaine](https://github.com/ffontaine) ([\#872](https://github.com/redis/hiredis/pull/872)) - Makefile: move SSL options into a block and refine rules [@pizhenwei](https://github.com/pizhenwei) ([\#997](https://github.com/redis/hiredis/pull/997)) - Update CMakeLists.txt for more portability [@EricDeng1001](https://github.com/EricDeng1001) ([\#1005](https://github.com/redis/hiredis/pull/1005)) - FreeBSD build fixes + CI [@michael-grunder](https://github.com/michael-grunder) ([\#1026](https://github.com/redis/hiredis/pull/1026)) - Add asynchronous test for pubsub using RESP3 [@bjosv](https://github.com/bjosv) ([\#1012](https://github.com/redis/hiredis/pull/1012)) - Trigger CI failure when Valgrind issues are found [@bjosv](https://github.com/bjosv) ([\#1011](https://github.com/redis/hiredis/pull/1011)) - Move to using make directly in Cygwin [@michael-grunder](https://github.com/michael-grunder) ([\#1020](https://github.com/redis/hiredis/pull/1020)) - Add asynchronous API tests [@bjosv](https://github.com/bjosv) ([\#1010](https://github.com/redis/hiredis/pull/1010)) - Correcting the build target `coverage` for enabled SSL [@bjosv](https://github.com/bjosv) ([\#1009](https://github.com/redis/hiredis/pull/1009)) - GH Actions: Run SSL tests during CI [@bjosv](https://github.com/bjosv) ([\#1008](https://github.com/redis/hiredis/pull/1008)) - GH: Actions - Add valgrind and CMake [@michael-grunder](https://github.com/michael-grunder) ([\#1004](https://github.com/redis/hiredis/pull/1004)) - Add Centos8 tests in GH Actions [@michael-grunder](https://github.com/michael-grunder) ([\#1001](https://github.com/redis/hiredis/pull/1001)) - We should run actions on PRs [@michael-grunder](https://github.com/michael-grunder) (([\#1000](https://github.com/redis/hiredis/pull/1000)) - Add Cygwin test in GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#999](https://github.com/redis/hiredis/pull/999)) - Add Windows tests in GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#996](https://github.com/redis/hiredis/pull/996)) - Switch to GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#995](https://github.com/redis/hiredis/pull/995)) - Minor refactor of CVE-2021-32765 fix. [@michael-grunder](https://github.com/michael-grunder) ([\#993](https://github.com/redis/hiredis/pull/993)) - Remove extra comma from CMake var. [@xkszltl](https://github.com/xkszltl) ([\#988](https://github.com/redis/hiredis/pull/988)) - Add REDIS\_OPT\_PREFER\_UNSPEC [@michael-grunder](https://github.com/michael-grunder) ([\#1101](https://github.com/redis/hiredis/pull/1101)) ## Contributors We'd like to thank all the contributors who worked on this release! ## [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) - (2021-10-07) Announcing Hiredis v1.0.2, which fixes CVE-2021-32765 but returns the SONAME to the correct value of `1.0.0`. - [Revert SONAME bump](https://github.com/redis/hiredis/commit/d4e6f109a064690cde64765c654e679fea1d3548) ([Michael Grunder](https://github.com/michael-grunder)) ## [1.0.1](https://github.com/redis/hiredis/tree/v1.0.1) - (2021-10-04) This release erroneously bumped the SONAME, please use [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) Announcing Hiredis v1.0.1, a security release fixing CVE-2021-32765 - Fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2) [commit](https://github.com/redis/hiredis/commit/76a7b10005c70babee357a7d0f2becf28ec7ed1e) ([Yossi Gottlieb](https://github.com/yossigo)) _Thanks to [Yossi Gottlieb](https://github.com/yossigo) for the security fix and to [Microsoft Security Vulnerability Research](https://www.microsoft.com/en-us/msrc/msvr) for finding the bug._ :sparkling_heart: ## [1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) - (2020-08-03) Announcing Hiredis v1.0.0, which adds support for RESP3, SSL connections, allocator injection, and better Windows support! :tada: _A big thanks to everyone who helped with this release. The following list includes everyone who contributed at least five lines, sorted by lines contributed._ :sparkling_heart: [Michael Grunder](https://github.com/michael-grunder), [Yossi Gottlieb](https://github.com/yossigo), [Mark Nunberg](https://github.com/mnunberg), [Marcus Geelnard](https://github.com/mbitsnbites), [Justin Brewer](https://github.com/justinbrewer), [Valentino Geron](https://github.com/valentinogeron), [Minun Dragonation](https://github.com/dragonation), [Omri Steiner](https://github.com/OmriSteiner), [Sangmoon Yi](https://github.com/jman-krafton), [Jinjiazh](https://github.com/jinjiazhang), [Odin Hultgren Van Der Horst](https://github.com/Miniwoffer), [Muhammad Zahalqa](https://github.com/tryfinally), [Nick Rivera](https://github.com/heronr), [Qi Yang](https://github.com/movebean), [kevin1018](https://github.com/kevin1018) [Full Changelog](https://github.com/redis/hiredis/compare/v0.14.1...v1.0.0) **BREAKING CHANGES**: * `redisOptions` now has two timeout fields. One for connecting, and one for commands. If you're presently using `options->timeout` you will need to change it to use `options->connect_timeout`. (See [example](https://github.com/redis/hiredis/commit/38b5ae543f5c99eb4ccabbe277770fc6bc81226f#diff-86ba39d37aa829c8c82624cce4f049fbL36)) * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`. * `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter. **New features:** - Support for RESP3 [\#697](https://github.com/redis/hiredis/pull/697), [\#805](https://github.com/redis/hiredis/pull/805), [\#819](https://github.com/redis/hiredis/pull/819), [\#841](https://github.com/redis/hiredis/pull/841) ([Yossi Gottlieb](https://github.com/yossigo), [Michael Grunder](https://github.com/michael-grunder)) - Support for SSL connections [\#645](https://github.com/redis/hiredis/pull/645), [\#699](https://github.com/redis/hiredis/pull/699), [\#702](https://github.com/redis/hiredis/pull/702), [\#708](https://github.com/redis/hiredis/pull/708), [\#711](https://github.com/redis/hiredis/pull/711), [\#821](https://github.com/redis/hiredis/pull/821), [more](https://github.com/redis/hiredis/pulls?q=is%3Apr+is%3Amerged+SSL) ([Mark Nunberg](https://github.com/mnunberg), [Yossi Gottlieb](https://github.com/yossigo)) - Run-time allocator injection [\#800](https://github.com/redis/hiredis/pull/800) ([Michael Grunder](https://github.com/michael-grunder)) - Improved Windows support (including MinGW and Windows CI) [\#652](https://github.com/redis/hiredis/pull/652), [\#663](https://github.com/redis/hiredis/pull/663) ([Marcus Geelnard](https://www.bitsnbites.eu/author/m/)) - Adds support for distinct connect and command timeouts [\#839](https://github.com/redis/hiredis/pull/839), [\#829](https://github.com/redis/hiredis/pull/829) ([Valentino Geron](https://github.com/valentinogeron)) - Add generic pointer and destructor to `redisContext` that users can use for context. [\#855](https://github.com/redis/hiredis/pull/855) ([Michael Grunder](https://github.com/michael-grunder)) **Closed issues (that involved code changes):** - Makefile does not install TLS libraries [\#809](https://github.com/redis/hiredis/issues/809) - redisConnectWithOptions should not set command timeout [\#722](https://github.com/redis/hiredis/issues/722), [\#829](https://github.com/redis/hiredis/pull/829) ([valentinogeron](https://github.com/valentinogeron)) - Fix integer overflow in `sdsrange` [\#827](https://github.com/redis/hiredis/issues/827) - INFO & CLUSTER commands failed when using RESP3 [\#802](https://github.com/redis/hiredis/issues/802) - Windows compatibility patches [\#687](https://github.com/redis/hiredis/issues/687), [\#838](https://github.com/redis/hiredis/issues/838), [\#842](https://github.com/redis/hiredis/issues/842) - RESP3 PUSH messages incorrectly use pending callback [\#825](https://github.com/redis/hiredis/issues/825) - Asynchronous PSUBSCRIBE command fails when using RESP3 [\#815](https://github.com/redis/hiredis/issues/815) - New SSL API [\#804](https://github.com/redis/hiredis/issues/804), [\#813](https://github.com/redis/hiredis/issues/813) - Hard-coded limit of nested reply depth [\#794](https://github.com/redis/hiredis/issues/794) - Fix TCP_NODELAY in Windows/OSX [\#679](https://github.com/redis/hiredis/issues/679), [\#690](https://github.com/redis/hiredis/issues/690), [\#779](https://github.com/redis/hiredis/issues/779), [\#785](https://github.com/redis/hiredis/issues/785), - Added timers to libev adapter. [\#778](https://github.com/redis/hiredis/issues/778), [\#795](https://github.com/redis/hiredis/pull/795) - Initialization discards const qualifier [\#777](https://github.com/redis/hiredis/issues/777) - \[BUG\]\[MinGW64\] Error setting socket timeout [\#775](https://github.com/redis/hiredis/issues/775) - undefined reference to hi_malloc [\#769](https://github.com/redis/hiredis/issues/769) - hiredis pkg-config file incorrectly ignores multiarch libdir spec'n [\#767](https://github.com/redis/hiredis/issues/767) - Don't use -G to build shared object on Solaris [\#757](https://github.com/redis/hiredis/issues/757) - error when make USE\_SSL=1 [\#748](https://github.com/redis/hiredis/issues/748) - Allow to change SSL Mode [\#646](https://github.com/redis/hiredis/issues/646) - hiredis/adapters/libevent.h memleak [\#618](https://github.com/redis/hiredis/issues/618) - redisLibuvPoll crash when server closes the connetion [\#545](https://github.com/redis/hiredis/issues/545) - about redisAsyncDisconnect question [\#518](https://github.com/redis/hiredis/issues/518) - hiredis adapters libuv error for help [\#508](https://github.com/redis/hiredis/issues/508) - API/ABI changes analysis [\#506](https://github.com/redis/hiredis/issues/506) - Memory leak patch in Redis [\#502](https://github.com/redis/hiredis/issues/502) - Remove the depth limitation [\#421](https://github.com/redis/hiredis/issues/421) **Merged pull requests:** - Move SSL management to a distinct private pointer [\#855](https://github.com/redis/hiredis/pull/855) ([michael-grunder](https://github.com/michael-grunder)) - Move include to sockcompat.h to maintain style [\#850](https://github.com/redis/hiredis/pull/850) ([michael-grunder](https://github.com/michael-grunder)) - Remove erroneous tag and add license to push example [\#849](https://github.com/redis/hiredis/pull/849) ([michael-grunder](https://github.com/michael-grunder)) - fix windows compiling with mingw [\#848](https://github.com/redis/hiredis/pull/848) ([rmalizia44](https://github.com/rmalizia44)) - Some Windows quality of life improvements. [\#846](https://github.com/redis/hiredis/pull/846) ([michael-grunder](https://github.com/michael-grunder)) - Use \_WIN32 define instead of WIN32 [\#845](https://github.com/redis/hiredis/pull/845) ([michael-grunder](https://github.com/michael-grunder)) - Non Linux CI fixes [\#844](https://github.com/redis/hiredis/pull/844) ([michael-grunder](https://github.com/michael-grunder)) - Resp3 oob push support [\#841](https://github.com/redis/hiredis/pull/841) ([michael-grunder](https://github.com/michael-grunder)) - fix \#785: defer TCP\_NODELAY in async tcp connections [\#836](https://github.com/redis/hiredis/pull/836) ([OmriSteiner](https://github.com/OmriSteiner)) - sdsrange overflow fix [\#830](https://github.com/redis/hiredis/pull/830) ([michael-grunder](https://github.com/michael-grunder)) - Use explicit pointer casting for c++ compatibility [\#826](https://github.com/redis/hiredis/pull/826) ([aureus1](https://github.com/aureus1)) - Document allocator injection and completeness fix in test.c [\#824](https://github.com/redis/hiredis/pull/824) ([michael-grunder](https://github.com/michael-grunder)) - Use unique names for allocator struct members [\#823](https://github.com/redis/hiredis/pull/823) ([michael-grunder](https://github.com/michael-grunder)) - New SSL API to replace redisSecureConnection\(\). [\#821](https://github.com/redis/hiredis/pull/821) ([yossigo](https://github.com/yossigo)) - Add logic to handle RESP3 push messages [\#819](https://github.com/redis/hiredis/pull/819) ([michael-grunder](https://github.com/michael-grunder)) - Use standrad isxdigit instead of custom helper function. [\#814](https://github.com/redis/hiredis/pull/814) ([tryfinally](https://github.com/tryfinally)) - Fix missing SSL build/install options. [\#812](https://github.com/redis/hiredis/pull/812) ([yossigo](https://github.com/yossigo)) - Add link to ABI tracker [\#808](https://github.com/redis/hiredis/pull/808) ([michael-grunder](https://github.com/michael-grunder)) - Resp3 verbatim string support [\#805](https://github.com/redis/hiredis/pull/805) ([michael-grunder](https://github.com/michael-grunder)) - Allow users to replace allocator and handle OOM everywhere. [\#800](https://github.com/redis/hiredis/pull/800) ([michael-grunder](https://github.com/michael-grunder)) - Remove nested depth limitation. [\#797](https://github.com/redis/hiredis/pull/797) ([michael-grunder](https://github.com/michael-grunder)) - Attempt to fix compilation on Solaris [\#796](https://github.com/redis/hiredis/pull/796) ([michael-grunder](https://github.com/michael-grunder)) - Support timeouts in libev adapater [\#795](https://github.com/redis/hiredis/pull/795) ([michael-grunder](https://github.com/michael-grunder)) - Fix pkgconfig when installing to a custom lib dir [\#793](https://github.com/redis/hiredis/pull/793) ([michael-grunder](https://github.com/michael-grunder)) - Fix USE\_SSL=1 make/cmake on OSX and CMake tests [\#789](https://github.com/redis/hiredis/pull/789) ([michael-grunder](https://github.com/michael-grunder)) - Use correct libuv call on Windows [\#784](https://github.com/redis/hiredis/pull/784) ([michael-grunder](https://github.com/michael-grunder)) - Added CMake package config and fixed hiredis\_ssl on Windows [\#783](https://github.com/redis/hiredis/pull/783) ([michael-grunder](https://github.com/michael-grunder)) - CMake: Set hiredis\_ssl shared object version. [\#780](https://github.com/redis/hiredis/pull/780) ([yossigo](https://github.com/yossigo)) - Win32 tests and timeout fix [\#776](https://github.com/redis/hiredis/pull/776) ([michael-grunder](https://github.com/michael-grunder)) - Provides an optional cleanup callback for async data. [\#768](https://github.com/redis/hiredis/pull/768) ([heronr](https://github.com/heronr)) - Housekeeping fixes [\#764](https://github.com/redis/hiredis/pull/764) ([michael-grunder](https://github.com/michael-grunder)) - install alloc.h [\#756](https://github.com/redis/hiredis/pull/756) ([ch1aki](https://github.com/ch1aki)) - fix spelling mistakes [\#746](https://github.com/redis/hiredis/pull/746) ([ShooterIT](https://github.com/ShooterIT)) - Free the reply in redisGetReply when passed NULL [\#741](https://github.com/redis/hiredis/pull/741) ([michael-grunder](https://github.com/michael-grunder)) - Fix dead code in sslLogCallback relating to should\_log variable. [\#737](https://github.com/redis/hiredis/pull/737) ([natoscott](https://github.com/natoscott)) - Fix typo in dict.c. [\#731](https://github.com/redis/hiredis/pull/731) ([Kevin-Xi](https://github.com/Kevin-Xi)) - Adding an option to DISABLE\_TESTS [\#727](https://github.com/redis/hiredis/pull/727) ([pbotros](https://github.com/pbotros)) - Update README with SSL support. [\#720](https://github.com/redis/hiredis/pull/720) ([yossigo](https://github.com/yossigo)) - Fixes leaks in unit tests [\#715](https://github.com/redis/hiredis/pull/715) ([michael-grunder](https://github.com/michael-grunder)) - SSL Tests [\#711](https://github.com/redis/hiredis/pull/711) ([yossigo](https://github.com/yossigo)) - SSL Reorganization [\#708](https://github.com/redis/hiredis/pull/708) ([yossigo](https://github.com/yossigo)) - Fix MSVC build. [\#706](https://github.com/redis/hiredis/pull/706) ([yossigo](https://github.com/yossigo)) - SSL: Properly report SSL\_connect\(\) errors. [\#702](https://github.com/redis/hiredis/pull/702) ([yossigo](https://github.com/yossigo)) - Silent SSL trace to stdout by default. [\#699](https://github.com/redis/hiredis/pull/699) ([yossigo](https://github.com/yossigo)) - Port RESP3 support from Redis. [\#697](https://github.com/redis/hiredis/pull/697) ([yossigo](https://github.com/yossigo)) - Removed whitespace before newline [\#691](https://github.com/redis/hiredis/pull/691) ([Miniwoffer](https://github.com/Miniwoffer)) - Add install adapters header files [\#688](https://github.com/redis/hiredis/pull/688) ([kevin1018](https://github.com/kevin1018)) - Remove unnecessary null check before free [\#684](https://github.com/redis/hiredis/pull/684) ([qlyoung](https://github.com/qlyoung)) - redisReaderGetReply leak memory [\#671](https://github.com/redis/hiredis/pull/671) ([movebean](https://github.com/movebean)) - fix timeout code in windows [\#670](https://github.com/redis/hiredis/pull/670) ([jman-krafton](https://github.com/jman-krafton)) - test: fix errstr matching for musl libc [\#665](https://github.com/redis/hiredis/pull/665) ([ghost](https://github.com/ghost)) - Windows: MinGW fixes and Windows Travis builders [\#663](https://github.com/redis/hiredis/pull/663) ([mbitsnbites](https://github.com/mbitsnbites)) - The setsockopt and getsockopt API diffs from BSD socket and WSA one [\#662](https://github.com/redis/hiredis/pull/662) ([dragonation](https://github.com/dragonation)) - Fix Compile Error On Windows \(Visual Studio\) [\#658](https://github.com/redis/hiredis/pull/658) ([jinjiazhang](https://github.com/jinjiazhang)) - Fix NXDOMAIN test case [\#653](https://github.com/redis/hiredis/pull/653) ([michael-grunder](https://github.com/michael-grunder)) - Add MinGW support [\#652](https://github.com/redis/hiredis/pull/652) ([mbitsnbites](https://github.com/mbitsnbites)) - SSL Support [\#645](https://github.com/redis/hiredis/pull/645) ([mnunberg](https://github.com/mnunberg)) - Fix Invalid argument after redisAsyncConnectUnix [\#644](https://github.com/redis/hiredis/pull/644) ([codehz](https://github.com/codehz)) - Makefile: use predefined AR [\#632](https://github.com/redis/hiredis/pull/632) ([Mic92](https://github.com/Mic92)) - FreeBSD build fix [\#628](https://github.com/redis/hiredis/pull/628) ([devnexen](https://github.com/devnexen)) - Fix errors not propagating properly with libuv.h. [\#624](https://github.com/redis/hiredis/pull/624) ([yossigo](https://github.com/yossigo)) - Update README.md [\#621](https://github.com/redis/hiredis/pull/621) ([Crunsher](https://github.com/Crunsher)) - Fix redisBufferRead documentation [\#620](https://github.com/redis/hiredis/pull/620) ([hacst](https://github.com/hacst)) - Add CPPFLAGS to REAL\_CFLAGS [\#614](https://github.com/redis/hiredis/pull/614) ([thomaslee](https://github.com/thomaslee)) - Update createArray to take size\_t [\#597](https://github.com/redis/hiredis/pull/597) ([justinbrewer](https://github.com/justinbrewer)) - fix common realloc mistake and add null check more [\#580](https://github.com/redis/hiredis/pull/580) ([charsyam](https://github.com/charsyam)) - Proper error reporting for connect failures [\#578](https://github.com/redis/hiredis/pull/578) ([mnunberg](https://github.com/mnunberg)) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* ## [1.0.0-rc1](https://github.com/redis/hiredis/tree/v1.0.0-rc1) - (2020-07-29) _Note: There were no changes to code between v1.0.0-rc1 and v1.0.0 so see v1.0.0 for changelog_ ### 0.14.1 (2020-03-13) * Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder) ### 0.14.0 (2018-09-25) **BREAKING CHANGES**: * Change `redisReply.len` to `size_t`, as it denotes the the size of a string User code should compare this to `size_t` values as well. If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. * Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) * Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) * Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) * Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) * Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) * Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) * Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) * Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) * Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) * Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) * Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) * Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) * Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) * Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) * Fix libevent leak (zfz [515228]) * Clean up GCC warning (Ichito Nagata [2ec774]) * Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) * Solaris compilation fix (Donald Whyte [41b07d]) * Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) * Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) * libuv use after free fix (Paul Scott [cbb956]) * Properly close socket fd on reconnect attempt (WSL [64d1ec]) * Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) * Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) * Update libevent (Chris Xin [386802]) * Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) * Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) * Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) * Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) * Compatibility fix for strerror_r (Tom Lee [bb1747]) * Properly detect integer parse/overflow errors (Justin Brewer [93421f]) * Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) * Catch a buffer overflow when formatting the error message * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 * Fix warnings, when compiled with -Wshadow * Make hiredis compile in Cygwin on Windows, now CI-tested * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`. * Remove backwards compatibility macro's This removes the following old function aliases, use the new name now: | Old | New | | --------------------------- | ---------------------- | | redisReplyReaderCreate | redisReaderCreate | | redisReplyReaderCreate | redisReaderCreate | | redisReplyReaderFree | redisReaderFree | | redisReplyReaderFeed | redisReaderFeed | | redisReplyReaderGetReply | redisReaderGetReply | | redisReplyReaderSetPrivdata | redisReaderSetPrivdata | | redisReplyReaderGetObject | redisReaderGetObject | | redisReplyReaderGetError | redisReaderGetError | * The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, due to debugging other software. By renaming we avoid unintentional name clashes. Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. ### 0.13.3 (2015-09-16) * Revert "Clear `REDIS_CONNECTED` flag when connection is closed". * Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) If the `REDIS_CONNECTED` flag is cleared, the async onDisconnect callback function will never be called. This causes problems as the disconnect is never reported back to the user. ### 0.13.2 (2015-08-25) * Prevent crash on pending replies in async code (Thanks, @switch-st) * Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) * Add MacOS X addapter (Thanks, @dizzus) * Add Qt adapter (Thanks, Pietro Cerutti) * Add Ivykis adapter (Thanks, Gergely Nagy) All adapters are provided as is and are only tested where possible. ### 0.13.1 (2015-05-03) This is a bug fix release. The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. Other non-C99 code can now use hiredis as usual again. Sorry for the inconvenience. * Fix memory leak in async reply handling (Salvatore Sanfilippo) * Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) ### 0.13.0 (2015-04-16) This release adds a minimal Windows compatibility layer. The parser, standalone since v0.12.0, can now be compiled on Windows (and thus used in other client libraries as well) * Windows compatibility layer for parser code (tzickel) * Properly escape data printed to PKGCONF file (Dan Skorupski) * Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) * Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) ### 0.12.1 (2015-01-26) * Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location * Fix `make test` as 32 bit build on 64 bit platform ### 0.12.0 (2015-01-22) * Add optional KeepAlive support * Try again on EINTR errors * Add libuv adapter * Add IPv6 support * Remove possibility of multiple close on same fd * Add ability to bind source address on connect * Add redisConnectFd() and redisFreeKeepFd() * Fix getaddrinfo() memory leak * Free string if it is unused (fixes memory leak) * Improve redisAppendCommandArgv performance 2.5x * Add support for SO_REUSEADDR * Fix redisvFormatCommand format parsing * Add GLib 2.0 adapter * Refactor reading code into read.c * Fix errno error buffers to not clobber errors * Generate pkgconf during build * Silence _BSD_SOURCE warnings * Improve digit counting for multibulk creation ### 0.11.0 * Increase the maximum multi-bulk reply depth to 7. * Increase the read buffer size from 2k to 16k. * Use poll(2) instead of select(2) to support large fds (>= 1024). ### 0.10.1 * Makefile overhaul. Important to check out if you override one or more variables using environment variables or via arguments to the "make" tool. * Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements being created by the default reply object functions. * Issue #43: Don't crash in an asynchronous context when Redis returns an error reply after the connection has been made (this happens when the maximum number of connections is reached). ### 0.10.0 * See commit log. redis-8.0.2/deps/hiredis/CMakeLists.txt000066400000000000000000000177011501533116600177600ustar00rootroot00000000000000CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0) OPTION(BUILD_SHARED_LIBS "Build shared libraries" ON) OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) OPTION(ENABLE_SSL_TESTS "Should we test SSL connections" OFF) OPTION(ENABLE_EXAMPLES "Enable building hiredis examples" OFF) OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h" VERSION_BIT REGEX ${VERSION_REGEX}) STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") ENDMACRO(getVersionBit) getVersionBit(HIREDIS_MAJOR) getVersionBit(HIREDIS_MINOR) getVersionBit(HIREDIS_PATCH) getVersionBit(HIREDIS_SONAME) SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") MESSAGE("Detected version: ${VERSION}") PROJECT(hiredis LANGUAGES "C" VERSION "${VERSION}") INCLUDE(GNUInstallDirs) # Hiredis requires C99 SET(CMAKE_C_STANDARD 99) SET(CMAKE_DEBUG_POSTFIX d) SET(hiredis_sources alloc.c async.c hiredis.c net.c read.c sds.c sockcompat.c) SET(hiredis_sources ${hiredis_sources}) IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN) ENDIF() ADD_LIBRARY(hiredis ${hiredis_sources}) ADD_LIBRARY(hiredis::hiredis ALIAS hiredis) set(hiredis_export_name hiredis CACHE STRING "Name of the exported target") set_target_properties(hiredis PROPERTIES EXPORT_NAME ${hiredis_export_name}) SET_TARGET_PROPERTIES(hiredis PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") IF(MSVC) SET_TARGET_PROPERTIES(hiredis PROPERTIES COMPILE_FLAGS /Z7) ENDIF() IF(WIN32) TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32) ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") TARGET_LINK_LIBRARIES(hiredis PUBLIC m) ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS") TARGET_LINK_LIBRARIES(hiredis PUBLIC socket) ENDIF() TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $ $) CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) set(CPACK_PACKAGE_VENDOR "Redis") set(CPACK_PACKAGE_DESCRIPTION "\ Hiredis is a minimalistic C client library for the Redis database. It is minimalistic because it just adds minimal support for the protocol, \ but at the same time it uses a high level printf-alike API in order to make \ it much higher level than otherwise suggested by its minimal code base and the \ lack of explicit bindings for every Redis command. Apart from supporting sending commands and receiving replies, it comes with a \ reply parser that is decoupled from the I/O layer. It is a stream parser designed \ for easy reusability, which can for instance be used in higher level language bindings \ for efficient reply parsing. Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis \ version >= 1.2.0. The library comes with multiple APIs. There is the synchronous API, the asynchronous API \ and the reply parsing API.") set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/redis/hiredis") set(CPACK_PACKAGE_CONTACT "michael dot grunder at gmail dot com") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_RPM_PACKAGE_AUTOREQPROV ON) include(CPack) INSTALL(TARGETS hiredis EXPORT hiredis-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) if (MSVC AND BUILD_SHARED_LIBS) INSTALL(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Debug RelWithDebInfo) endif() # For NuGet packages INSTALL(FILES hiredis.targets DESTINATION build/native) INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h sockcompat.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) INSTALL(DIRECTORY adapters DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) export(EXPORT hiredis-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake" NAMESPACE hiredis::) if(WIN32) SET(CMAKE_CONF_INSTALL_DIR share/hiredis) else() SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis) endif() SET(INCLUDE_INSTALL_DIR include) include(CMakePackageConfigHelpers) write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake" COMPATIBILITY SameMajorVersion) configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR) INSTALL(EXPORT hiredis-targets FILE hiredis-targets.cmake NAMESPACE hiredis:: DESTINATION ${CMAKE_CONF_INSTALL_DIR}) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake DESTINATION ${CMAKE_CONF_INSTALL_DIR}) IF(ENABLE_SSL) IF (NOT OPENSSL_ROOT_DIR) IF (APPLE) SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") ENDIF() ENDIF() FIND_PACKAGE(OpenSSL REQUIRED) SET(hiredis_ssl_sources ssl.c) ADD_LIBRARY(hiredis_ssl ${hiredis_ssl_sources}) ADD_LIBRARY(hiredis::hiredis_ssl ALIAS hiredis_ssl) IF (APPLE AND BUILD_SHARED_LIBS) SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup") ENDIF() SET_TARGET_PROPERTIES(hiredis_ssl PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") IF(MSVC) SET_TARGET_PROPERTIES(hiredis_ssl PROPERTIES COMPILE_FLAGS /Z7) ENDIF() TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE OpenSSL::SSL) IF(WIN32) TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis) ENDIF() CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) INSTALL(TARGETS hiredis_ssl EXPORT hiredis_ssl-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) if (MSVC AND BUILD_SHARED_LIBS) INSTALL(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Debug RelWithDebInfo) endif() INSTALL(FILES hiredis_ssl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) export(EXPORT hiredis_ssl-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake" NAMESPACE hiredis::) if(WIN32) SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl) else() SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis_ssl) endif() configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR) INSTALL(EXPORT hiredis_ssl-targets FILE hiredis_ssl-targets.cmake NAMESPACE hiredis:: DESTINATION ${CMAKE_CONF_INSTALL_DIR}) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake DESTINATION ${CMAKE_CONF_INSTALL_DIR}) ENDIF() IF(NOT DISABLE_TESTS) ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) TARGET_LINK_LIBRARIES(hiredis-test hiredis) IF(ENABLE_SSL_TESTS) ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1) TARGET_LINK_LIBRARIES(hiredis-test hiredis_ssl) ENDIF() IF(ENABLE_ASYNC_TESTS) ADD_DEFINITIONS(-DHIREDIS_TEST_ASYNC=1) TARGET_LINK_LIBRARIES(hiredis-test event) ENDIF() ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) ENDIF() # Add examples IF(ENABLE_EXAMPLES) ADD_SUBDIRECTORY(examples) ENDIF(ENABLE_EXAMPLES) redis-8.0.2/deps/hiredis/COPYING000066400000000000000000000030641501533116600162500ustar00rootroot00000000000000Copyright (c) 2009-2011, Salvatore Sanfilippo Copyright (c) 2010-2011, Pieter Noordhuis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. redis-8.0.2/deps/hiredis/Makefile000066400000000000000000000321431501533116600166550ustar00rootroot00000000000000# Hiredis Makefile # Copyright (C) 2010-2011 Salvatore Sanfilippo # Copyright (C) 2010-2011 Pieter Noordhuis # This file is released under the BSD license, see the COPYING file OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push hiredis-example-poll TESTS=hiredis-test LIBNAME=libhiredis PKGCONFNAME=hiredis.pc HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}') # Installation related variables and target PREFIX?=/usr/local INCLUDE_PATH?=include/hiredis LIBRARY_PATH?=lib PKGCONF_PATH?=pkgconfig INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) # redis-server configuration used for testing REDIS_PORT=56379 REDIS_SERVER=redis-server define REDIS_TEST_CONFIG daemonize yes pidfile /tmp/hiredis-test-redis.pid port $(REDIS_PORT) bind 127.0.0.1 unixsocket /tmp/hiredis-test-redis.sock endef export REDIS_TEST_CONFIG # Fallback to gcc when $CC is not in $PATH. CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') OPTIMIZATION?=-O3 WARNINGS=-Wall -Wextra -Werror -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers DEBUG_FLAGS?= -g -ggdb REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(PLATFORM_FLAGS) REAL_LDFLAGS=$(LDFLAGS) DYLIBSUFFIX=so STLIBSUFFIX=a DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIB_MAKE_CMD=$(AR) rcs #################### SSL variables start #################### SSL_OBJ=ssl.o SSL_LIBNAME=libhiredis_ssl SSL_PKGCONFNAME=hiredis_ssl.pc SSL_INSTALLNAME=install-ssl SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) SSL_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME) USE_SSL?=0 ifeq ($(USE_SSL),1) # This is required for test.c only CFLAGS+=-DHIREDIS_TEST_SSL EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl SSL_STLIB=$(SSL_STLIBNAME) SSL_DYLIB=$(SSL_DYLIBNAME) SSL_PKGCONF=$(SSL_PKGCONFNAME) SSL_INSTALL=$(SSL_INSTALLNAME) else SSL_STLIB= SSL_DYLIB= SSL_PKGCONF= SSL_INSTALL= endif ##################### SSL variables end ##################### # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') # This is required for test.c only ifeq ($(TEST_ASYNC),1) export CFLAGS+=-DHIREDIS_TEST_ASYNC endif ifeq ($(USE_SSL),1) ifndef OPENSSL_PREFIX ifeq ($(uname_S),Darwin) SEARCH_PATH1=/opt/homebrew/opt/openssl SEARCH_PATH2=/usr/local/opt/openssl ifneq ($(wildcard $(SEARCH_PATH1)),) OPENSSL_PREFIX=$(SEARCH_PATH1) else ifneq ($(wildcard $(SEARCH_PATH2)),) OPENSSL_PREFIX=$(SEARCH_PATH2) endif endif endif ifdef OPENSSL_PREFIX CFLAGS+=-I$(OPENSSL_PREFIX)/include SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib endif SSL_LDFLAGS+=-lssl -lcrypto endif ifeq ($(uname_S),FreeBSD) LDFLAGS+=-lm IS_GCC=$(shell sh -c '$(CC) --version 2>/dev/null |egrep -i -c "gcc"') ifeq ($(IS_GCC),1) REAL_CFLAGS+=-pedantic endif else REAL_CFLAGS+=-pedantic endif ifeq ($(uname_S),SunOS) IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"') ifeq ($(IS_SUN_CC),1) SUN_SHARED_FLAG=-G else SUN_SHARED_FLAG=-shared endif REAL_LDFLAGS+= -ldl -lnsl -lsocket DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS) endif ifeq ($(uname_S),Darwin) DYLIBSUFFIX=dylib DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS) DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup endif all: dynamic static hiredis-test pkgconfig dynamic: $(DYLIBNAME) $(SSL_DYLIB) static: $(STLIBNAME) $(SSL_STLIB) pkgconfig: $(PKGCONFNAME) $(SSL_PKGCONF) # Deps (use make dep to generate this) alloc.o: alloc.c fmacros.h alloc.h async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h dict.o: dict.c fmacros.h alloc.h dict.h hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h read.o: read.c fmacros.h alloc.h read.h sds.h win32.h sds.o: sds.c sds.h sdsalloc.h alloc.h sockcompat.o: sockcompat.c sockcompat.h test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h $(DYLIBNAME): $(OBJ) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) $(STLIBNAME): $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) #################### SSL building rules start #################### $(SSL_DYLIBNAME): $(SSL_OBJ) $(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS) $(SSL_STLIBNAME): $(SSL_OBJ) $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) $(SSL_OBJ): ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h #################### SSL building rules end #################### # Binaries: hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-libhv: examples/example-libhv.c adapters/libhv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lhv $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) hiredis-example-poll: examples/example-poll.c adapters/poll.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) ifndef AE_DIR hiredis-example-ae: @echo "Please specify AE_DIR (e.g. /src)" @false else hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) endif ifndef LIBUV_DIR # dynamic link libuv.so hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< -luv -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) else # use user provided static lib hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) endif ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) hiredis-example-qt: @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" @false else hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore endif hiredis-example: examples/example.c $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) hiredis-example-push: examples/example-push.c $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) examples: $(EXAMPLES) TEST_LIBS = $(STLIBNAME) $(SSL_STLIB) TEST_LDFLAGS = $(SSL_LDFLAGS) ifeq ($(USE_SSL),1) TEST_LDFLAGS += -pthread endif ifeq ($(TEST_ASYNC),1) TEST_LDFLAGS += -levent endif hiredis-test: test.o $(TEST_LIBS) $(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS) hiredis-%: %.o $(STLIBNAME) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS) test: hiredis-test ./hiredis-test check: hiredis-test TEST_SSL=$(USE_SSL) ./test.sh .c.o: $(CC) -std=c99 -c $(REAL_CFLAGS) $< clean: rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov dep: $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c INSTALL?= cp -pPR $(PKGCONFNAME): hiredis.h @echo "Generating $@ for pkgconfig..." @echo prefix=$(PREFIX) > $@ @echo exec_prefix=\$${prefix} >> $@ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ @echo includedir=$(PREFIX)/include >> $@ @echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ @echo >> $@ @echo Name: hiredis >> $@ @echo Description: Minimalistic C client library for Redis. >> $@ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Cflags: -I\$${pkgincludedir} -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ $(SSL_PKGCONFNAME): hiredis_ssl.h @echo "Generating $@ for pkgconfig..." @echo prefix=$(PREFIX) > $@ @echo exec_prefix=\$${prefix} >> $@ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ @echo includedir=$(PREFIX)/include >> $@ @echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ @echo >> $@ @echo Name: hiredis_ssl >> $@ @echo Description: SSL Support for hiredis. >> $@ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ @echo Requires: hiredis >> $@ @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ @echo Libs.private: -lssl -lcrypto >> $@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis.h async.h read.h sds.h alloc.h sockcompat.h $(INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH) $(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIB_MAJOR_NAME) $(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH) 32bit: @echo "" @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" @echo "" $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" 32bit-vars: $(eval CFLAGS=-m32) $(eval LDFLAGS=-m32) gprof: $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" gcov: $(MAKE) CFLAGS+="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" coverage: gcov make check mkdir -p tmp/lcov lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info lcov -q -l tmp/lcov/hiredis.info genhtml --legend -q -o tmp/lcov/report tmp/lcov/hiredis.info noopt: $(MAKE) OPTIMIZATION="" .PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt redis-8.0.2/deps/hiredis/README.md000066400000000000000000001060041501533116600164720ustar00rootroot00000000000000 [![Build Status](https://github.com/redis/hiredis/actions/workflows/build.yml/badge.svg)](https://github.com/redis/hiredis/actions/workflows/build.yml) **This Readme reflects the latest changed in the master branch. See [v1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) for the Readme and documentation for the latest release ([API/ABI history](https://abi-laboratory.pro/?view=timeline&l=hiredis)).** # HIREDIS Hiredis is a minimalistic C client library for the [Redis](https://redis.io/) database. It is minimalistic because it just adds minimal support for the protocol, but at the same time it uses a high level printf-alike API in order to make it much higher level than otherwise suggested by its minimal code base and the lack of explicit bindings for every Redis command. Apart from supporting sending commands and receiving replies, it comes with a reply parser that is decoupled from the I/O layer. It is a stream parser designed for easy reusability, which can for instance be used in higher level language bindings for efficient reply parsing. Hiredis only supports the binary-safe Redis protocol, so you can use it with any Redis version >= 1.2.0. The library comes with multiple APIs. There is the *synchronous API*, the *asynchronous API* and the *reply parsing API*. ## Upgrading to `1.1.0` Almost all users will simply need to recompile their applications against the newer version of hiredis. **NOTE**: Hiredis can now return `nan` in addition to `-inf` and `inf` in a `REDIS_REPLY_DOUBLE`. Applications that deal with `RESP3` doubles should make sure to account for this. ## Upgrading to `1.0.2` NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here. Version 1.0.2 is simply 1.0.0 with a fix for [CVE-2021-32765](https://github.com/redis/hiredis/security/advisories/GHSA-hfm9-39pp-55p2). They are otherwise identical. ## Upgrading to `1.0.0` Version 1.0.0 marks the first stable release of Hiredis. It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. It also bundles the updated `sds` library, to sync up with upstream and Redis. For code changes see the [Changelog](CHANGELOG.md). _Note: As described below, a few member names have been changed but most applications should be able to upgrade with minor code changes and recompiling._ ## IMPORTANT: Breaking changes from `0.14.1` -> `1.0.0` * `redisContext` has two additional members (`free_privdata`, and `privctx`). * `redisOptions.timeout` has been renamed to `redisOptions.connect_timeout`, and we've added `redisOptions.command_timeout`. * `redisReplyObjectFunctions.createArray` now takes `size_t` instead of `int` for its length parameter. ## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`. Change `redisReply.len` to `size_t`, as it denotes the the size of a string User code should compare this to `size_t` values as well. If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. ## Upgrading from `<0.9.0` Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing code using hiredis should not be a big pain. The key thing to keep in mind when upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to the stateless 0.0.1 that only has a file descriptor to work with. ## Synchronous API To consume the synchronous API, there are only a few function calls that need to be introduced: ```c redisContext *redisConnect(const char *ip, int port); void *redisCommand(redisContext *c, const char *format, ...); void freeReplyObject(void *reply); ``` ### Connecting The function `redisConnect` is used to create a so-called `redisContext`. The context is where Hiredis holds state for a connection. The `redisContext` struct has an integer `err` field that is non-zero when the connection is in an error state. The field `errstr` will contain a string with a description of the error. More information on errors can be found in the **Errors** section. After trying to connect to Redis using `redisConnect` you should check the `err` field to see if establishing the connection was successful: ```c redisContext *c = redisConnect("127.0.0.1", 6379); if (c == NULL || c->err) { if (c) { printf("Error: %s\n", c->errstr); // handle error } else { printf("Can't allocate redis context\n"); } } ``` One can also use `redisConnectWithOptions` which takes a `redisOptions` argument that can be configured with endpoint information as well as many different flags to change how the `redisContext` will be configured. ```c redisOptions opt = {0}; /* One can set the endpoint with one of our helper macros */ if (tcp) { REDIS_OPTIONS_SET_TCP(&opt, "localhost", 6379); } else { REDIS_OPTIONS_SET_UNIX(&opt, "/tmp/redis.sock"); } /* And privdata can be specified with another helper */ REDIS_OPTIONS_SET_PRIVDATA(&opt, myPrivData, myPrivDataDtor); /* Finally various options may be set via the `options` member, as described below */ opt->options |= REDIS_OPT_PREFER_IPV4; ``` If a connection is lost, `int redisReconnect(redisContext *c)` can be used to restore the connection using the same endpoint and options as the given context. ### Configurable redisOptions flags There are several flags you may set in the `redisOptions` struct to change default behavior. You can specify the flags via the `redisOptions->options` member. | Flag | Description | | --- | --- | | REDIS\_OPT\_NONBLOCK | Tells hiredis to make a non-blocking connection. | | REDIS\_OPT\_REUSEADDR | Tells hiredis to set the [SO_REUSEADDR](https://man7.org/linux/man-pages/man7/socket.7.html) socket option | | REDIS\_OPT\_PREFER\_IPV4
REDIS\_OPT\_PREFER_IPV6
REDIS\_OPT\_PREFER\_IP\_UNSPEC | Informs hiredis to either prefer IPv4 or IPv6 when invoking [getaddrinfo](https://man7.org/linux/man-pages/man3/gai_strerror.3.html). `REDIS_OPT_PREFER_IP_UNSPEC` will cause hiredis to specify `AF_UNSPEC` in the getaddrinfo call, which means both IPv4 and IPv6 addresses will be searched simultaneously.
Hiredis prefers IPv4 by default. | | REDIS\_OPT\_NO\_PUSH\_AUTOFREE | Tells hiredis to not install the default RESP3 PUSH handler (which just intercepts and frees the replies). This is useful in situations where you want to process these messages in-band. | | REDIS\_OPT\_NOAUTOFREEREPLIES | **ASYNC**: tells hiredis not to automatically invoke `freeReplyObject` after executing the reply callback. | | REDIS\_OPT\_NOAUTOFREE | **ASYNC**: Tells hiredis not to automatically free the `redisAsyncContext` on connection/communication failure, but only if the user makes an explicit call to `redisAsyncDisconnect` or `redisAsyncFree` | *Note: A `redisContext` is not thread-safe.* ### Other configuration using socket options The following socket options are applied directly to the underlying socket. The values are not stored in the `redisContext`, so they are not automatically applied when reconnecting using `redisReconnect()`. These functions return `REDIS_OK` on success. On failure, `REDIS_ERR` is returned and the underlying connection is closed. To configure these for an asyncronous context (see *Asynchronous API* below), use `ac->c` to get the redisContext out of an asyncRedisContext. ```C int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAliveWithInterval(redisContext *c, int interval); ``` Enables TCP keepalive by setting the following socket options (with some variations depending on OS): * `SO_KEEPALIVE`; * `TCP_KEEPALIVE` or `TCP_KEEPIDLE`, value configurable using the `interval` parameter, default 15 seconds; * `TCP_KEEPINTVL` set to 1/3 of `interval`; * `TCP_KEEPCNT` set to 3. ```C int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout); ``` Set the `TCP_USER_TIMEOUT` Linux-specific socket option which is as described in the `tcp` man page: > When the value is greater than 0, it specifies the maximum amount of time in milliseconds that trans mitted data may remain unacknowledged before TCP will forcibly close the corresponding connection and return ETIMEDOUT to the application. > If the option value is specified as 0, TCP will use the system default. ### Sending commands There are several ways to issue commands to Redis. The first that will be introduced is `redisCommand`. This function takes a format similar to printf. In the simplest form, it is used like this: ```c reply = redisCommand(context, "SET foo bar"); ``` The specifier `%s` interpolates a string in the command, and uses `strlen` to determine the length of the string: ```c reply = redisCommand(context, "SET foo %s", value); ``` When you need to pass binary safe strings in a command, the `%b` specifier can be used. Together with a pointer to the string, it requires a `size_t` length argument of the string: ```c reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); ``` Internally, Hiredis splits the command in different arguments and will convert it to the protocol used to communicate with Redis. One or more spaces separates arguments, so you can use the specifiers anywhere in an argument: ```c reply = redisCommand(context, "SET key:%s %s", myid, value); ``` ### Using replies The return value of `redisCommand` holds a reply when the command was successfully executed. When an error occurs, the return value is `NULL` and the `err` field in the context will be set (see section on **Errors**). Once an error is returned the context cannot be reused and you should set up a new connection. The standard replies that `redisCommand` are of the type `redisReply`. The `type` field in the `redisReply` should be used to test what kind of reply was received: ### RESP2 * **`REDIS_REPLY_STATUS`**: * The command replied with a status reply. The status string can be accessed using `reply->str`. The length of this string can be accessed using `reply->len`. * **`REDIS_REPLY_ERROR`**: * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. * **`REDIS_REPLY_INTEGER`**: * The command replied with an integer. The integer value can be accessed using the `reply->integer` field of type `long long`. * **`REDIS_REPLY_NIL`**: * The command replied with a **nil** object. There is no data to access. * **`REDIS_REPLY_STRING`**: * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. The length of this string can be accessed using `reply->len`. * **`REDIS_REPLY_ARRAY`**: * A multi bulk reply. The number of elements in the multi bulk reply is stored in `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well and can be accessed via `reply->element[..index..]`. Redis may reply with nested arrays but this is fully supported. ### RESP3 Hiredis also supports every new `RESP3` data type which are as follows. For more information about the protocol see the `RESP3` [specification.](https://github.com/antirez/RESP3/blob/master/spec.md) * **`REDIS_REPLY_DOUBLE`**: * The command replied with a double-precision floating point number. The value is stored as a string in the `str` member, and can be converted with `strtod` or similar. * **`REDIS_REPLY_BOOL`**: * A boolean true/false reply. The value is stored in the `integer` member and will be either `0` or `1`. * **`REDIS_REPLY_MAP`**: * An array with the added invariant that there will always be an even number of elements. The MAP is functionally equivalent to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant. * **`REDIS_REPLY_SET`**: * An array response where each entry is unique. Like the MAP type, the data is identical to an array response except there are no duplicate values. * **`REDIS_REPLY_PUSH`**: * An array that can be generated spontaneously by Redis. This array response will always contain at least two subelements. The first contains the type of `PUSH` message (e.g. `message`, or `invalidate`), and the second being a sub-array with the `PUSH` payload itself. * **`REDIS_REPLY_ATTR`**: * An array structurally identical to a `MAP` but intended as meta-data about a reply. _As of Redis 6.0.6 this reply type is not used in Redis_ * **`REDIS_REPLY_BIGNUM`**: * A string representing an arbitrarily large signed or unsigned integer value. The number will be encoded as a string in the `str` member of `redisReply`. * **`REDIS_REPLY_VERB`**: * A verbatim string, intended to be presented to the user without modification. The string payload is stored in the `str` member, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown). Replies should be freed using the `freeReplyObject()` function. Note that this function will take care of freeing sub-reply objects contained in arrays and nested arrays, so there is no need for the user to free the sub replies (it is actually harmful and will corrupt the memory). **Important:** the current version of hiredis (1.0.0) frees replies when the asynchronous API is used. This means you should not call `freeReplyObject` when you use this API. The reply is cleaned up by hiredis _after_ the callback returns. We may introduce a flag to make this configurable in future versions of the library. ### Cleaning up To disconnect and free the context the following function can be used: ```c void redisFree(redisContext *c); ``` This function immediately closes the socket and then frees the allocations done in creating the context. ### Sending commands (cont'd) Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. It has the following prototype: ```c void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); ``` It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments need to be binary safe, the entire array of lengths `argvlen` should be provided. The return value has the same semantic as `redisCommand`. ### Pipelining To explain how Hiredis supports pipelining in a blocking connection, there needs to be understanding of the internal execution flow. When any of the functions in the `redisCommand` family is called, Hiredis first formats the command according to the Redis protocol. The formatted command is then put in the output buffer of the context. This output buffer is dynamic, so it can hold any number of commands. After the command is put in the output buffer, `redisGetReply` is called. This function has the following two execution paths: 1. The input buffer is non-empty: * Try to parse a single reply from the input buffer and return it * If no reply could be parsed, continue at *2* 2. The input buffer is empty: * Write the **entire** output buffer to the socket * Read from the socket until a single reply could be parsed The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply is expected on the socket. To pipeline commands, the only thing that needs to be done is filling up the output buffer. For this cause, two commands can be used that are identical to the `redisCommand` family, apart from not returning a reply: ```c void redisAppendCommand(redisContext *c, const char *format, ...); void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); ``` After calling either function one or more times, `redisGetReply` can be used to receive the subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where the latter means an error occurred while reading a reply. Just as with the other commands, the `err` field in the context can be used to find out what the cause of this error is. The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and a single call to `read(2)`): ```c redisReply *reply; redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"GET foo"); redisGetReply(context,(void**)&reply); // reply for SET freeReplyObject(reply); redisGetReply(context,(void**)&reply); // reply for GET freeReplyObject(reply); ``` This API can also be used to implement a blocking subscriber: ```c reply = redisCommand(context,"SUBSCRIBE foo"); freeReplyObject(reply); while(redisGetReply(context,(void *)&reply) == REDIS_OK) { // consume message freeReplyObject(reply); } ``` ### Errors When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is returned. The `err` field inside the context will be non-zero and set to one of the following constants: * **`REDIS_ERR_IO`**: There was an I/O error while creating the connection, trying to write to the socket or read from the socket. If you included `errno.h` in your application, you can use the global `errno` variable to find out what is wrong. * **`REDIS_ERR_EOF`**: The server closed the connection which resulted in an empty read. * **`REDIS_ERR_PROTOCOL`**: There was an error while parsing the protocol. * **`REDIS_ERR_OTHER`**: Any other error. Currently, it is only used when a specified hostname to connect to cannot be resolved. In every case, the `errstr` field in the context will be set to hold a string representation of the error. ## Asynchronous API Hiredis comes with an asynchronous API that works easily with any event library. Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) and [libevent](http://monkey.org/~provos/libevent/). ### Connecting The function `redisAsyncConnect` can be used to establish a non-blocking connection to Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field should be checked after creation to see if there were errors creating the connection. Because the connection that will be created is non-blocking, the kernel is not able to instantly return if the specified host and port is able to accept a connection. In case of error, it is the caller's responsibility to free the context using `redisAsyncFree()` *Note: A `redisAsyncContext` is not thread-safe.* An application function creating a connection might look like this: ```c void appConnect(myAppData *appData) { redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { printf("Error: %s\n", c->errstr); // handle error redisAsyncFree(c); c = NULL; } else { appData->context = c; appData->connecting = 1; c->data = appData; /* store application pointer for the callbacks */ redisAsyncSetConnectCallback(c, appOnConnect); redisAsyncSetDisconnectCallback(c, appOnDisconnect); } } ``` The asynchronous context _should_ hold a *connect* callback function that is called when the connection attempt completes, either successfully or with an error. It _can_ also hold a *disconnect* callback function that is called when the connection is disconnected (either because of an error or per user request). Both callbacks should have the following prototype: ```c void(const redisAsyncContext *c, int status); ``` On a *connect*, the `status` argument is set to `REDIS_OK` if the connection attempt succeeded. In this case, the context is ready to accept commands. If it is called with `REDIS_ERR` then the connection attempt failed. The `err` field in the context can be accessed to find out the cause of the error. After a failed connection attempt, the context object is automatically freed by the library after calling the connect callback. This may be a good point to create a new context and retry the connection. On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` field in the context can be accessed to find out the cause of the error. The context object is always freed after the disconnect callback fired. When a reconnect is needed, the disconnect callback is a good point to do so. Setting the connect or disconnect callbacks can only be done once per context. For subsequent calls the api will return `REDIS_ERR`. The function to set the callbacks have the following prototype: ```c /* Alternatively you can use redisAsyncSetConnectCallbackNC which will be passed a non-const redisAsyncContext* on invocation (e.g. allowing writes to the privdata member). */ int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); ``` `ac->data` may be used to pass user data to both callbacks. A typical implementation might look something like this: ```c void appOnConnect(redisAsyncContext *c, int status) { myAppData *appData = (myAppData*)c->data; /* get my application specific context*/ appData->connecting = 0; if (status == REDIS_OK) { appData->connected = 1; } else { appData->connected = 0; appData->err = c->err; appData->context = NULL; /* avoid stale pointer when callback returns */ } appAttemptReconnect(); } void appOnDisconnect(redisAsyncContext *c, int status) { myAppData *appData = (myAppData*)c->data; /* get my application specific context*/ appData->connected = 0; appData->err = c->err; appData->context = NULL; /* avoid stale pointer when callback returns */ if (status == REDIS_OK) { appNotifyDisconnectCompleted(mydata); } else { appNotifyUnexpectedDisconnect(mydata); appAttemptReconnect(); } } ``` ### Sending commands and their callbacks In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. Therefore, unlike the synchronous API, there is only a single way to send commands. Because commands are sent to Redis asynchronously, issuing a command requires a callback function that is called when the reply is received. Reply callbacks should have the following prototype: ```c void(redisAsyncContext *c, void *reply, void *privdata); ``` The `privdata` argument can be used to curry arbitrary data to the callback from the point where the command is initially queued for execution. The functions that can be used to issue commands in an asynchronous context are: ```c int redisAsyncCommand( redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); int redisAsyncCommandArgv( redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); ``` Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is returned on calls to the `redisAsyncCommand` family. If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only valid for the duration of the callback. All pending callbacks are called with a `NULL` reply when the context encountered an error. For every command issued, with the exception of **SUBSCRIBE** and **PSUBSCRIBE**, the callback is called exactly once. Even if the context object id disconnected or deleted, every pending callback will be called with a `NULL` reply. For **SUBSCRIBE** and **PSUBSCRIBE**, the callbacks may be called repeatedly until an `unsubscribe` message arrives. This will be the last invocation of the callback. In case of error, the callbacks may receive a final `NULL` reply instead. ### Disconnecting An asynchronous connection can be terminated using: ```c void redisAsyncDisconnect(redisAsyncContext *ac); ``` When this function is called, the connection is **not** immediately terminated. Instead, new commands are no longer accepted and the connection is only terminated when all pending commands have been written to the socket, their respective replies have been read and their respective callbacks have been executed. After this, the disconnection callback is executed with the `REDIS_OK` status and the context object is freed. The connection can be forcefully disconnected using ```c void redisAsyncFree(redisAsyncContext *ac); ``` In this case, nothing more is written to the socket, all pending callbacks are called with a `NULL` reply and the disconnection callback is called with `REDIS_OK`, after which the context object is freed. ### Hooking it up to event library *X* There are a few hooks that need to be set on the context object after it is created. See the `adapters/` directory for bindings to *libev* and *libevent*. ## Reply parsing API Hiredis comes with a reply parsing API that makes it easy for writing higher level language bindings. The reply parsing API consists of the following functions: ```c redisReader *redisReaderCreate(void); void redisReaderFree(redisReader *reader); int redisReaderFeed(redisReader *reader, const char *buf, size_t len); int redisReaderGetReply(redisReader *reader, void **reply); ``` The same set of functions are used internally by hiredis when creating a normal Redis context, the above API just exposes it to the user for a direct usage. ### Usage The function `redisReaderCreate` creates a `redisReader` structure that holds a buffer with unparsed data and state for the protocol parser. Incoming data -- most likely from a socket -- can be placed in the internal buffer of the `redisReader` using `redisReaderFeed`. This function will make a copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed when `redisReaderGetReply` is called. This function returns an integer status and a reply object (as described above) via `void **reply`. The returned status can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went wrong (either a protocol error, or an out of memory error). The parser limits the level of nesting for multi bulk payloads to 7. If the multi bulk nesting level is higher than this, the parser returns an error. ### Customizing replies The function `redisReaderGetReply` creates `redisReply` and makes the function argument `reply` point to the created `redisReply` variable. For instance, if the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` will hold the status as a vanilla C string. However, the functions that are responsible for creating instances of the `redisReply` can be customized by setting the `fn` field on the `redisReader` struct. This should be done immediately after creating the `redisReader`. For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) uses customized reply object functions to create Ruby objects. ### Reader max buffer Both when using the Reader API directly or when using it indirectly via a normal Redis context, the redisReader structure uses a buffer in order to accumulate data from the server. Usually this buffer is destroyed when it is empty and is larger than 16 KiB in order to avoid wasting memory in unused buffers However when working with very big payloads destroying the buffer may slow down performances considerably, so it is possible to modify the max size of an idle buffer changing the value of the `maxbuf` field of the reader structure to the desired value. The special value of 0 means that there is no maximum value for an idle buffer, so the buffer will never get freed. For instance if you have a normal Redis context you can set the maximum idle buffer to zero (unlimited) just with: ```c context->reader->maxbuf = 0; ``` This should be done only in order to maximize performances when working with large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again as soon as possible in order to prevent allocation of useless memory. ### Reader max array elements By default the hiredis reply parser sets the maximum number of multi-bulk elements to 2^32 - 1 or 4,294,967,295 entries. If you need to process multi-bulk replies with more than this many elements you can set the value higher or to zero, meaning unlimited with: ```c context->reader->maxelements = 0; ``` ## SSL/TLS Support ### Building SSL/TLS support is not built by default and requires an explicit flag: make USE_SSL=1 This requires OpenSSL development package (e.g. including header files to be available. When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and `libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries unaffected so no additional dependencies are introduced. ### Using it First, you'll need to make sure you include the SSL header file: ```c #include #include ``` You will also need to link against `libhiredis_ssl`, **in addition** to `libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies. Hiredis implements SSL/TLS on top of its normal `redisContext` or `redisAsyncContext`, so you will need to establish a connection first and then initiate an SSL/TLS handshake. #### Hiredis OpenSSL Wrappers Before Hiredis can negotiate an SSL/TLS connection, it is necessary to initialize OpenSSL and create a context. You can do that in two ways: 1. Work directly with the OpenSSL API to initialize the library's global context and create `SSL_CTX *` and `SSL *` contexts. With an `SSL *` object you can call `redisInitiateSSL()`. 2. Work with a set of Hiredis-provided wrappers around OpenSSL, create a `redisSSLContext` object to hold configuration and use `redisInitiateSSLWithContext()` to initiate the SSL/TLS handshake. ```c /* An Hiredis SSL context. It holds SSL configuration and can be reused across * many contexts. */ redisSSLContext *ssl_context; /* An error variable to indicate what went wrong, if the context fails to * initialize. */ redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE; /* Initialize global OpenSSL state. * * You should call this only once when your app initializes, and only if * you don't explicitly or implicitly initialize OpenSSL it elsewhere. */ redisInitOpenSSL(); /* Create SSL context */ ssl_context = redisCreateSSLContext( "cacertbundle.crt", /* File name of trusted CA/ca bundle file, optional */ "/path/to/certs", /* Path of trusted certificates, optional */ "client_cert.pem", /* File name of client certificate file, optional */ "client_key.pem", /* File name of client private key, optional */ "redis.mydomain.com", /* Server name to request (SNI), optional */ &ssl_error); if(ssl_context == NULL || ssl_error != REDIS_SSL_CTX_NONE) { /* Handle error and abort... */ /* e.g. printf("SSL error: %s\n", (ssl_error != REDIS_SSL_CTX_NONE) ? redisSSLContextGetError(ssl_error) : "Unknown error"); // Abort */ } /* Create Redis context and establish connection */ c = redisConnect("localhost", 6443); if (c == NULL || c->err) { /* Handle error and abort... */ } /* Negotiate SSL/TLS */ if (redisInitiateSSLWithContext(c, ssl_context) != REDIS_OK) { /* Handle error, in c->err / c->errstr */ } ``` ## RESP3 PUSH replies Redis 6.0 introduced PUSH replies with the reply-type `>`. These messages are generated spontaneously and can arrive at any time, so must be handled using callbacks. ### Default behavior Hiredis installs handlers on `redisContext` and `redisAsyncContext` by default, which will intercept and free any PUSH replies detected. This means existing code will work as-is after upgrading to Redis 6 and switching to `RESP3`. ### Custom PUSH handler prototypes The callback prototypes differ between `redisContext` and `redisAsyncContext`. #### redisContext ```c void my_push_handler(void *privdata, void *reply) { /* Handle the reply */ /* Note: We need to free the reply in our custom handler for blocking contexts. This lets us keep the reply if we want. */ freeReplyObject(reply); } ``` #### redisAsyncContext ```c void my_async_push_handler(redisAsyncContext *ac, void *reply) { /* Handle the reply */ /* Note: Because async hiredis always frees replies, you should not call freeReplyObject in an async push callback. */ } ``` ### Installing a custom handler There are two ways to set your own PUSH handlers. 1. Set `push_cb` or `async_push_cb` in the `redisOptions` struct and connect with `redisConnectWithOptions` or `redisAsyncConnectWithOptions`. ```c redisOptions = {0}; REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); options->push_cb = my_push_handler; redisContext *context = redisConnectWithOptions(&options); ``` 2. Call `redisSetPushCallback` or `redisAsyncSetPushCallback` on a connected context. ```c redisContext *context = redisConnect("127.0.0.1", 6379); redisSetPushCallback(context, my_push_handler); ``` _Note `redisSetPushCallback` and `redisAsyncSetPushCallback` both return any currently configured handler, making it easy to override and then return to the old value._ ### Specifying no handler If you have a unique use-case where you don't want hiredis to automatically intercept and free PUSH replies, you will want to configure no handler at all. This can be done in two ways. 1. Set the `REDIS_OPT_NO_PUSH_AUTOFREE` flag in `redisOptions` and leave the callback function pointer `NULL`. ```c redisOptions = {0}; REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); options->options |= REDIS_OPT_NO_PUSH_AUTOFREE; redisContext *context = redisConnectWithOptions(&options); ``` 3. Call `redisSetPushCallback` with `NULL` once connected. ```c redisContext *context = redisConnect("127.0.0.1", 6379); redisSetPushCallback(context, NULL); ``` _Note: With no handler configured, calls to `redisCommand` may generate more than one reply, so this strategy is only applicable when there's some kind of blocking `redisGetReply()` loop (e.g. `MONITOR` or `SUBSCRIBE` workloads)._ ## Allocator injection Hiredis uses a pass-thru structure of function pointers defined in [alloc.h](https://github.com/redis/hiredis/blob/f5d25850/alloc.h#L41) that contain the currently configured allocation and deallocation functions. By default they just point to libc (`malloc`, `calloc`, `realloc`, etc). ### Overriding One can override the allocators like so: ```c hiredisAllocFuncs myfuncs = { .mallocFn = my_malloc, .callocFn = my_calloc, .reallocFn = my_realloc, .strdupFn = my_strdup, .freeFn = my_free, }; // Override allocators (function returns current allocators if needed) hiredisAllocFuncs orig = hiredisSetAllocators(&myfuncs); ``` To reset the allocators to their default libc function simply call: ```c hiredisResetAllocators(); ``` ## AUTHORS Salvatore Sanfilippo (antirez at gmail),\ Pieter Noordhuis (pcnoordhuis at gmail)\ Michael Grunder (michael dot grunder at gmail) _Hiredis is released under the BSD license._ redis-8.0.2/deps/hiredis/adapters/000077500000000000000000000000001501533116600170155ustar00rootroot00000000000000redis-8.0.2/deps/hiredis/adapters/ae.h000066400000000000000000000102571501533116600175600ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_AE_H__ #define __HIREDIS_AE_H__ #include #include #include "../hiredis.h" #include "../async.h" typedef struct redisAeEvents { redisAsyncContext *context; aeEventLoop *loop; int fd; int reading, writing; } redisAeEvents; static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { ((void)el); ((void)fd); ((void)mask); redisAeEvents *e = (redisAeEvents*)privdata; redisAsyncHandleRead(e->context); } static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { ((void)el); ((void)fd); ((void)mask); redisAeEvents *e = (redisAeEvents*)privdata; redisAsyncHandleWrite(e->context); } static void redisAeAddRead(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (!e->reading) { e->reading = 1; aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); } } static void redisAeDelRead(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (e->reading) { e->reading = 0; aeDeleteFileEvent(loop,e->fd,AE_READABLE); } } static void redisAeAddWrite(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (!e->writing) { e->writing = 1; aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); } } static void redisAeDelWrite(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; aeEventLoop *loop = e->loop; if (e->writing) { e->writing = 0; aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); } } static void redisAeCleanup(void *privdata) { redisAeEvents *e = (redisAeEvents*)privdata; redisAeDelRead(privdata); redisAeDelWrite(privdata); hi_free(e); } static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { redisContext *c = &(ac->c); redisAeEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisAeEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDIS_ERR; e->context = ac; e->loop = loop; e->fd = c->fd; e->reading = e->writing = 0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redisAeAddRead; ac->ev.delRead = redisAeDelRead; ac->ev.addWrite = redisAeAddWrite; ac->ev.delWrite = redisAeDelWrite; ac->ev.cleanup = redisAeCleanup; ac->ev.data = e; return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/glib.h000066400000000000000000000074161501533116600201130ustar00rootroot00000000000000#ifndef __HIREDIS_GLIB_H__ #define __HIREDIS_GLIB_H__ #include #include "../hiredis.h" #include "../async.h" typedef struct { GSource source; redisAsyncContext *ac; GPollFD poll_fd; } RedisSource; static void redis_source_add_read (gpointer data) { RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events |= G_IO_IN; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_del_read (gpointer data) { RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events &= ~G_IO_IN; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_add_write (gpointer data) { RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events |= G_IO_OUT; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_del_write (gpointer data) { RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events &= ~G_IO_OUT; g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_cleanup (gpointer data) { RedisSource *source = (RedisSource *)data; g_return_if_fail(source); redis_source_del_read(source); redis_source_del_write(source); /* * It is not our responsibility to remove ourself from the * current main loop. However, we will remove the GPollFD. */ if (source->poll_fd.fd >= 0) { g_source_remove_poll((GSource *)data, &source->poll_fd); source->poll_fd.fd = -1; } } static gboolean redis_source_prepare (GSource *source, gint *timeout_) { RedisSource *redis = (RedisSource *)source; *timeout_ = -1; return !!(redis->poll_fd.events & redis->poll_fd.revents); } static gboolean redis_source_check (GSource *source) { RedisSource *redis = (RedisSource *)source; return !!(redis->poll_fd.events & redis->poll_fd.revents); } static gboolean redis_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { RedisSource *redis = (RedisSource *)source; if ((redis->poll_fd.revents & G_IO_OUT)) { redisAsyncHandleWrite(redis->ac); redis->poll_fd.revents &= ~G_IO_OUT; } if ((redis->poll_fd.revents & G_IO_IN)) { redisAsyncHandleRead(redis->ac); redis->poll_fd.revents &= ~G_IO_IN; } if (callback) { return callback(user_data); } return TRUE; } static void redis_source_finalize (GSource *source) { RedisSource *redis = (RedisSource *)source; if (redis->poll_fd.fd >= 0) { g_source_remove_poll(source, &redis->poll_fd); redis->poll_fd.fd = -1; } } static GSource * redis_source_new (redisAsyncContext *ac) { static GSourceFuncs source_funcs = { .prepare = redis_source_prepare, .check = redis_source_check, .dispatch = redis_source_dispatch, .finalize = redis_source_finalize, }; redisContext *c = &ac->c; RedisSource *source; g_return_val_if_fail(ac != NULL, NULL); source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); if (source == NULL) return NULL; source->ac = ac; source->poll_fd.fd = c->fd; source->poll_fd.events = 0; source->poll_fd.revents = 0; g_source_add_poll((GSource *)source, &source->poll_fd); ac->ev.addRead = redis_source_add_read; ac->ev.delRead = redis_source_del_read; ac->ev.addWrite = redis_source_add_write; ac->ev.delWrite = redis_source_del_write; ac->ev.cleanup = redis_source_cleanup; ac->ev.data = source; return (GSource *)source; } #endif /* __HIREDIS_GLIB_H__ */ redis-8.0.2/deps/hiredis/adapters/ivykis.h000066400000000000000000000044271501533116600205130ustar00rootroot00000000000000#ifndef __HIREDIS_IVYKIS_H__ #define __HIREDIS_IVYKIS_H__ #include #include "../hiredis.h" #include "../async.h" typedef struct redisIvykisEvents { redisAsyncContext *context; struct iv_fd fd; } redisIvykisEvents; static void redisIvykisReadEvent(void *arg) { redisAsyncContext *context = (redisAsyncContext *)arg; redisAsyncHandleRead(context); } static void redisIvykisWriteEvent(void *arg) { redisAsyncContext *context = (redisAsyncContext *)arg; redisAsyncHandleWrite(context); } static void redisIvykisAddRead(void *privdata) { redisIvykisEvents *e = (redisIvykisEvents*)privdata; iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); } static void redisIvykisDelRead(void *privdata) { redisIvykisEvents *e = (redisIvykisEvents*)privdata; iv_fd_set_handler_in(&e->fd, NULL); } static void redisIvykisAddWrite(void *privdata) { redisIvykisEvents *e = (redisIvykisEvents*)privdata; iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); } static void redisIvykisDelWrite(void *privdata) { redisIvykisEvents *e = (redisIvykisEvents*)privdata; iv_fd_set_handler_out(&e->fd, NULL); } static void redisIvykisCleanup(void *privdata) { redisIvykisEvents *e = (redisIvykisEvents*)privdata; iv_fd_unregister(&e->fd); hi_free(e); } static int redisIvykisAttach(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisIvykisEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisIvykisEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDIS_ERR; e->context = ac; /* Register functions to start/stop listening for events */ ac->ev.addRead = redisIvykisAddRead; ac->ev.delRead = redisIvykisDelRead; ac->ev.addWrite = redisIvykisAddWrite; ac->ev.delWrite = redisIvykisDelWrite; ac->ev.cleanup = redisIvykisCleanup; ac->ev.data = e; /* Initialize and install read/write events */ IV_FD_INIT(&e->fd); e->fd.fd = c->fd; e->fd.handler_in = redisIvykisReadEvent; e->fd.handler_out = redisIvykisWriteEvent; e->fd.handler_err = NULL; e->fd.cookie = e->context; iv_fd_register(&e->fd); return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/libev.h000066400000000000000000000127351501533116600202770ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_LIBEV_H__ #define __HIREDIS_LIBEV_H__ #include #include #include #include "../hiredis.h" #include "../async.h" typedef struct redisLibevEvents { redisAsyncContext *context; struct ev_loop *loop; int reading, writing; ev_io rev, wev; ev_timer timer; } redisLibevEvents; static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY ((void)EV_A); #endif ((void)revents); redisLibevEvents *e = (redisLibevEvents*)watcher->data; redisAsyncHandleRead(e->context); } static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { #if EV_MULTIPLICITY ((void)EV_A); #endif ((void)revents); redisLibevEvents *e = (redisLibevEvents*)watcher->data; redisAsyncHandleWrite(e->context); } static void redisLibevAddRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (!e->reading) { e->reading = 1; ev_io_start(EV_A_ &e->rev); } } static void redisLibevDelRead(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (e->reading) { e->reading = 0; ev_io_stop(EV_A_ &e->rev); } } static void redisLibevAddWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (!e->writing) { e->writing = 1; ev_io_start(EV_A_ &e->wev); } } static void redisLibevDelWrite(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (e->writing) { e->writing = 0; ev_io_stop(EV_A_ &e->wev); } } static void redisLibevStopTimer(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif ev_timer_stop(EV_A_ &e->timer); } static void redisLibevCleanup(void *privdata) { redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevDelRead(privdata); redisLibevDelWrite(privdata); redisLibevStopTimer(privdata); hi_free(e); } static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) { #if EV_MULTIPLICITY ((void)EV_A); #endif ((void)revents); redisLibevEvents *e = (redisLibevEvents*)timer->data; redisAsyncHandleTimeout(e->context); } static void redisLibevSetTimeout(void *privdata, struct timeval tv) { redisLibevEvents *e = (redisLibevEvents*)privdata; #if EV_MULTIPLICITY struct ev_loop *loop = e->loop; #endif if (!ev_is_active(&e->timer)) { ev_init(&e->timer, redisLibevTimeout); e->timer.data = e; } e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00; ev_timer_again(EV_A_ &e->timer); } static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { redisContext *c = &(ac->c); redisLibevEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisLibevEvents*)hi_calloc(1, sizeof(*e)); if (e == NULL) return REDIS_ERR; e->context = ac; #if EV_MULTIPLICITY e->loop = EV_A; #else e->loop = NULL; #endif e->rev.data = e; e->wev.data = e; /* Register functions to start/stop listening for events */ ac->ev.addRead = redisLibevAddRead; ac->ev.delRead = redisLibevDelRead; ac->ev.addWrite = redisLibevAddWrite; ac->ev.delWrite = redisLibevDelWrite; ac->ev.cleanup = redisLibevCleanup; ac->ev.scheduleTimer = redisLibevSetTimeout; ac->ev.data = e; /* Initialize read/write events */ ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/libevent.h000066400000000000000000000127551501533116600210100ustar00rootroot00000000000000/* * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_LIBEVENT_H__ #define __HIREDIS_LIBEVENT_H__ #include #include "../hiredis.h" #include "../async.h" #define REDIS_LIBEVENT_DELETED 0x01 #define REDIS_LIBEVENT_ENTERED 0x02 typedef struct redisLibeventEvents { redisAsyncContext *context; struct event *ev; struct event_base *base; struct timeval tv; short flags; short state; } redisLibeventEvents; static void redisLibeventDestroy(redisLibeventEvents *e) { hi_free(e); } static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) { ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; e->state |= REDIS_LIBEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ redisLibeventDestroy(e);\ return; \ } if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleTimeout(e->context); CHECK_DELETED(); } if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleRead(e->context); CHECK_DELETED(); } if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { redisAsyncHandleWrite(e->context); CHECK_DELETED(); } e->state &= ~REDIS_LIBEVENT_ENTERED; #undef CHECK_DELETED } static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { redisLibeventEvents *e = (redisLibeventEvents *)privdata; const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; if (isRemove) { if ((e->flags & flag) == 0) { return; } else { e->flags &= ~flag; } } else { if (e->flags & flag) { return; } else { e->flags |= flag; } } event_del(e->ev); event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, redisLibeventHandler, privdata); event_add(e->ev, tv); } static void redisLibeventAddRead(void *privdata) { redisLibeventUpdate(privdata, EV_READ, 0); } static void redisLibeventDelRead(void *privdata) { redisLibeventUpdate(privdata, EV_READ, 1); } static void redisLibeventAddWrite(void *privdata) { redisLibeventUpdate(privdata, EV_WRITE, 0); } static void redisLibeventDelWrite(void *privdata) { redisLibeventUpdate(privdata, EV_WRITE, 1); } static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; if (!e) { return; } event_del(e->ev); event_free(e->ev); e->ev = NULL; if (e->state & REDIS_LIBEVENT_ENTERED) { e->state |= REDIS_LIBEVENT_DELETED; } else { redisLibeventDestroy(e); } } static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { redisLibeventEvents *e = (redisLibeventEvents *)privdata; short flags = e->flags; e->flags = 0; e->tv = tv; redisLibeventUpdate(e, flags, 0); } static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { redisContext *c = &(ac->c); redisLibeventEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e)); if (e == NULL) return REDIS_ERR; e->context = ac; /* Register functions to start/stop listening for events */ ac->ev.addRead = redisLibeventAddRead; ac->ev.delRead = redisLibeventDelRead; ac->ev.addWrite = redisLibeventAddWrite; ac->ev.delWrite = redisLibeventDelWrite; ac->ev.cleanup = redisLibeventCleanup; ac->ev.scheduleTimer = redisLibeventSetTimeout; ac->ev.data = e; /* Initialize and install read/write events */ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); e->base = base; return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/libhv.h000066400000000000000000000065061501533116600203010ustar00rootroot00000000000000#ifndef __HIREDIS_LIBHV_H__ #define __HIREDIS_LIBHV_H__ #include #include "../hiredis.h" #include "../async.h" typedef struct redisLibhvEvents { hio_t *io; htimer_t *timer; } redisLibhvEvents; static void redisLibhvHandleEvents(hio_t* io) { redisAsyncContext* context = (redisAsyncContext*)hevent_userdata(io); int events = hio_events(io); int revents = hio_revents(io); if (context && (events & HV_READ) && (revents & HV_READ)) { redisAsyncHandleRead(context); } if (context && (events & HV_WRITE) && (revents & HV_WRITE)) { redisAsyncHandleWrite(context); } } static void redisLibhvAddRead(void *privdata) { redisLibhvEvents* events = (redisLibhvEvents*)privdata; hio_add(events->io, redisLibhvHandleEvents, HV_READ); } static void redisLibhvDelRead(void *privdata) { redisLibhvEvents* events = (redisLibhvEvents*)privdata; hio_del(events->io, HV_READ); } static void redisLibhvAddWrite(void *privdata) { redisLibhvEvents* events = (redisLibhvEvents*)privdata; hio_add(events->io, redisLibhvHandleEvents, HV_WRITE); } static void redisLibhvDelWrite(void *privdata) { redisLibhvEvents* events = (redisLibhvEvents*)privdata; hio_del(events->io, HV_WRITE); } static void redisLibhvCleanup(void *privdata) { redisLibhvEvents* events = (redisLibhvEvents*)privdata; if (events->timer) htimer_del(events->timer); hio_close(events->io); hevent_set_userdata(events->io, NULL); hi_free(events); } static void redisLibhvTimeout(htimer_t* timer) { hio_t* io = (hio_t*)hevent_userdata(timer); redisAsyncHandleTimeout((redisAsyncContext*)hevent_userdata(io)); } static void redisLibhvSetTimeout(void *privdata, struct timeval tv) { redisLibhvEvents* events; uint32_t millis; hloop_t* loop; events = (redisLibhvEvents*)privdata; millis = tv.tv_sec * 1000 + tv.tv_usec / 1000; if (millis == 0) { /* Libhv disallows zero'd timers so treat this as a delete or NO OP */ if (events->timer) { htimer_del(events->timer); events->timer = NULL; } } else if (events->timer == NULL) { /* Add new timer */ loop = hevent_loop(events->io); events->timer = htimer_add(loop, redisLibhvTimeout, millis, 1); hevent_set_userdata(events->timer, events->io); } else { /* Update existing timer */ htimer_reset(events->timer, millis); } } static int redisLibhvAttach(redisAsyncContext* ac, hloop_t* loop) { redisContext *c = &(ac->c); redisLibhvEvents *events; hio_t* io = NULL; if (ac->ev.data != NULL) { return REDIS_ERR; } /* Create container struct to keep track of our io and any timer */ events = (redisLibhvEvents*)hi_malloc(sizeof(*events)); if (events == NULL) { return REDIS_ERR; } io = hio_get(loop, c->fd); if (io == NULL) { hi_free(events); return REDIS_ERR; } hevent_set_userdata(io, ac); events->io = io; events->timer = NULL; ac->ev.addRead = redisLibhvAddRead; ac->ev.delRead = redisLibhvDelRead; ac->ev.addWrite = redisLibhvAddWrite; ac->ev.delWrite = redisLibhvDelWrite; ac->ev.cleanup = redisLibhvCleanup; ac->ev.scheduleTimer = redisLibhvSetTimeout; ac->ev.data = events; return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/libsdevent.h000066400000000000000000000115311501533116600213260ustar00rootroot00000000000000#ifndef HIREDIS_LIBSDEVENT_H #define HIREDIS_LIBSDEVENT_H #include #include "../hiredis.h" #include "../async.h" #define REDIS_LIBSDEVENT_DELETED 0x01 #define REDIS_LIBSDEVENT_ENTERED 0x02 typedef struct redisLibsdeventEvents { redisAsyncContext *context; struct sd_event *event; struct sd_event_source *fdSource; struct sd_event_source *timerSource; int fd; short flags; short state; } redisLibsdeventEvents; static void redisLibsdeventDestroy(redisLibsdeventEvents *e) { if (e->fdSource) { e->fdSource = sd_event_source_disable_unref(e->fdSource); } if (e->timerSource) { e->timerSource = sd_event_source_disable_unref(e->timerSource); } sd_event_unref(e->event); hi_free(e); } static int redisLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) { ((void)s); ((void)usec); redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; redisAsyncHandleTimeout(e->context); return 0; } static int redisLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) { ((void)s); ((void)fd); redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; e->state |= REDIS_LIBSDEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDIS_LIBSDEVENT_DELETED) {\ redisLibsdeventDestroy(e);\ return 0; \ } if ((event & EPOLLIN) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) { redisAsyncHandleRead(e->context); CHECK_DELETED(); } if ((event & EPOLLOUT) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) { redisAsyncHandleWrite(e->context); CHECK_DELETED(); } e->state &= ~REDIS_LIBSDEVENT_ENTERED; #undef CHECK_DELETED return 0; } static void redisLibsdeventAddRead(void *userdata) { redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; if (e->flags & EPOLLIN) { return; } e->flags |= EPOLLIN; if (e->flags & EPOLLOUT) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e); } } static void redisLibsdeventDelRead(void *userdata) { redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; e->flags &= ~EPOLLIN; if (e->flags) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { e->fdSource = sd_event_source_disable_unref(e->fdSource); } } static void redisLibsdeventAddWrite(void *userdata) { redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; if (e->flags & EPOLLOUT) { return; } e->flags |= EPOLLOUT; if (e->flags & EPOLLIN) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e); } } static void redisLibsdeventDelWrite(void *userdata) { redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; e->flags &= ~EPOLLOUT; if (e->flags) { sd_event_source_set_io_events(e->fdSource, e->flags); } else { e->fdSource = sd_event_source_disable_unref(e->fdSource); } } static void redisLibsdeventCleanup(void *userdata) { redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata; if (!e) { return; } if (e->state & REDIS_LIBSDEVENT_ENTERED) { e->state |= REDIS_LIBSDEVENT_DELETED; } else { redisLibsdeventDestroy(e); } } static void redisLibsdeventSetTimeout(void *userdata, struct timeval tv) { redisLibsdeventEvents *e = (redisLibsdeventEvents *)userdata; uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec; if (!e->timerSource) { sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redisLibsdeventTimeoutHandler, e); } else { sd_event_source_set_time_relative(e->timerSource, usec); } } static int redisLibsdeventAttach(redisAsyncContext *ac, struct sd_event *event) { redisContext *c = &(ac->c); redisLibsdeventEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisLibsdeventEvents*)hi_calloc(1, sizeof(*e)); if (e == NULL) return REDIS_ERR; /* Initialize and increase event refcount */ e->context = ac; e->event = event; e->fd = c->fd; sd_event_ref(event); /* Register functions to start/stop listening for events */ ac->ev.addRead = redisLibsdeventAddRead; ac->ev.delRead = redisLibsdeventDelRead; ac->ev.addWrite = redisLibsdeventAddWrite; ac->ev.delWrite = redisLibsdeventDelWrite; ac->ev.cleanup = redisLibsdeventCleanup; ac->ev.scheduleTimer = redisLibsdeventSetTimeout; ac->ev.data = e; return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/libuv.h000066400000000000000000000106321501533116600203110ustar00rootroot00000000000000#ifndef __HIREDIS_LIBUV_H__ #define __HIREDIS_LIBUV_H__ #include #include #include "../hiredis.h" #include "../async.h" #include typedef struct redisLibuvEvents { redisAsyncContext* context; uv_poll_t handle; uv_timer_t timer; int events; } redisLibuvEvents; static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { redisLibuvEvents* p = (redisLibuvEvents*)handle->data; int ev = (status ? p->events : events); if (p->context != NULL && (ev & UV_READABLE)) { redisAsyncHandleRead(p->context); } if (p->context != NULL && (ev & UV_WRITABLE)) { redisAsyncHandleWrite(p->context); } } static void redisLibuvAddRead(void *privdata) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; if (p->events & UV_READABLE) { return; } p->events |= UV_READABLE; uv_poll_start(&p->handle, p->events, redisLibuvPoll); } static void redisLibuvDelRead(void *privdata) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; p->events &= ~UV_READABLE; if (p->events) { uv_poll_start(&p->handle, p->events, redisLibuvPoll); } else { uv_poll_stop(&p->handle); } } static void redisLibuvAddWrite(void *privdata) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; if (p->events & UV_WRITABLE) { return; } p->events |= UV_WRITABLE; uv_poll_start(&p->handle, p->events, redisLibuvPoll); } static void redisLibuvDelWrite(void *privdata) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; p->events &= ~UV_WRITABLE; if (p->events) { uv_poll_start(&p->handle, p->events, redisLibuvPoll); } else { uv_poll_stop(&p->handle); } } static void on_timer_close(uv_handle_t *handle) { redisLibuvEvents* p = (redisLibuvEvents*)handle->data; p->timer.data = NULL; if (!p->handle.data) { // both timer and handle are closed hi_free(p); } // else, wait for `on_handle_close` } static void on_handle_close(uv_handle_t *handle) { redisLibuvEvents* p = (redisLibuvEvents*)handle->data; p->handle.data = NULL; if (!p->timer.data) { // timer never started, or timer already destroyed hi_free(p); } // else, wait for `on_timer_close` } // libuv removed `status` parameter since v0.11.23 // see: https://github.com/libuv/libuv/blob/v0.11.23/include/uv.h #if (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR < 11) || \ (UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR == 11 && UV_VERSION_PATCH < 23) static void redisLibuvTimeout(uv_timer_t *timer, int status) { (void)status; // unused #else static void redisLibuvTimeout(uv_timer_t *timer) { #endif redisLibuvEvents *e = (redisLibuvEvents*)timer->data; redisAsyncHandleTimeout(e->context); } static void redisLibuvSetTimeout(void *privdata, struct timeval tv) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; uint64_t millsec = tv.tv_sec * 1000 + tv.tv_usec / 1000.0; if (!p->timer.data) { // timer is uninitialized if (uv_timer_init(p->handle.loop, &p->timer) != 0) { return; } p->timer.data = p; } // updates the timeout if the timer has already started // or start the timer uv_timer_start(&p->timer, redisLibuvTimeout, millsec, 0); } static void redisLibuvCleanup(void *privdata) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; p->context = NULL; // indicate that context might no longer exist if (p->timer.data) { uv_close((uv_handle_t*)&p->timer, on_timer_close); } uv_close((uv_handle_t*)&p->handle, on_handle_close); } static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { redisContext *c = &(ac->c); if (ac->ev.data != NULL) { return REDIS_ERR; } ac->ev.addRead = redisLibuvAddRead; ac->ev.delRead = redisLibuvDelRead; ac->ev.addWrite = redisLibuvAddWrite; ac->ev.delWrite = redisLibuvDelWrite; ac->ev.cleanup = redisLibuvCleanup; ac->ev.scheduleTimer = redisLibuvSetTimeout; redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p)); if (p == NULL) return REDIS_ERR; memset(p, 0, sizeof(*p)); if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) { return REDIS_ERR; } ac->ev.data = p; p->handle.data = p; p->context = ac; return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/macosx.h000066400000000000000000000074551501533116600204730ustar00rootroot00000000000000// // Created by Дмитрий Бахвалов on 13.07.15. // Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. // #ifndef __HIREDIS_MACOSX_H__ #define __HIREDIS_MACOSX_H__ #include #include "../hiredis.h" #include "../async.h" typedef struct { redisAsyncContext *context; CFSocketRef socketRef; CFRunLoopSourceRef sourceRef; } RedisRunLoop; static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { if( redisRunLoop != NULL ) { if( redisRunLoop->sourceRef != NULL ) { CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); CFRelease(redisRunLoop->sourceRef); } if( redisRunLoop->socketRef != NULL ) { CFSocketInvalidate(redisRunLoop->socketRef); CFRelease(redisRunLoop->socketRef); } hi_free(redisRunLoop); } return REDIS_ERR; } static void redisMacOSAddRead(void *privdata) { RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); } static void redisMacOSDelRead(void *privdata) { RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); } static void redisMacOSAddWrite(void *privdata) { RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); } static void redisMacOSDelWrite(void *privdata) { RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); } static void redisMacOSCleanup(void *privdata) { RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; freeRedisRunLoop(redisRunLoop); } static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { redisAsyncContext* context = (redisAsyncContext*) info; switch (callbackType) { case kCFSocketReadCallBack: redisAsyncHandleRead(context); break; case kCFSocketWriteCallBack: redisAsyncHandleWrite(context); break; default: break; } } static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { redisContext *redisCtx = &(redisAsyncCtx->c); /* Nothing should be attached when something is already attached */ if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop)); if (redisRunLoop == NULL) return REDIS_ERR; /* Setup redis stuff */ redisRunLoop->context = redisAsyncCtx; redisAsyncCtx->ev.addRead = redisMacOSAddRead; redisAsyncCtx->ev.delRead = redisMacOSDelRead; redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; redisAsyncCtx->ev.cleanup = redisMacOSCleanup; redisAsyncCtx->ev.data = redisRunLoop; /* Initialize and install read/write events */ CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, kCFSocketReadCallBack | kCFSocketWriteCallBack, redisMacOSAsyncCallback, &socketCtx); if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/adapters/poll.h000066400000000000000000000122141501533116600201340ustar00rootroot00000000000000 #ifndef HIREDIS_POLL_H #define HIREDIS_POLL_H #include "../async.h" #include "../sockcompat.h" #include // for memset #include /* Values to return from redisPollTick */ #define REDIS_POLL_HANDLED_READ 1 #define REDIS_POLL_HANDLED_WRITE 2 #define REDIS_POLL_HANDLED_TIMEOUT 4 /* An adapter to allow manual polling of the async context by checking the state * of the underlying file descriptor. Useful in cases where there is no formal * IO event loop but regular ticking can be used, such as in game engines. */ typedef struct redisPollEvents { redisAsyncContext *context; redisFD fd; char reading, writing; char in_tick; char deleted; double deadline; } redisPollEvents; static double redisPollTimevalToDouble(struct timeval *tv) { if (tv == NULL) return 0.0; return tv->tv_sec + tv->tv_usec / 1000000.00; } static double redisPollGetNow(void) { #ifndef _MSC_VER struct timeval tv; gettimeofday(&tv,NULL); return redisPollTimevalToDouble(&tv); #else FILETIME ft; ULARGE_INTEGER li; GetSystemTimeAsFileTime(&ft); li.HighPart = ft.dwHighDateTime; li.LowPart = ft.dwLowDateTime; return (double)li.QuadPart * 1e-7; #endif } /* Poll for io, handling any pending callbacks. The timeout argument can be * positive to wait for a maximum given time for IO, zero to poll, or negative * to wait forever */ static int redisPollTick(redisAsyncContext *ac, double timeout) { int reading, writing; struct pollfd pfd; int handled; int ns; int itimeout; redisPollEvents *e = (redisPollEvents*)ac->ev.data; if (!e) return 0; /* local flags, won't get changed during callbacks */ reading = e->reading; writing = e->writing; if (!reading && !writing) return 0; pfd.fd = e->fd; pfd.events = 0; if (reading) pfd.events = POLLIN; if (writing) pfd.events |= POLLOUT; if (timeout >= 0.0) { itimeout = (int)(timeout * 1000.0); } else { itimeout = -1; } ns = poll(&pfd, 1, itimeout); if (ns < 0) { /* ignore the EINTR error */ if (errno != EINTR) return ns; ns = 0; } handled = 0; e->in_tick = 1; if (ns) { if (reading && (pfd.revents & POLLIN)) { redisAsyncHandleRead(ac); handled |= REDIS_POLL_HANDLED_READ; } /* on Windows, connection failure is indicated with the Exception fdset. * handle it the same as writable. */ if (writing && (pfd.revents & (POLLOUT | POLLERR))) { /* context Read callback may have caused context to be deleted, e.g. by doing an redisAsyncDisconnect() */ if (!e->deleted) { redisAsyncHandleWrite(ac); handled |= REDIS_POLL_HANDLED_WRITE; } } } /* perform timeouts */ if (!e->deleted && e->deadline != 0.0) { double now = redisPollGetNow(); if (now >= e->deadline) { /* deadline has passed. disable timeout and perform callback */ e->deadline = 0.0; redisAsyncHandleTimeout(ac); handled |= REDIS_POLL_HANDLED_TIMEOUT; } } /* do a delayed cleanup if required */ if (e->deleted) hi_free(e); else e->in_tick = 0; return handled; } static void redisPollAddRead(void *data) { redisPollEvents *e = (redisPollEvents*)data; e->reading = 1; } static void redisPollDelRead(void *data) { redisPollEvents *e = (redisPollEvents*)data; e->reading = 0; } static void redisPollAddWrite(void *data) { redisPollEvents *e = (redisPollEvents*)data; e->writing = 1; } static void redisPollDelWrite(void *data) { redisPollEvents *e = (redisPollEvents*)data; e->writing = 0; } static void redisPollCleanup(void *data) { redisPollEvents *e = (redisPollEvents*)data; /* if we are currently processing a tick, postpone deletion */ if (e->in_tick) e->deleted = 1; else hi_free(e); } static void redisPollScheduleTimer(void *data, struct timeval tv) { redisPollEvents *e = (redisPollEvents*)data; double now = redisPollGetNow(); e->deadline = now + redisPollTimevalToDouble(&tv); } static int redisPollAttach(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisPollEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisPollEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDIS_ERR; memset(e, 0, sizeof(*e)); e->context = ac; e->fd = c->fd; e->reading = e->writing = 0; e->in_tick = e->deleted = 0; e->deadline = 0.0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redisPollAddRead; ac->ev.delRead = redisPollDelRead; ac->ev.addWrite = redisPollAddWrite; ac->ev.delWrite = redisPollDelWrite; ac->ev.scheduleTimer = redisPollScheduleTimer; ac->ev.cleanup = redisPollCleanup; ac->ev.data = e; return REDIS_OK; } #endif /* HIREDIS_POLL_H */ redis-8.0.2/deps/hiredis/adapters/qt.h000066400000000000000000000102111501533116600176050ustar00rootroot00000000000000/*- * Copyright (C) 2014 Pietro Cerutti * * 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 AUTHOR 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 AUTHOR 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. */ #ifndef __HIREDIS_QT_H__ #define __HIREDIS_QT_H__ #include #include "../async.h" static void RedisQtAddRead(void *); static void RedisQtDelRead(void *); static void RedisQtAddWrite(void *); static void RedisQtDelWrite(void *); static void RedisQtCleanup(void *); class RedisQtAdapter : public QObject { Q_OBJECT friend void RedisQtAddRead(void * adapter) { RedisQtAdapter * a = static_cast(adapter); a->addRead(); } friend void RedisQtDelRead(void * adapter) { RedisQtAdapter * a = static_cast(adapter); a->delRead(); } friend void RedisQtAddWrite(void * adapter) { RedisQtAdapter * a = static_cast(adapter); a->addWrite(); } friend void RedisQtDelWrite(void * adapter) { RedisQtAdapter * a = static_cast(adapter); a->delWrite(); } friend void RedisQtCleanup(void * adapter) { RedisQtAdapter * a = static_cast(adapter); a->cleanup(); } public: RedisQtAdapter(QObject * parent = 0) : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } ~RedisQtAdapter() { if (m_ctx != 0) { m_ctx->ev.data = NULL; } } int setContext(redisAsyncContext * ac) { if (ac->ev.data != NULL) { return REDIS_ERR; } m_ctx = ac; m_ctx->ev.data = this; m_ctx->ev.addRead = RedisQtAddRead; m_ctx->ev.delRead = RedisQtDelRead; m_ctx->ev.addWrite = RedisQtAddWrite; m_ctx->ev.delWrite = RedisQtDelWrite; m_ctx->ev.cleanup = RedisQtCleanup; return REDIS_OK; } private: void addRead() { if (m_read) return; m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); } void delRead() { if (!m_read) return; delete m_read; m_read = 0; } void addWrite() { if (m_write) return; m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); } void delWrite() { if (!m_write) return; delete m_write; m_write = 0; } void cleanup() { delRead(); delWrite(); } private slots: void read() { redisAsyncHandleRead(m_ctx); } void write() { redisAsyncHandleWrite(m_ctx); } private: redisAsyncContext * m_ctx; QSocketNotifier * m_read; QSocketNotifier * m_write; }; #endif /* !__HIREDIS_QT_H__ */ redis-8.0.2/deps/hiredis/adapters/redismoduleapi.h000066400000000000000000000100321501533116600221700ustar00rootroot00000000000000#ifndef __HIREDIS_REDISMODULEAPI_H__ #define __HIREDIS_REDISMODULEAPI_H__ #include "redismodule.h" #include "../async.h" #include "../hiredis.h" #include typedef struct redisModuleEvents { redisAsyncContext *context; RedisModuleCtx *module_ctx; int fd; int reading, writing; int timer_active; RedisModuleTimerID timer_id; } redisModuleEvents; static inline void redisModuleReadEvent(int fd, void *privdata, int mask) { (void) fd; (void) mask; redisModuleEvents *e = (redisModuleEvents*)privdata; redisAsyncHandleRead(e->context); } static inline void redisModuleWriteEvent(int fd, void *privdata, int mask) { (void) fd; (void) mask; redisModuleEvents *e = (redisModuleEvents*)privdata; redisAsyncHandleWrite(e->context); } static inline void redisModuleAddRead(void *privdata) { redisModuleEvents *e = (redisModuleEvents*)privdata; if (!e->reading) { e->reading = 1; RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_READABLE, redisModuleReadEvent, e); } } static inline void redisModuleDelRead(void *privdata) { redisModuleEvents *e = (redisModuleEvents*)privdata; if (e->reading) { e->reading = 0; RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_READABLE); } } static inline void redisModuleAddWrite(void *privdata) { redisModuleEvents *e = (redisModuleEvents*)privdata; if (!e->writing) { e->writing = 1; RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_WRITABLE, redisModuleWriteEvent, e); } } static inline void redisModuleDelWrite(void *privdata) { redisModuleEvents *e = (redisModuleEvents*)privdata; if (e->writing) { e->writing = 0; RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_WRITABLE); } } static inline void redisModuleStopTimer(void *privdata) { redisModuleEvents *e = (redisModuleEvents*)privdata; if (e->timer_active) { RedisModule_StopTimer(e->module_ctx, e->timer_id, NULL); } e->timer_active = 0; } static inline void redisModuleCleanup(void *privdata) { redisModuleEvents *e = (redisModuleEvents*)privdata; redisModuleDelRead(privdata); redisModuleDelWrite(privdata); redisModuleStopTimer(privdata); hi_free(e); } static inline void redisModuleTimeout(RedisModuleCtx *ctx, void *privdata) { (void) ctx; redisModuleEvents *e = (redisModuleEvents*)privdata; e->timer_active = 0; redisAsyncHandleTimeout(e->context); } static inline void redisModuleSetTimeout(void *privdata, struct timeval tv) { redisModuleEvents* e = (redisModuleEvents*)privdata; redisModuleStopTimer(privdata); mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0; e->timer_id = RedisModule_CreateTimer(e->module_ctx, millis, redisModuleTimeout, e); e->timer_active = 1; } /* Check if Redis version is compatible with the adapter. */ static inline int redisModuleCompatibilityCheck(void) { if (!RedisModule_EventLoopAdd || !RedisModule_EventLoopDel || !RedisModule_CreateTimer || !RedisModule_StopTimer) { return REDIS_ERR; } return REDIS_OK; } static inline int redisModuleAttach(redisAsyncContext *ac, RedisModuleCtx *module_ctx) { redisContext *c = &(ac->c); redisModuleEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDIS_ERR; /* Create container for context and r/w events */ e = (redisModuleEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDIS_ERR; e->context = ac; e->module_ctx = module_ctx; e->fd = c->fd; e->reading = e->writing = 0; e->timer_active = 0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redisModuleAddRead; ac->ev.delRead = redisModuleDelRead; ac->ev.addWrite = redisModuleAddWrite; ac->ev.delWrite = redisModuleDelWrite; ac->ev.cleanup = redisModuleCleanup; ac->ev.scheduleTimer = redisModuleSetTimeout; ac->ev.data = e; return REDIS_OK; } #endif redis-8.0.2/deps/hiredis/alloc.c000066400000000000000000000055601501533116600164560ustar00rootroot00000000000000/* * Copyright (c) 2020, Michael Grunder * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include "alloc.h" #include #include hiredisAllocFuncs hiredisAllocFns = { .mallocFn = malloc, .callocFn = calloc, .reallocFn = realloc, .strdupFn = strdup, .freeFn = free, }; /* Override hiredis' allocators with ones supplied by the user */ hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) { hiredisAllocFuncs orig = hiredisAllocFns; hiredisAllocFns = *override; return orig; } /* Reset allocators to use libc defaults */ void hiredisResetAllocators(void) { hiredisAllocFns = (hiredisAllocFuncs) { .mallocFn = malloc, .callocFn = calloc, .reallocFn = realloc, .strdupFn = strdup, .freeFn = free, }; } #ifdef _WIN32 void *hi_malloc(size_t size) { return hiredisAllocFns.mallocFn(size); } void *hi_calloc(size_t nmemb, size_t size) { /* Overflow check as the user can specify any arbitrary allocator */ if (SIZE_MAX / size < nmemb) return NULL; return hiredisAllocFns.callocFn(nmemb, size); } void *hi_realloc(void *ptr, size_t size) { return hiredisAllocFns.reallocFn(ptr, size); } char *hi_strdup(const char *str) { return hiredisAllocFns.strdupFn(str); } void hi_free(void *ptr) { hiredisAllocFns.freeFn(ptr); } #endif redis-8.0.2/deps/hiredis/alloc.h000066400000000000000000000060721501533116600164620ustar00rootroot00000000000000/* * Copyright (c) 2020, Michael Grunder * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef HIREDIS_ALLOC_H #define HIREDIS_ALLOC_H #include /* for size_t */ #include #ifdef __cplusplus extern "C" { #endif /* Structure pointing to our actually configured allocators */ typedef struct hiredisAllocFuncs { void *(*mallocFn)(size_t); void *(*callocFn)(size_t,size_t); void *(*reallocFn)(void*,size_t); char *(*strdupFn)(const char*); void (*freeFn)(void*); } hiredisAllocFuncs; hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha); void hiredisResetAllocators(void); #ifndef _WIN32 /* Hiredis' configured allocator function pointer struct */ extern hiredisAllocFuncs hiredisAllocFns; static inline void *hi_malloc(size_t size) { return hiredisAllocFns.mallocFn(size); } static inline void *hi_calloc(size_t nmemb, size_t size) { /* Overflow check as the user can specify any arbitrary allocator */ if (SIZE_MAX / size < nmemb) return NULL; return hiredisAllocFns.callocFn(nmemb, size); } static inline void *hi_realloc(void *ptr, size_t size) { return hiredisAllocFns.reallocFn(ptr, size); } static inline char *hi_strdup(const char *str) { return hiredisAllocFns.strdupFn(str); } static inline void hi_free(void *ptr) { hiredisAllocFns.freeFn(ptr); } #else void *hi_malloc(size_t size); void *hi_calloc(size_t nmemb, size_t size); void *hi_realloc(void *ptr, size_t size); char *hi_strdup(const char *str); void hi_free(void *ptr); #endif #ifdef __cplusplus } #endif #endif /* HIREDIS_ALLOC_H */ redis-8.0.2/deps/hiredis/appveyor.yml000066400000000000000000000013471501533116600176070ustar00rootroot00000000000000# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) environment: matrix: - CYG_BASH: C:\cygwin64\bin\bash CC: gcc - CYG_BASH: C:\cygwin\bin\bash CC: gcc CFLAGS: -m32 CXXFLAGS: -m32 LDFLAGS: -m32 clone_depth: 1 # Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail init: - git config --global core.autocrlf input # Install needed build dependencies install: - '%CYG_BASH% -lc "cygcheck -dc cygwin"' build_script: - 'echo building...' - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include "alloc.h" #include #include #ifndef _MSC_VER #include #endif #include #include #include #include "async.h" #include "net.h" #include "dict.c" #include "sds.h" #include "win32.h" #include "async_private.h" #ifdef NDEBUG #undef assert #define assert(e) (void)(e) #endif /* Forward declarations of hiredis.c functions */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); void __redisSetError(redisContext *c, int type, const char *str); /* Functions managing dictionary of callbacks for pub/sub. */ static unsigned int callbackHash(const void *key) { return dictGenHashFunction((const unsigned char *)key, hi_sdslen((const hisds)key)); } static void *callbackValDup(void *privdata, const void *src) { ((void) privdata); redisCallback *dup; dup = hi_malloc(sizeof(*dup)); if (dup == NULL) return NULL; memcpy(dup,src,sizeof(*dup)); return dup; } static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { int l1, l2; ((void) privdata); l1 = hi_sdslen((const hisds)key1); l2 = hi_sdslen((const hisds)key2); if (l1 != l2) return 0; return memcmp(key1,key2,l1) == 0; } static void callbackKeyDestructor(void *privdata, void *key) { ((void) privdata); hi_sdsfree((hisds)key); } static void callbackValDestructor(void *privdata, void *val) { ((void) privdata); hi_free(val); } static dictType callbackDict = { callbackHash, NULL, callbackValDup, callbackKeyCompare, callbackKeyDestructor, callbackValDestructor }; static redisAsyncContext *redisAsyncInitialize(redisContext *c) { redisAsyncContext *ac; dict *channels = NULL, *patterns = NULL; channels = dictCreate(&callbackDict,NULL); if (channels == NULL) goto oom; patterns = dictCreate(&callbackDict,NULL); if (patterns == NULL) goto oom; ac = hi_realloc(c,sizeof(redisAsyncContext)); if (ac == NULL) goto oom; c = &(ac->c); /* The regular connect functions will always set the flag REDIS_CONNECTED. * For the async API, we want to wait until the first write event is * received up before setting this flag, so reset it here. */ c->flags &= ~REDIS_CONNECTED; ac->err = 0; ac->errstr = NULL; ac->data = NULL; ac->dataCleanup = NULL; ac->ev.data = NULL; ac->ev.addRead = NULL; ac->ev.delRead = NULL; ac->ev.addWrite = NULL; ac->ev.delWrite = NULL; ac->ev.cleanup = NULL; ac->ev.scheduleTimer = NULL; ac->onConnect = NULL; ac->onConnectNC = NULL; ac->onDisconnect = NULL; ac->replies.head = NULL; ac->replies.tail = NULL; ac->sub.replies.head = NULL; ac->sub.replies.tail = NULL; ac->sub.channels = channels; ac->sub.patterns = patterns; ac->sub.pending_unsubs = 0; return ac; oom: if (channels) dictRelease(channels); if (patterns) dictRelease(patterns); return NULL; } /* We want the error field to be accessible directly instead of requiring * an indirection to the redisContext struct. */ static void __redisAsyncCopyError(redisAsyncContext *ac) { if (!ac) return; redisContext *c = &(ac->c); ac->err = c->err; ac->errstr = c->errstr; } redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) { redisOptions myOptions = *options; redisContext *c; redisAsyncContext *ac; /* Clear any erroneously set sync callback and flag that we don't want to * use freeReplyObject by default. */ myOptions.push_cb = NULL; myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE; myOptions.options |= REDIS_OPT_NONBLOCK; c = redisConnectWithOptions(&myOptions); if (c == NULL) { return NULL; } ac = redisAsyncInitialize(c); if (ac == NULL) { redisFree(c); return NULL; } /* Set any configured async push handler */ redisAsyncSetPushCallback(ac, myOptions.async_push_cb); __redisAsyncCopyError(ac); return ac; } redisAsyncContext *redisAsyncConnect(const char *ip, int port) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, const char *source_addr) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.options |= REDIS_OPT_REUSEADDR; options.endpoint.tcp.source_addr = source_addr; return redisAsyncConnectWithOptions(&options); } redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); return redisAsyncConnectWithOptions(&options); } static int redisAsyncSetConnectCallbackImpl(redisAsyncContext *ac, redisConnectCallback *fn, redisConnectCallbackNC *fn_nc) { /* If either are already set, this is an error */ if (ac->onConnect || ac->onConnectNC) return REDIS_ERR; if (fn) { ac->onConnect = fn; } else if (fn_nc) { ac->onConnectNC = fn_nc; } /* The common way to detect an established connection is to wait for * the first write event to be fired. This assumes the related event * library functions are already set. */ _EL_ADD_WRITE(ac); return REDIS_OK; } int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { return redisAsyncSetConnectCallbackImpl(ac, fn, NULL); } int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn) { return redisAsyncSetConnectCallbackImpl(ac, NULL, fn); } int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { if (ac->onDisconnect == NULL) { ac->onDisconnect = fn; return REDIS_OK; } return REDIS_ERR; } /* Helper functions to push/shift callbacks */ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { redisCallback *cb; /* Copy callback from stack to heap */ cb = hi_malloc(sizeof(*cb)); if (cb == NULL) return REDIS_ERR_OOM; if (source != NULL) { memcpy(cb,source,sizeof(*cb)); cb->next = NULL; } /* Store callback in list */ if (list->head == NULL) list->head = cb; if (list->tail != NULL) list->tail->next = cb; list->tail = cb; return REDIS_OK; } static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { redisCallback *cb = list->head; if (cb != NULL) { list->head = cb->next; if (cb == list->tail) list->tail = NULL; /* Copy callback from heap to stack */ if (target != NULL) memcpy(target,cb,sizeof(*cb)); hi_free(cb); return REDIS_OK; } return REDIS_ERR; } static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { redisContext *c = &(ac->c); if (cb->fn != NULL) { c->flags |= REDIS_IN_CALLBACK; cb->fn(ac,reply,cb->privdata); c->flags &= ~REDIS_IN_CALLBACK; } } static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) { if (ac->push_cb != NULL) { ac->c.flags |= REDIS_IN_CALLBACK; ac->push_cb(ac, reply); ac->c.flags &= ~REDIS_IN_CALLBACK; } } static void __redisRunConnectCallback(redisAsyncContext *ac, int status) { if (ac->onConnect == NULL && ac->onConnectNC == NULL) return; if (!(ac->c.flags & REDIS_IN_CALLBACK)) { ac->c.flags |= REDIS_IN_CALLBACK; if (ac->onConnect) { ac->onConnect(ac, status); } else { ac->onConnectNC(ac, status); } ac->c.flags &= ~REDIS_IN_CALLBACK; } else { /* already in callback */ if (ac->onConnect) { ac->onConnect(ac, status); } else { ac->onConnectNC(ac, status); } } } static void __redisRunDisconnectCallback(redisAsyncContext *ac, int status) { if (ac->onDisconnect) { if (!(ac->c.flags & REDIS_IN_CALLBACK)) { ac->c.flags |= REDIS_IN_CALLBACK; ac->onDisconnect(ac, status); ac->c.flags &= ~REDIS_IN_CALLBACK; } else { /* already in callback */ ac->onDisconnect(ac, status); } } } /* Helper function to free the context. */ static void __redisAsyncFree(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisCallback cb; dictIterator it; dictEntry *de; /* Execute pending callbacks with NULL reply. */ while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) __redisRunCallback(ac,&cb,NULL); while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) __redisRunCallback(ac,&cb,NULL); /* Run subscription callbacks with NULL reply */ if (ac->sub.channels) { dictInitIterator(&it,ac->sub.channels); while ((de = dictNext(&it)) != NULL) __redisRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.channels); } if (ac->sub.patterns) { dictInitIterator(&it,ac->sub.patterns); while ((de = dictNext(&it)) != NULL) __redisRunCallback(ac,dictGetEntryVal(de),NULL); dictRelease(ac->sub.patterns); } /* Signal event lib to clean up */ _EL_CLEANUP(ac); /* Execute disconnect callback. When redisAsyncFree() initiated destroying * this context, the status will always be REDIS_OK. */ if (c->flags & REDIS_CONNECTED) { int status = ac->err == 0 ? REDIS_OK : REDIS_ERR; if (c->flags & REDIS_FREEING) status = REDIS_OK; __redisRunDisconnectCallback(ac, status); } if (ac->dataCleanup) { ac->dataCleanup(ac->data); } /* Cleanup self */ redisFree(c); } /* Free the async context. When this function is called from a callback, * control needs to be returned to redisProcessCallbacks() before actual * free'ing. To do so, a flag is set on the context which is picked up by * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ void redisAsyncFree(redisAsyncContext *ac) { if (ac == NULL) return; redisContext *c = &(ac->c); c->flags |= REDIS_FREEING; if (!(c->flags & REDIS_IN_CALLBACK)) __redisAsyncFree(ac); } /* Helper function to make the disconnect happen and clean up. */ void __redisAsyncDisconnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); /* Make sure error is accessible if there is any */ __redisAsyncCopyError(ac); if (ac->err == 0) { /* For clean disconnects, there should be no pending callbacks. */ int ret = __redisShiftCallback(&ac->replies,NULL); assert(ret == REDIS_ERR); } else { /* Disconnection is caused by an error, make sure that pending * callbacks cannot call new commands. */ c->flags |= REDIS_DISCONNECTING; } /* cleanup event library on disconnect. * this is safe to call multiple times */ _EL_CLEANUP(ac); /* For non-clean disconnects, __redisAsyncFree() will execute pending * callbacks with a NULL-reply. */ if (!(c->flags & REDIS_NO_AUTO_FREE)) { __redisAsyncFree(ac); } } /* Tries to do a clean disconnect from Redis, meaning it stops new commands * from being issued, but tries to flush the output buffer and execute * callbacks for all remaining replies. When this function is called from a * callback, there might be more replies and we can safely defer disconnecting * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately * when there are no pending callbacks. */ void redisAsyncDisconnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); c->flags |= REDIS_DISCONNECTING; /** unset the auto-free flag here, because disconnect undoes this */ c->flags &= ~REDIS_NO_AUTO_FREE; if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) __redisAsyncDisconnect(ac); } static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { redisContext *c = &(ac->c); dict *callbacks; redisCallback *cb = NULL; dictEntry *de; int pvariant; char *stype; hisds sname = NULL; /* Match reply with the expected format of a pushed message. * The type and number of elements (3 to 4) are specified at: * https://redis.io/docs/latest/develop/interact/pubsub/#format-of-pushed-messages */ if ((reply->type == REDIS_REPLY_ARRAY && !(c->flags & REDIS_SUPPORTS_PUSH) && reply->elements >= 3) || reply->type == REDIS_REPLY_PUSH) { assert(reply->element[0]->type == REDIS_REPLY_STRING); stype = reply->element[0]->str; pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; if (pvariant) callbacks = ac->sub.patterns; else callbacks = ac->sub.channels; /* Locate the right callback */ if (reply->element[1]->type == REDIS_REPLY_STRING) { sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len); if (sname == NULL) goto oom; if ((de = dictFind(callbacks,sname)) != NULL) { cb = dictGetEntryVal(de); memcpy(dstcb,cb,sizeof(*dstcb)); } } /* If this is an subscribe reply decrease pending counter. */ if (strcasecmp(stype+pvariant,"subscribe") == 0) { assert(cb != NULL); cb->pending_subs -= 1; } else if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { if (cb == NULL) ac->sub.pending_unsubs -= 1; else if (cb->pending_subs == 0) dictDelete(callbacks,sname); /* If this was the last unsubscribe message, revert to * non-subscribe mode. */ assert(reply->element[2]->type == REDIS_REPLY_INTEGER); /* Unset subscribed flag only when no pipelined pending subscribe * or pending unsubscribe replies. */ if (reply->element[2]->integer == 0 && dictSize(ac->sub.channels) == 0 && dictSize(ac->sub.patterns) == 0 && ac->sub.pending_unsubs == 0) { c->flags &= ~REDIS_SUBSCRIBED; /* Move ongoing regular command callbacks. */ redisCallback cb; while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) { __redisPushCallback(&ac->replies,&cb); } } } hi_sdsfree(sname); } else { /* Shift callback for pending command in subscribed context. */ __redisShiftCallback(&ac->sub.replies,dstcb); } return REDIS_OK; oom: __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory"); __redisAsyncCopyError(ac); return REDIS_ERR; } #define redisIsSpontaneousPushReply(r) \ (redisIsPushReply(r) && !redisIsSubscribeReply(r)) static int redisIsSubscribeReply(redisReply *reply) { char *str; size_t len, off; /* We will always have at least one string with the subscribe/message type */ if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING || reply->element[0]->len < sizeof("message") - 1) { return 0; } /* Get the string/len moving past 'p' if needed */ off = tolower(reply->element[0]->str[0]) == 'p'; str = reply->element[0]->str + off; len = reply->element[0]->len - off; return !strncasecmp(str, "subscribe", len) || !strncasecmp(str, "message", len) || !strncasecmp(str, "unsubscribe", len); } void redisProcessCallbacks(redisAsyncContext *ac) { redisContext *c = &(ac->c); void *reply = NULL; int status; while((status = redisGetReply(c,&reply)) == REDIS_OK) { if (reply == NULL) { /* When the connection is being disconnected and there are * no more replies, this is the cue to really disconnect. */ if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0 && ac->replies.head == NULL) { __redisAsyncDisconnect(ac); return; } /* When the connection is not being disconnected, simply stop * trying to get replies and wait for the next loop tick. */ break; } /* Keep track of push message support for subscribe handling */ if (redisIsPushReply(reply)) c->flags |= REDIS_SUPPORTS_PUSH; /* Send any non-subscribe related PUSH messages to our PUSH handler * while allowing subscribe related PUSH messages to pass through. * This allows existing code to be backward compatible and work in * either RESP2 or RESP3 mode. */ if (redisIsSpontaneousPushReply(reply)) { __redisRunPushCallback(ac, reply); c->reader->fn->freeObject(reply); continue; } /* Even if the context is subscribed, pending regular * callbacks will get a reply before pub/sub messages arrive. */ redisCallback cb = {NULL, NULL, 0, 0, NULL}; if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { /* * A spontaneous reply in a not-subscribed context can be the error * reply that is sent when a new connection exceeds the maximum * number of allowed connections on the server side. * * This is seen as an error instead of a regular reply because the * server closes the connection after sending it. * * To prevent the error from being overwritten by an EOF error the * connection is closed here. See issue #43. * * Another possibility is that the server is loading its dataset. * In this case we also want to close the connection, and have the * user wait until the server is ready to take our request. */ if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { c->err = REDIS_ERR_OTHER; snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); c->reader->fn->freeObject(reply); __redisAsyncDisconnect(ac); return; } /* No more regular callbacks and no errors, the context *must* be subscribed. */ assert(c->flags & REDIS_SUBSCRIBED); if (c->flags & REDIS_SUBSCRIBED) __redisGetSubscribeCallback(ac,reply,&cb); } if (cb.fn != NULL) { __redisRunCallback(ac,&cb,reply); if (!(c->flags & REDIS_NO_AUTO_FREE_REPLIES)){ c->reader->fn->freeObject(reply); } /* Proceed with free'ing when redisAsyncFree() was called. */ if (c->flags & REDIS_FREEING) { __redisAsyncFree(ac); return; } } else { /* No callback for this reply. This can either be a NULL callback, * or there were no callbacks to begin with. Either way, don't * abort with an error, but simply ignore it because the client * doesn't know what the server will spit out over the wire. */ c->reader->fn->freeObject(reply); } /* If in monitor mode, repush the callback */ if (c->flags & REDIS_MONITORING) { __redisPushCallback(&ac->replies,&cb); } } /* Disconnect when there was an error reading the reply */ if (status != REDIS_OK) __redisAsyncDisconnect(ac); } static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) { __redisRunConnectCallback(ac, REDIS_ERR); __redisAsyncDisconnect(ac); } /* Internal helper function to detect socket status the first time a read or * write event fires. When connecting was not successful, the connect callback * is called with a REDIS_ERR status and the context is free'd. */ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { int completed = 0; redisContext *c = &(ac->c); if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { /* Error! */ if (redisCheckSocketError(c) == REDIS_ERR) __redisAsyncCopyError(ac); __redisAsyncHandleConnectFailure(ac); return REDIS_ERR; } else if (completed == 1) { /* connected! */ if (c->connection_type == REDIS_CONN_TCP && redisSetTcpNoDelay(c) == REDIS_ERR) { __redisAsyncHandleConnectFailure(ac); return REDIS_ERR; } /* flag us as fully connect, but allow the callback * to disconnect. For that reason, permit the function * to delete the context here after callback return. */ c->flags |= REDIS_CONNECTED; __redisRunConnectCallback(ac, REDIS_OK); if ((ac->c.flags & REDIS_DISCONNECTING)) { redisAsyncDisconnect(ac); return REDIS_ERR; } else if ((ac->c.flags & REDIS_FREEING)) { redisAsyncFree(ac); return REDIS_ERR; } return REDIS_OK; } else { return REDIS_OK; } } void redisAsyncRead(redisAsyncContext *ac) { redisContext *c = &(ac->c); if (redisBufferRead(c) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Always re-schedule reads */ _EL_ADD_READ(ac); redisProcessCallbacks(ac); } } /* This function should be called when the socket is readable. * It processes all replies that can be read and executes their callbacks. */ void redisAsyncHandleRead(redisAsyncContext *ac) { redisContext *c = &(ac->c); /* must not be called from a callback */ assert(!(c->flags & REDIS_IN_CALLBACK)); if (!(c->flags & REDIS_CONNECTED)) { /* Abort connect was not successful. */ if (__redisAsyncHandleConnect(ac) != REDIS_OK) return; /* Try again later when the context is still not connected. */ if (!(c->flags & REDIS_CONNECTED)) return; } c->funcs->async_read(ac); } void redisAsyncWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); int done = 0; if (redisBufferWrite(c,&done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { /* Continue writing when not done, stop writing otherwise */ if (!done) _EL_ADD_WRITE(ac); else _EL_DEL_WRITE(ac); /* Always schedule reads after writes */ _EL_ADD_READ(ac); } } void redisAsyncHandleWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); /* must not be called from a callback */ assert(!(c->flags & REDIS_IN_CALLBACK)); if (!(c->flags & REDIS_CONNECTED)) { /* Abort connect was not successful. */ if (__redisAsyncHandleConnect(ac) != REDIS_OK) return; /* Try again later when the context is still not connected. */ if (!(c->flags & REDIS_CONNECTED)) return; } c->funcs->async_write(ac); } void redisAsyncHandleTimeout(redisAsyncContext *ac) { redisContext *c = &(ac->c); redisCallback cb; /* must not be called from a callback */ assert(!(c->flags & REDIS_IN_CALLBACK)); if ((c->flags & REDIS_CONNECTED)) { if (ac->replies.head == NULL && ac->sub.replies.head == NULL) { /* Nothing to do - just an idle timeout */ return; } if (!ac->c.command_timeout || (!ac->c.command_timeout->tv_sec && !ac->c.command_timeout->tv_usec)) { /* A belated connect timeout arriving, ignore */ return; } } if (!c->err) { __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); __redisAsyncCopyError(ac); } if (!(c->flags & REDIS_CONNECTED)) { __redisRunConnectCallback(ac, REDIS_ERR); } while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { __redisRunCallback(ac, &cb, NULL); } /** * TODO: Don't automatically sever the connection, * rather, allow to ignore responses before the queue is clear */ __redisAsyncDisconnect(ac); } /* Sets a pointer to the first argument and its length starting at p. Returns * the number of bytes to skip to get to the following argument. */ static const char *nextArgument(const char *start, const char **str, size_t *len) { const char *p = start; if (p[0] != '$') { p = strchr(p,'$'); if (p == NULL) return NULL; } *len = (int)strtol(p+1,NULL,10); p = strchr(p,'\r'); assert(p); *str = p+2; return p+2+(*len)+2; } /* Helper function for the redisAsyncCommand* family of functions. Writes a * formatted command to the output buffer and registers the provided callback * function with the context. */ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { redisContext *c = &(ac->c); redisCallback cb; struct dict *cbdict; dictIterator it; dictEntry *de; redisCallback *existcb; int pvariant, hasnext; const char *cstr, *astr; size_t clen, alen; const char *p; hisds sname; int ret; /* Don't accept new commands when the connection is about to be closed. */ if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; /* Setup callback */ cb.fn = fn; cb.privdata = privdata; cb.pending_subs = 1; cb.unsubscribe_sent = 0; /* Find out which command will be appended. */ p = nextArgument(cmd,&cstr,&clen); assert(p != NULL); hasnext = (p[0] == '$'); pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; cstr += pvariant; clen -= pvariant; if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { c->flags |= REDIS_SUBSCRIBED; /* Add every channel/pattern to the list of subscription callbacks. */ while ((p = nextArgument(p,&astr,&alen)) != NULL) { sname = hi_sdsnewlen(astr,alen); if (sname == NULL) goto oom; if (pvariant) cbdict = ac->sub.patterns; else cbdict = ac->sub.channels; de = dictFind(cbdict,sname); if (de != NULL) { existcb = dictGetEntryVal(de); cb.pending_subs = existcb->pending_subs + 1; } ret = dictReplace(cbdict,sname,&cb); if (ret == 0) hi_sdsfree(sname); } } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { /* It is only useful to call (P)UNSUBSCRIBE when the context is * subscribed to one or more channels or patterns. */ if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; if (pvariant) cbdict = ac->sub.patterns; else cbdict = ac->sub.channels; if (hasnext) { /* Send an unsubscribe with specific channels/patterns. * Bookkeeping the number of expected replies */ while ((p = nextArgument(p,&astr,&alen)) != NULL) { sname = hi_sdsnewlen(astr,alen); if (sname == NULL) goto oom; de = dictFind(cbdict,sname); if (de != NULL) { existcb = dictGetEntryVal(de); if (existcb->unsubscribe_sent == 0) existcb->unsubscribe_sent = 1; else /* Already sent, reply to be ignored */ ac->sub.pending_unsubs += 1; } else { /* Not subscribed to, reply to be ignored */ ac->sub.pending_unsubs += 1; } hi_sdsfree(sname); } } else { /* Send an unsubscribe without specific channels/patterns. * Bookkeeping the number of expected replies */ int no_subs = 1; dictInitIterator(&it,cbdict); while ((de = dictNext(&it)) != NULL) { existcb = dictGetEntryVal(de); if (existcb->unsubscribe_sent == 0) { existcb->unsubscribe_sent = 1; no_subs = 0; } } /* Unsubscribing to all channels/patterns, where none is * subscribed to, results in a single reply to be ignored. */ if (no_subs == 1) ac->sub.pending_unsubs += 1; } /* (P)UNSUBSCRIBE does not have its own response: every channel or * pattern that is unsubscribed will receive a message. This means we * should not append a callback function for this command. */ } else if (strncasecmp(cstr,"monitor\r\n",9) == 0) { /* Set monitor flag and push callback */ c->flags |= REDIS_MONITORING; if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) goto oom; } else { if (c->flags & REDIS_SUBSCRIBED) { if (__redisPushCallback(&ac->sub.replies,&cb) != REDIS_OK) goto oom; } else { if (__redisPushCallback(&ac->replies,&cb) != REDIS_OK) goto oom; } } __redisAppendCommand(c,cmd,len); /* Always schedule a write when the write buffer is non-empty */ _EL_ADD_WRITE(ac); return REDIS_OK; oom: __redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory"); __redisAsyncCopyError(ac); return REDIS_ERR; } int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { char *cmd; int len; int status; len = redisvFormatCommand(&cmd,format,ap); /* We don't want to pass -1 or -2 to future functions as a length. */ if (len < 0) return REDIS_ERR; status = __redisAsyncCommand(ac,fn,privdata,cmd,len); hi_free(cmd); return status; } int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { va_list ap; int status; va_start(ap,format); status = redisvAsyncCommand(ac,fn,privdata,format,ap); va_end(ap); return status; } int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { hisds cmd; long long len; int status; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len < 0) return REDIS_ERR; status = __redisAsyncCommand(ac,fn,privdata,cmd,len); hi_sdsfree(cmd); return status; } int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); return status; } redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) { redisAsyncPushFn *old = ac->push_cb; ac->push_cb = fn; return old; } int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { if (!ac->c.command_timeout) { ac->c.command_timeout = hi_calloc(1, sizeof(tv)); if (ac->c.command_timeout == NULL) { __redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory"); __redisAsyncCopyError(ac); return REDIS_ERR; } } if (tv.tv_sec != ac->c.command_timeout->tv_sec || tv.tv_usec != ac->c.command_timeout->tv_usec) { *ac->c.command_timeout = tv; } return REDIS_OK; } redis-8.0.2/deps/hiredis/async.h000066400000000000000000000142201501533116600164770ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_ASYNC_H #define __HIREDIS_ASYNC_H #include "hiredis.h" #ifdef __cplusplus extern "C" { #endif struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ struct dict; /* dictionary header is included in async.c */ /* Reply callback prototype and container */ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); typedef struct redisCallback { struct redisCallback *next; /* simple singly linked list */ redisCallbackFn *fn; int pending_subs; int unsubscribe_sent; void *privdata; } redisCallback; /* List of callbacks for either regular replies or pub/sub */ typedef struct redisCallbackList { redisCallback *head, *tail; } redisCallbackList; /* Connection callback prototypes */ typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status); typedef void(redisTimerCallback)(void *timer, void *privdata); /* Context for an async connection to Redis */ typedef struct redisAsyncContext { /* Hold the regular context, so it can be realloc'ed. */ redisContext c; /* Setup error flags so they can be used directly. */ int err; char *errstr; /* Not used by hiredis */ void *data; void (*dataCleanup)(void *privdata); /* Event library data and hooks */ struct { void *data; /* Hooks that are called when the library expects to start * reading/writing. These functions should be idempotent. */ void (*addRead)(void *privdata); void (*delRead)(void *privdata); void (*addWrite)(void *privdata); void (*delWrite)(void *privdata); void (*cleanup)(void *privdata); void (*scheduleTimer)(void *privdata, struct timeval tv); } ev; /* Called when either the connection is terminated due to an error or per * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ redisDisconnectCallback *onDisconnect; /* Called when the first write event was received. */ redisConnectCallback *onConnect; redisConnectCallbackNC *onConnectNC; /* Regular command callbacks */ redisCallbackList replies; /* Address used for connect() */ struct sockaddr *saddr; size_t addrlen; /* Subscription callbacks */ struct { redisCallbackList replies; struct dict *channels; struct dict *patterns; int pending_unsubs; } sub; /* Any configured RESP3 PUSH handler */ redisAsyncPushFn *push_cb; } redisAsyncContext; /* Functions that proxy to hiredis */ redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn); int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac); /* Handle read/write events */ void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac); void redisAsyncHandleTimeout(redisAsyncContext *ac); void redisAsyncRead(redisAsyncContext *ac); void redisAsyncWrite(redisAsyncContext *ac); /* Command functions for an async context. Write the command to the * output buffer and register the provided callback. */ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); #ifdef __cplusplus } #endif #endif redis-8.0.2/deps/hiredis/async_private.h000066400000000000000000000064471501533116600202450ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_ASYNC_PRIVATE_H #define __HIREDIS_ASYNC_PRIVATE_H #define _EL_ADD_READ(ctx) \ do { \ refreshTimeout(ctx); \ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ } while (0) #define _EL_DEL_READ(ctx) do { \ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ } while(0) #define _EL_ADD_WRITE(ctx) \ do { \ refreshTimeout(ctx); \ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ } while (0) #define _EL_DEL_WRITE(ctx) do { \ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ } while(0) #define _EL_CLEANUP(ctx) do { \ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ ctx->ev.cleanup = NULL; \ } while(0) static inline void refreshTimeout(redisAsyncContext *ctx) { #define REDIS_TIMER_ISSET(tvp) \ (tvp && ((tvp)->tv_sec || (tvp)->tv_usec)) #define REDIS_EL_TIMER(ac, tvp) \ if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \ (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \ } if (ctx->c.flags & REDIS_CONNECTED) { REDIS_EL_TIMER(ctx, ctx->c.command_timeout); } else { REDIS_EL_TIMER(ctx, ctx->c.connect_timeout); } } void __redisAsyncDisconnect(redisAsyncContext *ac); void redisProcessCallbacks(redisAsyncContext *ac); #endif /* __HIREDIS_ASYNC_PRIVATE_H */ redis-8.0.2/deps/hiredis/dict.c000066400000000000000000000246321501533116600163100ustar00rootroot00000000000000/* Hash table implementation. * * This file implements in memory hash tables with insert/del/replace/find/ * get-random-element operations. Hash tables will auto resize if needed * tables of power of two in size are used, collisions are handled by * chaining. See the source code for more information... :) * * Copyright (c) 2006-2010, Salvatore Sanfilippo * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include "alloc.h" #include #include #include #include "dict.h" /* -------------------------- private prototypes ---------------------------- */ static int _dictExpandIfNeeded(dict *ht); static unsigned long _dictNextPower(unsigned long size); static int _dictKeyIndex(dict *ht, const void *key); static int _dictInit(dict *ht, dictType *type, void *privDataPtr); /* -------------------------- hash functions -------------------------------- */ /* Generic hash function (a popular one from Bernstein). * I tested a few and this was the best. */ static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { unsigned int hash = 5381; while (len--) hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ return hash; } /* ----------------------------- API implementation ------------------------- */ /* Reset an hashtable already initialized with ht_init(). * NOTE: This function should only called by ht_destroy(). */ static void _dictReset(dict *ht) { ht->table = NULL; ht->size = 0; ht->sizemask = 0; ht->used = 0; } /* Create a new hash table */ static dict *dictCreate(dictType *type, void *privDataPtr) { dict *ht = hi_malloc(sizeof(*ht)); if (ht == NULL) return NULL; _dictInit(ht,type,privDataPtr); return ht; } /* Initialize the hash table */ static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { _dictReset(ht); ht->type = type; ht->privdata = privDataPtr; return DICT_OK; } /* Expand or create the hashtable */ static int dictExpand(dict *ht, unsigned long size) { dict n; /* the new hashtable */ unsigned long realsize = _dictNextPower(size), i; /* the size is invalid if it is smaller than the number of * elements already inside the hashtable */ if (ht->used > size) return DICT_ERR; _dictInit(&n, ht->type, ht->privdata); n.size = realsize; n.sizemask = realsize-1; n.table = hi_calloc(realsize,sizeof(dictEntry*)); if (n.table == NULL) return DICT_ERR; /* Copy all the elements from the old to the new table: * note that if the old hash table is empty ht->size is zero, * so dictExpand just creates an hash table. */ n.used = ht->used; for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; if (ht->table[i] == NULL) continue; /* For each hash entry on this slot... */ he = ht->table[i]; while(he) { unsigned int h; nextHe = he->next; /* Get the new element index */ h = dictHashKey(ht, he->key) & n.sizemask; he->next = n.table[h]; n.table[h] = he; ht->used--; /* Pass to the next element */ he = nextHe; } } assert(ht->used == 0); hi_free(ht->table); /* Remap the new hashtable in the old */ *ht = n; return DICT_OK; } /* Add an element to the target hash table */ static int dictAdd(dict *ht, void *key, void *val) { int index; dictEntry *entry; /* Get the index of the new element, or -1 if * the element already exists. */ if ((index = _dictKeyIndex(ht, key)) == -1) return DICT_ERR; /* Allocates the memory and stores key */ entry = hi_malloc(sizeof(*entry)); if (entry == NULL) return DICT_ERR; entry->next = ht->table[index]; ht->table[index] = entry; /* Set the hash entry fields. */ dictSetHashKey(ht, entry, key); dictSetHashVal(ht, entry, val); ht->used++; return DICT_OK; } /* Add an element, discarding the old if the key already exists. * Return 1 if the key was added from scratch, 0 if there was already an * element with such key and dictReplace() just performed a value update * operation. */ static int dictReplace(dict *ht, void *key, void *val) { dictEntry *entry, auxentry; /* Try to add the element. If the key * does not exists dictAdd will succeed. */ if (dictAdd(ht, key, val) == DICT_OK) return 1; /* It already exists, get the entry */ entry = dictFind(ht, key); if (entry == NULL) return 0; /* Free the old value and set the new one */ /* Set the new value and free the old one. Note that it is important * to do that in this order, as the value may just be exactly the same * as the previous one. In this context, think to reference counting, * you want to increment (set), and then decrement (free), and not the * reverse. */ auxentry = *entry; dictSetHashVal(ht, entry, val); dictFreeEntryVal(ht, &auxentry); return 0; } /* Search and remove an element */ static int dictDelete(dict *ht, const void *key) { unsigned int h; dictEntry *de, *prevde; if (ht->size == 0) return DICT_ERR; h = dictHashKey(ht, key) & ht->sizemask; de = ht->table[h]; prevde = NULL; while(de) { if (dictCompareHashKeys(ht,key,de->key)) { /* Unlink the element from the list */ if (prevde) prevde->next = de->next; else ht->table[h] = de->next; dictFreeEntryKey(ht,de); dictFreeEntryVal(ht,de); hi_free(de); ht->used--; return DICT_OK; } prevde = de; de = de->next; } return DICT_ERR; /* not found */ } /* Destroy an entire hash table */ static int _dictClear(dict *ht) { unsigned long i; /* Free all the elements */ for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; if ((he = ht->table[i]) == NULL) continue; while(he) { nextHe = he->next; dictFreeEntryKey(ht, he); dictFreeEntryVal(ht, he); hi_free(he); ht->used--; he = nextHe; } } /* Free the table and the allocated cache structure */ hi_free(ht->table); /* Re-initialize the table */ _dictReset(ht); return DICT_OK; /* never fails */ } /* Clear & Release the hash table */ static void dictRelease(dict *ht) { _dictClear(ht); hi_free(ht); } static dictEntry *dictFind(dict *ht, const void *key) { dictEntry *he; unsigned int h; if (ht->size == 0) return NULL; h = dictHashKey(ht, key) & ht->sizemask; he = ht->table[h]; while(he) { if (dictCompareHashKeys(ht, key, he->key)) return he; he = he->next; } return NULL; } static void dictInitIterator(dictIterator *iter, dict *ht) { iter->ht = ht; iter->index = -1; iter->entry = NULL; iter->nextEntry = NULL; } static dictEntry *dictNext(dictIterator *iter) { while (1) { if (iter->entry == NULL) { iter->index++; if (iter->index >= (signed)iter->ht->size) break; iter->entry = iter->ht->table[iter->index]; } else { iter->entry = iter->nextEntry; } if (iter->entry) { /* We need to save the 'next' here, the iterator user * may delete the entry we are returning. */ iter->nextEntry = iter->entry->next; return iter->entry; } } return NULL; } /* ------------------------- private functions ------------------------------ */ /* Expand the hash table if needed */ static int _dictExpandIfNeeded(dict *ht) { /* If the hash table is empty expand it to the initial size, * if the table is "full" double its size. */ if (ht->size == 0) return dictExpand(ht, DICT_HT_INITIAL_SIZE); if (ht->used == ht->size) return dictExpand(ht, ht->size*2); return DICT_OK; } /* Our hash table capability is a power of two */ static unsigned long _dictNextPower(unsigned long size) { unsigned long i = DICT_HT_INITIAL_SIZE; if (size >= LONG_MAX) return LONG_MAX; while(1) { if (i >= size) return i; i *= 2; } } /* Returns the index of a free slot that can be populated with * an hash entry for the given 'key'. * If the key already exists, -1 is returned. */ static int _dictKeyIndex(dict *ht, const void *key) { unsigned int h; dictEntry *he; /* Expand the hashtable if needed */ if (_dictExpandIfNeeded(ht) == DICT_ERR) return -1; /* Compute the key hash value */ h = dictHashKey(ht, key) & ht->sizemask; /* Search if this slot does not already contain the given key */ he = ht->table[h]; while(he) { if (dictCompareHashKeys(ht, key, he->key)) return -1; he = he->next; } return h; } redis-8.0.2/deps/hiredis/dict.h000066400000000000000000000110521501533116600163050ustar00rootroot00000000000000/* Hash table implementation. * * This file implements in memory hash tables with insert/del/replace/find/ * get-random-element operations. Hash tables will auto resize if needed * tables of power of two in size are used, collisions are handled by * chaining. See the source code for more information... :) * * Copyright (c) 2006-2010, Salvatore Sanfilippo * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __DICT_H #define __DICT_H #define DICT_OK 0 #define DICT_ERR 1 /* Unused arguments generate annoying warnings... */ #define DICT_NOTUSED(V) ((void) V) typedef struct dictEntry { void *key; void *val; struct dictEntry *next; } dictEntry; typedef struct dictType { unsigned int (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType; typedef struct dict { dictEntry **table; dictType *type; unsigned long size; unsigned long sizemask; unsigned long used; void *privdata; } dict; typedef struct dictIterator { dict *ht; int index; dictEntry *entry, *nextEntry; } dictIterator; /* This is the initial size of every hash table */ #define DICT_HT_INITIAL_SIZE 4 /* ------------------------------- Macros ------------------------------------*/ #define dictFreeEntryVal(ht, entry) \ if ((ht)->type->valDestructor) \ (ht)->type->valDestructor((ht)->privdata, (entry)->val) #define dictSetHashVal(ht, entry, _val_) do { \ if ((ht)->type->valDup) \ entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ else \ entry->val = (_val_); \ } while(0) #define dictFreeEntryKey(ht, entry) \ if ((ht)->type->keyDestructor) \ (ht)->type->keyDestructor((ht)->privdata, (entry)->key) #define dictSetHashKey(ht, entry, _key_) do { \ if ((ht)->type->keyDup) \ entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ else \ entry->key = (_key_); \ } while(0) #define dictCompareHashKeys(ht, key1, key2) \ (((ht)->type->keyCompare) ? \ (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ (key1) == (key2)) #define dictHashKey(ht, key) (ht)->type->hashFunction(key) #define dictGetEntryKey(he) ((he)->key) #define dictGetEntryVal(he) ((he)->val) #define dictSlots(ht) ((ht)->size) #define dictSize(ht) ((ht)->used) /* API */ static unsigned int dictGenHashFunction(const unsigned char *buf, int len); static dict *dictCreate(dictType *type, void *privDataPtr); static int dictExpand(dict *ht, unsigned long size); static int dictAdd(dict *ht, void *key, void *val); static int dictReplace(dict *ht, void *key, void *val); static int dictDelete(dict *ht, const void *key); static void dictRelease(dict *ht); static dictEntry * dictFind(dict *ht, const void *key); static void dictInitIterator(dictIterator *iter, dict *ht); static dictEntry *dictNext(dictIterator *iter); #endif /* __DICT_H */ redis-8.0.2/deps/hiredis/examples/000077500000000000000000000000001501533116600170305ustar00rootroot00000000000000redis-8.0.2/deps/hiredis/examples/CMakeLists.txt000066400000000000000000000032001501533116600215630ustar00rootroot00000000000000INCLUDE(FindPkgConfig) # Check for GLib PKG_CHECK_MODULES(GLIB2 glib-2.0) if (GLIB2_FOUND) INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS}) ADD_EXECUTABLE(example-glib example-glib.c) TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES}) ENDIF(GLIB2_FOUND) FIND_PATH(LIBEV ev.h HINTS /usr/local /usr/opt/local ENV LIBEV_INCLUDE_DIR) if (LIBEV) # Just compile and link with libev ADD_EXECUTABLE(example-libev example-libev.c) TARGET_LINK_LIBRARIES(example-libev hiredis ev) ENDIF() FIND_PATH(LIBEVENT event.h) if (LIBEVENT) ADD_EXECUTABLE(example-libevent example-libevent.c) TARGET_LINK_LIBRARIES(example-libevent hiredis event) ENDIF() FIND_PATH(LIBHV hv/hv.h) IF (LIBHV) ADD_EXECUTABLE(example-libhv example-libhv.c) TARGET_LINK_LIBRARIES(example-libhv hiredis hv) ENDIF() FIND_PATH(LIBUV uv.h) IF (LIBUV) ADD_EXECUTABLE(example-libuv example-libuv.c) TARGET_LINK_LIBRARIES(example-libuv hiredis uv) ENDIF() FIND_PATH(LIBSDEVENT systemd/sd-event.h) IF (LIBSDEVENT) ADD_EXECUTABLE(example-libsdevent example-libsdevent.c) TARGET_LINK_LIBRARIES(example-libsdevent hiredis systemd) ENDIF() IF (APPLE) FIND_LIBRARY(CF CoreFoundation) ADD_EXECUTABLE(example-macosx example-macosx.c) TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF}) ENDIF() IF (ENABLE_SSL) ADD_EXECUTABLE(example-ssl example-ssl.c) TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl) ENDIF() ADD_EXECUTABLE(example example.c) TARGET_LINK_LIBRARIES(example hiredis) ADD_EXECUTABLE(example-push example-push.c) TARGET_LINK_LIBRARIES(example-push hiredis) redis-8.0.2/deps/hiredis/examples/example-ae.c000066400000000000000000000030571501533116600212170ustar00rootroot00000000000000#include #include #include #include #include #include #include /* Put event loop in the global scope, so it can be explicitly stopped */ static aeEventLoop *loop; void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); aeStop(loop); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); aeStop(loop); return; } printf("Disconnected...\n"); aeStop(loop); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } loop = aeCreateEventLoop(64); redisAeAttach(loop, c); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); aeMain(loop); return 0; } redis-8.0.2/deps/hiredis/examples/example-glib.c000066400000000000000000000031401501533116600215400ustar00rootroot00000000000000#include #include #include #include static GMainLoop *mainloop; static void connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, int status) { if (status != REDIS_OK) { g_printerr("Failed to connect: %s\n", ac->errstr); g_main_loop_quit(mainloop); } else { g_printerr("Connected...\n"); } } static void disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, int status) { if (status != REDIS_OK) { g_error("Failed to disconnect: %s", ac->errstr); } else { g_printerr("Disconnected...\n"); g_main_loop_quit(mainloop); } } static void command_cb(redisAsyncContext *ac, gpointer r, gpointer user_data G_GNUC_UNUSED) { redisReply *reply = r; if (reply) { g_print("REPLY: %s\n", reply->str); } redisAsyncDisconnect(ac); } gint main (gint argc G_GNUC_UNUSED, gchar *argv[] G_GNUC_UNUSED) { redisAsyncContext *ac; GMainContext *context = NULL; GSource *source; ac = redisAsyncConnect("127.0.0.1", 6379); if (ac->err) { g_printerr("%s\n", ac->errstr); exit(EXIT_FAILURE); } source = redis_source_new(ac); mainloop = g_main_loop_new(context, FALSE); g_source_attach(source, context); redisAsyncSetConnectCallback(ac, connect_cb); redisAsyncSetDisconnectCallback(ac, disconnect_cb); redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); redisAsyncCommand(ac, command_cb, NULL, "GET key"); g_main_loop_run(mainloop); return EXIT_SUCCESS; } redis-8.0.2/deps/hiredis/examples/example-ivykis.c000066400000000000000000000026401501533116600221450ustar00rootroot00000000000000#include #include #include #include #include #include #include void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif iv_init(); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redisIvykisAttach(c); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); iv_main(); iv_deinit(); return 0; } redis-8.0.2/deps/hiredis/examples/example-libev.c000066400000000000000000000026231501533116600217310ustar00rootroot00000000000000#include #include #include #include #include #include #include void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redisLibevAttach(EV_DEFAULT_ c); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); ev_loop(EV_DEFAULT_ 0); return 0; } redis-8.0.2/deps/hiredis/examples/example-libevent-ssl.c000066400000000000000000000044471501533116600232450ustar00rootroot00000000000000#include #include #include #include #include #include #include #include void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif struct event_base *base = event_base_new(); if (argc < 5) { fprintf(stderr, "Usage: %s [ca]\n", argv[0]); exit(1); } const char *value = argv[1]; size_t nvalue = strlen(value); const char *hostname = argv[2]; int port = atoi(argv[3]); const char *cert = argv[4]; const char *certKey = argv[5]; const char *caCert = argc > 5 ? argv[6] : NULL; redisSSLContext *ssl; redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE; redisInitOpenSSL(); ssl = redisCreateSSLContext(caCert, NULL, cert, certKey, NULL, &ssl_error); if (!ssl) { printf("Error: %s\n", redisSSLContextGetError(ssl_error)); return 1; } redisAsyncContext *c = redisAsyncConnect(hostname, port); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) { printf("SSL Error!\n"); exit(1); } redisLibeventAttach(c,base); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); event_base_dispatch(base); redisFreeSSLContext(ssl); return 0; } redis-8.0.2/deps/hiredis/examples/example-libevent.c000066400000000000000000000033261501533116600224410ustar00rootroot00000000000000#include #include #include #include #include #include #include void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) { if (c->errstr) { printf("errstr: %s\n", c->errstr); } return; } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif struct event_base *base = event_base_new(); redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); struct timeval tv = {0}; tv.tv_sec = 1; options.connect_timeout = &tv; redisAsyncContext *c = redisAsyncConnectWithOptions(&options); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redisLibeventAttach(c,base); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); event_base_dispatch(base); return 0; } redis-8.0.2/deps/hiredis/examples/example-libhv.c000066400000000000000000000036201501533116600217320ustar00rootroot00000000000000#include #include #include #include #include #include #include void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void debugCallback(redisAsyncContext *c, void *r, void *privdata) { (void)privdata; redisReply *reply = r; if (reply == NULL) { printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } hloop_t* loop = hloop_new(HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS); redisLibhvAttach(c, loop); redisAsyncSetTimeout(c, (struct timeval){.tv_sec = 0, .tv_usec = 500000}); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %d", 1); hloop_run(loop); hloop_free(&loop); return 0; } redis-8.0.2/deps/hiredis/examples/example-libsdevent.c000066400000000000000000000054221501533116600227670ustar00rootroot00000000000000#include #include #include #include #include #include #include void debugCallback(redisAsyncContext *c, void *r, void *privdata) { (void)privdata; redisReply *reply = r; if (reply == NULL) { /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ redisAsyncDisconnect(c); } void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) { printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str); /* start another request that demonstrate timeout */ redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("connect error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("disconnect because of error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); struct sd_event *event; sd_event_default(&event); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { printf("Error: %s\n", c->errstr); redisAsyncFree(c); return 1; } redisLibsdeventAttach(c,event); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); /* In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libsdevent adapter. Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. Because we have set a 1 second timeout to the connection, the command will always fail with a timeout error, which is shown in the `debugCallback`. */ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); /* sd-event does not quit when there are no handlers registered. Manually exit after 1.5 seconds */ sd_event_source *s; sd_event_add_time_relative(event, &s, CLOCK_MONOTONIC, 1500000, 1, NULL, NULL); sd_event_loop(event); sd_event_source_disable_unref(s); sd_event_unref(event); return 0; } redis-8.0.2/deps/hiredis/examples/example-libuv.c000066400000000000000000000050141501533116600217460ustar00rootroot00000000000000#include #include #include #include #include #include #include void debugCallback(redisAsyncContext *c, void *r, void *privdata) { (void)privdata; //unused redisReply *reply = r; if (reply == NULL) { /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ redisAsyncDisconnect(c); } void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) { printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str); /* start another request that demonstrate timeout */ redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("connect error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("disconnect because of error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif uv_loop_t* loop = uv_default_loop(); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redisLibuvAttach(c,loop); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); /* In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libuv adapter. Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. Because we have set a 1 second timeout to the connection, the command will always fail with a timeout error, which is shown in the `debugCallback`. */ redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); uv_run(loop, UV_RUN_DEFAULT); return 0; } redis-8.0.2/deps/hiredis/examples/example-macosx.c000066400000000000000000000031661501533116600221250ustar00rootroot00000000000000// // Created by Дмитрий Бахвалов on 13.07.15. // Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. // #include #include #include #include void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } CFRunLoopStop(CFRunLoopGetCurrent()); printf("Disconnected...\n"); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); CFRunLoopRef loop = CFRunLoopGetCurrent(); if( !loop ) { printf("Error: Cannot get current run loop\n"); return 1; } redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redisMacOSAttach(c, loop); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); CFRunLoopRun(); return 0; } redis-8.0.2/deps/hiredis/examples/example-poll.c000066400000000000000000000030411501533116600215710ustar00rootroot00000000000000#include #include #include #include #include #include #include /* Put in the global scope, so that loop can be explicitly stopped */ static int exit_loop = 0; void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) return; printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisAsyncDisconnect(c); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); exit_loop = 1; return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { exit_loop = 1; if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } redisPollAttach(c); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); while (!exit_loop) { redisPollTick(c, 0.1); } return 0; } redis-8.0.2/deps/hiredis/examples/example-push.c000066400000000000000000000135711501533116600216130ustar00rootroot00000000000000/* * Copyright (c) 2020, Michael Grunder * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include #include #include #include #define KEY_COUNT 5 #define panicAbort(fmt, ...) \ do { \ fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \ exit(-1); \ } while (0) static void assertReplyAndFree(redisContext *context, redisReply *reply, int type) { if (reply == NULL) panicAbort("NULL reply from server (error: %s)", context->errstr); if (reply->type != type) { if (reply->type == REDIS_REPLY_ERROR) fprintf(stderr, "Redis Error: %s\n", reply->str); panicAbort("Expected reply type %d but got type %d", type, reply->type); } freeReplyObject(reply); } /* Switch to the RESP3 protocol and enable client tracking */ static void enableClientTracking(redisContext *c) { redisReply *reply = redisCommand(c, "HELLO 3"); if (reply == NULL || c->err) { panicAbort("NULL reply or server error (error: %s)", c->errstr); } if (reply->type != REDIS_REPLY_MAP) { fprintf(stderr, "Error: Can't send HELLO 3 command. Are you sure you're "); fprintf(stderr, "connected to redis-server >= 6.0.0?\nRedis error: %s\n", reply->type == REDIS_REPLY_ERROR ? reply->str : "(unknown)"); exit(-1); } freeReplyObject(reply); /* Enable client tracking */ reply = redisCommand(c, "CLIENT TRACKING ON"); assertReplyAndFree(c, reply, REDIS_REPLY_STATUS); } void pushReplyHandler(void *privdata, void *r) { redisReply *reply = r; int *invalidations = privdata; /* Sanity check on the invalidation reply */ if (reply->type != REDIS_REPLY_PUSH || reply->elements != 2 || reply->element[1]->type != REDIS_REPLY_ARRAY || reply->element[1]->element[0]->type != REDIS_REPLY_STRING) { panicAbort("%s", "Can't parse PUSH message!"); } /* Increment our invalidation count */ *invalidations += 1; printf("pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\n", reply->element[1]->element[0]->str, *invalidations); freeReplyObject(reply); } /* We aren't actually freeing anything here, but it is included to show that we can * have hiredis call our data destructor when freeing the context */ void privdata_dtor(void *privdata) { unsigned int *icount = privdata; printf("privdata_dtor(): In context privdata dtor (invalidations: %u)\n", *icount); } int main(int argc, char **argv) { unsigned int j, invalidations = 0; redisContext *c; redisReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; int port = (argc > 2) ? atoi(argv[2]) : 6379; redisOptions o = {0}; REDIS_OPTIONS_SET_TCP(&o, hostname, port); /* Set our context privdata to the address of our invalidation counter. Each * time our PUSH handler is called, hiredis will pass the privdata for context. * * This could also be done after we create the context like so: * * c->privdata = &invalidations; * c->free_privdata = privdata_dtor; */ REDIS_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor); /* Set our custom PUSH message handler */ o.push_cb = pushReplyHandler; c = redisConnectWithOptions(&o); if (c == NULL || c->err) panicAbort("Connection error: %s", c ? c->errstr : "OOM"); /* Enable RESP3 and turn on client tracking */ enableClientTracking(c); /* Set some keys and then read them back. Once we do that, Redis will deliver * invalidation push messages whenever the key is modified */ for (j = 0; j < KEY_COUNT; j++) { reply = redisCommand(c, "SET key:%d initial:%d", j, j); assertReplyAndFree(c, reply, REDIS_REPLY_STATUS); reply = redisCommand(c, "GET key:%d", j); assertReplyAndFree(c, reply, REDIS_REPLY_STRING); } /* Trigger invalidation messages by updating keys we just read */ for (j = 0; j < KEY_COUNT; j++) { printf(" main(): SET key:%d update:%d\n", j, j); reply = redisCommand(c, "SET key:%d update:%d", j, j); assertReplyAndFree(c, reply, REDIS_REPLY_STATUS); printf(" main(): SET REPLY OK\n"); } printf("\nTotal detected invalidations: %d, expected: %d\n", invalidations, KEY_COUNT); /* PING server */ redisFree(c); } redis-8.0.2/deps/hiredis/examples/example-qt.cpp000066400000000000000000000020271501533116600216120ustar00rootroot00000000000000#include using namespace std; #include #include #include "example-qt.h" void getCallback(redisAsyncContext *, void * r, void * privdata) { redisReply * reply = static_cast(r); ExampleQt * ex = static_cast(privdata); if (reply == nullptr || ex == nullptr) return; cout << "key: " << reply->str << endl; ex->finish(); } void ExampleQt::run() { m_ctx = redisAsyncConnect("localhost", 6379); if (m_ctx->err) { cerr << "Error: " << m_ctx->errstr << endl; redisAsyncFree(m_ctx); emit finished(); } m_adapter.setContext(m_ctx); redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); redisAsyncCommand(m_ctx, getCallback, this, "GET key"); } int main (int argc, char **argv) { QCoreApplication app(argc, argv); ExampleQt example(argv[argc-1]); QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); QTimer::singleShot(0, &example, SLOT(run())); return app.exec(); } redis-8.0.2/deps/hiredis/examples/example-qt.h000066400000000000000000000011541501533116600212570ustar00rootroot00000000000000#ifndef __HIREDIS_EXAMPLE_QT_H #define __HIREDIS_EXAMPLE_QT_H #include class ExampleQt : public QObject { Q_OBJECT public: ExampleQt(const char * value, QObject * parent = 0) : QObject(parent), m_value(value) {} signals: void finished(); public slots: void run(); private: void finish() { emit finished(); } private: const char * m_value; redisAsyncContext * m_ctx; RedisQtAdapter m_adapter; friend void getCallback(redisAsyncContext *, void *, void *); }; #endif /* !__HIREDIS_EXAMPLE_QT_H */ redis-8.0.2/deps/hiredis/examples/example-redismoduleapi.c000066400000000000000000000063501501533116600236370ustar00rootroot00000000000000#include #include #include #include #include #include #include void debugCallback(redisAsyncContext *c, void *r, void *privdata) { (void)privdata; //unused redisReply *reply = r; if (reply == NULL) { /* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */ printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error"); return; } /* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/ redisAsyncDisconnect(c); } void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; if (reply == NULL) { if (c->errstr) { printf("errstr: %s\n", c->errstr); } return; } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* start another request that demonstrate timeout */ redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5); } void connectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Connected...\n"); } void disconnectCallback(const redisAsyncContext *c, int status) { if (status != REDIS_OK) { printf("Error: %s\n", c->errstr); return; } printf("Disconnected...\n"); } /* * This example requires Redis 7.0 or above. * * 1- Compile this file as a shared library. Directory of "redismodule.h" must * be in the include path. * gcc -fPIC -shared -I../../redis/src/ -I.. example-redismoduleapi.c -o example-redismoduleapi.so * * 2- Load module: * redis-server --loadmodule ./example-redismoduleapi.so value */ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int ret = RedisModule_Init(ctx, "example-redismoduleapi", 1, REDISMODULE_APIVER_1); if (ret != REDISMODULE_OK) { printf("error module init \n"); return REDISMODULE_ERR; } if (redisModuleCompatibilityCheck() != REDIS_OK) { printf("Redis 7.0 or above is required! \n"); return REDISMODULE_ERR; } redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); return 1; } size_t len; const char *val = RedisModule_StringPtrLen(argv[argc-1], &len); RedisModuleCtx *module_ctx = RedisModule_GetDetachedThreadSafeContext(ctx); redisModuleAttach(c, module_ctx); redisAsyncSetConnectCallback(c,connectCallback); redisAsyncSetDisconnectCallback(c,disconnectCallback); redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0}); /* In this demo, we first `set key`, then `get key` to demonstrate the basic usage of the adapter. Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request. Because we have set a 1 second timeout to the connection, the command will always fail with a timeout error, which is shown in the `debugCallback`. */ redisAsyncCommand(c, NULL, NULL, "SET key %b", val, len); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); return 0; } redis-8.0.2/deps/hiredis/examples/example-ssl.c000066400000000000000000000061741501533116600214360ustar00rootroot00000000000000#include #include #include #include #include #ifdef _MSC_VER #include /* For struct timeval */ #endif int main(int argc, char **argv) { unsigned int j; redisSSLContext *ssl; redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE; redisContext *c; redisReply *reply; if (argc < 4) { printf("Usage: %s [ca]\n", argv[0]); exit(1); } const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; int port = atoi(argv[2]); const char *cert = argv[3]; const char *key = argv[4]; const char *ca = argc > 4 ? argv[5] : NULL; redisInitOpenSSL(); ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error); if (!ssl || ssl_error != REDIS_SSL_CTX_NONE) { printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error)); exit(1); } struct timeval tv = { 1, 500000 }; // 1.5 seconds redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, hostname, port); options.connect_timeout = &tv; c = redisConnectWithOptions(&options); if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); redisFree(c); } else { printf("Connection error: can't allocate redis context\n"); } exit(1); } if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) { printf("Couldn't initialize SSL!\n"); printf("Error: %s\n", c->errstr); redisFree(c); exit(1); } /* PING server */ reply = redisCommand(c,"PING"); printf("PING: %s\n", reply->str); freeReplyObject(reply); /* Set a key */ reply = redisCommand(c,"SET %s %s", "foo", "hello world"); printf("SET: %s\n", reply->str); freeReplyObject(reply); /* Set a key using binary safe API */ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); /* Try a GET and two INCR */ reply = redisCommand(c,"GET foo"); printf("GET foo: %s\n", reply->str); freeReplyObject(reply); reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* again ... */ reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* Create a list of numbers, from 0 to 9 */ reply = redisCommand(c,"DEL mylist"); freeReplyObject(reply); for (j = 0; j < 10; j++) { char buf[64]; snprintf(buf,64,"%u",j); reply = redisCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } /* Let's check what we have inside the list */ reply = redisCommand(c,"LRANGE mylist 0 -1"); if (reply->type == REDIS_REPLY_ARRAY) { for (j = 0; j < reply->elements; j++) { printf("%u) %s\n", j, reply->element[j]->str); } } freeReplyObject(reply); /* Disconnects and frees the context */ redisFree(c); redisFreeSSLContext(ssl); return 0; } redis-8.0.2/deps/hiredis/examples/example.c000066400000000000000000000100551501533116600206300ustar00rootroot00000000000000#include #include #include #include #ifdef _MSC_VER #include /* For struct timeval */ #endif static void example_argv_command(redisContext *c, size_t n) { char **argv, tmp[42]; size_t *argvlen; redisReply *reply; /* We're allocating two additional elements for command and key */ argv = malloc(sizeof(*argv) * (2 + n)); argvlen = malloc(sizeof(*argvlen) * (2 + n)); /* First the command */ argv[0] = (char*)"RPUSH"; argvlen[0] = sizeof("RPUSH") - 1; /* Now our key */ argv[1] = (char*)"argvlist"; argvlen[1] = sizeof("argvlist") - 1; /* Now add the entries we wish to add to the list */ for (size_t i = 2; i < (n + 2); i++) { argvlen[i] = snprintf(tmp, sizeof(tmp), "argv-element-%zu", i - 2); argv[i] = strdup(tmp); } /* Execute the command using redisCommandArgv. We're sending the arguments with * two explicit arrays. One for each argument's string, and the other for its * length. */ reply = redisCommandArgv(c, n + 2, (const char **)argv, (const size_t*)argvlen); if (reply == NULL || c->err) { fprintf(stderr, "Error: Couldn't execute redisCommandArgv\n"); exit(1); } if (reply->type == REDIS_REPLY_INTEGER) { printf("%s reply: %lld\n", argv[0], reply->integer); } freeReplyObject(reply); /* Clean up */ for (size_t i = 2; i < (n + 2); i++) { free(argv[i]); } free(argv); free(argvlen); } int main(int argc, char **argv) { unsigned int j, isunix = 0; redisContext *c; redisReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; if (argc > 2) { if (*argv[2] == 'u' || *argv[2] == 'U') { isunix = 1; /* in this case, host is the path to the unix socket */ printf("Will connect to unix socket @%s\n", hostname); } } int port = (argc > 2) ? atoi(argv[2]) : 6379; struct timeval timeout = { 1, 500000 }; // 1.5 seconds if (isunix) { c = redisConnectUnixWithTimeout(hostname, timeout); } else { c = redisConnectWithTimeout(hostname, port, timeout); } if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); redisFree(c); } else { printf("Connection error: can't allocate redis context\n"); } exit(1); } /* PING server */ reply = redisCommand(c,"PING"); printf("PING: %s\n", reply->str); freeReplyObject(reply); /* Set a key */ reply = redisCommand(c,"SET %s %s", "foo", "hello world"); printf("SET: %s\n", reply->str); freeReplyObject(reply); /* Set a key using binary safe API */ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); /* Try a GET and two INCR */ reply = redisCommand(c,"GET foo"); printf("GET foo: %s\n", reply->str); freeReplyObject(reply); reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* again ... */ reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* Create a list of numbers, from 0 to 9 */ reply = redisCommand(c,"DEL mylist"); freeReplyObject(reply); for (j = 0; j < 10; j++) { char buf[64]; snprintf(buf,64,"%u",j); reply = redisCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } /* Let's check what we have inside the list */ reply = redisCommand(c,"LRANGE mylist 0 -1"); if (reply->type == REDIS_REPLY_ARRAY) { for (j = 0; j < reply->elements; j++) { printf("%u) %s\n", j, reply->element[j]->str); } } freeReplyObject(reply); /* See function for an example of redisCommandArgv */ example_argv_command(c, 10); /* Disconnects and frees the context */ redisFree(c); return 0; } redis-8.0.2/deps/hiredis/fmacros.h000066400000000000000000000003651501533116600170210ustar00rootroot00000000000000#ifndef __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H #ifndef _AIX #define _XOPEN_SOURCE 600 #define _POSIX_C_SOURCE 200112L #endif #if defined(__APPLE__) && defined(__MACH__) /* Enable TCP_KEEPALIVE */ #define _DARWIN_C_SOURCE #endif #endif redis-8.0.2/deps/hiredis/fuzzing/000077500000000000000000000000001501533116600167065ustar00rootroot00000000000000redis-8.0.2/deps/hiredis/fuzzing/format_command_fuzzer.c000066400000000000000000000043021501533116600234440ustar00rootroot00000000000000/* * Copyright (c) 2020, Salvatore Sanfilippo * Copyright (c) 2020, Pieter Noordhuis * Copyright (c) 2020, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include #include #include "hiredis.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { char *new_str, *cmd; if (size < 3) return 0; new_str = malloc(size+1); if (new_str == NULL) return 0; memcpy(new_str, data, size); new_str[size] = '\0'; if (redisFormatCommand(&cmd, new_str) != -1) hi_free(cmd); free(new_str); return 0; } redis-8.0.2/deps/hiredis/hiredis-config.cmake.in000066400000000000000000000005201501533116600215100ustar00rootroot00000000000000@PACKAGE_INIT@ set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") IF (NOT TARGET hiredis::@hiredis_export_name@) INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake) ENDIF() SET(hiredis_LIBRARIES hiredis::@hiredis_export_name@) SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR}) check_required_components(hiredis) redis-8.0.2/deps/hiredis/hiredis.c000066400000000000000000001104461501533116600170130ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include #include #include #include #include #include "hiredis.h" #include "net.h" #include "sds.h" #include "async.h" #include "win32.h" extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout); extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); static redisContextFuncs redisContextDefaultFuncs = { .close = redisNetClose, .free_privctx = NULL, .async_read = redisAsyncRead, .async_write = redisAsyncWrite, .read = redisNetRead, .write = redisNetWrite }; static redisReply *createReplyObject(int type); static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createArrayObject(const redisReadTask *task, size_t elements); static void *createIntegerObject(const redisReadTask *task, long long value); static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); static void *createNilObject(const redisReadTask *task); static void *createBoolObject(const redisReadTask *task, int bval); /* Default set of functions to build the reply. Keep in mind that such a * function returning NULL is interpreted as OOM. */ static redisReplyObjectFunctions defaultFunctions = { createStringObject, createArrayObject, createIntegerObject, createDoubleObject, createNilObject, createBoolObject, freeReplyObject }; /* Create a reply object */ static redisReply *createReplyObject(int type) { redisReply *r = hi_calloc(1,sizeof(*r)); if (r == NULL) return NULL; r->type = type; return r; } /* Free a reply object */ void freeReplyObject(void *reply) { redisReply *r = reply; size_t j; if (r == NULL) return; switch(r->type) { case REDIS_REPLY_INTEGER: case REDIS_REPLY_NIL: case REDIS_REPLY_BOOL: break; /* Nothing to free */ case REDIS_REPLY_ARRAY: case REDIS_REPLY_MAP: case REDIS_REPLY_SET: case REDIS_REPLY_PUSH: if (r->element != NULL) { for (j = 0; j < r->elements; j++) freeReplyObject(r->element[j]); hi_free(r->element); } break; case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: case REDIS_REPLY_DOUBLE: case REDIS_REPLY_VERB: case REDIS_REPLY_BIGNUM: hi_free(r->str); break; } hi_free(r); } static void *createStringObject(const redisReadTask *task, char *str, size_t len) { redisReply *r, *parent; char *buf; r = createReplyObject(task->type); if (r == NULL) return NULL; assert(task->type == REDIS_REPLY_ERROR || task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STRING || task->type == REDIS_REPLY_VERB || task->type == REDIS_REPLY_BIGNUM); /* Copy string value */ if (task->type == REDIS_REPLY_VERB) { buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */ if (buf == NULL) goto oom; memcpy(r->vtype,str,3); r->vtype[3] = '\0'; memcpy(buf,str+4,len-4); buf[len-4] = '\0'; r->len = len - 4; } else { buf = hi_malloc(len+1); if (buf == NULL) goto oom; memcpy(buf,str,len); buf[len] = '\0'; r->len = len; } r->str = buf; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; oom: freeReplyObject(r); return NULL; } static void *createArrayObject(const redisReadTask *task, size_t elements) { redisReply *r, *parent; r = createReplyObject(task->type); if (r == NULL) return NULL; if (elements > 0) { r->element = hi_calloc(elements,sizeof(redisReply*)); if (r->element == NULL) { freeReplyObject(r); return NULL; } } r->elements = elements; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createIntegerObject(const redisReadTask *task, long long value) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_INTEGER); if (r == NULL) return NULL; r->integer = value; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { redisReply *r, *parent; if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX return NULL; r = createReplyObject(REDIS_REPLY_DOUBLE); if (r == NULL) return NULL; r->dval = value; r->str = hi_malloc(len+1); if (r->str == NULL) { freeReplyObject(r); return NULL; } /* The double reply also has the original protocol string representing a * double as a null terminated string. This way the caller does not need * to format back for string conversion, especially since Redis does efforts * to make the string more human readable avoiding the calssical double * decimal string conversion artifacts. */ memcpy(r->str, str, len); r->str[len] = '\0'; r->len = len; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createNilObject(const redisReadTask *task) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_NIL); if (r == NULL) return NULL; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } static void *createBoolObject(const redisReadTask *task, int bval) { redisReply *r, *parent; r = createReplyObject(REDIS_REPLY_BOOL); if (r == NULL) return NULL; r->integer = bval != 0; if (task->parent) { parent = task->parent->obj; assert(parent->type == REDIS_REPLY_ARRAY || parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_SET || parent->type == REDIS_REPLY_PUSH); parent->element[task->idx] = r; } return r; } /* Return the number of digits of 'v' when converted to string in radix 10. * Implementation borrowed from link in redis/src/util.c:string2ll(). */ static uint32_t countDigits(uint64_t v) { uint32_t result = 1; for (;;) { if (v < 10) return result; if (v < 100) return result + 1; if (v < 1000) return result + 2; if (v < 10000) return result + 3; v /= 10000U; result += 4; } } /* Helper that calculates the bulk length given a certain string length. */ static size_t bulklen(size_t len) { return 1+countDigits(len)+2+len+2; } int redisvFormatCommand(char **target, const char *format, va_list ap) { const char *c = format; char *cmd = NULL; /* final command */ int pos; /* position in final command */ hisds curarg, newarg; /* current argument */ int touched = 0; /* was the current argument touched? */ char **curargv = NULL, **newargv = NULL; int argc = 0; int totlen = 0; int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ int j; /* Abort if there is not target to set */ if (target == NULL) return -1; /* Build the command string accordingly to protocol */ curarg = hi_sdsempty(); if (curarg == NULL) return -1; while(*c != '\0') { if (*c != '%' || c[1] == '\0') { if (*c == ' ') { if (touched) { newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; totlen += bulklen(hi_sdslen(curarg)); /* curarg is put in argv so it can be overwritten. */ curarg = hi_sdsempty(); if (curarg == NULL) goto memory_err; touched = 0; } } else { newarg = hi_sdscatlen(curarg,c,1); if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; } } else { char *arg; size_t size; /* Set newarg so it can be checked even if it is not touched. */ newarg = curarg; switch(c[1]) { case 's': arg = va_arg(ap,char*); size = strlen(arg); if (size > 0) newarg = hi_sdscatlen(curarg,arg,size); break; case 'b': arg = va_arg(ap,char*); size = va_arg(ap,size_t); if (size > 0) newarg = hi_sdscatlen(curarg,arg,size); break; case '%': newarg = hi_sdscat(curarg,"%"); break; default: /* Try to detect printf format */ { static const char intfmts[] = "diouxX"; static const char flags[] = "#0-+ "; char _format[16]; const char *_p = c+1; size_t _l = 0; va_list _cpy; /* Flags */ while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; /* Field width */ while (*_p != '\0' && isdigit((int) *_p)) _p++; /* Precision */ if (*_p == '.') { _p++; while (*_p != '\0' && isdigit((int) *_p)) _p++; } /* Copy va_list before consuming with va_arg */ va_copy(_cpy,ap); /* Make sure we have more characters otherwise strchr() accepts * '\0' as an integer specifier. This is checked after above * va_copy() to avoid UB in fmt_invalid's call to va_end(). */ if (*_p == '\0') goto fmt_invalid; /* Integer conversion (without modifiers) */ if (strchr(intfmts,*_p) != NULL) { va_arg(ap,int); goto fmt_valid; } /* Double conversion (without modifiers) */ if (strchr("eEfFgGaA",*_p) != NULL) { va_arg(ap,double); goto fmt_valid; } /* Size: char */ if (_p[0] == 'h' && _p[1] == 'h') { _p += 2; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,int); /* char gets promoted to int */ goto fmt_valid; } goto fmt_invalid; } /* Size: short */ if (_p[0] == 'h') { _p += 1; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,int); /* short gets promoted to int */ goto fmt_valid; } goto fmt_invalid; } /* Size: long long */ if (_p[0] == 'l' && _p[1] == 'l') { _p += 2; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,long long); goto fmt_valid; } goto fmt_invalid; } /* Size: long */ if (_p[0] == 'l') { _p += 1; if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { va_arg(ap,long); goto fmt_valid; } goto fmt_invalid; } fmt_invalid: va_end(_cpy); goto format_err; fmt_valid: _l = (_p+1)-c; if (_l < sizeof(_format)-2) { memcpy(_format,c,_l); _format[_l] = '\0'; newarg = hi_sdscatvprintf(curarg,_format,_cpy); /* Update current position (note: outer blocks * increment c twice so compensate here) */ c = _p-1; } va_end(_cpy); break; } } if (newarg == NULL) goto memory_err; curarg = newarg; touched = 1; c++; if (*c == '\0') break; } c++; } /* Add the last argument if needed */ if (touched) { newargv = hi_realloc(curargv,sizeof(char*)*(argc+1)); if (newargv == NULL) goto memory_err; curargv = newargv; curargv[argc++] = curarg; totlen += bulklen(hi_sdslen(curarg)); } else { hi_sdsfree(curarg); } /* Clear curarg because it was put in curargv or was free'd. */ curarg = NULL; /* Add bytes needed to hold multi bulk count */ totlen += 1+countDigits(argc)+2; /* Build the command at protocol level */ cmd = hi_malloc(totlen+1); if (cmd == NULL) goto memory_err; pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j])); memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j])); pos += hi_sdslen(curargv[j]); hi_sdsfree(curargv[j]); cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); cmd[pos] = '\0'; hi_free(curargv); *target = cmd; return totlen; format_err: error_type = -2; goto cleanup; memory_err: error_type = -1; goto cleanup; cleanup: if (curargv) { while(argc--) hi_sdsfree(curargv[argc]); hi_free(curargv); } hi_sdsfree(curarg); hi_free(cmd); return error_type; } /* Format a command according to the Redis protocol. This function * takes a format similar to printf: * * %s represents a C null terminated string you want to interpolate * %b represents a binary safe string * * When using %b you need to provide both the pointer to the string * and the length in bytes as a size_t. Examples: * * len = redisFormatCommand(target, "GET %s", mykey); * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); */ int redisFormatCommand(char **target, const char *format, ...) { va_list ap; int len; va_start(ap,format); len = redisvFormatCommand(target,format,ap); va_end(ap); /* The API says "-1" means bad result, but we now also return "-2" in some * cases. Force the return value to always be -1. */ if (len < 0) len = -1; return len; } /* Format a command according to the Redis protocol using an hisds string and * hi_sdscatfmt for the processing of arguments. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ long long redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv, const size_t *argvlen) { hisds cmd, aux; unsigned long long totlen, len; int j; /* Abort on a NULL target */ if (target == NULL) return -1; /* Calculate our total size */ totlen = 1+countDigits(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); totlen += bulklen(len); } /* Use an SDS string for command construction */ cmd = hi_sdsempty(); if (cmd == NULL) return -1; /* We already know how much storage we need */ aux = hi_sdsMakeRoomFor(cmd, totlen); if (aux == NULL) { hi_sdsfree(cmd); return -1; } cmd = aux; /* Construct command */ cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc); for (j=0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); cmd = hi_sdscatfmt(cmd, "$%U\r\n", len); cmd = hi_sdscatlen(cmd, argv[j], len); cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); } assert(hi_sdslen(cmd)==totlen); *target = cmd; return totlen; } void redisFreeSdsCommand(hisds cmd) { hi_sdsfree(cmd); } /* Format a command according to the Redis protocol. This function takes the * number of arguments, an array with arguments and an array with their * lengths. If the latter is set to NULL, strlen will be used to compute the * argument lengths. */ long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { char *cmd = NULL; /* final command */ size_t pos; /* position in final command */ size_t len, totlen; int j; /* Abort on a NULL target */ if (target == NULL) return -1; /* Calculate number of bytes needed for the command */ totlen = 1+countDigits(argc)+2; for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); totlen += bulklen(len); } /* Build the command at protocol level */ cmd = hi_malloc(totlen+1); if (cmd == NULL) return -1; pos = sprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { len = argvlen ? argvlen[j] : strlen(argv[j]); pos += sprintf(cmd+pos,"$%zu\r\n",len); memcpy(cmd+pos,argv[j],len); pos += len; cmd[pos++] = '\r'; cmd[pos++] = '\n'; } assert(pos == totlen); cmd[pos] = '\0'; *target = cmd; return totlen; } void redisFreeCommand(char *cmd) { hi_free(cmd); } void __redisSetError(redisContext *c, int type, const char *str) { size_t len; c->err = type; if (str != NULL) { len = strlen(str); len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); memcpy(c->errstr,str,len); c->errstr[len] = '\0'; } else { /* Only REDIS_ERR_IO may lack a description! */ assert(type == REDIS_ERR_IO); strerror_r(errno, c->errstr, sizeof(c->errstr)); } } redisReader *redisReaderCreate(void) { return redisReaderCreateWithFunctions(&defaultFunctions); } static void redisPushAutoFree(void *privdata, void *reply) { (void)privdata; freeReplyObject(reply); } static redisContext *redisContextInit(void) { redisContext *c; c = hi_calloc(1, sizeof(*c)); if (c == NULL) return NULL; c->funcs = &redisContextDefaultFuncs; c->obuf = hi_sdsempty(); c->reader = redisReaderCreate(); c->fd = REDIS_INVALID_FD; if (c->obuf == NULL || c->reader == NULL) { redisFree(c); return NULL; } return c; } void redisFree(redisContext *c) { if (c == NULL) return; if (c->funcs && c->funcs->close) { c->funcs->close(c); } hi_sdsfree(c->obuf); redisReaderFree(c->reader); hi_free(c->tcp.host); hi_free(c->tcp.source_addr); hi_free(c->unix_sock.path); hi_free(c->connect_timeout); hi_free(c->command_timeout); hi_free(c->saddr); if (c->privdata && c->free_privdata) c->free_privdata(c->privdata); if (c->funcs && c->funcs->free_privctx) c->funcs->free_privctx(c->privctx); memset(c, 0xff, sizeof(*c)); hi_free(c); } redisFD redisFreeKeepFd(redisContext *c) { redisFD fd = c->fd; c->fd = REDIS_INVALID_FD; redisFree(c); return fd; } int redisReconnect(redisContext *c) { c->err = 0; memset(c->errstr, '\0', strlen(c->errstr)); if (c->privctx && c->funcs->free_privctx) { c->funcs->free_privctx(c->privctx); c->privctx = NULL; } if (c->funcs && c->funcs->close) { c->funcs->close(c); } hi_sdsfree(c->obuf); redisReaderFree(c->reader); c->obuf = hi_sdsempty(); c->reader = redisReaderCreate(); if (c->obuf == NULL || c->reader == NULL) { __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } int ret = REDIS_ERR; if (c->connection_type == REDIS_CONN_TCP) { ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, c->connect_timeout, c->tcp.source_addr); } else if (c->connection_type == REDIS_CONN_UNIX) { ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout); } else { /* Something bad happened here and shouldn't have. There isn't enough information in the context to reconnect. */ __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); ret = REDIS_ERR; } if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { redisContextSetTimeout(c, *c->command_timeout); } return ret; } redisContext *redisConnectWithOptions(const redisOptions *options) { redisContext *c = redisContextInit(); if (c == NULL) { return NULL; } if (!(options->options & REDIS_OPT_NONBLOCK)) { c->flags |= REDIS_BLOCK; } if (options->options & REDIS_OPT_REUSEADDR) { c->flags |= REDIS_REUSEADDR; } if (options->options & REDIS_OPT_NOAUTOFREE) { c->flags |= REDIS_NO_AUTO_FREE; } if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) { c->flags |= REDIS_NO_AUTO_FREE_REPLIES; } if (options->options & REDIS_OPT_PREFER_IPV4) { c->flags |= REDIS_PREFER_IPV4; } if (options->options & REDIS_OPT_PREFER_IPV6) { c->flags |= REDIS_PREFER_IPV6; } /* Set any user supplied RESP3 PUSH handler or use freeReplyObject * as a default unless specifically flagged that we don't want one. */ if (options->push_cb != NULL) redisSetPushCallback(c, options->push_cb); else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE)) redisSetPushCallback(c, redisPushAutoFree); c->privdata = options->privdata; c->free_privdata = options->free_privdata; if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK || redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) { __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return c; } if (options->type == REDIS_CONN_TCP) { redisContextConnectBindTcp(c, options->endpoint.tcp.ip, options->endpoint.tcp.port, options->connect_timeout, options->endpoint.tcp.source_addr); } else if (options->type == REDIS_CONN_UNIX) { redisContextConnectUnix(c, options->endpoint.unix_socket, options->connect_timeout); } else if (options->type == REDIS_CONN_USERFD) { c->fd = options->endpoint.fd; c->flags |= REDIS_CONNECTED; } else { redisFree(c); return NULL; } if (c->err == 0 && c->fd != REDIS_INVALID_FD && options->command_timeout != NULL && (c->flags & REDIS_BLOCK)) { redisContextSetTimeout(c, *options->command_timeout); } return c; } /* Connect to a Redis instance. On error the field error in the returned * context will be set to the return value of the error function. * When no set of reply functions is given, the default set will be used. */ redisContext *redisConnect(const char *ip, int port) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); return redisConnectWithOptions(&options); } redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.connect_timeout = &tv; return redisConnectWithOptions(&options); } redisContext *redisConnectNonBlock(const char *ip, int port) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.options |= REDIS_OPT_NONBLOCK; return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; options.options |= REDIS_OPT_NONBLOCK; return redisConnectWithOptions(&options); } redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, ip, port); options.endpoint.tcp.source_addr = source_addr; options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; return redisConnectWithOptions(&options); } redisContext *redisConnectUnix(const char *path) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); return redisConnectWithOptions(&options); } redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); options.connect_timeout = &tv; return redisConnectWithOptions(&options); } redisContext *redisConnectUnixNonBlock(const char *path) { redisOptions options = {0}; REDIS_OPTIONS_SET_UNIX(&options, path); options.options |= REDIS_OPT_NONBLOCK; return redisConnectWithOptions(&options); } redisContext *redisConnectFd(redisFD fd) { redisOptions options = {0}; options.type = REDIS_CONN_USERFD; options.endpoint.fd = fd; return redisConnectWithOptions(&options); } /* Set read/write timeout on a blocking socket. */ int redisSetTimeout(redisContext *c, const struct timeval tv) { if (c->flags & REDIS_BLOCK) return redisContextSetTimeout(c,tv); return REDIS_ERR; } int redisEnableKeepAliveWithInterval(redisContext *c, int interval) { return redisKeepAlive(c, interval); } /* Enable connection KeepAlive. */ int redisEnableKeepAlive(redisContext *c) { return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL); } /* Set the socket option TCP_USER_TIMEOUT. */ int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) { return redisContextSetTcpUserTimeout(c, timeout); } /* Set a user provided RESP3 PUSH handler and return any old one set. */ redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) { redisPushFn *old = c->push_cb; c->push_cb = fn; return old; } /* Use this function to handle a read event on the descriptor. It will try * and read some bytes from the socket and feed them to the reply parser. * * After this function is called, you may use redisGetReplyFromReader to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[1024*16]; int nread; /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; nread = c->funcs->read(c, buf, sizeof(buf)); if (nread < 0) { return REDIS_ERR; } if (nread > 0 && redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; } return REDIS_OK; } /* Write the output buffer to the socket. * * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * successfully written to the socket. When the buffer is empty after the * write operation, "done" is set to 1 (if given). * * Returns REDIS_ERR if an unrecoverable error occurred in the underlying * c->funcs->write function. */ int redisBufferWrite(redisContext *c, int *done) { /* Return early when the context has seen an error. */ if (c->err) return REDIS_ERR; if (hi_sdslen(c->obuf) > 0) { ssize_t nwritten = c->funcs->write(c); if (nwritten < 0) { return REDIS_ERR; } else if (nwritten > 0) { if (nwritten == (ssize_t)hi_sdslen(c->obuf)) { hi_sdsfree(c->obuf); c->obuf = hi_sdsempty(); if (c->obuf == NULL) goto oom; } else { if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom; } } } if (done != NULL) *done = (hi_sdslen(c->obuf) == 0); return REDIS_OK; oom: __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } /* Internal helper that returns 1 if the reply was a RESP3 PUSH * message and we handled it with a user-provided callback. */ static int redisHandledPushReply(redisContext *c, void *reply) { if (reply && c->push_cb && redisIsPushReply(reply)) { c->push_cb(c->privdata, reply); return 1; } return 0; } /* Get a reply from our reader or set an error in the context. */ int redisGetReplyFromReader(redisContext *c, void **reply) { if (redisReaderGetReply(c->reader, reply) == REDIS_ERR) { __redisSetError(c,c->reader->err,c->reader->errstr); return REDIS_ERR; } return REDIS_OK; } /* Internal helper to get the next reply from our reader while handling * any PUSH messages we encounter along the way. This is separate from * redisGetReplyFromReader so as to not change its behavior. */ static int redisNextInBandReplyFromReader(redisContext *c, void **reply) { do { if (redisGetReplyFromReader(c, reply) == REDIS_ERR) return REDIS_ERR; } while (redisHandledPushReply(c, *reply)); return REDIS_OK; } int redisGetReply(redisContext *c, void **reply) { int wdone = 0; void *aux = NULL; /* Try to read pending replies */ if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; /* For the blocking context, flush output buffer and read reply */ if (aux == NULL && c->flags & REDIS_BLOCK) { /* Write until done */ do { if (redisBufferWrite(c,&wdone) == REDIS_ERR) return REDIS_ERR; } while (!wdone); /* Read until there is a reply */ do { if (redisBufferRead(c) == REDIS_ERR) return REDIS_ERR; if (redisNextInBandReplyFromReader(c,&aux) == REDIS_ERR) return REDIS_ERR; } while (aux == NULL); } /* Set reply or free it if we were passed NULL */ if (reply != NULL) { *reply = aux; } else { freeReplyObject(aux); } return REDIS_OK; } /* Helper function for the redisAppendCommand* family of functions. * * Write a formatted command to the output buffer. When this family * is used, you need to call redisGetReply yourself to retrieve * the reply (or replies in pub/sub). */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { hisds newbuf; newbuf = hi_sdscatlen(c->obuf,cmd,len); if (newbuf == NULL) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } c->obuf = newbuf; return REDIS_OK; } int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { return REDIS_ERR; } return REDIS_OK; } int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { char *cmd; int len; len = redisvFormatCommand(&cmd,format,ap); if (len == -1) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } else if (len == -2) { __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); return REDIS_ERR; } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { hi_free(cmd); return REDIS_ERR; } hi_free(cmd); return REDIS_OK; } int redisAppendCommand(redisContext *c, const char *format, ...) { va_list ap; int ret; va_start(ap,format); ret = redisvAppendCommand(c,format,ap); va_end(ap); return ret; } int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { hisds cmd; long long len; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); if (len == -1) { __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); return REDIS_ERR; } if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { hi_sdsfree(cmd); return REDIS_ERR; } hi_sdsfree(cmd); return REDIS_OK; } /* Helper function for the redisCommand* family of functions. * * Write a formatted command to the output buffer. If the given context is * blocking, immediately read the reply into the "reply" pointer. When the * context is non-blocking, the "reply" pointer will not be used and the * command is simply appended to the write buffer. * * Returns the reply when a reply was successfully retrieved. Returns NULL * otherwise. When NULL is returned in a blocking context, the error field * in the context will be set. */ static void *__redisBlockForReply(redisContext *c) { void *reply; if (c->flags & REDIS_BLOCK) { if (redisGetReply(c,&reply) != REDIS_OK) return NULL; return reply; } return NULL; } void *redisvCommand(redisContext *c, const char *format, va_list ap) { if (redisvAppendCommand(c,format,ap) != REDIS_OK) return NULL; return __redisBlockForReply(c); } void *redisCommand(redisContext *c, const char *format, ...) { va_list ap; va_start(ap,format); void *reply = redisvCommand(c,format,ap); va_end(ap); return reply; } void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) return NULL; return __redisBlockForReply(c); } redis-8.0.2/deps/hiredis/hiredis.h000066400000000000000000000337651501533116600170300ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_H #define __HIREDIS_H #include "read.h" #include /* for va_list */ #ifndef _MSC_VER #include /* for struct timeval */ #else struct timeval; /* forward declaration */ typedef long long ssize_t; #endif #include /* uintXX_t, etc */ #include "sds.h" /* for hisds */ #include "alloc.h" /* for allocation wrappers */ #define HIREDIS_MAJOR 1 #define HIREDIS_MINOR 2 #define HIREDIS_PATCH 0 #define HIREDIS_SONAME 1.1.0 /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ #define REDIS_BLOCK 0x1 /* Connection may be disconnected before being free'd. The second bit * in the flags field is set when the context is connected. */ #define REDIS_CONNECTED 0x2 /* The async API might try to disconnect cleanly and flush the output * buffer and read all subsequent replies before disconnecting. * This flag means no new commands can come in and the connection * should be terminated once all replies have been read. */ #define REDIS_DISCONNECTING 0x4 /* Flag specific to the async API which means that the context should be clean * up as soon as possible. */ #define REDIS_FREEING 0x8 /* Flag that is set when an async callback is executed. */ #define REDIS_IN_CALLBACK 0x10 /* Flag that is set when the async context has one or more subscriptions. */ #define REDIS_SUBSCRIBED 0x20 /* Flag that is set when monitor mode is active */ #define REDIS_MONITORING 0x40 /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 /* Flag that is set when the async connection supports push replies. */ #define REDIS_SUPPORTS_PUSH 0x100 /** * Flag that indicates the user does not want the context to * be automatically freed upon error */ #define REDIS_NO_AUTO_FREE 0x200 /* Flag that indicates the user does not want replies to be automatically freed */ #define REDIS_NO_AUTO_FREE_REPLIES 0x400 /* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set, * AF_UNSPEC is used.) */ #define REDIS_PREFER_IPV4 0x800 #define REDIS_PREFER_IPV6 0x1000 #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ /* number of times we retry to connect in the case of EADDRNOTAVAIL and * SO_REUSEADDR is being used. */ #define REDIS_CONNECT_RETRIES 10 /* Forward declarations for structs defined elsewhere */ struct redisAsyncContext; struct redisContext; /* RESP3 push helpers and callback prototypes */ #define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH) typedef void (redisPushFn)(void *, void *); typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *); #ifdef __cplusplus extern "C" { #endif /* This is the reply object returned by redisCommand() */ typedef struct redisReply { int type; /* REDIS_REPLY_* */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ double dval; /* The double when type is REDIS_REPLY_DOUBLE */ size_t len; /* Length of string */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval), and REDIS_REPLY_BIGNUM. */ char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null terminated 3 character content type, such as "txt". */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ } redisReply; redisReader *redisReaderCreate(void); /* Function to free the reply objects hiredis returns by default. */ void freeReplyObject(void *reply); /* Functions to format a command according to the protocol. */ int redisvFormatCommand(char **target, const char *format, va_list ap); int redisFormatCommand(char **target, const char *format, ...); long long redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); long long redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen); void redisFreeCommand(char *cmd); void redisFreeSdsCommand(hisds cmd); enum redisConnectionType { REDIS_CONN_TCP, REDIS_CONN_UNIX, REDIS_CONN_USERFD }; struct redisSsl; #define REDIS_OPT_NONBLOCK 0x01 #define REDIS_OPT_REUSEADDR 0x02 #define REDIS_OPT_NOAUTOFREE 0x04 /* Don't automatically free the async * object on a connection failure, or * other implicit conditions. Only free * on an explicit call to disconnect() * or free() */ #define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 /* Don't automatically intercept and * free RESP3 PUSH replies. */ #define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */ #define REDIS_OPT_PREFER_IPV4 0x20 /* Prefer IPv4 in DNS lookups. */ #define REDIS_OPT_PREFER_IPV6 0x40 /* Prefer IPv6 in DNS lookups. */ #define REDIS_OPT_PREFER_IP_UNSPEC (REDIS_OPT_PREFER_IPV4 | REDIS_OPT_PREFER_IPV6) /* In Unix systems a file descriptor is a regular signed int, with -1 * representing an invalid descriptor. In Windows it is a SOCKET * (32- or 64-bit unsigned integer depending on the architecture), where * all bits set (~0) is INVALID_SOCKET. */ #ifndef _WIN32 typedef int redisFD; #define REDIS_INVALID_FD -1 #else #ifdef _WIN64 typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ #else typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ #endif #define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ #endif typedef struct { /* * the type of connection to use. This also indicates which * `endpoint` member field to use */ int type; /* bit field of REDIS_OPT_xxx */ int options; /* timeout value for connect operation. If NULL, no timeout is used */ const struct timeval *connect_timeout; /* timeout value for commands. If NULL, no timeout is used. This can be * updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */ const struct timeval *command_timeout; union { /** use this field for tcp/ip connections */ struct { const char *source_addr; const char *ip; int port; } tcp; /** use this field for unix domain sockets */ const char *unix_socket; /** * use this field to have hiredis operate an already-open * file descriptor */ redisFD fd; } endpoint; /* Optional user defined data/destructor */ void *privdata; void (*free_privdata)(void *); /* A user defined PUSH message callback */ redisPushFn *push_cb; redisAsyncPushFn *async_push_cb; } redisOptions; /** * Helper macros to initialize options to their specified fields. */ #define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) do { \ (opts)->type = REDIS_CONN_TCP; \ (opts)->endpoint.tcp.ip = ip_; \ (opts)->endpoint.tcp.port = port_; \ } while(0) #define REDIS_OPTIONS_SET_UNIX(opts, path) do { \ (opts)->type = REDIS_CONN_UNIX; \ (opts)->endpoint.unix_socket = path; \ } while(0) #define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \ (opts)->privdata = data; \ (opts)->free_privdata = dtor; \ } while(0) typedef struct redisContextFuncs { void (*close)(struct redisContext *); void (*free_privctx)(void *); void (*async_read)(struct redisAsyncContext *); void (*async_write)(struct redisAsyncContext *); /* Read/Write data to the underlying communication stream, returning the * number of bytes read/written. In the event of an unrecoverable error * these functions shall return a value < 0. In the event of a * recoverable error, they should return 0. */ ssize_t (*read)(struct redisContext *, char *, size_t); ssize_t (*write)(struct redisContext *); } redisContextFuncs; /* Context for a connection to Redis */ typedef struct redisContext { const redisContextFuncs *funcs; /* Function table */ int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ redisFD fd; int flags; char *obuf; /* Write buffer */ redisReader *reader; /* Protocol reader */ enum redisConnectionType connection_type; struct timeval *connect_timeout; struct timeval *command_timeout; struct { char *host; char *source_addr; int port; } tcp; struct { char *path; } unix_sock; /* For non-blocking connect */ struct sockaddr *saddr; size_t addrlen; /* Optional data and corresponding destructor users can use to provide * context to a given redisContext. Not used by hiredis. */ void *privdata; void (*free_privdata)(void *); /* Internal context pointer presently used by hiredis to manage * SSL connections. */ void *privctx; /* An optional RESP3 PUSH handler */ redisPushFn *push_cb; } redisContext; redisContext *redisConnectWithOptions(const redisOptions *options); redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr); redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, const char *source_addr); redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectFd(redisFD fd); /** * Reconnect the given context using the saved information. * * This re-uses the exact same connect options as in the initial connection. * host, ip (or path), timeout and bind address are reused, * flags are used unmodified from the existing context. * * Returns REDIS_OK on successful connect or REDIS_ERR otherwise. */ int redisReconnect(redisContext *c); redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn); int redisSetTimeout(redisContext *c, const struct timeval tv); int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAliveWithInterval(redisContext *c, int interval); int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout); void redisFree(redisContext *c); redisFD redisFreeKeepFd(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); /* In a blocking context, this function first checks if there are unconsumed * replies to return and returns one if so. Otherwise, it flushes the output * buffer to the socket and reads until it has a reply. In a non-blocking * context, it will return unconsumed replies until there are no more. */ int redisGetReply(redisContext *c, void **reply); int redisGetReplyFromReader(redisContext *c, void **reply); /* Write a formatted command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); /* Write a command to the output buffer. Use these functions in blocking mode * to get a pipeline of commands. */ int redisvAppendCommand(redisContext *c, const char *format, va_list ap); int redisAppendCommand(redisContext *c, const char *format, ...); int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); /* Issue a command to Redis. In a blocking context, it is identical to calling * redisAppendCommand, followed by redisGetReply. The function will return * NULL if there was an error in performing the request, otherwise it will * return the reply. In a non-blocking context, it is identical to calling * only redisAppendCommand and will always return NULL. */ void *redisvCommand(redisContext *c, const char *format, va_list ap); void *redisCommand(redisContext *c, const char *format, ...); void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); #ifdef __cplusplus } #endif #endif redis-8.0.2/deps/hiredis/hiredis.pc.in000066400000000000000000000006011501533116600175670ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ install_libdir=@CMAKE_INSTALL_LIBDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/${install_libdir} includedir=${prefix}/include pkgincludedir=${includedir}/hiredis Name: hiredis Description: Minimalistic C client library for Redis. Version: @PROJECT_VERSION@ Libs: -L${libdir} -lhiredis Cflags: -I${pkgincludedir} -I${includedir} -D_FILE_OFFSET_BITS=64 redis-8.0.2/deps/hiredis/hiredis.targets000066400000000000000000000010141501533116600202300ustar00rootroot00000000000000 $(MSBuildThisFileDirectory)\..\..\include;%(AdditionalIncludeDirectories) $(MSBuildThisFileDirectory)\..\..\lib;%(AdditionalLibraryDirectories) redis-8.0.2/deps/hiredis/hiredis_ssl-config.cmake.in000066400000000000000000000006201501533116600223720ustar00rootroot00000000000000@PACKAGE_INIT@ set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") include(CMakeFindDependencyMacro) find_dependency(OpenSSL) IF (NOT TARGET hiredis::hiredis_ssl) INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake) ENDIF() SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) SET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR}) check_required_components(hiredis_ssl) redis-8.0.2/deps/hiredis/hiredis_ssl.h000066400000000000000000000137001501533116600176740ustar00rootroot00000000000000 /* * Copyright (c) 2019, Redis Labs * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_SSL_H #define __HIREDIS_SSL_H #ifdef __cplusplus extern "C" { #endif /* This is the underlying struct for SSL in ssl.h, which is not included to * keep build dependencies short here. */ struct ssl_st; /* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly * calling OpenSSL. */ typedef struct redisSSLContext redisSSLContext; /** * Initialization errors that redisCreateSSLContext() may return. */ typedef enum { REDIS_SSL_CTX_NONE = 0, /* No Error */ REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */ REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */ REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */ REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */ REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED, /* Failed to set client default certificate directory */ REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */ REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */ REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */ } redisSSLContextError; /* Constants that mirror OpenSSL's verify modes. By default, * REDIS_SSL_VERIFY_PEER is used with redisCreateSSLContext(). * Some Redis clients disable peer verification if there are no * certificates specified. */ #define REDIS_SSL_VERIFY_NONE 0x00 #define REDIS_SSL_VERIFY_PEER 0x01 #define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02 #define REDIS_SSL_VERIFY_CLIENT_ONCE 0x04 #define REDIS_SSL_VERIFY_POST_HANDSHAKE 0x08 /* Options to create an OpenSSL context. */ typedef struct { const char *cacert_filename; const char *capath; const char *cert_filename; const char *private_key_filename; const char *server_name; int verify_mode; } redisSSLOptions; /** * Return the error message corresponding with the specified error code. */ const char *redisSSLContextGetError(redisSSLContextError error); /** * Helper function to initialize the OpenSSL library. * * OpenSSL requires one-time initialization before it can be used. Callers should * call this function only once, and only if OpenSSL is not directly initialized * elsewhere. */ int redisInitOpenSSL(void); /** * Helper function to initialize an OpenSSL context that can be used * to initiate SSL connections. * * cacert_filename is an optional name of a CA certificate/bundle file to load * and use for validation. * * capath is an optional directory path where trusted CA certificate files are * stored in an OpenSSL-compatible structure. * * cert_filename and private_key_filename are optional names of a client side * certificate and private key files to use for authentication. They need to * be both specified or omitted. * * server_name is an optional and will be used as a server name indication * (SNI) TLS extension. * * If error is non-null, it will be populated in case the context creation fails * (returning a NULL). */ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath, const char *cert_filename, const char *private_key_filename, const char *server_name, redisSSLContextError *error); /** * Helper function to initialize an OpenSSL context that can be used * to initiate SSL connections. This is a more extensible version of redisCreateSSLContext(). * * options contains a structure of SSL options to use. * * If error is non-null, it will be populated in case the context creation fails * (returning a NULL). */ redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error); /** * Free a previously created OpenSSL context. */ void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx); /** * Initiate SSL on an existing redisContext. * * This is similar to redisInitiateSSL() but does not require the caller * to directly interact with OpenSSL, and instead uses a redisSSLContext * previously created using redisCreateSSLContext(). */ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx); /** * Initiate SSL/TLS negotiation on a provided OpenSSL SSL object. */ int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); #ifdef __cplusplus } #endif #endif /* __HIREDIS_SSL_H */ redis-8.0.2/deps/hiredis/hiredis_ssl.pc.in000066400000000000000000000005461501533116600204600ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ install_libdir=@CMAKE_INSTALL_LIBDIR@ exec_prefix=${prefix} libdir=${exec_prefix}/${install_libdir} includedir=${prefix}/include pkgincludedir=${includedir}/hiredis Name: hiredis_ssl Description: SSL Support for hiredis. Version: @PROJECT_VERSION@ Requires: hiredis Libs: -L${libdir} -lhiredis_ssl Libs.private: -lssl -lcrypto redis-8.0.2/deps/hiredis/net.c000066400000000000000000000506261501533116600161550ustar00rootroot00000000000000/* Extracted from anet.c to work properly with Hiredis error reporting. * * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include #include #include #include #include #include #include #include #include "net.h" #include "sds.h" #include "sockcompat.h" #include "win32.h" /* Defined in hiredis.c */ void __redisSetError(redisContext *c, int type, const char *str); int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); void redisNetClose(redisContext *c) { if (c && c->fd != REDIS_INVALID_FD) { close(c->fd); c->fd = REDIS_INVALID_FD; } } ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { ssize_t nread = recv(c->fd, buf, bufcap, 0); if (nread == -1) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ return 0; } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { /* especially in windows */ __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); return -1; } else { __redisSetError(c, REDIS_ERR_IO, strerror(errno)); return -1; } } else if (nread == 0) { __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); return -1; } else { return nread; } } ssize_t redisNetWrite(redisContext *c) { ssize_t nwritten; nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0); if (nwritten < 0) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again */ return 0; } else { __redisSetError(c, REDIS_ERR_IO, strerror(errno)); return -1; } } return nwritten; } static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { int errorno = errno; /* snprintf() may change errno */ char buf[128] = { 0 }; size_t len = 0; if (prefix != NULL) len = snprintf(buf,sizeof(buf),"%s: ",prefix); strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); __redisSetError(c,type,buf); } static int redisSetReuseAddr(redisContext *c) { int on = 1; if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); redisNetClose(c); return REDIS_ERR; } return REDIS_OK; } static int redisCreateSocket(redisContext *c, int type) { redisFD s; if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } c->fd = s; if (type == AF_INET) { if (redisSetReuseAddr(c) == REDIS_ERR) { return REDIS_ERR; } } return REDIS_OK; } static int redisSetBlocking(redisContext *c, int blocking) { #ifndef _WIN32 int flags; /* Set the socket nonblocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(c->fd, F_GETFL)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); redisNetClose(c); return REDIS_ERR; } if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; if (fcntl(c->fd, F_SETFL, flags) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); redisNetClose(c); return REDIS_ERR; } #else u_long mode = blocking ? 0 : 1; if (ioctl(c->fd, FIONBIO, &mode) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); redisNetClose(c); return REDIS_ERR; } #endif /* _WIN32 */ return REDIS_OK; } int redisKeepAlive(redisContext *c, int interval) { int val = 1; redisFD fd = c->fd; #ifndef _WIN32 if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } val = interval; #if defined(__APPLE__) && defined(__MACH__) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } #else #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } val = interval/3; if (val == 0) val = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } val = 3; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); return REDIS_ERR; } #endif #endif #else int res; res = win32_redisKeepAlive(fd, interval * 1000); if (res != 0) { __redisSetError(c, REDIS_ERR_OTHER, strerror(res)); return REDIS_ERR; } #endif return REDIS_OK; } int redisSetTcpNoDelay(redisContext *c) { int yes = 1; if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); redisNetClose(c); return REDIS_ERR; } return REDIS_OK; } int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) { int res; #ifdef TCP_USER_TIMEOUT res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout)); #else res = -1; errno = ENOTSUP; (void)timeout; #endif if (res == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)"); redisNetClose(c); return REDIS_ERR; } return REDIS_OK; } #define __MAX_MSEC (((LONG_MAX) - 999) / 1000) static int redisContextTimeoutMsec(redisContext *c, long *result) { const struct timeval *timeout = c->connect_timeout; long msec = -1; /* Only use timeout when not NULL. */ if (timeout != NULL) { if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); *result = msec; return REDIS_ERR; } msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); if (msec < 0 || msec > INT_MAX) { msec = INT_MAX; } } *result = msec; return REDIS_OK; } static int redisContextWaitReady(redisContext *c, long msec) { struct pollfd wfd[1]; wfd[0].fd = c->fd; wfd[0].events = POLLOUT; if (errno == EINPROGRESS) { int res; if ((res = poll(wfd, 1, msec)) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); redisNetClose(c); return REDIS_ERR; } else if (res == 0) { errno = ETIMEDOUT; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); redisNetClose(c); return REDIS_ERR; } if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { redisCheckSocketError(c); return REDIS_ERR; } return REDIS_OK; } __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); redisNetClose(c); return REDIS_ERR; } int redisCheckConnectDone(redisContext *c, int *completed) { int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); if (rc == 0) { *completed = 1; return REDIS_OK; } int error = errno; if (error == EINPROGRESS) { /* must check error to see if connect failed. Get the socket error */ int fail, so_error; socklen_t optlen = sizeof(so_error); fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen); if (fail == 0) { if (so_error == 0) { /* Socket is connected! */ *completed = 1; return REDIS_OK; } /* connection error; */ errno = so_error; error = so_error; } } switch (error) { case EISCONN: *completed = 1; return REDIS_OK; case EALREADY: case EWOULDBLOCK: *completed = 0; return REDIS_OK; default: return REDIS_ERR; } } int redisCheckSocketError(redisContext *c) { int err = 0, errno_saved = errno; socklen_t errlen = sizeof(err); if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); return REDIS_ERR; } if (err == 0) { err = errno_saved; } if (err) { errno = err; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } return REDIS_OK; } int redisContextSetTimeout(redisContext *c, const struct timeval tv) { const void *to_ptr = &tv; size_t to_sz = sizeof(tv); if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) { __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDIS_ERR; } if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); return REDIS_ERR; } return REDIS_OK; } int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) { /* Same timeval struct, short circuit */ if (c->connect_timeout == timeout) return REDIS_OK; /* Allocate context timeval if we need to */ if (c->connect_timeout == NULL) { c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout)); if (c->connect_timeout == NULL) return REDIS_ERR; } memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout)); return REDIS_OK; } int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) { /* Same timeval struct, short circuit */ if (c->command_timeout == timeout) return REDIS_OK; /* Allocate context timeval if we need to */ if (c->command_timeout == NULL) { c->command_timeout = hi_malloc(sizeof(*c->command_timeout)); if (c->command_timeout == NULL) return REDIS_ERR; } memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout)); return REDIS_OK; } static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { redisFD s; int rv, n; char _port[6]; /* strlen("65535"); */ struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDIS_BLOCK); int reuseaddr = (c->flags & REDIS_REUSEADDR); int reuses = 0; long timeout_msec = -1; servinfo = NULL; c->connection_type = REDIS_CONN_TCP; c->tcp.port = port; /* We need to take possession of the passed parameters * to make them reusable for a reconnect. * We also carefully check we don't free data we already own, * as in the case of the reconnect method. * * This is a bit ugly, but atleast it works and doesn't leak memory. **/ if (c->tcp.host != addr) { hi_free(c->tcp.host); c->tcp.host = hi_strdup(addr); if (c->tcp.host == NULL) goto oom; } if (timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) goto oom; } else { hi_free(c->connect_timeout); c->connect_timeout = NULL; } if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { goto error; } if (source_addr == NULL) { hi_free(c->tcp.source_addr); c->tcp.source_addr = NULL; } else if (c->tcp.source_addr != source_addr) { hi_free(c->tcp.source_addr); c->tcp.source_addr = hi_strdup(source_addr); } snprintf(_port, 6, "%d", port); memset(&hints,0,sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and * IPv6. By default, for historical reasons, we try IPv4 first and then we * try IPv6 only if no IPv4 address was found. */ if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4) hints.ai_family = AF_UNSPEC; else if (c->flags & REDIS_PREFER_IPV6) hints.ai_family = AF_INET6; else hints.ai_family = AF_INET; rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); if (rv != 0 && hints.ai_family != AF_UNSPEC) { /* Try again with the other IP version. */ hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET; rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); } if (rv != 0) { __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv)); return REDIS_ERR; } for (p = servinfo; p != NULL; p = p->ai_next) { addrretry: if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) continue; c->fd = s; if (redisSetBlocking(c,0) != REDIS_OK) goto error; if (c->tcp.source_addr) { int bound = 0; /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { char buf[128]; snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } if (reuseaddr) { n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, sizeof(n)) < 0) { freeaddrinfo(bservinfo); goto error; } } for (b = bservinfo; b != NULL; b = b->ai_next) { if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { bound = 1; break; } } freeaddrinfo(bservinfo); if (!bound) { char buf[128]; snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } } /* For repeat connection */ hi_free(c->saddr); c->saddr = hi_malloc(p->ai_addrlen); if (c->saddr == NULL) goto oom; memcpy(c->saddr, p->ai_addr, p->ai_addrlen); c->addrlen = p->ai_addrlen; if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { redisNetClose(c); continue; } else if (errno == EINPROGRESS) { if (blocking) { goto wait_for_ready; } /* This is ok. * Note that even when it's in blocking mode, we unset blocking * for `connect()` */ } else if (errno == EADDRNOTAVAIL && reuseaddr) { if (++reuses >= REDIS_CONNECT_RETRIES) { goto error; } else { redisNetClose(c); goto addrretry; } } else { wait_for_ready: if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) goto error; if (redisSetTcpNoDelay(c) != REDIS_OK) goto error; } } if (blocking && redisSetBlocking(c,1) != REDIS_OK) goto error; c->flags |= REDIS_CONNECTED; rv = REDIS_OK; goto end; } if (p == NULL) { char buf[128]; snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,buf); goto error; } oom: __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); error: rv = REDIS_ERR; end: if(servinfo) { freeaddrinfo(servinfo); } return rv; // Need to return REDIS_OK if alright } int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) { return _redisContextConnectTcp(c, addr, port, timeout, NULL); } int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { return _redisContextConnectTcp(c, addr, port, timeout, source_addr); } int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { #ifndef _WIN32 int blocking = (c->flags & REDIS_BLOCK); struct sockaddr_un *sa; long timeout_msec = -1; if (redisCreateSocket(c,AF_UNIX) < 0) return REDIS_ERR; if (redisSetBlocking(c,0) != REDIS_OK) return REDIS_ERR; c->connection_type = REDIS_CONN_UNIX; if (c->unix_sock.path != path) { hi_free(c->unix_sock.path); c->unix_sock.path = hi_strdup(path); if (c->unix_sock.path == NULL) goto oom; } if (timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) goto oom; } else { hi_free(c->connect_timeout); c->connect_timeout = NULL; } if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) return REDIS_ERR; /* Don't leak sockaddr if we're reconnecting */ if (c->saddr) hi_free(c->saddr); sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un))); if (sa == NULL) goto oom; c->addrlen = sizeof(struct sockaddr_un); sa->sun_family = AF_UNIX; strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { if (errno == EINPROGRESS && !blocking) { /* This is ok. */ } else { if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) return REDIS_ERR; } } /* Reset socket to be blocking after connect(2). */ if (blocking && redisSetBlocking(c,1) != REDIS_OK) return REDIS_ERR; c->flags |= REDIS_CONNECTED; return REDIS_OK; #else /* We currently do not support Unix sockets for Windows. */ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ errno = EPROTONOSUPPORT; return REDIS_ERR; #endif /* _WIN32 */ oom: __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } redis-8.0.2/deps/hiredis/net.h000066400000000000000000000054241501533116600161560ustar00rootroot00000000000000/* Extracted from anet.c to work properly with Hiredis error reporting. * * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2014, Pieter Noordhuis * Copyright (c) 2015, Matt Stancliff , * Jan-Erik Rediger * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __NET_H #define __NET_H #include "hiredis.h" void redisNetClose(redisContext *c); ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap); ssize_t redisNetWrite(redisContext *c); int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); int redisCheckConnectDone(redisContext *c, int *completed); int redisSetTcpNoDelay(redisContext *c); int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout); #endif redis-8.0.2/deps/hiredis/read.c000066400000000000000000000557111501533116600163020ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include #include #ifndef _MSC_VER #include #include #endif #include #include #include #include #include #include "alloc.h" #include "read.h" #include "sds.h" #include "win32.h" /* Initial size of our nested reply stack and how much we grow it when needd */ #define REDIS_READER_STACK_SIZE 9 static void __redisReaderSetError(redisReader *r, int type, const char *str) { size_t len; if (r->reply != NULL && r->fn && r->fn->freeObject) { r->fn->freeObject(r->reply); r->reply = NULL; } /* Clear input buffer on errors. */ hi_sdsfree(r->buf); r->buf = NULL; r->pos = r->len = 0; /* Reset task stack. */ r->ridx = -1; /* Set error. */ r->err = type; len = strlen(str); len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); memcpy(r->errstr,str,len); r->errstr[len] = '\0'; } static size_t chrtos(char *buf, size_t size, char byte) { size_t len = 0; switch(byte) { case '\\': case '"': len = snprintf(buf,size,"\"\\%c\"",byte); break; case '\n': len = snprintf(buf,size,"\"\\n\""); break; case '\r': len = snprintf(buf,size,"\"\\r\""); break; case '\t': len = snprintf(buf,size,"\"\\t\""); break; case '\a': len = snprintf(buf,size,"\"\\a\""); break; case '\b': len = snprintf(buf,size,"\"\\b\""); break; default: if (isprint(byte)) len = snprintf(buf,size,"\"%c\"",byte); else len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); break; } return len; } static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { char cbuf[8], sbuf[128]; chrtos(cbuf,sizeof(cbuf),byte); snprintf(sbuf,sizeof(sbuf), "Protocol error, got %s as reply type byte", cbuf); __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); } static void __redisReaderSetErrorOOM(redisReader *r) { __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); } static char *readBytes(redisReader *r, unsigned int bytes) { char *p; if (r->len-r->pos >= bytes) { p = r->buf+r->pos; r->pos += bytes; return p; } return NULL; } /* Find pointer to \r\n. */ static char *seekNewline(char *s, size_t len) { char *ret; /* We cannot match with fewer than 2 bytes */ if (len < 2) return NULL; /* Search up to len - 1 characters */ len--; /* Look for the \r */ while ((ret = memchr(s, '\r', len)) != NULL) { if (ret[1] == '\n') { /* Found. */ break; } /* Continue searching. */ ret++; len -= ret - s; s = ret; } return ret; } /* Convert a string into a long long. Returns REDIS_OK if the string could be * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value * will be set to the parsed value when appropriate. * * Note that this function demands that the string strictly represents * a long long: no spaces or other characters before or after the string * representing the number are accepted, nor zeroes at the start if not * for the string "0" representing the zero number. * * Because of its strictness, it is safe to use this function to check if * you can convert a string into a long long, and obtain back the string * from the number without any loss in the string representation. */ static int string2ll(const char *s, size_t slen, long long *value) { const char *p = s; size_t plen = 0; int negative = 0; unsigned long long v; if (plen == slen) return REDIS_ERR; /* Special case: first and only digit is 0. */ if (slen == 1 && p[0] == '0') { if (value != NULL) *value = 0; return REDIS_OK; } if (p[0] == '-') { negative = 1; p++; plen++; /* Abort on only a negative sign. */ if (plen == slen) return REDIS_ERR; } /* First digit should be 1-9, otherwise the string should just be 0. */ if (p[0] >= '1' && p[0] <= '9') { v = p[0]-'0'; p++; plen++; } else if (p[0] == '0' && slen == 1) { *value = 0; return REDIS_OK; } else { return REDIS_ERR; } while (plen < slen && p[0] >= '0' && p[0] <= '9') { if (v > (ULLONG_MAX / 10)) /* Overflow. */ return REDIS_ERR; v *= 10; if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ return REDIS_ERR; v += p[0]-'0'; p++; plen++; } /* Return if not all bytes were used. */ if (plen < slen) return REDIS_ERR; if (negative) { if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ return REDIS_ERR; if (value != NULL) *value = -v; } else { if (v > LLONG_MAX) /* Overflow. */ return REDIS_ERR; if (value != NULL) *value = v; } return REDIS_OK; } static char *readLine(redisReader *r, int *_len) { char *p, *s; int len; p = r->buf+r->pos; s = seekNewline(p,(r->len-r->pos)); if (s != NULL) { len = s-(r->buf+r->pos); r->pos += len+2; /* skip \r\n */ if (_len) *_len = len; return p; } return NULL; } static void moveToNextTask(redisReader *r) { redisReadTask *cur, *prv; while (r->ridx >= 0) { /* Return a.s.a.p. when the stack is now empty. */ if (r->ridx == 0) { r->ridx--; return; } cur = r->task[r->ridx]; prv = r->task[r->ridx-1]; assert(prv->type == REDIS_REPLY_ARRAY || prv->type == REDIS_REPLY_MAP || prv->type == REDIS_REPLY_SET || prv->type == REDIS_REPLY_PUSH); if (cur->idx == prv->elements-1) { r->ridx--; } else { /* Reset the type because the next item can be anything */ assert(cur->idx < prv->elements); cur->type = -1; cur->elements = -1; cur->idx++; return; } } } static int processLineItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; void *obj; char *p; int len; if ((p = readLine(r,&len)) != NULL) { if (cur->type == REDIS_REPLY_INTEGER) { long long v; if (string2ll(p, len, &v) == REDIS_ERR) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad integer value"); return REDIS_ERR; } if (r->fn && r->fn->createInteger) { obj = r->fn->createInteger(cur,v); } else { obj = (void*)REDIS_REPLY_INTEGER; } } else if (cur->type == REDIS_REPLY_DOUBLE) { char buf[326], *eptr; double d; if ((size_t)len >= sizeof(buf)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Double value is too large"); return REDIS_ERR; } memcpy(buf,p,len); buf[len] = '\0'; if (len == 3 && strcasecmp(buf,"inf") == 0) { d = INFINITY; /* Positive infinite. */ } else if (len == 4 && strcasecmp(buf,"-inf") == 0) { d = -INFINITY; /* Negative infinite. */ } else if ((len == 3 && strcasecmp(buf,"nan") == 0) || (len == 4 && strcasecmp(buf, "-nan") == 0)) { d = NAN; /* nan. */ } else { d = strtod((char*)buf,&eptr); /* RESP3 only allows "inf", "-inf", and finite values, while * strtod() allows other variations on infinity, * etc. We explicity handle our two allowed infinite cases and NaN * above, so strtod() should only result in finite values. */ if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad double value"); return REDIS_ERR; } } if (r->fn && r->fn->createDouble) { obj = r->fn->createDouble(cur,d,buf,len); } else { obj = (void*)REDIS_REPLY_DOUBLE; } } else if (cur->type == REDIS_REPLY_NIL) { if (len != 0) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad nil value"); return REDIS_ERR; } if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; } else if (cur->type == REDIS_REPLY_BOOL) { int bval; if (len != 1 || !strchr("tTfF", p[0])) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad bool value"); return REDIS_ERR; } bval = p[0] == 't' || p[0] == 'T'; if (r->fn && r->fn->createBool) obj = r->fn->createBool(cur,bval); else obj = (void*)REDIS_REPLY_BOOL; } else if (cur->type == REDIS_REPLY_BIGNUM) { /* Ensure all characters are decimal digits (with possible leading * minus sign). */ for (int i = 0; i < len; i++) { /* XXX Consider: Allow leading '+'? Error on leading '0's? */ if (i == 0 && p[0] == '-') continue; if (p[i] < '0' || p[i] > '9') { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad bignum value"); return REDIS_ERR; } } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else obj = (void*)REDIS_REPLY_BIGNUM; } else { /* Type will be error or status. */ for (int i = 0; i < len; i++) { if (p[i] == '\r' || p[i] == '\n') { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad simple string value"); return REDIS_ERR; } } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,p,len); else obj = (void*)(uintptr_t)(cur->type); } if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); return REDIS_OK; } return REDIS_ERR; } static int processBulkItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; void *obj = NULL; char *p, *s; long long len; unsigned long bytelen; int success = 0; p = r->buf+r->pos; s = seekNewline(p,r->len-r->pos); if (s != NULL) { p = r->buf+r->pos; bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad bulk string length"); return REDIS_ERR; } if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bulk string length out of range"); return REDIS_ERR; } if (len == -1) { /* The nil object can always be created. */ if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; success = 1; } else { /* Only continue when the buffer contains the entire bulk item. */ bytelen += len+2; /* include \r\n */ if (r->pos+bytelen <= r->len) { if ((cur->type == REDIS_REPLY_VERB && len < 4) || (cur->type == REDIS_REPLY_VERB && s[5] != ':')) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Verbatim string 4 bytes of content type are " "missing or incorrectly encoded."); return REDIS_ERR; } if (r->fn && r->fn->createString) obj = r->fn->createString(cur,s+2,len); else obj = (void*)(uintptr_t)cur->type; success = 1; } } /* Proceed when obj was created. */ if (success) { if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } r->pos += bytelen; /* Set reply if this is the root object. */ if (r->ridx == 0) r->reply = obj; moveToNextTask(r); return REDIS_OK; } } return REDIS_ERR; } static int redisReaderGrow(redisReader *r) { redisReadTask **aux; int newlen; /* Grow our stack size */ newlen = r->tasks + REDIS_READER_STACK_SIZE; aux = hi_realloc(r->task, sizeof(*r->task) * newlen); if (aux == NULL) goto oom; r->task = aux; /* Allocate new tasks */ for (; r->tasks < newlen; r->tasks++) { r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); if (r->task[r->tasks] == NULL) goto oom; } return REDIS_OK; oom: __redisReaderSetErrorOOM(r); return REDIS_ERR; } /* Process the array, map and set types. */ static int processAggregateItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; void *obj; char *p; long long elements; int root = 0, len; if (r->ridx == r->tasks - 1) { if (redisReaderGrow(r) == REDIS_ERR) return REDIS_ERR; } if ((p = readLine(r,&len)) != NULL) { if (string2ll(p, len, &elements) == REDIS_ERR) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Bad multi-bulk length"); return REDIS_ERR; } root = (r->ridx == 0); if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) || (r->maxelements > 0 && elements > r->maxelements)) { __redisReaderSetError(r,REDIS_ERR_PROTOCOL, "Multi-bulk length out of range"); return REDIS_ERR; } if (elements == -1) { if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); else obj = (void*)REDIS_REPLY_NIL; if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } moveToNextTask(r); } else { if (cur->type == REDIS_REPLY_MAP) elements *= 2; if (r->fn && r->fn->createArray) obj = r->fn->createArray(cur,elements); else obj = (void*)(uintptr_t)cur->type; if (obj == NULL) { __redisReaderSetErrorOOM(r); return REDIS_ERR; } /* Modify task stack when there are more than 0 elements. */ if (elements > 0) { cur->elements = elements; cur->obj = obj; r->ridx++; r->task[r->ridx]->type = -1; r->task[r->ridx]->elements = -1; r->task[r->ridx]->idx = 0; r->task[r->ridx]->obj = NULL; r->task[r->ridx]->parent = cur; r->task[r->ridx]->privdata = r->privdata; } else { moveToNextTask(r); } } /* Set reply if this is the root object. */ if (root) r->reply = obj; return REDIS_OK; } return REDIS_ERR; } static int processItem(redisReader *r) { redisReadTask *cur = r->task[r->ridx]; char *p; /* check if we need to read type */ if (cur->type < 0) { if ((p = readBytes(r,1)) != NULL) { switch (p[0]) { case '-': cur->type = REDIS_REPLY_ERROR; break; case '+': cur->type = REDIS_REPLY_STATUS; break; case ':': cur->type = REDIS_REPLY_INTEGER; break; case ',': cur->type = REDIS_REPLY_DOUBLE; break; case '_': cur->type = REDIS_REPLY_NIL; break; case '$': cur->type = REDIS_REPLY_STRING; break; case '*': cur->type = REDIS_REPLY_ARRAY; break; case '%': cur->type = REDIS_REPLY_MAP; break; case '~': cur->type = REDIS_REPLY_SET; break; case '#': cur->type = REDIS_REPLY_BOOL; break; case '=': cur->type = REDIS_REPLY_VERB; break; case '>': cur->type = REDIS_REPLY_PUSH; break; case '(': cur->type = REDIS_REPLY_BIGNUM; break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; } } else { /* could not consume 1 byte */ return REDIS_ERR; } } /* process typed item */ switch(cur->type) { case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_INTEGER: case REDIS_REPLY_DOUBLE: case REDIS_REPLY_NIL: case REDIS_REPLY_BOOL: case REDIS_REPLY_BIGNUM: return processLineItem(r); case REDIS_REPLY_STRING: case REDIS_REPLY_VERB: return processBulkItem(r); case REDIS_REPLY_ARRAY: case REDIS_REPLY_MAP: case REDIS_REPLY_SET: case REDIS_REPLY_PUSH: return processAggregateItem(r); default: assert(NULL); return REDIS_ERR; /* Avoid warning. */ } } redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *r; r = hi_calloc(1,sizeof(redisReader)); if (r == NULL) return NULL; r->buf = hi_sdsempty(); if (r->buf == NULL) goto oom; r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task)); if (r->task == NULL) goto oom; for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) { r->task[r->tasks] = hi_calloc(1, sizeof(**r->task)); if (r->task[r->tasks] == NULL) goto oom; } r->fn = fn; r->maxbuf = REDIS_READER_MAX_BUF; r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS; r->ridx = -1; return r; oom: redisReaderFree(r); return NULL; } void redisReaderFree(redisReader *r) { if (r == NULL) return; if (r->reply != NULL && r->fn && r->fn->freeObject) r->fn->freeObject(r->reply); if (r->task) { /* We know r->task[i] is allocated if i < r->tasks */ for (int i = 0; i < r->tasks; i++) { hi_free(r->task[i]); } hi_free(r->task); } hi_sdsfree(r->buf); hi_free(r); } int redisReaderFeed(redisReader *r, const char *buf, size_t len) { hisds newbuf; /* Return early when this reader is in an erroneous state. */ if (r->err) return REDIS_ERR; /* Copy the provided buffer. */ if (buf != NULL && len >= 1) { /* Destroy internal buffer when it is empty and is quite large. */ if (r->len == 0 && r->maxbuf != 0 && hi_sdsavail(r->buf) > r->maxbuf) { hi_sdsfree(r->buf); r->buf = hi_sdsempty(); if (r->buf == 0) goto oom; r->pos = 0; } newbuf = hi_sdscatlen(r->buf,buf,len); if (newbuf == NULL) goto oom; r->buf = newbuf; r->len = hi_sdslen(r->buf); } return REDIS_OK; oom: __redisReaderSetErrorOOM(r); return REDIS_ERR; } int redisReaderGetReply(redisReader *r, void **reply) { /* Default target pointer to NULL. */ if (reply != NULL) *reply = NULL; /* Return early when this reader is in an erroneous state. */ if (r->err) return REDIS_ERR; /* When the buffer is empty, there will never be a reply. */ if (r->len == 0) return REDIS_OK; /* Set first item to process when the stack is empty. */ if (r->ridx == -1) { r->task[0]->type = -1; r->task[0]->elements = -1; r->task[0]->idx = -1; r->task[0]->obj = NULL; r->task[0]->parent = NULL; r->task[0]->privdata = r->privdata; r->ridx = 0; } /* Process items in reply. */ while (r->ridx >= 0) if (processItem(r) != REDIS_OK) break; /* Return ASAP when an error occurred. */ if (r->err) return REDIS_ERR; /* Discard part of the buffer when we've consumed at least 1k, to avoid * doing unnecessary calls to memmove() in sds.c. */ if (r->pos >= 1024) { if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR; r->pos = 0; r->len = hi_sdslen(r->buf); } /* Emit a reply when there is one. */ if (r->ridx == -1) { if (reply != NULL) { *reply = r->reply; } else if (r->reply != NULL && r->fn && r->fn->freeObject) { r->fn->freeObject(r->reply); } r->reply = NULL; } return REDIS_OK; } redis-8.0.2/deps/hiredis/read.h000066400000000000000000000114661501533116600163060ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __HIREDIS_READ_H #define __HIREDIS_READ_H #include /* for size_t */ #define REDIS_ERR -1 #define REDIS_OK 0 /* When an error occurs, the err flag in a context is set to hold the type of * error that occurred. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */ #define REDIS_ERR_IO 1 /* Error in read or write */ #define REDIS_ERR_EOF 3 /* End of file */ #define REDIS_ERR_PROTOCOL 4 /* Protocol error */ #define REDIS_ERR_OOM 5 /* Out of memory */ #define REDIS_ERR_TIMEOUT 6 /* Timed out */ #define REDIS_ERR_OTHER 2 /* Everything else... */ #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 #define REDIS_REPLY_INTEGER 3 #define REDIS_REPLY_NIL 4 #define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_ERROR 6 #define REDIS_REPLY_DOUBLE 7 #define REDIS_REPLY_BOOL 8 #define REDIS_REPLY_MAP 9 #define REDIS_REPLY_SET 10 #define REDIS_REPLY_ATTR 11 #define REDIS_REPLY_PUSH 12 #define REDIS_REPLY_BIGNUM 13 #define REDIS_REPLY_VERB 14 /* Default max unused reader buffer. */ #define REDIS_READER_MAX_BUF (1024*16) /* Default multi-bulk element limit */ #define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1) #ifdef __cplusplus extern "C" { #endif typedef struct redisReadTask { int type; long long elements; /* number of elements in multibulk container */ int idx; /* index in parent (array) object */ void *obj; /* holds user-generated value for a read task */ struct redisReadTask *parent; /* parent task */ void *privdata; /* user-settable arbitrary field */ } redisReadTask; typedef struct redisReplyObjectFunctions { void *(*createString)(const redisReadTask*, char*, size_t); void *(*createArray)(const redisReadTask*, size_t); void *(*createInteger)(const redisReadTask*, long long); void *(*createDouble)(const redisReadTask*, double, char*, size_t); void *(*createNil)(const redisReadTask*); void *(*createBool)(const redisReadTask*, int); void (*freeObject)(void*); } redisReplyObjectFunctions; typedef struct redisReader { int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ char *buf; /* Read buffer */ size_t pos; /* Buffer cursor */ size_t len; /* Buffer length */ size_t maxbuf; /* Max length of unused buffer */ long long maxelements; /* Max multi-bulk elements */ redisReadTask **task; int tasks; int ridx; /* Index of current read task */ void *reply; /* Temporary reply pointer */ redisReplyObjectFunctions *fn; void *privdata; } redisReader; /* Public API for the protocol parser. */ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); void redisReaderFree(redisReader *r); int redisReaderFeed(redisReader *r, const char *buf, size_t len); int redisReaderGetReply(redisReader *r, void **reply); #define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) #define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) #define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) #ifdef __cplusplus } #endif #endif redis-8.0.2/deps/hiredis/sds.c000066400000000000000000001216341501533116600161560ustar00rootroot00000000000000/* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "fmacros.h" #include #include #include #include #include #include #include "sds.h" #include "sdsalloc.h" static inline int hi_sdsHdrSize(char type) { switch(type&HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: return sizeof(struct hisdshdr5); case HI_SDS_TYPE_8: return sizeof(struct hisdshdr8); case HI_SDS_TYPE_16: return sizeof(struct hisdshdr16); case HI_SDS_TYPE_32: return sizeof(struct hisdshdr32); case HI_SDS_TYPE_64: return sizeof(struct hisdshdr64); } return 0; } static inline char hi_sdsReqType(size_t string_size) { if (string_size < 32) return HI_SDS_TYPE_5; if (string_size < 0xff) return HI_SDS_TYPE_8; if (string_size < 0xffff) return HI_SDS_TYPE_16; if (string_size < 0xffffffff) return HI_SDS_TYPE_32; return HI_SDS_TYPE_64; } /* Create a new hisds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. * * The string is always null-terminated (all the hisds strings are, always) so * even if you create an hisds string with: * * mystring = hi_sdsnewlen("abc",3); * * You can print the string with printf() as there is an implicit \0 at the * end of the string. However the string is binary safe and can contain * \0 characters in the middle, as the length is stored in the hisds header. */ hisds hi_sdsnewlen(const void *init, size_t initlen) { void *sh; hisds s; char type = hi_sdsReqType(initlen); /* Empty strings are usually created in order to append. Use type 8 * since type 5 is not good at this. */ if (type == HI_SDS_TYPE_5 && initlen == 0) type = HI_SDS_TYPE_8; int hdrlen = hi_sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */ sh = hi_s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (!init) memset(sh, 0, hdrlen+initlen+1); s = (char*)sh+hdrlen; fp = ((unsigned char*)s)-1; switch(type) { case HI_SDS_TYPE_5: { *fp = type | (initlen << HI_SDS_TYPE_BITS); break; } case HI_SDS_TYPE_8: { HI_SDS_HDR_VAR(8,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case HI_SDS_TYPE_16: { HI_SDS_HDR_VAR(16,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case HI_SDS_TYPE_32: { HI_SDS_HDR_VAR(32,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } case HI_SDS_TYPE_64: { HI_SDS_HDR_VAR(64,s); sh->len = initlen; sh->alloc = initlen; *fp = type; break; } } if (initlen && init) memcpy(s, init, initlen); s[initlen] = '\0'; return s; } /* Create an empty (zero length) hisds string. Even in this case the string * always has an implicit null term. */ hisds hi_sdsempty(void) { return hi_sdsnewlen("",0); } /* Create a new hisds string starting from a null terminated C string. */ hisds hi_sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return hi_sdsnewlen(init, initlen); } /* Duplicate an hisds string. */ hisds hi_sdsdup(const hisds s) { return hi_sdsnewlen(s, hi_sdslen(s)); } /* Free an hisds string. No operation is performed if 's' is NULL. */ void hi_sdsfree(hisds s) { if (s == NULL) return; hi_s_free((char*)s-hi_sdsHdrSize(s[-1])); } /* Set the hisds string length to the length as obtained with strlen(), so * considering as content only up to the first null term character. * * This function is useful when the hisds string is hacked manually in some * way, like in the following example: * * s = hi_sdsnew("foobar"); * s[2] = '\0'; * hi_sdsupdatelen(s); * printf("%d\n", hi_sdslen(s)); * * The output will be "2", but if we comment out the call to hi_sdsupdatelen() * the output will be "6" as the string was modified but the logical length * remains 6 bytes. */ void hi_sdsupdatelen(hisds s) { size_t reallen = strlen(s); hi_sdssetlen(s, reallen); } /* Modify an hisds string in-place to make it empty (zero length). * However all the existing buffer is not discarded but set as free space * so that next append operations will not require allocations up to the * number of bytes previously available. */ void hi_sdsclear(hisds s) { hi_sdssetlen(s, 0); s[0] = '\0'; } /* Enlarge the free space at the end of the hisds string so that the caller * is sure that after calling this function can overwrite up to addlen * bytes after the end of the string, plus one more byte for nul term. * * Note: this does not change the *length* of the hisds string as returned * by hi_sdslen(), but only the free buffer space we have. */ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) { void *sh, *newsh; size_t avail = hi_sdsavail(s); size_t len, newlen, reqlen; char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; int hdrlen; /* Return ASAP if there is enough space left. */ if (avail >= addlen) return s; len = hi_sdslen(s); sh = (char*)s-hi_sdsHdrSize(oldtype); reqlen = newlen = (len+addlen); if (newlen <= len) return NULL; /* Catch size_t overflow */ if (newlen < HI_SDS_MAX_PREALLOC) newlen *= 2; else newlen += HI_SDS_MAX_PREALLOC; type = hi_sdsReqType(newlen); /* Don't use type 5: the user is appending to the string and type 5 is * not able to remember empty space, so hi_sdsMakeRoomFor() must be called * at every appending operation. */ if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8; hdrlen = hi_sdsHdrSize(type); if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */ if (oldtype==type) { newsh = hi_s_realloc(sh, hdrlen+newlen+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ newsh = hi_s_malloc(hdrlen+newlen+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); hi_s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; hi_sdssetlen(s, len); } hi_sdssetalloc(s, newlen); return s; } /* Reallocate the hisds string so that it has no free space at the end. The * contained string remains not altered, but next concatenation operations * will require a reallocation. * * After the call, the passed hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ hisds hi_sdsRemoveFreeSpace(hisds s) { void *sh, *newsh; char type, oldtype = s[-1] & HI_SDS_TYPE_MASK; int hdrlen; size_t len = hi_sdslen(s); sh = (char*)s-hi_sdsHdrSize(oldtype); type = hi_sdsReqType(len); hdrlen = hi_sdsHdrSize(type); if (oldtype==type) { newsh = hi_s_realloc(sh, hdrlen+len+1); if (newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { newsh = hi_s_malloc(hdrlen+len+1); if (newsh == NULL) return NULL; memcpy((char*)newsh+hdrlen, s, len+1); hi_s_free(sh); s = (char*)newsh+hdrlen; s[-1] = type; hi_sdssetlen(s, len); } hi_sdssetalloc(s, len); return s; } /* Return the total size of the allocation of the specifed hisds string, * including: * 1) The hisds header before the pointer. * 2) The string. * 3) The free buffer at the end if any. * 4) The implicit null term. */ size_t hi_sdsAllocSize(hisds s) { size_t alloc = hi_sdsalloc(s); return hi_sdsHdrSize(s[-1])+alloc+1; } /* Return the pointer of the actual SDS allocation (normally SDS strings * are referenced by the start of the string buffer). */ void *hi_sdsAllocPtr(hisds s) { return (void*) (s-hi_sdsHdrSize(s[-1])); } /* Increment the hisds length and decrements the left free space at the * end of the string according to 'incr'. Also set the null term * in the new end of the string. * * This function is used in order to fix the string length after the * user calls hi_sdsMakeRoomFor(), writes something after the end of * the current string, and finally needs to set the new length. * * Note: it is possible to use a negative increment in order to * right-trim the string. * * Usage example: * * Using hi_sdsIncrLen() and hi_sdsMakeRoomFor() it is possible to mount the * following schema, to cat bytes coming from the kernel to the end of an * hisds string without copying into an intermediate buffer: * * oldlen = hi_hi_sdslen(s); * s = hi_sdsMakeRoomFor(s, BUFFER_SIZE); * nread = read(fd, s+oldlen, BUFFER_SIZE); * ... check for nread <= 0 and handle it ... * hi_sdsIncrLen(s, nread); */ void hi_sdsIncrLen(hisds s, int incr) { unsigned char flags = s[-1]; size_t len; switch(flags&HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char oldlen = HI_SDS_TYPE_5_LEN(flags); assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); *fp = HI_SDS_TYPE_5 | ((oldlen+incr) << HI_SDS_TYPE_BITS); len = oldlen+incr; break; } case HI_SDS_TYPE_8: { HI_SDS_HDR_VAR(8,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case HI_SDS_TYPE_16: { HI_SDS_HDR_VAR(16,s); assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case HI_SDS_TYPE_32: { HI_SDS_HDR_VAR(32,s); assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); len = (sh->len += incr); break; } case HI_SDS_TYPE_64: { HI_SDS_HDR_VAR(64,s); assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); len = (sh->len += incr); break; } default: len = 0; /* Just to avoid compilation warnings. */ } s[len] = '\0'; } /* Grow the hisds to have the specified length. Bytes that were not part of * the original length of the hisds will be set to zero. * * if the specified length is smaller than the current length, no operation * is performed. */ hisds hi_sdsgrowzero(hisds s, size_t len) { size_t curlen = hi_sdslen(s); if (len <= curlen) return s; s = hi_sdsMakeRoomFor(s,len-curlen); if (s == NULL) return NULL; /* Make sure added region doesn't contain garbage */ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ hi_sdssetlen(s, len); return s; } /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the * end of the specified hisds string 's'. * * After the call, the passed hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ hisds hi_sdscatlen(hisds s, const void *t, size_t len) { size_t curlen = hi_sdslen(s); s = hi_sdsMakeRoomFor(s,len); if (s == NULL) return NULL; memcpy(s+curlen, t, len); hi_sdssetlen(s, curlen+len); s[curlen+len] = '\0'; return s; } /* Append the specified null termianted C string to the hisds string 's'. * * After the call, the passed hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ hisds hi_sdscat(hisds s, const char *t) { return hi_sdscatlen(s, t, strlen(t)); } /* Append the specified hisds 't' to the existing hisds 's'. * * After the call, the modified hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ hisds hi_sdscatsds(hisds s, const hisds t) { return hi_sdscatlen(s, t, hi_sdslen(t)); } /* Destructively modify the hisds string 's' to hold the specified binary * safe string pointed by 't' of length 'len' bytes. */ hisds hi_sdscpylen(hisds s, const char *t, size_t len) { if (hi_sdsalloc(s) < len) { s = hi_sdsMakeRoomFor(s,len-hi_sdslen(s)); if (s == NULL) return NULL; } memcpy(s, t, len); s[len] = '\0'; hi_sdssetlen(s, len); return s; } /* Like hi_sdscpylen() but 't' must be a null-terminated string so that the length * of the string is obtained with strlen(). */ hisds hi_sdscpy(hisds s, const char *t) { return hi_sdscpylen(s, t, strlen(t)); } /* Helper for hi_sdscatlonglong() doing the actual number -> string * conversion. 's' must point to a string with room for at least * HI_SDS_LLSTR_SIZE bytes. * * The function returns the length of the null-terminated string * representation stored at 's'. */ #define HI_SDS_LLSTR_SIZE 21 int hi_sdsll2str(char *s, long long value) { char *p, aux; unsigned long long v; size_t l; /* Generate the string representation, this method produces * an reversed string. */ v = (value < 0) ? -value : value; p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); if (value < 0) *p++ = '-'; /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } return l; } /* Identical hi_sdsll2str(), but for unsigned long long type. */ int hi_sdsull2str(char *s, unsigned long long v) { char *p, aux; size_t l; /* Generate the string representation, this method produces * an reversed string. */ p = s; do { *p++ = '0'+(v%10); v /= 10; } while(v); /* Compute length and add null term. */ l = p-s; *p = '\0'; /* Reverse the string. */ p--; while(s < p) { aux = *s; *s = *p; *p = aux; s++; p--; } return l; } /* Create an hisds string from a long long value. It is much faster than: * * hi_sdscatprintf(hi_sdsempty(),"%lld\n", value); */ hisds hi_sdsfromlonglong(long long value) { char buf[HI_SDS_LLSTR_SIZE]; int len = hi_sdsll2str(buf,value); return hi_sdsnewlen(buf,len); } /* Like hi_sdscatprintf() but gets va_list instead of being variadic. */ hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap) { va_list cpy; char staticbuf[1024], *buf = staticbuf, *t; size_t buflen = strlen(fmt)*2; /* We try to start using a static buffer for speed. * If not possible we revert to heap allocation. */ if (buflen > sizeof(staticbuf)) { buf = hi_s_malloc(buflen); if (buf == NULL) return NULL; } else { buflen = sizeof(staticbuf); } /* Try with buffers two times bigger every time we fail to * fit the string in the current buffer size. */ while(1) { buf[buflen-2] = '\0'; va_copy(cpy,ap); vsnprintf(buf, buflen, fmt, cpy); va_end(cpy); if (buf[buflen-2] != '\0') { if (buf != staticbuf) hi_s_free(buf); buflen *= 2; buf = hi_s_malloc(buflen); if (buf == NULL) return NULL; continue; } break; } /* Finally concat the obtained string to the SDS string and return it. */ t = hi_sdscat(s, buf); if (buf != staticbuf) hi_s_free(buf); return t; } /* Append to the hisds string 's' a string obtained using printf-alike format * specifier. * * After the call, the modified hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * * s = hi_sdsnew("Sum is: "); * s = hi_sdscatprintf(s,"%d+%d = %d",a,b,a+b). * * Often you need to create a string from scratch with the printf-alike * format. When this is the need, just use hi_sdsempty() as the target string: * * s = hi_sdscatprintf(hi_sdsempty(), "... your format ...", args); */ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) { va_list ap; char *t; va_start(ap, fmt); t = hi_sdscatvprintf(s,fmt,ap); va_end(ap); return t; } /* This function is similar to hi_sdscatprintf, but much faster as it does * not rely on sprintf() family functions implemented by the libc that * are often very slow. Moreover directly handling the hisds string as * new data is concatenated provides a performance improvement. * * However this function only handles an incompatible subset of printf-alike * format specifiers: * * %s - C String * %S - SDS string * %i - signed int * %I - 64 bit signed integer (long long, int64_t) * %u - unsigned int * %U - 64 bit unsigned integer (unsigned long long, uint64_t) * %% - Verbatim "%" character. */ hisds hi_sdscatfmt(hisds s, char const *fmt, ...) { const char *f = fmt; long i; va_list ap; va_start(ap,fmt); i = hi_sdslen(s); /* Position of the next byte to write to dest str. */ while(*f) { char next, *str; size_t l; long long num; unsigned long long unum; /* Make sure there is always space for at least 1 char. */ if (hi_sdsavail(s)==0) { s = hi_sdsMakeRoomFor(s,1); if (s == NULL) goto fmt_error; } switch(*f) { case '%': next = *(f+1); f++; switch(next) { case 's': case 'S': str = va_arg(ap,char*); l = (next == 's') ? strlen(str) : hi_sdslen(str); if (hi_sdsavail(s) < l) { s = hi_sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,str,l); hi_sdsinclen(s,l); i += l; break; case 'i': case 'I': if (next == 'i') num = va_arg(ap,int); else num = va_arg(ap,long long); { char buf[HI_SDS_LLSTR_SIZE]; l = hi_sdsll2str(buf,num); if (hi_sdsavail(s) < l) { s = hi_sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); hi_sdsinclen(s,l); i += l; } break; case 'u': case 'U': if (next == 'u') unum = va_arg(ap,unsigned int); else unum = va_arg(ap,unsigned long long); { char buf[HI_SDS_LLSTR_SIZE]; l = hi_sdsull2str(buf,unum); if (hi_sdsavail(s) < l) { s = hi_sdsMakeRoomFor(s,l); if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); hi_sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %. */ s[i++] = next; hi_sdsinclen(s,1); break; } break; default: s[i++] = *f; hi_sdsinclen(s,1); break; } f++; } va_end(ap); /* Add null-term */ s[i] = '\0'; return s; fmt_error: va_end(ap); return NULL; } /* Remove the part of the string from left and from right composed just of * contiguous characters found in 'cset', that is a null terminted C string. * * After the call, the modified hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. * * Example: * * s = hi_sdsnew("AA...AA.a.aa.aHelloWorld :::"); * s = hi_sdstrim(s,"Aa. :"); * printf("%s\n", s); * * Output will be just "Hello World". */ hisds hi_sdstrim(hisds s, const char *cset) { char *start, *end, *sp, *ep; size_t len; sp = start = s; ep = end = s+hi_sdslen(s)-1; while(sp <= end && strchr(cset, *sp)) sp++; while(ep > sp && strchr(cset, *ep)) ep--; len = (sp > ep) ? 0 : ((ep-sp)+1); if (s != sp) memmove(s, sp, len); s[len] = '\0'; hi_sdssetlen(s,len); return s; } /* Turn the string into a smaller (or equal) string containing only the * substring specified by the 'start' and 'end' indexes. * * start and end can be negative, where -1 means the last character of the * string, -2 the penultimate character, and so forth. * * The interval is inclusive, so the start and end characters will be part * of the resulting string. * * The string is modified in-place. * * Return value: * -1 (error) if hi_sdslen(s) is larger than maximum positive ssize_t value. * 0 on success. * * Example: * * s = hi_sdsnew("Hello World"); * hi_sdsrange(s,1,-1); => "ello World" */ int hi_sdsrange(hisds s, ssize_t start, ssize_t end) { size_t newlen, len = hi_sdslen(s); if (len > SSIZE_MAX) return -1; if (len == 0) return 0; if (start < 0) { start = len+start; if (start < 0) start = 0; } if (end < 0) { end = len+end; if (end < 0) end = 0; } newlen = (start > end) ? 0 : (end-start)+1; if (newlen != 0) { if (start >= (ssize_t)len) { newlen = 0; } else if (end >= (ssize_t)len) { end = len-1; newlen = (start > end) ? 0 : (end-start)+1; } } else { start = 0; } if (start && newlen) memmove(s, s+start, newlen); s[newlen] = 0; hi_sdssetlen(s,newlen); return 0; } /* Apply tolower() to every character of the sds string 's'. */ void hi_sdstolower(hisds s) { size_t len = hi_sdslen(s), j; for (j = 0; j < len; j++) s[j] = tolower(s[j]); } /* Apply toupper() to every character of the sds string 's'. */ void hi_sdstoupper(hisds s) { size_t len = hi_sdslen(s), j; for (j = 0; j < len; j++) s[j] = toupper(s[j]); } /* Compare two hisds strings s1 and s2 with memcmp(). * * Return value: * * positive if s1 > s2. * negative if s1 < s2. * 0 if s1 and s2 are exactly the same binary string. * * If two strings share exactly the same prefix, but one of the two has * additional characters, the longer string is considered to be greater than * the smaller one. */ int hi_sdscmp(const hisds s1, const hisds s2) { size_t l1, l2, minlen; int cmp; l1 = hi_sdslen(s1); l2 = hi_sdslen(s2); minlen = (l1 < l2) ? l1 : l2; cmp = memcmp(s1,s2,minlen); if (cmp == 0) return l1-l2; return cmp; } /* Split 's' with separator in 'sep'. An array * of hisds strings is returned. *count will be set * by reference to the number of tokens returned. * * On out of memory, zero length string, zero length * separator, NULL is returned. * * Note that 'sep' is able to split a string using * a multi-character separator. For example * hi_sdssplit("foo_-_bar","_-_"); will return two * elements "foo" and "bar". * * This version of the function is binary-safe but * requires length arguments. hi_sdssplit() is just the * same function but for zero-terminated strings. */ hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { int elements = 0, slots = 5, start = 0, j; hisds *tokens; if (seplen < 1 || len < 0) return NULL; tokens = hi_s_malloc(sizeof(hisds)*slots); if (tokens == NULL) return NULL; if (len == 0) { *count = 0; return tokens; } for (j = 0; j < (len-(seplen-1)); j++) { /* make sure there is room for the next element and the final one */ if (slots < elements+2) { hisds *newtokens; slots *= 2; newtokens = hi_s_realloc(tokens,sizeof(hisds)*slots); if (newtokens == NULL) goto cleanup; tokens = newtokens; } /* search the separator */ if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { tokens[elements] = hi_sdsnewlen(s+start,j-start); if (tokens[elements] == NULL) goto cleanup; elements++; start = j+seplen; j = j+seplen-1; /* skip the separator */ } } /* Add the final element. We are sure there is room in the tokens array. */ tokens[elements] = hi_sdsnewlen(s+start,len-start); if (tokens[elements] == NULL) goto cleanup; elements++; *count = elements; return tokens; cleanup: { int i; for (i = 0; i < elements; i++) hi_sdsfree(tokens[i]); hi_s_free(tokens); *count = 0; return NULL; } } /* Free the result returned by hi_sdssplitlen(), or do nothing if 'tokens' is NULL. */ void hi_sdsfreesplitres(hisds *tokens, int count) { if (!tokens) return; while(count--) hi_sdsfree(tokens[count]); hi_s_free(tokens); } /* Append to the hisds string "s" an escaped string representation where * all the non-printable characters (tested with isprint()) are turned into * escapes in the form "\n\r\a...." or "\x". * * After the call, the modified hisds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ hisds hi_sdscatrepr(hisds s, const char *p, size_t len) { s = hi_sdscatlen(s,"\"",1); while(len--) { switch(*p) { case '\\': case '"': s = hi_sdscatprintf(s,"\\%c",*p); break; case '\n': s = hi_sdscatlen(s,"\\n",2); break; case '\r': s = hi_sdscatlen(s,"\\r",2); break; case '\t': s = hi_sdscatlen(s,"\\t",2); break; case '\a': s = hi_sdscatlen(s,"\\a",2); break; case '\b': s = hi_sdscatlen(s,"\\b",2); break; default: if (isprint(*p)) s = hi_sdscatprintf(s,"%c",*p); else s = hi_sdscatprintf(s,"\\x%02x",(unsigned char)*p); break; } p++; } return hi_sdscatlen(s,"\"",1); } /* Helper function for hi_sdssplitargs() that converts a hex digit into an * integer from 0 to 15 */ static int hi_hex_digit_to_int(char c) { switch(c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return 0; } } /* Split a line into arguments, where every argument can be in the * following programming-language REPL-alike form: * * foo bar "newline are supported\n" and "\xff\x00otherstuff" * * The number of arguments is stored into *argc, and an array * of hisds is returned. * * The caller should free the resulting array of hisds strings with * hi_sdsfreesplitres(). * * Note that hi_sdscatrepr() is able to convert back a string into * a quoted string in the same format hi_sdssplitargs() is able to parse. * * The function returns the allocated tokens on success, even when the * input string is empty, or NULL if the input contains unbalanced * quotes or closed quotes followed by non space characters * as in: "foo"bar or "foo' */ hisds *hi_sdssplitargs(const char *line, int *argc) { const char *p = line; char *current = NULL; char **vector = NULL; *argc = 0; while(1) { /* skip blanks */ while(*p && isspace((int) *p)) p++; if (*p) { /* get a token */ int inq=0; /* set to 1 if we are in "quotes" */ int insq=0; /* set to 1 if we are in 'single quotes' */ int done=0; if (current == NULL) current = hi_sdsempty(); while(!done) { if (inq) { if (*p == '\\' && *(p+1) == 'x' && isxdigit((int) *(p+2)) && isxdigit((int) *(p+3))) { unsigned char byte; byte = (hi_hex_digit_to_int(*(p+2))*16)+ hi_hex_digit_to_int(*(p+3)); current = hi_sdscatlen(current,(char*)&byte,1); p += 3; } else if (*p == '\\' && *(p+1)) { char c; p++; switch(*p) { case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'b': c = '\b'; break; case 'a': c = '\a'; break; default: c = *p; break; } current = hi_sdscatlen(current,&c,1); } else if (*p == '"') { /* closing quote must be followed by a space or * nothing at all. */ if (*(p+1) && !isspace((int) *(p+1))) goto err; done=1; } else if (!*p) { /* unterminated quotes */ goto err; } else { current = hi_sdscatlen(current,p,1); } } else if (insq) { if (*p == '\\' && *(p+1) == '\'') { p++; current = hi_sdscatlen(current,"'",1); } else if (*p == '\'') { /* closing quote must be followed by a space or * nothing at all. */ if (*(p+1) && !isspace((int) *(p+1))) goto err; done=1; } else if (!*p) { /* unterminated quotes */ goto err; } else { current = hi_sdscatlen(current,p,1); } } else { switch(*p) { case ' ': case '\n': case '\r': case '\t': case '\0': done=1; break; case '"': inq=1; break; case '\'': insq=1; break; default: current = hi_sdscatlen(current,p,1); break; } } if (*p) p++; } /* add the token to the vector */ { char **new_vector = hi_s_realloc(vector,((*argc)+1)*sizeof(char*)); if (new_vector == NULL) { hi_s_free(vector); return NULL; } vector = new_vector; vector[*argc] = current; (*argc)++; current = NULL; } } else { /* Even on empty input string return something not NULL. */ if (vector == NULL) vector = hi_s_malloc(sizeof(void*)); return vector; } } err: while((*argc)--) hi_sdsfree(vector[*argc]); hi_s_free(vector); if (current) hi_sdsfree(current); *argc = 0; return NULL; } /* Modify the string substituting all the occurrences of the set of * characters specified in the 'from' string to the corresponding character * in the 'to' array. * * For instance: hi_sdsmapchars(mystring, "ho", "01", 2) * will have the effect of turning the string "hello" into "0ell1". * * The function returns the hisds string pointer, that is always the same * as the input pointer since no resize is needed. */ hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen) { size_t j, i, l = hi_sdslen(s); for (j = 0; j < l; j++) { for (i = 0; i < setlen; i++) { if (s[j] == from[i]) { s[j] = to[i]; break; } } } return s; } /* Join an array of C strings using the specified separator (also a C string). * Returns the result as an hisds string. */ hisds hi_sdsjoin(char **argv, int argc, char *sep) { hisds join = hi_sdsempty(); int j; for (j = 0; j < argc; j++) { join = hi_sdscat(join, argv[j]); if (j != argc-1) join = hi_sdscat(join,sep); } return join; } /* Like hi_sdsjoin, but joins an array of SDS strings. */ hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen) { hisds join = hi_sdsempty(); int j; for (j = 0; j < argc; j++) { join = hi_sdscatsds(join, argv[j]); if (j != argc-1) join = hi_sdscatlen(join,sep,seplen); } return join; } /* Wrappers to the allocators used by SDS. Note that SDS will actually * just use the macros defined into sdsalloc.h in order to avoid to pay * the overhead of function calls. Here we define these wrappers only for * the programs SDS is linked to, if they want to touch the SDS internals * even if they use a different allocator. */ void *hi_sds_malloc(size_t size) { return hi_s_malloc(size); } void *hi_sds_realloc(void *ptr, size_t size) { return hi_s_realloc(ptr,size); } void hi_sds_free(void *ptr) { hi_s_free(ptr); } #if defined(HI_SDS_TEST_MAIN) #include #include "testhelp.h" #include "limits.h" #define UNUSED(x) (void)(x) int hi_sdsTest(void) { { hisds x = hi_sdsnew("foo"), y; test_cond("Create a string and obtain the length", hi_sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) hi_sdsfree(x); x = hi_sdsnewlen("foo",2); test_cond("Create a string with specified length", hi_sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) x = hi_sdscat(x,"bar"); test_cond("Strings concatenation", hi_sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); x = hi_sdscpy(x,"a"); test_cond("hi_sdscpy() against an originally longer string", hi_sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) x = hi_sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); test_cond("hi_sdscpy() against an originally shorter string", hi_sdslen(x) == 33 && memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) hi_sdsfree(x); x = hi_sdscatprintf(hi_sdsempty(),"%d",123); test_cond("hi_sdscatprintf() seems working in the base case", hi_sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) hi_sdsfree(x); x = hi_sdsnew("--"); x = hi_sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); test_cond("hi_sdscatfmt() seems working in the base case", hi_sdslen(x) == 60 && memcmp(x,"--Hello Hi! World -9223372036854775808," "9223372036854775807--",60) == 0) printf("[%s]\n",x); hi_sdsfree(x); x = hi_sdsnew("--"); x = hi_sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); test_cond("hi_sdscatfmt() seems working with unsigned numbers", hi_sdslen(x) == 35 && memcmp(x,"--4294967295,18446744073709551615--",35) == 0) hi_sdsfree(x); x = hi_sdsnew(" x "); hi_sdstrim(x," x"); test_cond("hi_sdstrim() works when all chars match", hi_sdslen(x) == 0) hi_sdsfree(x); x = hi_sdsnew(" x "); hi_sdstrim(x," "); test_cond("hi_sdstrim() works when a single char remains", hi_sdslen(x) == 1 && x[0] == 'x') hi_sdsfree(x); x = hi_sdsnew("xxciaoyyy"); hi_sdstrim(x,"xy"); test_cond("hi_sdstrim() correctly trims characters", hi_sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) y = hi_sdsdup(x); hi_sdsrange(y,1,1); test_cond("hi_sdsrange(...,1,1)", hi_sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) hi_sdsfree(y); y = hi_sdsdup(x); hi_sdsrange(y,1,-1); test_cond("hi_sdsrange(...,1,-1)", hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) hi_sdsfree(y); y = hi_sdsdup(x); hi_sdsrange(y,-2,-1); test_cond("hi_sdsrange(...,-2,-1)", hi_sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) hi_sdsfree(y); y = hi_sdsdup(x); hi_sdsrange(y,2,1); test_cond("hi_sdsrange(...,2,1)", hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) hi_sdsfree(y); y = hi_sdsdup(x); hi_sdsrange(y,1,100); test_cond("hi_sdsrange(...,1,100)", hi_sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) hi_sdsfree(y); y = hi_sdsdup(x); hi_sdsrange(y,100,100); test_cond("hi_sdsrange(...,100,100)", hi_sdslen(y) == 0 && memcmp(y,"\0",1) == 0) hi_sdsfree(y); hi_sdsfree(x); x = hi_sdsnew("foo"); y = hi_sdsnew("foa"); test_cond("hi_sdscmp(foo,foa)", hi_sdscmp(x,y) > 0) hi_sdsfree(y); hi_sdsfree(x); x = hi_sdsnew("bar"); y = hi_sdsnew("bar"); test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) == 0) hi_sdsfree(y); hi_sdsfree(x); x = hi_sdsnew("aar"); y = hi_sdsnew("bar"); test_cond("hi_sdscmp(bar,bar)", hi_sdscmp(x,y) < 0) hi_sdsfree(y); hi_sdsfree(x); x = hi_sdsnewlen("\a\n\0foo\r",7); y = hi_sdscatrepr(hi_sdsempty(),x,hi_sdslen(x)); test_cond("hi_sdscatrepr(...data...)", memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) { unsigned int oldfree; char *p; int step = 10, j, i; hi_sdsfree(x); hi_sdsfree(y); x = hi_sdsnew("0"); test_cond("hi_sdsnew() free/len buffers", hi_sdslen(x) == 1 && hi_sdsavail(x) == 0); /* Run the test a few times in order to hit the first two * SDS header types. */ for (i = 0; i < 10; i++) { int oldlen = hi_sdslen(x); x = hi_sdsMakeRoomFor(x,step); int type = x[-1]&HI_SDS_TYPE_MASK; test_cond("sdsMakeRoomFor() len", hi_sdslen(x) == oldlen); if (type != HI_SDS_TYPE_5) { test_cond("hi_sdsMakeRoomFor() free", hi_sdsavail(x) >= step); oldfree = hi_sdsavail(x); } p = x+oldlen; for (j = 0; j < step; j++) { p[j] = 'A'+j; } hi_sdsIncrLen(x,step); } test_cond("hi_sdsMakeRoomFor() content", memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); test_cond("sdsMakeRoomFor() final length",hi_sdslen(x)==101); hi_sdsfree(x); } } test_report(); return 0; } #endif #ifdef HI_SDS_TEST_MAIN int main(void) { return hi_sdsTest(); } #endif redis-8.0.2/deps/hiredis/sds.h000066400000000000000000000231061501533116600161560ustar00rootroot00000000000000/* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef HIREDIS_SDS_H #define HIREDIS_SDS_H #define HI_SDS_MAX_PREALLOC (1024*1024) #ifdef _MSC_VER typedef long long ssize_t; #define SSIZE_MAX (LLONG_MAX >> 1) #ifndef __clang__ #define __attribute__(x) #endif #endif #include #include #include typedef char *hisds; /* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */ struct __attribute__ ((__packed__)) hisdshdr5 { unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr16 { uint16_t len; /* used */ uint16_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr32 { uint32_t len; /* used */ uint32_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; struct __attribute__ ((__packed__)) hisdshdr64 { uint64_t len; /* used */ uint64_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; #define HI_SDS_TYPE_5 0 #define HI_SDS_TYPE_8 1 #define HI_SDS_TYPE_16 2 #define HI_SDS_TYPE_32 3 #define HI_SDS_TYPE_64 4 #define HI_SDS_TYPE_MASK 7 #define HI_SDS_TYPE_BITS 3 #define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))); #define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)))) #define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS) static inline size_t hi_sdslen(const hisds s) { unsigned char flags = s[-1]; switch(flags & HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: return HI_SDS_TYPE_5_LEN(flags); case HI_SDS_TYPE_8: return HI_SDS_HDR(8,s)->len; case HI_SDS_TYPE_16: return HI_SDS_HDR(16,s)->len; case HI_SDS_TYPE_32: return HI_SDS_HDR(32,s)->len; case HI_SDS_TYPE_64: return HI_SDS_HDR(64,s)->len; } return 0; } static inline size_t hi_sdsavail(const hisds s) { unsigned char flags = s[-1]; switch(flags&HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: { return 0; } case HI_SDS_TYPE_8: { HI_SDS_HDR_VAR(8,s); return sh->alloc - sh->len; } case HI_SDS_TYPE_16: { HI_SDS_HDR_VAR(16,s); return sh->alloc - sh->len; } case HI_SDS_TYPE_32: { HI_SDS_HDR_VAR(32,s); return sh->alloc - sh->len; } case HI_SDS_TYPE_64: { HI_SDS_HDR_VAR(64,s); return sh->alloc - sh->len; } } return 0; } static inline void hi_sdssetlen(hisds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; *fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS)); } break; case HI_SDS_TYPE_8: HI_SDS_HDR(8,s)->len = (uint8_t)newlen; break; case HI_SDS_TYPE_16: HI_SDS_HDR(16,s)->len = (uint16_t)newlen; break; case HI_SDS_TYPE_32: HI_SDS_HDR(32,s)->len = (uint32_t)newlen; break; case HI_SDS_TYPE_64: HI_SDS_HDR(64,s)->len = (uint64_t)newlen; break; } } static inline void hi_sdsinclen(hisds s, size_t inc) { unsigned char flags = s[-1]; switch(flags&HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: { unsigned char *fp = ((unsigned char*)s)-1; unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc; *fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS); } break; case HI_SDS_TYPE_8: HI_SDS_HDR(8,s)->len += (uint8_t)inc; break; case HI_SDS_TYPE_16: HI_SDS_HDR(16,s)->len += (uint16_t)inc; break; case HI_SDS_TYPE_32: HI_SDS_HDR(32,s)->len += (uint32_t)inc; break; case HI_SDS_TYPE_64: HI_SDS_HDR(64,s)->len += (uint64_t)inc; break; } } /* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */ static inline size_t hi_sdsalloc(const hisds s) { unsigned char flags = s[-1]; switch(flags & HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: return HI_SDS_TYPE_5_LEN(flags); case HI_SDS_TYPE_8: return HI_SDS_HDR(8,s)->alloc; case HI_SDS_TYPE_16: return HI_SDS_HDR(16,s)->alloc; case HI_SDS_TYPE_32: return HI_SDS_HDR(32,s)->alloc; case HI_SDS_TYPE_64: return HI_SDS_HDR(64,s)->alloc; } return 0; } static inline void hi_sdssetalloc(hisds s, size_t newlen) { unsigned char flags = s[-1]; switch(flags&HI_SDS_TYPE_MASK) { case HI_SDS_TYPE_5: /* Nothing to do, this type has no total allocation info. */ break; case HI_SDS_TYPE_8: HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen; break; case HI_SDS_TYPE_16: HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen; break; case HI_SDS_TYPE_32: HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen; break; case HI_SDS_TYPE_64: HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen; break; } } hisds hi_sdsnewlen(const void *init, size_t initlen); hisds hi_sdsnew(const char *init); hisds hi_sdsempty(void); hisds hi_sdsdup(const hisds s); void hi_sdsfree(hisds s); hisds hi_sdsgrowzero(hisds s, size_t len); hisds hi_sdscatlen(hisds s, const void *t, size_t len); hisds hi_sdscat(hisds s, const char *t); hisds hi_sdscatsds(hisds s, const hisds t); hisds hi_sdscpylen(hisds s, const char *t, size_t len); hisds hi_sdscpy(hisds s, const char *t); hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap); #ifdef __GNUC__ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) __attribute__((format(printf, 2, 3))); #else hisds hi_sdscatprintf(hisds s, const char *fmt, ...); #endif hisds hi_sdscatfmt(hisds s, char const *fmt, ...); hisds hi_sdstrim(hisds s, const char *cset); int hi_sdsrange(hisds s, ssize_t start, ssize_t end); void hi_sdsupdatelen(hisds s); void hi_sdsclear(hisds s); int hi_sdscmp(const hisds s1, const hisds s2); hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); void hi_sdsfreesplitres(hisds *tokens, int count); void hi_sdstolower(hisds s); void hi_sdstoupper(hisds s); hisds hi_sdsfromlonglong(long long value); hisds hi_sdscatrepr(hisds s, const char *p, size_t len); hisds *hi_sdssplitargs(const char *line, int *argc); hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen); hisds hi_sdsjoin(char **argv, int argc, char *sep); hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen); /* Low level functions exposed to the user API */ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen); void hi_sdsIncrLen(hisds s, int incr); hisds hi_sdsRemoveFreeSpace(hisds s); size_t hi_sdsAllocSize(hisds s); void *hi_sdsAllocPtr(hisds s); /* Export the allocator used by SDS to the program using SDS. * Sometimes the program SDS is linked to, may use a different set of * allocators, but may want to allocate or free things that SDS will * respectively free or allocate. */ void *hi_sds_malloc(size_t size); void *hi_sds_realloc(void *ptr, size_t size); void hi_sds_free(void *ptr); #ifdef REDIS_TEST int hi_sdsTest(int argc, char *argv[]); #endif #endif /* HIREDIS_SDS_H */ redis-8.0.2/deps/hiredis/sdsalloc.h000066400000000000000000000041221501533116600171660ustar00rootroot00000000000000/* SDSLib 2.0 -- A C dynamic strings library * * Copyright (c) 2006-2015, Salvatore Sanfilippo * Copyright (c) 2015, Oran Agra * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ /* SDS allocator selection. * * This file is used in order to change the SDS allocator at compile time. * Just define the following defines to what you want to use. Also add * the include of your alternate allocator if needed (not needed in order * to use the default libc allocator). */ #include "alloc.h" #define hi_s_malloc hi_malloc #define hi_s_realloc hi_realloc #define hi_s_free hi_free redis-8.0.2/deps/hiredis/sdscompat.h000066400000000000000000000067441501533116600173730ustar00rootroot00000000000000/* * Copyright (c) 2020, Michael Grunder * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ /* * SDS compatibility header. * * This simple file maps sds types and calls to their unique hiredis symbol names. * It's useful when we build Hiredis as a dependency of Redis and want to call * Hiredis' sds symbols rather than the ones built into Redis, as the libraries * have slightly diverged and could cause hard to track down ABI incompatibility * bugs. * */ #ifndef HIREDIS_SDS_COMPAT #define HIREDIS_SDS_COMPAT #define sds hisds #define sdslen hi_sdslen #define sdsavail hi_sdsavail #define sdssetlen hi_sdssetlen #define sdsinclen hi_sdsinclen #define sdsalloc hi_sdsalloc #define sdssetalloc hi_sdssetalloc #define sdsAllocPtr hi_sdsAllocPtr #define sdsAllocSize hi_sdsAllocSize #define sdscat hi_sdscat #define sdscatfmt hi_sdscatfmt #define sdscatlen hi_sdscatlen #define sdscatprintf hi_sdscatprintf #define sdscatrepr hi_sdscatrepr #define sdscatsds hi_sdscatsds #define sdscatvprintf hi_sdscatvprintf #define sdsclear hi_sdsclear #define sdscmp hi_sdscmp #define sdscpy hi_sdscpy #define sdscpylen hi_sdscpylen #define sdsdup hi_sdsdup #define sdsempty hi_sdsempty #define sds_free hi_sds_free #define sdsfree hi_sdsfree #define sdsfreesplitres hi_sdsfreesplitres #define sdsfromlonglong hi_sdsfromlonglong #define sdsgrowzero hi_sdsgrowzero #define sdsIncrLen hi_sdsIncrLen #define sdsjoin hi_sdsjoin #define sdsjoinsds hi_sdsjoinsds #define sdsll2str hi_sdsll2str #define sdsMakeRoomFor hi_sdsMakeRoomFor #define sds_malloc hi_sds_malloc #define sdsmapchars hi_sdsmapchars #define sdsnew hi_sdsnew #define sdsnewlen hi_sdsnewlen #define sdsrange hi_sdsrange #define sds_realloc hi_sds_realloc #define sdsRemoveFreeSpace hi_sdsRemoveFreeSpace #define sdssplitargs hi_sdssplitargs #define sdssplitlen hi_sdssplitlen #define sdstolower hi_sdstolower #define sdstoupper hi_sdstoupper #define sdstrim hi_sdstrim #define sdsull2str hi_sdsull2str #define sdsupdatelen hi_sdsupdatelen #endif /* HIREDIS_SDS_COMPAT */ redis-8.0.2/deps/hiredis/sockcompat.c000066400000000000000000000232411501533116600175230ustar00rootroot00000000000000/* * Copyright (c) 2019, Marcus Geelnard * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #define REDIS_SOCKCOMPAT_IMPLEMENTATION #include "sockcompat.h" #ifdef _WIN32 static int _wsaErrorToErrno(int err) { switch (err) { case WSAEWOULDBLOCK: return EWOULDBLOCK; case WSAEINPROGRESS: return EINPROGRESS; case WSAEALREADY: return EALREADY; case WSAENOTSOCK: return ENOTSOCK; case WSAEDESTADDRREQ: return EDESTADDRREQ; case WSAEMSGSIZE: return EMSGSIZE; case WSAEPROTOTYPE: return EPROTOTYPE; case WSAENOPROTOOPT: return ENOPROTOOPT; case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; case WSAEOPNOTSUPP: return EOPNOTSUPP; case WSAEAFNOSUPPORT: return EAFNOSUPPORT; case WSAEADDRINUSE: return EADDRINUSE; case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; case WSAENETDOWN: return ENETDOWN; case WSAENETUNREACH: return ENETUNREACH; case WSAENETRESET: return ENETRESET; case WSAECONNABORTED: return ECONNABORTED; case WSAECONNRESET: return ECONNRESET; case WSAENOBUFS: return ENOBUFS; case WSAEISCONN: return EISCONN; case WSAENOTCONN: return ENOTCONN; case WSAETIMEDOUT: return ETIMEDOUT; case WSAECONNREFUSED: return ECONNREFUSED; case WSAELOOP: return ELOOP; case WSAENAMETOOLONG: return ENAMETOOLONG; case WSAEHOSTUNREACH: return EHOSTUNREACH; case WSAENOTEMPTY: return ENOTEMPTY; default: /* We just return a generic I/O error if we could not find a relevant error. */ return EIO; } } static void _updateErrno(int success) { errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError()); } static int _initWinsock() { static int s_initialized = 0; if (!s_initialized) { static WSADATA wsadata; int err = WSAStartup(MAKEWORD(2,2), &wsadata); if (err != 0) { errno = _wsaErrorToErrno(err); return 0; } s_initialized = 1; } return 1; } int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { /* Note: This function is likely to be called before other functions, so run init here. */ if (!_initWinsock()) { return EAI_FAIL; } switch (getaddrinfo(node, service, hints, res)) { case 0: return 0; case WSATRY_AGAIN: return EAI_AGAIN; case WSAEINVAL: return EAI_BADFLAGS; case WSAEAFNOSUPPORT: return EAI_FAMILY; case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY; case WSAHOST_NOT_FOUND: return EAI_NONAME; case WSATYPE_NOT_FOUND: return EAI_SERVICE; case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE; default: return EAI_FAIL; /* Including WSANO_RECOVERY */ } } const char *win32_gai_strerror(int errcode) { switch (errcode) { case 0: errcode = 0; break; case EAI_AGAIN: errcode = WSATRY_AGAIN; break; case EAI_BADFLAGS: errcode = WSAEINVAL; break; case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break; case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break; case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break; case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break; case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break; default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */ } return gai_strerror(errcode); } void win32_freeaddrinfo(struct addrinfo *res) { freeaddrinfo(res); } SOCKET win32_socket(int domain, int type, int protocol) { SOCKET s; /* Note: This function is likely to be called before other functions, so run init here. */ if (!_initWinsock()) { return INVALID_SOCKET; } _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET); return s; } int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) { int ret = ioctlsocket(fd, (long)request, argp); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { int ret = bind(sockfd, addr, addrlen); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { int ret = connect(sockfd, addr, addrlen); _updateErrno(ret != SOCKET_ERROR); /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX * logic consistent. * Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is * translated to EIO. Convert appropriately */ int err = errno; if (err == EWOULDBLOCK) { errno = EINPROGRESS; } else if (err == EIO) { errno = EALREADY; } return ret != SOCKET_ERROR ? ret : -1; } int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { if (*optlen >= sizeof (struct timeval)) { struct timeval *tv = optval; DWORD timeout = 0; socklen_t dwlen = 0; ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); tv->tv_sec = timeout / 1000; tv->tv_usec = (timeout * 1000) % 1000000; } else { ret = WSAEFAULT; } *optlen = sizeof (struct timeval); } else { ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); } if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) { /* translate SO_ERROR codes, if non-zero */ int err = *(int*)optval; if (err != 0) { err = _wsaErrorToErrno(err); *(int*)optval = err; } } _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { int ret = 0; if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { const struct timeval *tv = optval; DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); } else { ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); } _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_close(SOCKET fd) { int ret = closesocket(fd); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) { int ret = recv(sockfd, (char*)buf, (int)len, flags); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) { int ret = send(sockfd, (const char*)buf, (int)len, flags); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { int ret = WSAPoll(fds, nfds, timeout); _updateErrno(ret != SOCKET_ERROR); return ret != SOCKET_ERROR ? ret : -1; } int win32_redisKeepAlive(SOCKET sockfd, int interval_ms) { struct tcp_keepalive cfg; DWORD bytes_in; int res; cfg.onoff = 1; cfg.keepaliveinterval = interval_ms; cfg.keepalivetime = interval_ms; res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg, sizeof(struct tcp_keepalive), NULL, 0, &bytes_in, NULL, NULL); return res == 0 ? 0 : _wsaErrorToErrno(res); } #endif /* _WIN32 */ redis-8.0.2/deps/hiredis/sockcompat.h000066400000000000000000000104711501533116600175310ustar00rootroot00000000000000/* * Copyright (c) 2019, Marcus Geelnard * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef __SOCKCOMPAT_H #define __SOCKCOMPAT_H #ifndef _WIN32 /* For POSIX systems we use the standard BSD socket API. */ #include #include #include #include #include #include #include #include #include #else /* For Windows we use winsock. */ #undef _WIN32_WINNT #define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ #include #include #include #include #include #ifdef _MSC_VER typedef long long ssize_t; #endif /* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); const char *win32_gai_strerror(int errcode); void win32_freeaddrinfo(struct addrinfo *res); SOCKET win32_socket(int domain, int type, int protocol); int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); int win32_close(SOCKET fd); ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); typedef ULONG nfds_t; int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); int win32_redisKeepAlive(SOCKET sockfd, int interval_ms); #ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION #define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) #undef gai_strerror #define gai_strerror(errcode) win32_gai_strerror(errcode) #define freeaddrinfo(res) win32_freeaddrinfo(res) #define socket(domain, type, protocol) win32_socket(domain, type, protocol) #define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) #define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) #define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) #define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) #define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) #define close(fd) win32_close(fd) #define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) #define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) #define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) #endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */ #endif /* _WIN32 */ #endif /* __SOCKCOMPAT_H */ redis-8.0.2/deps/hiredis/ssl.c000066400000000000000000000420671501533116600161700ustar00rootroot00000000000000/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * Copyright (c) 2010-2011, Pieter Noordhuis * Copyright (c) 2019, Redis Labs * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include "hiredis.h" #include "async.h" #include "net.h" #include #include #include #ifdef _WIN32 #include #include #ifdef OPENSSL_IS_BORINGSSL #undef X509_NAME #undef X509_EXTENSIONS #undef PKCS7_ISSUER_AND_SERIAL #undef PKCS7_SIGNER_INFO #undef OCSP_REQUEST #undef OCSP_RESPONSE #endif #else #include #endif #include #include #include "win32.h" #include "async_private.h" #include "hiredis_ssl.h" #define OPENSSL_1_1_0 0x10100000L void __redisSetError(redisContext *c, int type, const char *str); struct redisSSLContext { /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */ SSL_CTX *ssl_ctx; /* Requested SNI, or NULL */ char *server_name; }; /* The SSL connection context is attached to SSL/TLS connections as a privdata. */ typedef struct redisSSL { /** * OpenSSL SSL object. */ SSL *ssl; /** * SSL_write() requires to be called again with the same arguments it was * previously called with in the event of an SSL_read/SSL_write situation */ size_t lastLen; /** Whether the SSL layer requires read (possibly before a write) */ int wantRead; /** * Whether a write was requested prior to a read. If set, the write() * should resume whenever a read takes place, if possible */ int pendingWrite; } redisSSL; /* Forward declaration */ redisContextFuncs redisContextSSLFuncs; /** * OpenSSL global initialization and locking handling callbacks. * Note that this is only required for OpenSSL < 1.1.0. */ #if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 #define HIREDIS_USE_CRYPTO_LOCKS #endif #ifdef HIREDIS_USE_CRYPTO_LOCKS #ifdef _WIN32 typedef CRITICAL_SECTION sslLockType; static void sslLockInit(sslLockType* l) { InitializeCriticalSection(l); } static void sslLockAcquire(sslLockType* l) { EnterCriticalSection(l); } static void sslLockRelease(sslLockType* l) { LeaveCriticalSection(l); } #else typedef pthread_mutex_t sslLockType; static void sslLockInit(sslLockType *l) { pthread_mutex_init(l, NULL); } static void sslLockAcquire(sslLockType *l) { pthread_mutex_lock(l); } static void sslLockRelease(sslLockType *l) { pthread_mutex_unlock(l); } #endif static sslLockType* ossl_locks; static void opensslDoLock(int mode, int lkid, const char *f, int line) { sslLockType *l = ossl_locks + lkid; if (mode & CRYPTO_LOCK) { sslLockAcquire(l); } else { sslLockRelease(l); } (void)f; (void)line; } static int initOpensslLocks(void) { unsigned ii, nlocks; if (CRYPTO_get_locking_callback() != NULL) { /* Someone already set the callback before us. Don't destroy it! */ return REDIS_OK; } nlocks = CRYPTO_num_locks(); ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks); if (ossl_locks == NULL) return REDIS_ERR; for (ii = 0; ii < nlocks; ii++) { sslLockInit(ossl_locks + ii); } CRYPTO_set_locking_callback(opensslDoLock); return REDIS_OK; } #endif /* HIREDIS_USE_CRYPTO_LOCKS */ int redisInitOpenSSL(void) { SSL_library_init(); #ifdef HIREDIS_USE_CRYPTO_LOCKS initOpensslLocks(); #endif return REDIS_OK; } /** * redisSSLContext helper context destruction. */ const char *redisSSLContextGetError(redisSSLContextError error) { switch (error) { case REDIS_SSL_CTX_NONE: return "No Error"; case REDIS_SSL_CTX_CREATE_FAILED: return "Failed to create OpenSSL SSL_CTX"; case REDIS_SSL_CTX_CERT_KEY_REQUIRED: return "Client cert and key must both be specified or skipped"; case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED: return "Failed to load CA Certificate or CA Path"; case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED: return "Failed to load client certificate"; case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED: return "Failed to load private key"; case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED: return "Failed to open system certificate store"; case REDIS_SSL_CTX_OS_CERT_ADD_FAILED: return "Failed to add CA certificates obtained from system to the SSL context"; default: return "Unknown error code"; } } void redisFreeSSLContext(redisSSLContext *ctx) { if (!ctx) return; if (ctx->server_name) { hi_free(ctx->server_name); ctx->server_name = NULL; } if (ctx->ssl_ctx) { SSL_CTX_free(ctx->ssl_ctx); ctx->ssl_ctx = NULL; } hi_free(ctx); } /** * redisSSLContext helper context initialization. */ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath, const char *cert_filename, const char *private_key_filename, const char *server_name, redisSSLContextError *error) { redisSSLOptions options = { .cacert_filename = cacert_filename, .capath = capath, .cert_filename = cert_filename, .private_key_filename = private_key_filename, .server_name = server_name, .verify_mode = REDIS_SSL_VERIFY_PEER, }; return redisCreateSSLContextWithOptions(&options, error); } redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) { const char *cacert_filename = options->cacert_filename; const char *capath = options->capath; const char *cert_filename = options->cert_filename; const char *private_key_filename = options->private_key_filename; const char *server_name = options->server_name; #ifdef _WIN32 HCERTSTORE win_store = NULL; PCCERT_CONTEXT win_ctx = NULL; #endif redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext)); if (ctx == NULL) goto error; const SSL_METHOD *ssl_method; #if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0 ssl_method = TLS_client_method(); #else ssl_method = SSLv23_client_method(); #endif ctx->ssl_ctx = SSL_CTX_new(ssl_method); if (!ctx->ssl_ctx) { if (error) *error = REDIS_SSL_CTX_CREATE_FAILED; goto error; } #if OPENSSL_VERSION_NUMBER >= OPENSSL_1_1_0 SSL_CTX_set_min_proto_version(ctx->ssl_ctx, TLS1_2_VERSION); #else SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); #endif SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL); if ((cert_filename != NULL && private_key_filename == NULL) || (private_key_filename != NULL && cert_filename == NULL)) { if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED; goto error; } if (capath || cacert_filename) { #ifdef _WIN32 if (0 == strcmp(cacert_filename, "wincert")) { win_store = CertOpenSystemStore(NULL, "Root"); if (!win_store) { if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED; goto error; } X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx); while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) { X509* x509 = NULL; x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded); if (x509) { if ((1 != X509_STORE_add_cert(store, x509)) || (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509))) { if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED; goto error; } X509_free(x509); } } CertFreeCertificateContext(win_ctx); CertCloseStore(win_store, 0); } else #endif if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) { if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; goto error; } } else { if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) { if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED; goto error; } } if (cert_filename) { if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) { if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED; goto error; } if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) { if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED; goto error; } } if (server_name) ctx->server_name = hi_strdup(server_name); return ctx; error: #ifdef _WIN32 CertFreeCertificateContext(win_ctx); CertCloseStore(win_store, 0); #endif redisFreeSSLContext(ctx); return NULL; } /** * SSL Connection initialization. */ static int redisSSLConnect(redisContext *c, SSL *ssl) { if (c->privctx) { __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); return REDIS_ERR; } redisSSL *rssl = hi_calloc(1, sizeof(redisSSL)); if (rssl == NULL) { __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } c->funcs = &redisContextSSLFuncs; rssl->ssl = ssl; SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_set_fd(rssl->ssl, c->fd); SSL_set_connect_state(rssl->ssl); ERR_clear_error(); int rv = SSL_connect(rssl->ssl); if (rv == 1) { c->privctx = rssl; return REDIS_OK; } rv = SSL_get_error(rssl->ssl, rv); if (((c->flags & REDIS_BLOCK) == 0) && (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { c->privctx = rssl; return REDIS_OK; } if (c->err == 0) { char err[512]; if (rv == SSL_ERROR_SYSCALL) snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno)); else { unsigned long e = ERR_peek_last_error(); snprintf(err,sizeof(err)-1,"SSL_connect failed: %s", ERR_reason_error_string(e)); } __redisSetError(c, REDIS_ERR_IO, err); } hi_free(rssl); return REDIS_ERR; } /** * A wrapper around redisSSLConnect() for users who manage their own context and * create their own SSL object. */ int redisInitiateSSL(redisContext *c, SSL *ssl) { return redisSSLConnect(c, ssl); } /** * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't * manage their own SSL objects. */ int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx) { if (!c || !redis_ssl_ctx) return REDIS_ERR; /* We want to verify that redisSSLConnect() won't fail on this, as it will * not own the SSL object in that case and we'll end up leaking. */ if (c->privctx) return REDIS_ERR; SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx); if (!ssl) { __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); goto error; } if (redis_ssl_ctx->server_name) { if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) { __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI"); goto error; } } if (redisSSLConnect(c, ssl) != REDIS_OK) { goto error; } return REDIS_OK; error: if (ssl) SSL_free(ssl); return REDIS_ERR; } static int maybeCheckWant(redisSSL *rssl, int rv) { /** * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set * and true is returned. False is returned otherwise */ if (rv == SSL_ERROR_WANT_READ) { rssl->wantRead = 1; return 1; } else if (rv == SSL_ERROR_WANT_WRITE) { rssl->pendingWrite = 1; return 1; } else { return 0; } } /** * Implementation of redisContextFuncs for SSL connections. */ static void redisSSLFree(void *privctx){ redisSSL *rsc = privctx; if (!rsc) return; if (rsc->ssl) { SSL_free(rsc->ssl); rsc->ssl = NULL; } hi_free(rsc); } static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) { redisSSL *rssl = c->privctx; int nread = SSL_read(rssl->ssl, buf, bufcap); if (nread > 0) { return nread; } else if (nread == 0) { __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); return -1; } else { int err = SSL_get_error(rssl->ssl, nread); if (c->flags & REDIS_BLOCK) { /** * In blocking mode, we should never end up in a situation where * we get an error without it being an actual error, except * in the case of EINTR, which can be spuriously received from * debuggers or whatever. */ if (errno == EINTR) { return 0; } else { const char *msg = NULL; if (errno == EAGAIN) { msg = "Resource temporarily unavailable"; } __redisSetError(c, REDIS_ERR_IO, msg); return -1; } } /** * We can very well get an EWOULDBLOCK/EAGAIN, however */ if (maybeCheckWant(rssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); return -1; } } } static ssize_t redisSSLWrite(redisContext *c) { redisSSL *rssl = c->privctx; size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf); int rv = SSL_write(rssl->ssl, c->obuf, len); if (rv > 0) { rssl->lastLen = 0; } else if (rv < 0) { rssl->lastLen = len; int err = SSL_get_error(rssl->ssl, rv); if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) { return 0; } else { __redisSetError(c, REDIS_ERR_IO, NULL); return -1; } } return rv; } static void redisSSLAsyncRead(redisAsyncContext *ac) { int rv; redisSSL *rssl = ac->c.privctx; redisContext *c = &ac->c; rssl->wantRead = 0; if (rssl->pendingWrite) { int done; /* This is probably just a write event */ rssl->pendingWrite = 0; rv = redisBufferWrite(c, &done); if (rv == REDIS_ERR) { __redisAsyncDisconnect(ac); return; } else if (!done) { _EL_ADD_WRITE(ac); } } rv = redisBufferRead(c); if (rv == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { _EL_ADD_READ(ac); redisProcessCallbacks(ac); } } static void redisSSLAsyncWrite(redisAsyncContext *ac) { int rv, done = 0; redisSSL *rssl = ac->c.privctx; redisContext *c = &ac->c; rssl->pendingWrite = 0; rv = redisBufferWrite(c, &done); if (rv == REDIS_ERR) { __redisAsyncDisconnect(ac); return; } if (!done) { if (rssl->wantRead) { /* Need to read-before-write */ rssl->pendingWrite = 1; _EL_DEL_WRITE(ac); } else { /* No extra reads needed, just need to write more */ _EL_ADD_WRITE(ac); } } else { /* Already done! */ _EL_DEL_WRITE(ac); } /* Always reschedule a read */ _EL_ADD_READ(ac); } redisContextFuncs redisContextSSLFuncs = { .close = redisNetClose, .free_privctx = redisSSLFree, .async_read = redisSSLAsyncRead, .async_write = redisSSLAsyncWrite, .read = redisSSLRead, .write = redisSSLWrite }; redis-8.0.2/deps/hiredis/test.c000066400000000000000000002464061501533116600163510ustar00rootroot00000000000000#include "fmacros.h" #include "sockcompat.h" #include #include #include #ifndef _WIN32 #include #include #endif #include #include #include #include #include #include "hiredis.h" #include "async.h" #include "adapters/poll.h" #ifdef HIREDIS_TEST_SSL #include "hiredis_ssl.h" #endif #ifdef HIREDIS_TEST_ASYNC #include "adapters/libevent.h" #include #endif #include "net.h" #include "win32.h" enum connection_type { CONN_TCP, CONN_UNIX, CONN_FD, CONN_SSL }; struct config { enum connection_type type; struct timeval connect_timeout; struct { const char *host; int port; } tcp; struct { const char *path; } unix_sock; struct { const char *host; int port; const char *ca_cert; const char *cert; const char *key; } ssl; }; struct privdata { int dtor_counter; }; struct pushCounters { int nil; int str; }; static int insecure_calloc_calls; #ifdef HIREDIS_TEST_SSL redisSSLContext *_ssl_ctx = NULL; #endif /* The following lines make up our testing "framework" :) */ static int tests = 0, fails = 0, skips = 0; #define test(_s) { printf("#%02d ", ++tests); printf(_s); } #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} #define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; } static void millisleep(int ms) { #ifdef _MSC_VER Sleep(ms); #else usleep(ms*1000); #endif } static long long usec(void) { #ifndef _MSC_VER struct timeval tv; gettimeofday(&tv,NULL); return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; #else FILETIME ft; GetSystemTimeAsFileTime(&ft); return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10; #endif } /* The assert() calls below have side effects, so we need assert() * even if we are compiling without asserts (-DNDEBUG). */ #ifdef NDEBUG #undef assert #define assert(e) (void)(e) #endif /* Helper to extract Redis version information. Aborts on any failure. */ #define REDIS_VERSION_FIELD "redis_version:" void get_redis_version(redisContext *c, int *majorptr, int *minorptr) { redisReply *reply; char *eptr, *s, *e; int major, minor; reply = redisCommand(c, "INFO"); if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING) goto abort; if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL) goto abort; s += strlen(REDIS_VERSION_FIELD); /* We need a field terminator and at least 'x.y.z' (5) bytes of data */ if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5) goto abort; /* Extract version info */ major = strtol(s, &eptr, 10); if (*eptr != '.') goto abort; minor = strtol(eptr+1, NULL, 10); /* Push info the caller wants */ if (majorptr) *majorptr = major; if (minorptr) *minorptr = minor; freeReplyObject(reply); return; abort: freeReplyObject(reply); fprintf(stderr, "Error: Cannot determine Redis version, aborting\n"); exit(1); } static redisContext *select_database(redisContext *c) { redisReply *reply; /* Switch to DB 9 for testing, now that we know we can chat. */ reply = redisCommand(c,"SELECT 9"); assert(reply != NULL); freeReplyObject(reply); /* Make sure the DB is emtpy */ reply = redisCommand(c,"DBSIZE"); assert(reply != NULL); if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { /* Awesome, DB 9 is empty and we can continue. */ freeReplyObject(reply); } else { printf("Database #9 is not empty, test can not continue\n"); exit(1); } return c; } /* Switch protocol */ static void send_hello(redisContext *c, int version) { redisReply *reply; int expected; reply = redisCommand(c, "HELLO %d", version); expected = version == 3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY; assert(reply != NULL && reply->type == expected); freeReplyObject(reply); } /* Togggle client tracking */ static void send_client_tracking(redisContext *c, const char *str) { redisReply *reply; reply = redisCommand(c, "CLIENT TRACKING %s", str); assert(reply != NULL && reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); } static int disconnect(redisContext *c, int keep_fd) { redisReply *reply; /* Make sure we're on DB 9. */ reply = redisCommand(c,"SELECT 9"); assert(reply != NULL); freeReplyObject(reply); reply = redisCommand(c,"FLUSHDB"); assert(reply != NULL); freeReplyObject(reply); /* Free the context as well, but keep the fd if requested. */ if (keep_fd) return redisFreeKeepFd(c); redisFree(c); return -1; } static void do_ssl_handshake(redisContext *c) { #ifdef HIREDIS_TEST_SSL redisInitiateSSLWithContext(c, _ssl_ctx); if (c->err) { printf("SSL error: %s\n", c->errstr); redisFree(c); exit(1); } #else (void) c; #endif } static redisContext *do_connect(struct config config) { redisContext *c = NULL; if (config.type == CONN_TCP) { c = redisConnect(config.tcp.host, config.tcp.port); } else if (config.type == CONN_SSL) { c = redisConnect(config.ssl.host, config.ssl.port); } else if (config.type == CONN_UNIX) { c = redisConnectUnix(config.unix_sock.path); } else if (config.type == CONN_FD) { /* Create a dummy connection just to get an fd to inherit */ redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path); if (dummy_ctx) { int fd = disconnect(dummy_ctx, 1); printf("Connecting to inherited fd %d\n", fd); c = redisConnectFd(fd); } } else { assert(NULL); } if (c == NULL) { printf("Connection error: can't allocate redis context\n"); exit(1); } else if (c->err) { printf("Connection error: %s\n", c->errstr); redisFree(c); exit(1); } if (config.type == CONN_SSL) { do_ssl_handshake(c); } return select_database(c); } static void do_reconnect(redisContext *c, struct config config) { redisReconnect(c); if (config.type == CONN_SSL) { do_ssl_handshake(c); } } static void test_format_commands(void) { char *cmd; int len; test("Format command without interpolation: "); len = redisFormatCommand(&cmd,"SET foo bar"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command with %%s string interpolation: "); len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command with %%s and an empty string: "); len = redisFormatCommand(&cmd,"SET %s %s","foo",""); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(0+2)); hi_free(cmd); test("Format command with an empty string in between proper interpolations: "); len = redisFormatCommand(&cmd,"SET %s %s","","foo"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && len == 4+4+(3+2)+4+(0+2)+4+(3+2)); hi_free(cmd); test("Format command with %%b string interpolation: "); len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command with %%b and an empty string: "); len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(0+2)); hi_free(cmd); test("Format command with literal %%: "); len = redisFormatCommand(&cmd,"SET %% %%"); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && len == 4+4+(3+2)+4+(1+2)+4+(1+2)); hi_free(cmd); /* Vararg width depends on the type. These tests make sure that the * width is correctly determined using the format and subsequent varargs * can correctly be interpolated. */ #define INTEGER_WIDTH_TEST(fmt, type) do { \ type value = 123; \ test("Format command with printf-delegation (" #type "): "); \ len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ len == 4+5+(12+2)+4+(9+2)); \ hi_free(cmd); \ } while(0) #define FLOAT_WIDTH_TEST(type) do { \ type value = 123.0; \ test("Format command with printf-delegation (" #type "): "); \ len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ len == 4+5+(12+2)+4+(9+2)); \ hi_free(cmd); \ } while(0) INTEGER_WIDTH_TEST("d", int); INTEGER_WIDTH_TEST("hhd", char); INTEGER_WIDTH_TEST("hd", short); INTEGER_WIDTH_TEST("ld", long); INTEGER_WIDTH_TEST("lld", long long); INTEGER_WIDTH_TEST("u", unsigned int); INTEGER_WIDTH_TEST("hhu", unsigned char); INTEGER_WIDTH_TEST("hu", unsigned short); INTEGER_WIDTH_TEST("lu", unsigned long); INTEGER_WIDTH_TEST("llu", unsigned long long); FLOAT_WIDTH_TEST(float); FLOAT_WIDTH_TEST(double); test("Format command with unhandled printf format (specifier 'p' not supported): "); len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); test_cond(len == -1); test("Format command with invalid printf format (specifier missing): "); len = redisFormatCommand(&cmd,"%-"); test_cond(len == -1); const char *argv[3]; argv[0] = "SET"; argv[1] = "foo\0xxx"; argv[2] = "bar"; size_t lens[3] = { 3, 7, 3 }; int argc = 3; test("Format command by passing argc/argv without lengths: "); len = redisFormatCommandArgv(&cmd,argc,argv,NULL); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_free(cmd); test("Format command by passing argc/argv with lengths: "); len = redisFormatCommandArgv(&cmd,argc,argv,lens); test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(7+2)+4+(3+2)); hi_free(cmd); hisds sds_cmd; sds_cmd = NULL; test("Format command into hisds by passing argc/argv without lengths: "); len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(3+2)+4+(3+2)); hi_sdsfree(sds_cmd); sds_cmd = NULL; test("Format command into hisds by passing argc/argv with lengths: "); len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && len == 4+4+(3+2)+4+(7+2)+4+(3+2)); hi_sdsfree(sds_cmd); } static void test_append_formatted_commands(struct config config) { redisContext *c; redisReply *reply; char *cmd; int len; c = do_connect(config); test("Append format command: "); len = redisFormatCommand(&cmd, "SET foo bar"); test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); assert(redisGetReply(c, (void*)&reply) == REDIS_OK); hi_free(cmd); freeReplyObject(reply); disconnect(c, 0); } static void test_tcp_options(struct config cfg) { redisContext *c; c = do_connect(cfg); test("We can enable TCP_KEEPALIVE: "); test_cond(redisEnableKeepAlive(c) == REDIS_OK); #ifdef TCP_USER_TIMEOUT test("We can set TCP_USER_TIMEOUT: "); test_cond(redisSetTcpUserTimeout(c, 100) == REDIS_OK); #else test("Setting TCP_USER_TIMEOUT errors when unsupported: "); test_cond(redisSetTcpUserTimeout(c, 100) == REDIS_ERR && c->err == REDIS_ERR_IO); #endif redisFree(c); } static void test_reply_reader(void) { redisReader *reader; void *reply, *root; int ret; int i; test("Error handling in reply parser: "); reader = redisReaderCreate(); redisReaderFeed(reader,(char*)"@foo\r\n",6); ret = redisReaderGetReply(reader,NULL); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); redisReaderFree(reader); /* when the reply already contains multiple items, they must be free'd * on an error. valgrind will bark when this doesn't happen. */ test("Memory cleanup in reply parser: "); reader = redisReaderCreate(); redisReaderFeed(reader,(char*)"*2\r\n",4); redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); redisReaderFeed(reader,(char*)"@foo\r\n",6); ret = redisReaderGetReply(reader,NULL); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); redisReaderFree(reader); reader = redisReaderCreate(); test("Can handle arbitrarily nested multi-bulks: "); for (i = 0; i < 128; i++) { redisReaderFeed(reader,(char*)"*1\r\n", 4); } redisReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12); ret = redisReaderGetReply(reader,&reply); root = reply; /* Keep track of the root reply */ test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && ((redisReply*)reply)->elements == 1); test("Can parse arbitrarily nested multi-bulks correctly: "); while(i--) { assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY); reply = ((redisReply*)reply)->element[0]; } test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING && !memcmp(((redisReply*)reply)->str, "LOLWUT", 6)); freeReplyObject(root); redisReaderFree(reader); test("Correctly parses LLONG_MAX: "); reader = redisReaderCreate(); redisReaderFeed(reader, ":9223372036854775807\r\n",22); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && ((redisReply*)reply)->integer == LLONG_MAX); freeReplyObject(reply); redisReaderFree(reader); test("Set error when > LLONG_MAX: "); reader = redisReaderCreate(); redisReaderFeed(reader, ":9223372036854775808\r\n",22); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bad integer value") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Correctly parses LLONG_MIN: "); reader = redisReaderCreate(); redisReaderFeed(reader, ":-9223372036854775808\r\n",23); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && ((redisReply*)reply)->integer == LLONG_MIN); freeReplyObject(reply); redisReaderFree(reader); test("Set error when < LLONG_MIN: "); reader = redisReaderCreate(); redisReaderFeed(reader, ":-9223372036854775809\r\n",23); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bad integer value") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Set error when array < -1: "); reader = redisReaderCreate(); redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Set error when bulk < -1: "); reader = redisReaderCreate(); redisReaderFeed(reader, "$-2\r\nasdf\r\n",11); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bulk string length out of range") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Can configure maximum multi-bulk elements: "); reader = redisReaderCreate(); reader->maxelements = 1024; redisReaderFeed(reader, "*1025\r\n", 7); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Multi-bulk never overflows regardless of maxelements: "); size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3; char bad_mbulk_reply[100]; snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n", (unsigned long long) bad_mbulk_len); reader = redisReaderCreate(); reader->maxelements = 0; /* Don't rely on default limit */ redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply)); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0); freeReplyObject(reply); redisReaderFree(reader); #if LLONG_MAX > SIZE_MAX test("Set error when array > SIZE_MAX: "); reader = redisReaderCreate(); redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Set error when bulk > SIZE_MAX: "); reader = redisReaderCreate(); redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bulk string length out of range") == 0); freeReplyObject(reply); redisReaderFree(reader); #endif test("Works with NULL functions for reply: "); reader = redisReaderCreate(); reader->fn = NULL; redisReaderFeed(reader,(char*)"+OK\r\n",5); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); redisReaderFree(reader); test("Works when a single newline (\\r\\n) covers two calls to feed: "); reader = redisReaderCreate(); reader->fn = NULL; redisReaderFeed(reader,(char*)"+OK\r",4); ret = redisReaderGetReply(reader,&reply); assert(ret == REDIS_OK && reply == NULL); redisReaderFeed(reader,(char*)"\n",1); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); redisReaderFree(reader); test("Don't reset state after protocol error: "); reader = redisReaderCreate(); reader->fn = NULL; redisReaderFeed(reader,(char*)"x",1); ret = redisReaderGetReply(reader,&reply); assert(ret == REDIS_ERR); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && reply == NULL); redisReaderFree(reader); test("Don't reset state after protocol error(not segfault): "); reader = redisReaderCreate(); redisReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25); ret = redisReaderGetReply(reader,&reply); assert(ret == REDIS_OK); redisReaderFeed(reader,(char*)"3\r\nval\r\n", 8); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && ((redisReply*)reply)->elements == 3); freeReplyObject(reply); redisReaderFree(reader); /* Regression test for issue #45 on GitHub. */ test("Don't do empty allocation for empty multi bulk: "); reader = redisReaderCreate(); redisReaderFeed(reader,(char*)"*0\r\n",4); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && ((redisReply*)reply)->elements == 0); freeReplyObject(reply); redisReaderFree(reader); /* RESP3 verbatim strings (GitHub issue #802) */ test("Can parse RESP3 verbatim strings: "); reader = redisReaderCreate(); redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_VERB && !memcmp(((redisReply*)reply)->str,"LOLWUT", 6)); freeReplyObject(reply); redisReaderFree(reader); /* RESP3 push messages (Github issue #815) */ test("Can parse RESP3 push messages: "); reader = redisReaderCreate(); redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_PUSH && ((redisReply*)reply)->elements == 2 && ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING && !memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) && ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER && ((redisReply*)reply)->element[1]->integer == 42); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 doubles: "); reader = redisReaderCreate(); redisReaderFeed(reader, ",3.14159265358979323846\r\n",25); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && fabs(((redisReply*)reply)->dval - 3.14159265358979323846) < 0.00000001 && ((redisReply*)reply)->len == 22 && strcmp(((redisReply*)reply)->str, "3.14159265358979323846") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Set error on invalid RESP3 double: "); reader = redisReaderCreate(); redisReaderFeed(reader, ",3.14159\000265358979323846\r\n",26); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bad double value") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Correctly parses RESP3 double INFINITY: "); reader = redisReaderCreate(); redisReaderFeed(reader, ",inf\r\n",6); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && isinf(((redisReply*)reply)->dval) && ((redisReply*)reply)->dval > 0); freeReplyObject(reply); redisReaderFree(reader); test("Correctly parses RESP3 double NaN: "); reader = redisReaderCreate(); redisReaderFeed(reader, ",nan\r\n",6); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && isnan(((redisReply*)reply)->dval)); freeReplyObject(reply); redisReaderFree(reader); test("Correctly parses RESP3 double -Nan: "); reader = redisReaderCreate(); redisReaderFeed(reader, ",-nan\r\n", 7); ret = redisReaderGetReply(reader, &reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE && isnan(((redisReply*)reply)->dval)); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 nil: "); reader = redisReaderCreate(); redisReaderFeed(reader, "_\r\n",3); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_NIL); freeReplyObject(reply); redisReaderFree(reader); test("Set error on invalid RESP3 nil: "); reader = redisReaderCreate(); redisReaderFeed(reader, "_nil\r\n",6); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bad nil value") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 bool (true): "); reader = redisReaderCreate(); redisReaderFeed(reader, "#t\r\n",4); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_BOOL && ((redisReply*)reply)->integer); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 bool (false): "); reader = redisReaderCreate(); redisReaderFeed(reader, "#f\r\n",4); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_BOOL && !((redisReply*)reply)->integer); freeReplyObject(reply); redisReaderFree(reader); test("Set error on invalid RESP3 bool: "); reader = redisReaderCreate(); redisReaderFeed(reader, "#foobar\r\n",9); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr,"Bad bool value") == 0); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 map: "); reader = redisReaderCreate(); redisReaderFeed(reader, "%2\r\n+first\r\n:123\r\n$6\r\nsecond\r\n#t\r\n",34); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_MAP && ((redisReply*)reply)->elements == 4 && ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS && ((redisReply*)reply)->element[0]->len == 5 && !strcmp(((redisReply*)reply)->element[0]->str,"first") && ((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER && ((redisReply*)reply)->element[1]->integer == 123 && ((redisReply*)reply)->element[2]->type == REDIS_REPLY_STRING && ((redisReply*)reply)->element[2]->len == 6 && !strcmp(((redisReply*)reply)->element[2]->str,"second") && ((redisReply*)reply)->element[3]->type == REDIS_REPLY_BOOL && ((redisReply*)reply)->element[3]->integer); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 set: "); reader = redisReaderCreate(); redisReaderFeed(reader, "~5\r\n+orange\r\n$5\r\napple\r\n#f\r\n:100\r\n:999\r\n",40); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_SET && ((redisReply*)reply)->elements == 5 && ((redisReply*)reply)->element[0]->type == REDIS_REPLY_STATUS && ((redisReply*)reply)->element[0]->len == 6 && !strcmp(((redisReply*)reply)->element[0]->str,"orange") && ((redisReply*)reply)->element[1]->type == REDIS_REPLY_STRING && ((redisReply*)reply)->element[1]->len == 5 && !strcmp(((redisReply*)reply)->element[1]->str,"apple") && ((redisReply*)reply)->element[2]->type == REDIS_REPLY_BOOL && !((redisReply*)reply)->element[2]->integer && ((redisReply*)reply)->element[3]->type == REDIS_REPLY_INTEGER && ((redisReply*)reply)->element[3]->integer == 100 && ((redisReply*)reply)->element[4]->type == REDIS_REPLY_INTEGER && ((redisReply*)reply)->element[4]->integer == 999); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 bignum: "); reader = redisReaderCreate(); redisReaderFeed(reader,"(3492890328409238509324850943850943825024385\r\n",46); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_BIGNUM && ((redisReply*)reply)->len == 43 && !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385")); freeReplyObject(reply); redisReaderFree(reader); test("Can parse RESP3 doubles in an array: "); reader = redisReaderCreate(); redisReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31); ret = redisReaderGetReply(reader,&reply); test_cond(ret == REDIS_OK && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && ((redisReply*)reply)->elements == 1 && ((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE && fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 && ((redisReply*)reply)->element[0]->len == 22 && strcmp(((redisReply*)reply)->element[0]->str, "3.14159265358979323846") == 0); freeReplyObject(reply); redisReaderFree(reader); } static void test_free_null(void) { void *redisCtx = NULL; void *reply = NULL; test("Don't fail when redisFree is passed a NULL value: "); redisFree(redisCtx); test_cond(redisCtx == NULL); test("Don't fail when freeReplyObject is passed a NULL value: "); freeReplyObject(reply); test_cond(reply == NULL); } static void *hi_malloc_fail(size_t size) { (void)size; return NULL; } static void *hi_calloc_fail(size_t nmemb, size_t size) { (void)nmemb; (void)size; return NULL; } static void *hi_calloc_insecure(size_t nmemb, size_t size) { (void)nmemb; (void)size; insecure_calloc_calls++; return (void*)0xdeadc0de; } static void *hi_realloc_fail(void *ptr, size_t size) { (void)ptr; (void)size; return NULL; } static void test_allocator_injection(void) { void *ptr; hiredisAllocFuncs ha = { .mallocFn = hi_malloc_fail, .callocFn = hi_calloc_fail, .reallocFn = hi_realloc_fail, .strdupFn = strdup, .freeFn = free, }; // Override hiredis allocators hiredisSetAllocators(&ha); test("redisContext uses injected allocators: "); redisContext *c = redisConnect("localhost", 6379); test_cond(c == NULL); test("redisReader uses injected allocators: "); redisReader *reader = redisReaderCreate(); test_cond(reader == NULL); /* Make sure hiredis itself protects against a non-overflow checking calloc */ test("hiredis calloc wrapper protects against overflow: "); ha.callocFn = hi_calloc_insecure; hiredisSetAllocators(&ha); ptr = hi_calloc((SIZE_MAX / sizeof(void*)) + 3, sizeof(void*)); test_cond(ptr == NULL && insecure_calloc_calls == 0); // Return allocators to default hiredisResetAllocators(); } #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" static void test_blocking_connection_errors(void) { struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo *ai_tmp = NULL; redisContext *c; int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); if (rv != 0) { // Address does *not* exist test("Returns error when host cannot be resolved: "); // First see if this domain name *actually* resolves to NXDOMAIN c = redisConnect(HIREDIS_BAD_DOMAIN, 6379); test_cond( c->err == REDIS_ERR_OTHER && (strcmp(c->errstr, "Name or service not known") == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "node name or service name not known") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 || strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 || strcmp(c->errstr, "no address associated with name") == 0 || strcmp(c->errstr, "No such host is known. ") == 0)); redisFree(c); } else { printf("Skipping NXDOMAIN test. Found evil ISP!\n"); freeaddrinfo(ai_tmp); } #ifndef _WIN32 redisOptions opt = {0}; struct timeval tv; test("Returns error when the port is not open: "); c = redisConnect((char*)"localhost", 1); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr,"Connection refused") == 0); redisFree(c); /* Verify we don't regress from the fix in PR #1180 */ test("We don't clobber connection exception with setsockopt error: "); tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000}; opt.command_timeout = opt.connect_timeout = &tv; REDIS_OPTIONS_SET_TCP(&opt, "localhost", 10337); c = redisConnectWithOptions(&opt); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Connection refused") == 0); redisFree(c); test("Returns error when the unix_sock socket path doesn't accept connections: "); c = redisConnectUnix((char*)"/tmp/idontexist.sock"); test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ redisFree(c); #endif } /* Test push handler */ void push_handler(void *privdata, void *r) { struct pushCounters *pcounts = privdata; redisReply *reply = r, *payload; assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2); payload = reply->element[1]; if (payload->type == REDIS_REPLY_ARRAY) { payload = payload->element[0]; } if (payload->type == REDIS_REPLY_STRING) { pcounts->str++; } else if (payload->type == REDIS_REPLY_NIL) { pcounts->nil++; } freeReplyObject(reply); } /* Dummy function just to test setting a callback with redisOptions */ void push_handler_async(redisAsyncContext *ac, void *reply) { (void)ac; (void)reply; } static void test_resp3_push_handler(redisContext *c) { struct pushCounters pc = {0}; redisPushFn *old = NULL; redisReply *reply; void *privdata; /* Switch to RESP3 and turn on client tracking */ send_hello(c, 3); send_client_tracking(c, "ON"); privdata = c->privdata; c->privdata = &pc; reply = redisCommand(c, "GET key:0"); assert(reply != NULL); freeReplyObject(reply); test("RESP3 PUSH messages are handled out of band by default: "); reply = redisCommand(c, "SET key:0 val:0"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); assert((reply = redisCommand(c, "GET key:0")) != NULL); freeReplyObject(reply); old = redisSetPushCallback(c, push_handler); test("We can set a custom RESP3 PUSH handler: "); reply = redisCommand(c, "SET key:0 val:0"); /* We need another command because depending on the version of Redis, the * notification may be delivered after the command's reply. */ assert(reply != NULL); freeReplyObject(reply); reply = redisCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); freeReplyObject(reply); test("We properly handle a NIL invalidation payload: "); reply = redisCommand(c, "FLUSHDB"); assert(reply != NULL); freeReplyObject(reply); reply = redisCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1); freeReplyObject(reply); /* Unset the push callback and generate an invalidate message making * sure it is not handled out of band. */ test("With no handler, PUSH replies come in-band: "); redisSetPushCallback(c, NULL); assert((reply = redisCommand(c, "GET key:0")) != NULL); freeReplyObject(reply); assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL); /* Depending on Redis version, we may receive either push notification or * status reply. Both cases are valid. */ if (reply->type == REDIS_REPLY_STATUS) { freeReplyObject(reply); reply = redisCommand(c, "PING"); } test_cond(reply->type == REDIS_REPLY_PUSH); freeReplyObject(reply); test("With no PUSH handler, no replies are lost: "); assert(redisGetReply(c, (void**)&reply) == REDIS_OK); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); /* Return to the originally set PUSH handler */ assert(old != NULL); redisSetPushCallback(c, old); /* Switch back to RESP2 and disable tracking */ c->privdata = privdata; send_client_tracking(c, "OFF"); send_hello(c, 2); } redisOptions get_redis_tcp_options(struct config config) { redisOptions options = {0}; REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port); return options; } static void test_resp3_push_options(struct config config) { redisAsyncContext *ac; redisContext *c; redisOptions options; test("We set a default RESP3 handler for redisContext: "); options = get_redis_tcp_options(config); assert((c = redisConnectWithOptions(&options)) != NULL); test_cond(c->push_cb != NULL); redisFree(c); test("We don't set a default RESP3 push handler for redisAsyncContext: "); options = get_redis_tcp_options(config); assert((ac = redisAsyncConnectWithOptions(&options)) != NULL); test_cond(ac->c.push_cb == NULL); redisAsyncFree(ac); test("Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: "); options = get_redis_tcp_options(config); options.options |= REDIS_OPT_NO_PUSH_AUTOFREE; assert((c = redisConnectWithOptions(&options)) != NULL); test_cond(c->push_cb == NULL); redisFree(c); test("We can use redisOptions to set a custom PUSH handler for redisContext: "); options = get_redis_tcp_options(config); options.push_cb = push_handler; assert((c = redisConnectWithOptions(&options)) != NULL); test_cond(c->push_cb == push_handler); redisFree(c); test("We can use redisOptions to set a custom PUSH handler for redisAsyncContext: "); options = get_redis_tcp_options(config); options.async_push_cb = push_handler_async; assert((ac = redisAsyncConnectWithOptions(&options)) != NULL); test_cond(ac->push_cb == push_handler_async); redisAsyncFree(ac); } void free_privdata(void *privdata) { struct privdata *data = privdata; data->dtor_counter++; } static void test_privdata_hooks(struct config config) { struct privdata data = {0}; redisOptions options; redisContext *c; test("We can use redisOptions to set privdata: "); options = get_redis_tcp_options(config); REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata); assert((c = redisConnectWithOptions(&options)) != NULL); test_cond(c->privdata == &data); test("Our privdata destructor fires when we free the context: "); redisFree(c); test_cond(data.dtor_counter == 1); } static void test_blocking_connection(struct config config) { redisContext *c; redisReply *reply; int major; c = do_connect(config); test("Is able to deliver commands: "); reply = redisCommand(c,"PING"); test_cond(reply->type == REDIS_REPLY_STATUS && strcasecmp(reply->str,"pong") == 0) freeReplyObject(reply); test("Is a able to send commands verbatim: "); reply = redisCommand(c,"SET foo bar"); test_cond (reply->type == REDIS_REPLY_STATUS && strcasecmp(reply->str,"ok") == 0) freeReplyObject(reply); test("%%s String interpolation works: "); reply = redisCommand(c,"SET %s %s","foo","hello world"); freeReplyObject(reply); reply = redisCommand(c,"GET foo"); test_cond(reply->type == REDIS_REPLY_STRING && strcmp(reply->str,"hello world") == 0); freeReplyObject(reply); test("%%b String interpolation works: "); reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); freeReplyObject(reply); reply = redisCommand(c,"GET foo"); test_cond(reply->type == REDIS_REPLY_STRING && memcmp(reply->str,"hello\x00world",11) == 0) test("Binary reply length is correct: "); test_cond(reply->len == 11) freeReplyObject(reply); test("Can parse nil replies: "); reply = redisCommand(c,"GET nokey"); test_cond(reply->type == REDIS_REPLY_NIL) freeReplyObject(reply); /* test 7 */ test("Can parse integer replies: "); reply = redisCommand(c,"INCR mycounter"); test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) freeReplyObject(reply); test("Can parse multi bulk replies: "); freeReplyObject(redisCommand(c,"LPUSH mylist foo")); freeReplyObject(redisCommand(c,"LPUSH mylist bar")); reply = redisCommand(c,"LRANGE mylist 0 -1"); test_cond(reply->type == REDIS_REPLY_ARRAY && reply->elements == 2 && !memcmp(reply->element[0]->str,"bar",3) && !memcmp(reply->element[1]->str,"foo",3)) freeReplyObject(reply); /* m/e with multi bulk reply *before* other reply. * specifically test ordering of reply items to parse. */ test("Can handle nested multi bulk replies: "); freeReplyObject(redisCommand(c,"MULTI")); freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); freeReplyObject(redisCommand(c,"PING")); reply = (redisCommand(c,"EXEC")); test_cond(reply->type == REDIS_REPLY_ARRAY && reply->elements == 2 && reply->element[0]->type == REDIS_REPLY_ARRAY && reply->element[0]->elements == 2 && !memcmp(reply->element[0]->element[0]->str,"bar",3) && !memcmp(reply->element[0]->element[1]->str,"foo",3) && reply->element[1]->type == REDIS_REPLY_STATUS && strcasecmp(reply->element[1]->str,"pong") == 0); freeReplyObject(reply); test("Send command by passing argc/argv: "); const char *argv[3] = {"SET", "foo", "bar"}; size_t argvlen[3] = {3, 3, 3}; reply = redisCommandArgv(c,3,argv,argvlen); test_cond(reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); /* Make sure passing NULL to redisGetReply is safe */ test("Can pass NULL to redisGetReply: "); assert(redisAppendCommand(c, "PING") == REDIS_OK); test_cond(redisGetReply(c, NULL) == REDIS_OK); get_redis_version(c, &major, NULL); if (major >= 6) test_resp3_push_handler(c); test_resp3_push_options(config); test_privdata_hooks(config); disconnect(c, 0); } /* Send DEBUG SLEEP 0 to detect if we have this command */ static int detect_debug_sleep(redisContext *c) { int detected; redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n"); if (reply == NULL || c->err) { const char *cause = c->err ? c->errstr : "(none)"; fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause); exit(-1); } detected = reply->type == REDIS_REPLY_STATUS; freeReplyObject(reply); return detected; } static void test_blocking_connection_timeouts(struct config config) { redisContext *c; redisReply *reply; ssize_t s; const char *sleep_cmd = "DEBUG SLEEP 3\r\n"; struct timeval tv; c = do_connect(config); test("Successfully completes a command when the timeout is not exceeded: "); reply = redisCommand(c,"SET foo fast"); freeReplyObject(reply); tv.tv_sec = 0; tv.tv_usec = 10000; redisSetTimeout(c, tv); reply = redisCommand(c, "GET foo"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); freeReplyObject(reply); disconnect(c, 0); c = do_connect(config); test("Does not return a reply when the command times out: "); if (detect_debug_sleep(c)) { redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd)); // flush connection buffer without waiting for the reply s = c->funcs->write(c); assert(s == (ssize_t)hi_sdslen(c->obuf)); hi_sdsfree(c->obuf); c->obuf = hi_sdsempty(); tv.tv_sec = 0; tv.tv_usec = 10000; redisSetTimeout(c, tv); reply = redisCommand(c, "GET foo"); #ifndef _WIN32 test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); #else test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT && strcmp(c->errstr, "recv timeout") == 0); #endif freeReplyObject(reply); // wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests millisleep(3000); } else { test_skipped(); } test("Reconnect properly reconnects after a timeout: "); do_reconnect(c, config); reply = redisCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); freeReplyObject(reply); test("Reconnect properly uses owned parameters: "); config.tcp.host = "foo"; config.unix_sock.path = "foo"; do_reconnect(c, config); reply = redisCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); freeReplyObject(reply); disconnect(c, 0); } static void test_blocking_io_errors(struct config config) { redisContext *c; redisReply *reply; void *_reply; int major, minor; /* Connect to target given by config. */ c = do_connect(config); get_redis_version(c, &major, &minor); test("Returns I/O error when the connection is lost: "); reply = redisCommand(c,"QUIT"); if (major > 2 || (major == 2 && minor > 0)) { /* > 2.0 returns OK on QUIT and read() should be issued once more * to know the descriptor is at EOF. */ test_cond(strcasecmp(reply->str,"OK") == 0 && redisGetReply(c,&_reply) == REDIS_ERR); freeReplyObject(reply); } else { test_cond(reply == NULL); } #ifndef _WIN32 /* On 2.0, QUIT will cause the connection to be closed immediately and * the read(2) for the reply on QUIT will set the error to EOF. * On >2.0, QUIT will return with OK and another read(2) needed to be * issued to find out the socket was closed by the server. In both * conditions, the error will be set to EOF. */ assert(c->err == REDIS_ERR_EOF && strcmp(c->errstr,"Server closed the connection") == 0); #endif redisFree(c); c = do_connect(config); test("Returns I/O error on socket timeout: "); struct timeval tv = { 0, 1000 }; assert(redisSetTimeout(c,tv) == REDIS_OK); int respcode = redisGetReply(c,&_reply); #ifndef _WIN32 test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN); #else test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT); #endif redisFree(c); } static void test_invalid_timeout_errors(struct config config) { redisContext *c; test("Set error when an invalid timeout usec value is used during connect: "); config.connect_timeout.tv_sec = 0; config.connect_timeout.tv_usec = 10000001; if (config.type == CONN_TCP || config.type == CONN_SSL) { c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout); } else if(config.type == CONN_UNIX) { c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout); } else { assert(NULL); } test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); redisFree(c); test("Set error when an invalid timeout sec value is used during connect: "); config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; config.connect_timeout.tv_usec = 0; if (config.type == CONN_TCP || config.type == CONN_SSL) { c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout); } else if(config.type == CONN_UNIX) { c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout); } else { assert(NULL); } test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); redisFree(c); } /* Wrap malloc to abort on failure so OOM checks don't make the test logic * harder to follow. */ void *hi_malloc_safe(size_t size) { void *ptr = hi_malloc(size); if (ptr == NULL) { fprintf(stderr, "Error: Out of memory\n"); exit(-1); } return ptr; } static void test_throughput(struct config config) { redisContext *c = do_connect(config); redisReply **replies; int i, num; long long t1, t2; test("Throughput:\n"); for (i = 0; i < 500; i++) freeReplyObject(redisCommand(c,"LPUSH mylist foo")); num = 1000; replies = hi_malloc_safe(sizeof(redisReply*)*num); t1 = usec(); for (i = 0; i < num; i++) { replies[i] = redisCommand(c,"PING"); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redisReply*)*num); t1 = usec(); for (i = 0; i < num; i++) { replies[i] = redisCommand(c,"LRANGE mylist 0 499"); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); assert(replies[i] != NULL && replies[i]->elements == 500); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redisReply*)*num); t1 = usec(); for (i = 0; i < num; i++) { replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0); num = 10000; replies = hi_malloc_safe(sizeof(redisReply*)*num); for (i = 0; i < num; i++) redisAppendCommand(c,"PING"); t1 = usec(); for (i = 0; i < num; i++) { assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redisReply*)*num); for (i = 0; i < num; i++) redisAppendCommand(c,"LRANGE mylist 0 499"); t1 = usec(); for (i = 0; i < num; i++) { assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); assert(replies[i] != NULL && replies[i]->elements == 500); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); replies = hi_malloc_safe(sizeof(redisReply*)*num); for (i = 0; i < num; i++) redisAppendCommand(c,"INCRBY incrkey %d", 1000000); t1 = usec(); for (i = 0; i < num; i++) { assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); } t2 = usec(); for (i = 0; i < num; i++) freeReplyObject(replies[i]); hi_free(replies); printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); disconnect(c, 0); } // static long __test_callback_flags = 0; // static void __test_callback(redisContext *c, void *privdata) { // ((void)c); // /* Shift to detect execution order */ // __test_callback_flags <<= 8; // __test_callback_flags |= (long)privdata; // } // // static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { // ((void)c); // /* Shift to detect execution order */ // __test_callback_flags <<= 8; // __test_callback_flags |= (long)privdata; // if (reply) freeReplyObject(reply); // } // // static redisContext *__connect_nonblock() { // /* Reset callback flags */ // __test_callback_flags = 0; // return redisConnectNonBlock("127.0.0.1", port, NULL); // } // // static void test_nonblocking_connection() { // redisContext *c; // int wdone = 0; // // test("Calls command callback when command is issued: "); // c = __connect_nonblock(); // redisSetCommandCallback(c,__test_callback,(void*)1); // redisCommand(c,"PING"); // test_cond(__test_callback_flags == 1); // redisFree(c); // // test("Calls disconnect callback on redisDisconnect: "); // c = __connect_nonblock(); // redisSetDisconnectCallback(c,__test_callback,(void*)2); // redisDisconnect(c); // test_cond(__test_callback_flags == 2); // redisFree(c); // // test("Calls disconnect callback and free callback on redisFree: "); // c = __connect_nonblock(); // redisSetDisconnectCallback(c,__test_callback,(void*)2); // redisSetFreeCallback(c,__test_callback,(void*)4); // redisFree(c); // test_cond(__test_callback_flags == ((2 << 8) | 4)); // // test("redisBufferWrite against empty write buffer: "); // c = __connect_nonblock(); // test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); // redisFree(c); // // test("redisBufferWrite against not yet connected fd: "); // c = __connect_nonblock(); // redisCommand(c,"PING"); // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && // strncmp(c->error,"write:",6) == 0); // redisFree(c); // // test("redisBufferWrite against closed fd: "); // c = __connect_nonblock(); // redisCommand(c,"PING"); // redisDisconnect(c); // test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && // strncmp(c->error,"write:",6) == 0); // redisFree(c); // // test("Process callbacks in the right sequence: "); // c = __connect_nonblock(); // redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); // redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); // // /* Write output buffer */ // wdone = 0; // while(!wdone) { // usleep(500); // redisBufferWrite(c,&wdone); // } // // /* Read until at least one callback is executed (the 3 replies will // * arrive in a single packet, causing all callbacks to be executed in // * a single pass). */ // while(__test_callback_flags == 0) { // assert(redisBufferRead(c) == REDIS_OK); // redisProcessCallbacks(c); // } // test_cond(__test_callback_flags == 0x010203); // redisFree(c); // // test("redisDisconnect executes pending callbacks with NULL reply: "); // c = __connect_nonblock(); // redisSetDisconnectCallback(c,__test_callback,(void*)1); // redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); // redisDisconnect(c); // test_cond(__test_callback_flags == 0x0201); // redisFree(c); // } #ifdef HIREDIS_TEST_ASYNC #pragma GCC diagnostic ignored "-Woverlength-strings" /* required on gcc 4.8.x due to assert statements */ struct event_base *base; typedef struct TestState { redisOptions *options; int checkpoint; int resp3; int disconnect; } TestState; /* Helper to disconnect and stop event loop */ void async_disconnect(redisAsyncContext *ac) { redisAsyncDisconnect(ac); event_base_loopbreak(base); } /* Testcase timeout, will trigger a failure */ void timeout_cb(int fd, short event, void *arg) { (void) fd; (void) event; (void) arg; printf("Timeout in async testing!\n"); exit(1); } /* Unexpected call, will trigger a failure */ void unexpected_cb(redisAsyncContext *ac, void *r, void *privdata) { (void) ac; (void) r; printf("Unexpected call: %s\n",(char*)privdata); exit(1); } /* Helper function to publish a message via own client. */ void publish_msg(redisOptions *options, const char* channel, const char* msg) { redisContext *c = redisConnectWithOptions(options); assert(c != NULL); redisReply *reply = redisCommand(c,"PUBLISH %s %s",channel,msg); assert(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1); freeReplyObject(reply); disconnect(c, 0); } /* Expect a reply of type INTEGER */ void integer_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == REDIS_REPLY_INTEGER); state->checkpoint++; if (state->disconnect) async_disconnect(ac); } /* Subscribe callback for test_pubsub_handling and test_pubsub_handling_resp3: * - a published message triggers an unsubscribe * - a command is sent before the unsubscribe response is received. */ void subscribe_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && reply->element[2]->str == NULL); publish_msg(state->options,"mychannel","Hello!"); } else if (strcmp(reply->element[0]->str,"message") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && strcmp(reply->element[2]->str,"Hello!") == 0); state->checkpoint++; /* Unsubscribe after receiving the published message. Send unsubscribe * which should call the callback registered during subscribe */ redisAsyncCommand(ac,unexpected_cb, (void*)"unsubscribe should call subscribe_cb()", "unsubscribe"); /* Send a regular command after unsubscribing, then disconnect */ state->disconnect = 1; redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && reply->element[2]->str == NULL); } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } /* Expect a reply of type ARRAY */ void array_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY); state->checkpoint++; if (state->disconnect) async_disconnect(ac); } /* Expect a NULL reply */ void null_cb(redisAsyncContext *ac, void *r, void *privdata) { (void) ac; assert(r == NULL); TestState *state = privdata; state->checkpoint++; } static void test_pubsub_handling(struct config config) { test("Subscribe, handle published message and unsubscribe: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base, timeout_cb, NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout, &timeout_tv); /* Connect */ redisOptions options = get_redis_tcp_options(config); redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redisLibeventAttach(ac,base); /* Start subscribe */ TestState state = {.options = &options}; redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); /* Make sure non-subscribe commands are handled */ redisAsyncCommand(ac,array_cb,&state,"PING"); /* Start event dispatching loop */ test_cond(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ assert(state.checkpoint == 3); } /* Unexpected push message, will trigger a failure */ void unexpected_push_cb(redisAsyncContext *ac, void *r) { (void) ac; (void) r; printf("Unexpected call to the PUSH callback!\n"); exit(1); } static void test_pubsub_handling_resp3(struct config config) { test("Subscribe, handle published message and unsubscribe using RESP3: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base, timeout_cb, NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout, &timeout_tv); /* Connect */ redisOptions options = get_redis_tcp_options(config); redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redisLibeventAttach(ac,base); /* Not expecting any push messages in this test */ redisAsyncSetPushCallback(ac, unexpected_push_cb); /* Switch protocol */ redisAsyncCommand(ac,NULL,NULL,"HELLO 3"); /* Start subscribe */ TestState state = {.options = &options, .resp3 = 1}; redisAsyncCommand(ac,subscribe_cb,&state,"subscribe mychannel"); /* Make sure non-subscribe commands are handled in RESP3 */ redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); redisAsyncCommand(ac,integer_cb,&state,"LPUSH mylist foo"); /* Handle an array with 3 elements as a non-subscribe command */ redisAsyncCommand(ac,array_cb,&state,"LRANGE mylist 0 2"); /* Start event dispatching loop */ test_cond(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ assert(state.checkpoint == 6); } /* Subscribe callback for test_command_timeout_during_pubsub: * - a subscribe response triggers a published message * - the published message triggers a command that times out * - the command timeout triggers a disconnect */ void subscribe_with_timeout_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; /* The non-clean disconnect should trigger the * subscription callback with a NULL reply. */ if (reply == NULL) { state->checkpoint++; event_base_loopbreak(base); return; } assert(reply->type == (state->resp3 ? REDIS_REPLY_PUSH : REDIS_REPLY_ARRAY) && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && reply->element[2]->str == NULL); publish_msg(state->options,"mychannel","Hello!"); state->checkpoint++; } else if (strcmp(reply->element[0]->str,"message") == 0) { assert(strcmp(reply->element[1]->str,"mychannel") == 0 && strcmp(reply->element[2]->str,"Hello!") == 0); state->checkpoint++; /* Send a command that will trigger a timeout */ redisAsyncCommand(ac,null_cb,state,"DEBUG SLEEP 3"); redisAsyncCommand(ac,null_cb,state,"LPUSH mylist foo"); } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } static void test_command_timeout_during_pubsub(struct config config) { test("Command timeout during Pub/Sub: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base,timeout_cb,NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout,&timeout_tv); /* Connect */ redisOptions options = get_redis_tcp_options(config); redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redisLibeventAttach(ac,base); /* Configure a command timout */ struct timeval command_timeout = {.tv_sec = 2}; redisAsyncSetTimeout(ac,command_timeout); /* Not expecting any push messages in this test */ redisAsyncSetPushCallback(ac,unexpected_push_cb); /* Switch protocol */ redisAsyncCommand(ac,NULL,NULL,"HELLO 3"); /* Start subscribe */ TestState state = {.options = &options, .resp3 = 1}; redisAsyncCommand(ac,subscribe_with_timeout_cb,&state,"subscribe mychannel"); /* Start event dispatching loop */ assert(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ test_cond(state.checkpoint == 5); } /* Subscribe callback for test_pubsub_multiple_channels */ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"A") == 0); publish_msg(state->options,"A","Hello!"); state->checkpoint++; } else if (strcmp(reply->element[0]->str,"message") == 0) { assert(strcmp(reply->element[1]->str,"A") == 0 && strcmp(reply->element[2]->str,"Hello!") == 0); state->checkpoint++; /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */ redisAsyncCommand(ac,unexpected_cb, (void*)"unsubscribe should not call unexpected_cb()", "unsubscribe B X A A Z"); /* Unsubscribe to patterns, none which we subscribe to */ redisAsyncCommand(ac,unexpected_cb, (void*)"punsubscribe should not call unexpected_cb()", "punsubscribe"); /* Send a regular command after unsubscribing, then disconnect */ state->disconnect = 1; redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { assert(strcmp(reply->element[1]->str,"A") == 0); state->checkpoint++; } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } /* Subscribe callback for test_pubsub_multiple_channels */ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; (void)ac; assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && reply->elements == 3); if (strcmp(reply->element[0]->str,"subscribe") == 0) { assert(strcmp(reply->element[1]->str,"B") == 0); state->checkpoint++; } else if (strcmp(reply->element[0]->str,"unsubscribe") == 0) { assert(strcmp(reply->element[1]->str,"B") == 0); state->checkpoint++; } else { printf("Unexpected pubsub command: %s\n", reply->element[0]->str); exit(1); } } /* Test handling of multiple channels * - subscribe to channel A and B * - a published message on A triggers an unsubscribe of channel B, X, A and Z * where channel X and Z are not subscribed to. * - the published message also triggers an unsubscribe to patterns. Since no * pattern is subscribed to the responded pattern element type is NIL. * - a command sent after unsubscribe triggers a disconnect */ static void test_pubsub_multiple_channels(struct config config) { test("Subscribe to multiple channels: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base,timeout_cb,NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout,&timeout_tv); /* Connect */ redisOptions options = get_redis_tcp_options(config); redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redisLibeventAttach(ac,base); /* Not expecting any push messages in this test */ redisAsyncSetPushCallback(ac,unexpected_push_cb); /* Start subscribing to two channels */ TestState state = {.options = &options}; redisAsyncCommand(ac,subscribe_channel_a_cb,&state,"subscribe A"); redisAsyncCommand(ac,subscribe_channel_b_cb,&state,"subscribe B"); /* Start event dispatching loop */ assert(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ test_cond(state.checkpoint == 6); } /* Command callback for test_monitor() */ void monitor_cb(redisAsyncContext *ac, void *r, void *privdata) { redisReply *reply = r; TestState *state = privdata; /* NULL reply is received when BYE triggers a disconnect. */ if (reply == NULL) { event_base_loopbreak(base); return; } assert(reply != NULL && reply->type == REDIS_REPLY_STATUS); state->checkpoint++; if (state->checkpoint == 1) { /* Response from MONITOR */ redisContext *c = redisConnectWithOptions(state->options); assert(c != NULL); redisReply *reply = redisCommand(c,"SET first 1"); assert(reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); redisFree(c); } else if (state->checkpoint == 2) { /* Response for monitored command 'SET first 1' */ assert(strstr(reply->str,"first") != NULL); redisContext *c = redisConnectWithOptions(state->options); assert(c != NULL); redisReply *reply = redisCommand(c,"SET second 2"); assert(reply->type == REDIS_REPLY_STATUS); freeReplyObject(reply); redisFree(c); } else if (state->checkpoint == 3) { /* Response for monitored command 'SET second 2' */ assert(strstr(reply->str,"second") != NULL); /* Send QUIT to disconnect */ redisAsyncCommand(ac,NULL,NULL,"QUIT"); } } /* Test handling of the monitor command * - sends MONITOR to enable monitoring. * - sends SET commands via separate clients to be monitored. * - sends QUIT to stop monitoring and disconnect. */ static void test_monitor(struct config config) { test("Enable monitoring: "); /* Setup event dispatcher with a testcase timeout */ base = event_base_new(); struct event *timeout = evtimer_new(base, timeout_cb, NULL); assert(timeout != NULL); evtimer_assign(timeout,base,timeout_cb,NULL); struct timeval timeout_tv = {.tv_sec = 10}; evtimer_add(timeout, &timeout_tv); /* Connect */ redisOptions options = get_redis_tcp_options(config); redisAsyncContext *ac = redisAsyncConnectWithOptions(&options); assert(ac != NULL && ac->err == 0); redisLibeventAttach(ac,base); /* Not expecting any push messages in this test */ redisAsyncSetPushCallback(ac,unexpected_push_cb); /* Start monitor */ TestState state = {.options = &options}; redisAsyncCommand(ac,monitor_cb,&state,"monitor"); /* Start event dispatching loop */ test_cond(event_base_dispatch(base) == 0); event_free(timeout); event_base_free(base); /* Verify test checkpoints */ assert(state.checkpoint == 3); } #endif /* HIREDIS_TEST_ASYNC */ /* tests for async api using polling adapter, requires no extra libraries*/ /* enum for the test cases, the callbacks have different logic based on them */ typedef enum astest_no { ASTEST_CONNECT=0, ASTEST_CONN_TIMEOUT, ASTEST_PINGPONG, ASTEST_PINGPONG_TIMEOUT, ASTEST_ISSUE_931, ASTEST_ISSUE_931_PING }astest_no; /* a static context for the async tests */ struct _astest { redisAsyncContext *ac; astest_no testno; int counter; int connects; int connect_status; int disconnects; int pongs; int disconnect_status; int connected; int err; char errstr[256]; }; static struct _astest astest; /* async callbacks */ static void asCleanup(void* data) { struct _astest *t = (struct _astest *)data; t->ac = NULL; } static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata); static void connectCallback(redisAsyncContext *c, int status) { struct _astest *t = (struct _astest *)c->data; assert(t == &astest); assert(t->connects == 0); t->err = c->err; strcpy(t->errstr, c->errstr); t->connects++; t->connect_status = status; t->connected = status == REDIS_OK ? 1 : -1; if (t->testno == ASTEST_ISSUE_931) { /* disconnect again */ redisAsyncDisconnect(c); } else if (t->testno == ASTEST_ISSUE_931_PING) { redisAsyncCommand(c, commandCallback, NULL, "PING"); } } static void disconnectCallback(const redisAsyncContext *c, int status) { assert(c->data == (void*)&astest); assert(astest.disconnects == 0); astest.err = c->err; strcpy(astest.errstr, c->errstr); astest.disconnects++; astest.disconnect_status = status; astest.connected = 0; } static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata) { redisReply *reply = (redisReply*)_reply; struct _astest *t = (struct _astest *)ac->data; assert(t == &astest); (void)_privdata; t->err = ac->err; strcpy(t->errstr, ac->errstr); t->counter++; if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING) { assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); t->pongs++; redisAsyncFree(ac); } if (t->testno == ASTEST_PINGPONG_TIMEOUT) { /* two ping pongs */ assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); t->pongs++; if (t->counter == 1) { int status = redisAsyncCommand(ac, commandCallback, NULL, "PING"); assert(status == REDIS_OK); } else { redisAsyncFree(ac); } } } static redisAsyncContext *do_aconnect(struct config config, astest_no testno) { redisOptions options = {0}; memset(&astest, 0, sizeof(astest)); astest.testno = testno; astest.connect_status = astest.disconnect_status = -2; if (config.type == CONN_TCP) { options.type = REDIS_CONN_TCP; options.connect_timeout = &config.connect_timeout; REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port); } else if (config.type == CONN_SSL) { options.type = REDIS_CONN_TCP; options.connect_timeout = &config.connect_timeout; REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port); } else if (config.type == CONN_UNIX) { options.type = REDIS_CONN_UNIX; options.endpoint.unix_socket = config.unix_sock.path; } else if (config.type == CONN_FD) { options.type = REDIS_CONN_USERFD; /* Create a dummy connection just to get an fd to inherit */ redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path); if (dummy_ctx) { redisFD fd = disconnect(dummy_ctx, 1); printf("Connecting to inherited fd %d\n", (int)fd); options.endpoint.fd = fd; } } redisAsyncContext *c = redisAsyncConnectWithOptions(&options); assert(c); astest.ac = c; c->data = &astest; c->dataCleanup = asCleanup; redisPollAttach(c); redisAsyncSetConnectCallbackNC(c, connectCallback); redisAsyncSetDisconnectCallback(c, disconnectCallback); return c; } static void as_printerr(void) { printf("Async err %d : %s\n", astest.err, astest.errstr); } #define ASASSERT(e) do { \ if (!(e)) \ as_printerr(); \ assert(e); \ } while (0); static void test_async_polling(struct config config) { int status; redisAsyncContext *c; struct config defaultconfig = config; test("Async connect: "); c = do_aconnect(config, ASTEST_CONNECT); assert(c); while(astest.connected == 0) redisPollTick(c, 0.1); assert(astest.connects == 1); ASASSERT(astest.connect_status == REDIS_OK); assert(astest.disconnects == 0); test_cond(astest.connected == 1); test("Async free after connect: "); assert(astest.ac != NULL); redisAsyncFree(c); assert(astest.disconnects == 1); assert(astest.ac == NULL); test_cond(astest.disconnect_status == REDIS_OK); if (config.type == CONN_TCP || config.type == CONN_SSL) { /* timeout can only be simulated with network */ test("Async connect timeout: "); config.tcp.host = "192.168.254.254"; /* blackhole ip */ config.connect_timeout.tv_usec = 100000; c = do_aconnect(config, ASTEST_CONN_TIMEOUT); assert(c); assert(c->err == 0); while(astest.connected == 0) redisPollTick(c, 0.1); assert(astest.connected == -1); /* * freeing should not be done, clearing should have happened. *redisAsyncFree(c); */ assert(astest.ac == NULL); test_cond(astest.connect_status == REDIS_ERR); config = defaultconfig; } /* Test a ping/pong after connection */ test("Async PING/PONG: "); c = do_aconnect(config, ASTEST_PINGPONG); while(astest.connected == 0) redisPollTick(c, 0.1); status = redisAsyncCommand(c, commandCallback, NULL, "PING"); assert(status == REDIS_OK); while(astest.ac) redisPollTick(c, 0.1); test_cond(astest.pongs == 1); /* Test a ping/pong after connection that didn't time out. * see https://github.com/redis/hiredis/issues/945 */ if (config.type == CONN_TCP || config.type == CONN_SSL) { test("Async PING/PONG after connect timeout: "); config.connect_timeout.tv_usec = 10000; /* 10ms */ c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT); while(astest.connected == 0) redisPollTick(c, 0.1); /* sleep 0.1 s, allowing old timeout to arrive */ millisleep(10); status = redisAsyncCommand(c, commandCallback, NULL, "PING"); assert(status == REDIS_OK); while(astest.ac) redisPollTick(c, 0.1); test_cond(astest.pongs == 2); config = defaultconfig; } /* Test disconnect from an on_connect callback * see https://github.com/redis/hiredis/issues/931 */ test("Disconnect from onConnected callback (Issue #931): "); c = do_aconnect(config, ASTEST_ISSUE_931); while(astest.disconnects == 0) redisPollTick(c, 0.1); assert(astest.connected == 0); assert(astest.connects == 1); test_cond(astest.disconnects == 1); /* Test ping/pong from an on_connect callback * see https://github.com/redis/hiredis/issues/931 */ test("Ping/Pong from onConnected callback (Issue #931): "); c = do_aconnect(config, ASTEST_ISSUE_931_PING); /* connect callback issues ping, reponse callback destroys context */ while(astest.ac) redisPollTick(c, 0.1); assert(astest.connected == 0); assert(astest.connects == 1); assert(astest.disconnects == 1); test_cond(astest.pongs == 1); } /* End of Async polling_adapter driven tests */ int main(int argc, char **argv) { struct config cfg = { .tcp = { .host = "127.0.0.1", .port = 6379 }, .unix_sock = { .path = "/tmp/redis.sock" } }; int throughput = 1; int test_inherit_fd = 1; int skips_as_fails = 0; int test_unix_socket; /* Parse command line options. */ argv++; argc--; while (argc) { if (argc >= 2 && !strcmp(argv[0],"-h")) { argv++; argc--; cfg.tcp.host = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"-p")) { argv++; argc--; cfg.tcp.port = atoi(argv[0]); } else if (argc >= 2 && !strcmp(argv[0],"-s")) { argv++; argc--; cfg.unix_sock.path = argv[0]; } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { throughput = 0; } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { test_inherit_fd = 0; } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) { skips_as_fails = 1; #ifdef HIREDIS_TEST_SSL } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) { argv++; argc--; cfg.ssl.port = atoi(argv[0]); } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) { argv++; argc--; cfg.ssl.host = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) { argv++; argc--; cfg.ssl.ca_cert = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) { argv++; argc--; cfg.ssl.cert = argv[0]; } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) { argv++; argc--; cfg.ssl.key = argv[0]; #endif } else { fprintf(stderr, "Invalid argument: %s\n", argv[0]); exit(1); } argv++; argc--; } #ifndef _WIN32 /* Ignore broken pipe signal (for I/O error tests). */ signal(SIGPIPE, SIG_IGN); test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0; #else /* Unix sockets don't exist in Windows */ test_unix_socket = 0; #endif test_allocator_injection(); test_format_commands(); test_reply_reader(); test_blocking_connection_errors(); test_free_null(); printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); cfg.type = CONN_TCP; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); test_append_formatted_commands(cfg); test_tcp_options(cfg); if (throughput) test_throughput(cfg); printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path); if (test_unix_socket) { printf("\n"); cfg.type = CONN_UNIX; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); if (throughput) test_throughput(cfg); } else { test_skipped(); } #ifdef HIREDIS_TEST_SSL if (cfg.ssl.port && cfg.ssl.host) { redisInitOpenSSL(); _ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL); assert(_ssl_ctx != NULL); printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port); cfg.type = CONN_SSL; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); test_invalid_timeout_errors(cfg); test_append_formatted_commands(cfg); if (throughput) test_throughput(cfg); redisFreeSSLContext(_ssl_ctx); _ssl_ctx = NULL; } #endif #ifdef HIREDIS_TEST_ASYNC cfg.type = CONN_TCP; printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); cfg.type = CONN_TCP; int major; redisContext *c = do_connect(cfg); get_redis_version(c, &major, NULL); disconnect(c, 0); test_pubsub_handling(cfg); test_pubsub_multiple_channels(cfg); test_monitor(cfg); if (major >= 6) { test_pubsub_handling_resp3(cfg); test_command_timeout_during_pubsub(cfg); } #endif /* HIREDIS_TEST_ASYNC */ cfg.type = CONN_TCP; printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); test_async_polling(cfg); if (test_unix_socket) { cfg.type = CONN_UNIX; printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path); test_async_polling(cfg); } if (test_inherit_fd) { printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); if (test_unix_socket) { printf("\n"); cfg.type = CONN_FD; test_blocking_connection(cfg); } else { test_skipped(); } } if (fails || (skips_as_fails && skips)) { printf("*** %d TESTS FAILED ***\n", fails); if (skips) { printf("*** %d TESTS SKIPPED ***\n", skips); } return 1; } printf("ALL TESTS PASSED (%d skipped)\n", skips); return 0; } redis-8.0.2/deps/hiredis/test.sh000077500000000000000000000056511501533116600165370ustar00rootroot00000000000000#!/bin/sh -ue REDIS_SERVER=${REDIS_SERVER:-redis-server} REDIS_PORT=${REDIS_PORT:-56379} REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} TEST_SSL=${TEST_SSL:-0} SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0} ENABLE_DEBUG_CMD= SSL_TEST_ARGS= SKIPS_ARG=${SKIPS_ARG:-} REDIS_DOCKER=${REDIS_DOCKER:-} # We need to enable the DEBUG command for redis-server >= 7.0.0 REDIS_MAJOR_VERSION="$(redis-server --version|awk -F'[^0-9]+' '{ print $2 }')" if [ "$REDIS_MAJOR_VERSION" -gt "6" ]; then ENABLE_DEBUG_CMD="enable-debug-command local" fi tmpdir=$(mktemp -d) PID_FILE=${tmpdir}/hiredis-test-redis.pid SOCK_FILE=${tmpdir}/hiredis-test-redis.sock if [ "$TEST_SSL" = "1" ]; then SSL_CA_CERT=${tmpdir}/ca.crt SSL_CA_KEY=${tmpdir}/ca.key SSL_CERT=${tmpdir}/redis.crt SSL_KEY=${tmpdir}/redis.key openssl genrsa -out ${tmpdir}/ca.key 4096 openssl req \ -x509 -new -nodes -sha256 \ -key ${SSL_CA_KEY} \ -days 3650 \ -subj '/CN=Hiredis Test CA' \ -out ${SSL_CA_CERT} openssl genrsa -out ${SSL_KEY} 2048 openssl req \ -new -sha256 \ -key ${SSL_KEY} \ -subj '/CN=Hiredis Test Cert' | \ openssl x509 \ -req -sha256 \ -CA ${SSL_CA_CERT} \ -CAkey ${SSL_CA_KEY} \ -CAserial ${tmpdir}/ca.txt \ -CAcreateserial \ -days 365 \ -out ${SSL_CERT} SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}" fi cleanup() { if [ -n "${REDIS_DOCKER}" ] ; then docker kill redis-test-server else set +e kill $(cat ${PID_FILE}) fi rm -rf ${tmpdir} } trap cleanup INT TERM EXIT # base config cat > ${tmpdir}/redis.conf <> ${tmpdir}/redis.conf <> ${tmpdir}/redis.conf < /* for struct timeval */ #ifndef inline #define inline __inline #endif #ifndef strcasecmp #define strcasecmp stricmp #endif #ifndef strncasecmp #define strncasecmp strnicmp #endif #ifndef va_copy #define va_copy(d,s) ((d) = (s)) #endif #ifndef snprintf #define snprintf c99_snprintf __inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) { int count = -1; if (size != 0) count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); if (count == -1) count = _vscprintf(format, ap); return count; } __inline int c99_snprintf(char* str, size_t size, const char* format, ...) { int count; va_list ap; va_start(ap, format); count = c99_vsnprintf(str, size, format, ap); va_end(ap); return count; } #endif #endif /* _MSC_VER */ #ifdef _WIN32 #define strerror_r(errno,buf,len) strerror_s(buf,len,errno) #endif /* _WIN32 */ #endif /* _WIN32_HELPER_INCLUDE */ redis-8.0.2/deps/jemalloc/000077500000000000000000000000001501533116600153515ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/.appveyor.yml000066400000000000000000000016501501533116600200210ustar00rootroot00000000000000version: '{build}' environment: matrix: - MSYSTEM: MINGW64 CPU: x86_64 MSVC: amd64 CONFIG_FLAGS: --enable-debug - MSYSTEM: MINGW64 CPU: x86_64 CONFIG_FLAGS: --enable-debug - MSYSTEM: MINGW32 CPU: i686 MSVC: x86 CONFIG_FLAGS: --enable-debug - MSYSTEM: MINGW32 CPU: i686 CONFIG_FLAGS: --enable-debug - MSYSTEM: MINGW64 CPU: x86_64 MSVC: amd64 - MSYSTEM: MINGW64 CPU: x86_64 - MSYSTEM: MINGW32 CPU: i686 MSVC: x86 - MSYSTEM: MINGW32 CPU: i686 install: - set PATH=c:\msys64\%MSYSTEM%\bin;c:\msys64\usr\bin;%PATH% - if defined MSVC call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %MSVC% - if defined MSVC pacman --noconfirm -Rsc mingw-w64-%CPU%-gcc gcc build_script: - bash -c "autoconf" - bash -c "./configure $CONFIG_FLAGS" - mingw32-make - file lib/jemalloc.dll - mingw32-make tests - mingw32-make -k check redis-8.0.2/deps/jemalloc/.autom4te.cfg000066400000000000000000000001531501533116600176510ustar00rootroot00000000000000begin-language: "Autoconf-without-aclocal-m4" args: --no-cache end-language: "Autoconf-without-aclocal-m4" redis-8.0.2/deps/jemalloc/.cirrus.yml000066400000000000000000000022751501533116600174670ustar00rootroot00000000000000env: CIRRUS_CLONE_DEPTH: 1 ARCH: amd64 task: matrix: env: DEBUG_CONFIG: --enable-debug env: DEBUG_CONFIG: --disable-debug matrix: - env: PROF_CONFIG: --enable-prof - env: PROF_CONFIG: --disable-prof matrix: - name: 64-bit env: CC: CXX: - name: 32-bit env: CC: cc -m32 CXX: c++ -m32 matrix: - env: UNCOMMON_CONFIG: - env: UNCOMMON_CONFIG: --with-lg-page=16 --with-malloc-conf=tcache:false freebsd_instance: matrix: image: freebsd-12-3-release-amd64 install_script: - sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf - pkg upgrade -y - pkg install -y autoconf gmake script: - autoconf # We don't perfectly track freebsd stdlib.h definitions. This is fine when # we count as a system header, but breaks otherwise, like during these # tests. - ./configure --with-jemalloc-prefix=ci_ ${DEBUG_CONFIG} ${PROF_CONFIG} ${UNCOMMON_CONFIG} - export JFLAG=`sysctl -n kern.smp.cpus` - gmake -j${JFLAG} - gmake -j${JFLAG} tests - gmake check redis-8.0.2/deps/jemalloc/.clang-format000066400000000000000000000070101501533116600177220ustar00rootroot00000000000000# jemalloc targets clang-format version 8. We include every option it supports # here, but comment out the ones that aren't relevant for us. --- # AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: AllDefinitions AlwaysBreakBeforeMultilineStrings: true # AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false # BreakAfterJavaFieldAnnotations: true BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true # BreakConstructorInitializers: BeforeColon # BreakInheritanceList: BeforeColon BreakStringLiterals: false ColumnLimit: 80 # CommentPragmas: '' # CompactNamespaces: true # ConstructorInitializerAllOnOneLineOrOnePerLine: true # ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 2 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: [ ql_foreach, qr_foreach, ] # IncludeBlocks: Preserve # IncludeCategories: # - Regex: '^<.*\.h(pp)?>' # Priority: 1 # IncludeIsMainRegex: '' IndentCaseLabels: false IndentPPDirectives: AfterHash IndentWidth: 4 IndentWrappedFunctionNames: false # JavaImportGroups: [] # JavaScriptQuotes: Leave # JavaScriptWrapImports: True KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 # NamespaceIndentation: None # ObjCBinPackProtocolList: Auto # ObjCBlockIndentWidth: 2 # ObjCSpaceAfterProperty: false # ObjCSpaceBeforeProtocolList: false PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 # PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right # RawStringFormats: # - Language: TextProto # Delimiters: # - 'pb' # - 'proto' # EnclosingFunctions: # - 'PARSE_TEXT_PROTO' # BasedOnStyle: google # - Language: Cpp # Delimiters: # - 'cc' # - 'cpp' # BasedOnStyle: llvm # CanonicalDelimiter: 'cc' ReflowComments: true SortIncludes: false SpaceAfterCStyleCast: false # SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true # SpaceBeforeCpp11BracedList: false # SpaceBeforeCtorInitializerColon: true # SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements # SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInCStyleCastParentheses: false # SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false # Standard: Cpp11 # This is nominally supported in clang-format version 8, but not in the build # used by some of the core jemalloc developers. # StatementMacros: [] TabWidth: 8 UseTab: Never ... redis-8.0.2/deps/jemalloc/.gitattributes000066400000000000000000000000231501533116600202370ustar00rootroot00000000000000* text=auto eol=lf redis-8.0.2/deps/jemalloc/.gitignore000066400000000000000000000037531501533116600173510ustar00rootroot00000000000000/bin/jemalloc-config /bin/jemalloc.sh /bin/jeprof /config.stamp /config.log /config.status /configure /doc/html.xsl /doc/manpages.xsl /doc/jemalloc.xml /doc/jemalloc.html /doc/jemalloc.3 /doc_internal/PROFILING_INTERNALS.pdf /jemalloc.pc /lib/ /Makefile /include/jemalloc/internal/jemalloc_preamble.h /include/jemalloc/internal/jemalloc_internal_defs.h /include/jemalloc/internal/private_namespace.gen.h /include/jemalloc/internal/private_namespace.h /include/jemalloc/internal/private_namespace_jet.gen.h /include/jemalloc/internal/private_namespace_jet.h /include/jemalloc/internal/private_symbols.awk /include/jemalloc/internal/private_symbols_jet.awk /include/jemalloc/internal/public_namespace.h /include/jemalloc/internal/public_symbols.txt /include/jemalloc/internal/public_unnamespace.h /include/jemalloc/jemalloc.h /include/jemalloc/jemalloc_defs.h /include/jemalloc/jemalloc_macros.h /include/jemalloc/jemalloc_mangle.h /include/jemalloc/jemalloc_mangle_jet.h /include/jemalloc/jemalloc_protos.h /include/jemalloc/jemalloc_protos_jet.h /include/jemalloc/jemalloc_rename.h /include/jemalloc/jemalloc_typedefs.h /src/*.[od] /src/*.sym /run_tests.out/ /test/test.sh test/include/test/jemalloc_test.h test/include/test/jemalloc_test_defs.h /test/integration/[A-Za-z]* !/test/integration/cpp/ !/test/integration/[A-Za-z]*.* /test/integration/*.[od] /test/integration/*.out /test/integration/cpp/[A-Za-z]* !/test/integration/cpp/[A-Za-z]*.* /test/integration/cpp/*.[od] /test/integration/cpp/*.out /test/src/*.[od] /test/stress/[A-Za-z]* !/test/stress/[A-Za-z]*.* /test/stress/*.[od] /test/stress/*.out /test/unit/[A-Za-z]* !/test/unit/[A-Za-z]*.* /test/unit/*.[od] /test/unit/*.out /test/analyze/[A-Za-z]* !/test/analyze/[A-Za-z]*.* /test/analyze/*.[od] /test/analyze/*.out /VERSION *.pdb *.sdf *.opendb *.VC.db *.opensdf *.cachefile *.suo *.user *.sln.docstates *.tmp .vs/ /msvc/Win32/ /msvc/x64/ /msvc/projects/*/*/Debug*/ /msvc/projects/*/*/Release*/ /msvc/projects/*/*/Win32/ /msvc/projects/*/*/x64/ redis-8.0.2/deps/jemalloc/.travis.yml000066400000000000000000000516341501533116600174730ustar00rootroot00000000000000# This config file is generated by ./scripts/gen_travis.py. # Do not edit by hand. # We use 'minimal', because 'generic' makes Windows VMs hang at startup. Also # the software provided by 'generic' is simply not needed for our tests. # Differences are explained here: # https://docs.travis-ci.com/user/languages/minimal-and-generic/ language: minimal dist: focal jobs: include: - os: windows arch: amd64 env: CC=gcc CXX=g++ EXTRA_CFLAGS="-fcommon" - os: windows arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-fcommon" - os: windows arch: amd64 env: CC=cl.exe CXX=cl.exe - os: windows arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes EXTRA_CFLAGS="-fcommon" - os: windows arch: amd64 env: CC=cl.exe CXX=cl.exe CONFIGURE_FLAGS="--enable-debug" - os: windows arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-fcommon" - os: windows arch: amd64 env: CC=cl.exe CXX=cl.exe CROSS_COMPILE_32BIT=yes - os: windows arch: amd64 env: CC=cl.exe CXX=cl.exe CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-debug" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --enable-prof-libunwind" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --enable-prof --enable-prof-libunwind" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-debug" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --enable-prof-libunwind --with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-prof --enable-prof-libunwind" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --enable-prof --enable-prof-libunwind --with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-debug --enable-prof --enable-prof-libunwind" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-debug --with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-prof --enable-prof-libunwind --with-lg-page=16 --with-malloc-conf=tcache:false" - os: freebsd arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes CONFIGURE_FLAGS="--enable-debug --enable-prof --enable-prof-libunwind --with-lg-page=16 --with-malloc-conf=tcache:false" - os: linux arch: amd64 env: CC=gcc CXX=g++ EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=clang CXX=clang++ EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=clang CXX=clang++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=clang CXX=clang++ CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl --enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl --with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks --with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16 --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16 --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16 --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16 --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false,dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false,percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false,background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=dss:primary,percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=dss:primary,background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu,background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: linux arch: ppc64le env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds" - os: osx arch: amd64 env: CC=gcc CXX=g++ EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CROSS_COMPILE_32BIT=yes EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--disable-libdl" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-opt-safety-checks" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-lg-page=16" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" - os: osx arch: amd64 env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds -Wno-unknown-warning-option -Wno-ignored-attributes -Wno-deprecated-declarations" # Development build - os: linux env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --disable-cache-oblivious --enable-stats --enable-log --enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds" # --enable-expermental-smallocx: - os: linux env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug --enable-experimental-smallocx --enable-stats --enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds" before_install: - |- if test -f "./scripts/$TRAVIS_OS_NAME/before_install.sh"; then source ./scripts/$TRAVIS_OS_NAME/before_install.sh fi before_script: - |- if test -f "./scripts/$TRAVIS_OS_NAME/before_script.sh"; then source ./scripts/$TRAVIS_OS_NAME/before_script.sh else scripts/gen_travis.py > travis_script && diff .travis.yml travis_script autoconf # If COMPILER_FLAGS are not empty, add them to CC and CXX ./configure ${COMPILER_FLAGS:+ CC="$CC $COMPILER_FLAGS" CXX="$CXX $COMPILER_FLAGS"} $CONFIGURE_FLAGS make -j3 make -j3 tests fi script: - |- if test -f "./scripts/$TRAVIS_OS_NAME/script.sh"; then source ./scripts/$TRAVIS_OS_NAME/script.sh else make check fi redis-8.0.2/deps/jemalloc/COPYING000066400000000000000000000032551501533116600164110ustar00rootroot00000000000000Unless otherwise specified, files in the jemalloc source distribution are subject to the following license: -------------------------------------------------------------------------------- Copyright (C) 2002-present Jason Evans . All rights reserved. Copyright (C) 2007-2012 Mozilla Foundation. All rights reserved. Copyright (C) 2009-present Facebook, Inc. 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(s), this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice(s), 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(S) ``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(S) 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. -------------------------------------------------------------------------------- redis-8.0.2/deps/jemalloc/ChangeLog000066400000000000000000002331431501533116600171310ustar00rootroot00000000000000Following are change highlights associated with official releases. Important bug fixes are all mentioned, but some internal enhancements are omitted here for brevity. Much more detail can be found in the git revision history: https://github.com/jemalloc/jemalloc * 5.3.0 (May 6, 2022) This release contains many speed and space optimizations, from micro optimizations on common paths to rework of internal data structures and locking schemes, and many more too detailed to list below. Multiple percent of system level metric improvements were measured in tested production workloads. The release has gone through large-scale production testing. New features: - Add the thread.idle mallctl which hints that the calling thread will be idle for a nontrivial period of time. (@davidtgoldblatt) - Allow small size classes to be the maximum size class to cache in the thread-specific cache, through the opt.[lg_]tcache_max option. (@interwq, @jordalgo) - Make the behavior of realloc(ptr, 0) configurable with opt.zero_realloc. (@davidtgoldblatt) - Add 'make uninstall' support. (@sangshuduo, @Lapenkov) - Support C++17 over-aligned allocation. (@marksantaniello) - Add the thread.peak mallctl for approximate per-thread peak memory tracking. (@davidtgoldblatt) - Add interval-based stats output opt.stats_interval. (@interwq) - Add prof.prefix to override filename prefixes for dumps. (@zhxchen17) - Add high resolution timestamp support for profiling. (@tyroguru) - Add the --collapsed flag to jeprof for flamegraph generation. (@igorwwwwwwwwwwwwwwwwwwww) - Add the --debug-syms-by-id option to jeprof for debug symbols discovery. (@DeannaGelbart) - Add the opt.prof_leak_error option to exit with error code when leak is detected using opt.prof_final. (@yunxuo) - Add opt.cache_oblivious as an runtime alternative to config.cache_oblivious. (@interwq) - Add mallctl interfaces: + opt.zero_realloc (@davidtgoldblatt) + opt.cache_oblivious (@interwq) + opt.prof_leak_error (@yunxuo) + opt.stats_interval (@interwq) + opt.stats_interval_opts (@interwq) + opt.tcache_max (@interwq) + opt.trust_madvise (@azat) + prof.prefix (@zhxchen17) + stats.zero_reallocs (@davidtgoldblatt) + thread.idle (@davidtgoldblatt) + thread.peak.{read,reset} (@davidtgoldblatt) Bug fixes: - Fix the synchronization around explicit tcache creation which could cause invalid tcache identifiers. This regression was first released in 5.0.0. (@yoshinorim, @davidtgoldblatt) - Fix a profiling biasing issue which could cause incorrect heap usage and object counts. This issue existed in all previous releases with the heap profiling feature. (@davidtgoldblatt) - Fix the order of stats counter updating on large realloc which could cause failed assertions. This regression was first released in 5.0.0. (@azat) - Fix the locking on the arena destroy mallctl, which could cause concurrent arena creations to fail. This functionality was first introduced in 5.0.0. (@interwq) Portability improvements: - Remove nothrow from system function declarations on macOS and FreeBSD. (@davidtgoldblatt, @fredemmott, @leres) - Improve overcommit and page alignment settings on NetBSD. (@zoulasc) - Improve CPU affinity support on BSD platforms. (@devnexen) - Improve utrace detection and support. (@devnexen) - Improve QEMU support with MADV_DONTNEED zeroed pages detection. (@azat) - Add memcntl support on Solaris / illumos. (@devnexen) - Improve CPU_SPINWAIT on ARM. (@AWSjswinney) - Improve TSD cleanup on FreeBSD. (@Lapenkov) - Disable percpu_arena if the CPU count cannot be reliably detected. (@azat) - Add malloc_size(3) override support. (@devnexen) - Add mmap VM_MAKE_TAG support. (@devnexen) - Add support for MADV_[NO]CORE. (@devnexen) - Add support for DragonFlyBSD. (@devnexen) - Fix the QUANTUM setting on MIPS64. (@brooksdavis) - Add the QUANTUM setting for ARC. (@vineetgarc) - Add the QUANTUM setting for LoongArch. (@wangjl-uos) - Add QNX support. (@jqian-aurora) - Avoid atexit(3) calls unless the relevant profiling features are enabled. (@BusyJay, @laiwei-rice, @interwq) - Fix unknown option detection when using Clang. (@Lapenkov) - Fix symbol conflict with musl libc. (@georgthegreat) - Add -Wimplicit-fallthrough checks. (@nickdesaulniers) - Add __forceinline support on MSVC. (@santagada) - Improve FreeBSD and Windows CI support. (@Lapenkov) - Add CI support for PPC64LE architecture. (@ezeeyahoo) Incompatible changes: - Maximum size class allowed in tcache (opt.[lg_]tcache_max) now has an upper bound of 8MiB. (@interwq) Optimizations and refactors (@davidtgoldblatt, @Lapenkov, @interwq): - Optimize the common cases of the thread cache operations. - Optimize internal data structures, including RB tree and pairing heap. - Optimize the internal locking on extent management. - Extract and refactor the internal page allocator and interface modules. Documentation: - Fix doc build with --with-install-suffix. (@lawmurray, @interwq) - Add PROFILING_INTERNALS.md. (@davidtgoldblatt) - Ensure the proper order of doc building and installation. (@Mingli-Yu) * 5.2.1 (August 5, 2019) This release is primarily about Windows. A critical virtual memory leak is resolved on all Windows platforms. The regression was present in all releases since 5.0.0. Bug fixes: - Fix a severe virtual memory leak on Windows. This regression was first released in 5.0.0. (@Ignition, @j0t, @frederik-h, @davidtgoldblatt, @interwq) - Fix size 0 handling in posix_memalign(). This regression was first released in 5.2.0. (@interwq) - Fix the prof_log unit test which may observe unexpected backtraces from compiler optimizations. The test was first added in 5.2.0. (@marxin, @gnzlbg, @interwq) - Fix the declaration of the extent_avail tree. This regression was first released in 5.1.0. (@zoulasc) - Fix an incorrect reference in jeprof. This functionality was first released in 3.0.0. (@prehistoric-penguin) - Fix an assertion on the deallocation fast-path. This regression was first released in 5.2.0. (@yinan1048576) - Fix the TLS_MODEL attribute in headers. This regression was first released in 5.0.0. (@zoulasc, @interwq) Optimizations and refactors: - Implement opt.retain on Windows and enable by default on 64-bit. (@interwq, @davidtgoldblatt) - Optimize away a branch on the operator delete[] path. (@mgrice) - Add format annotation to the format generator function. (@zoulasc) - Refactor and improve the size class header generation. (@yinan1048576) - Remove best fit. (@djwatson) - Avoid blocking on background thread locks for stats. (@oranagra, @interwq) * 5.2.0 (April 2, 2019) This release includes a few notable improvements, which are summarized below: 1) improved fast-path performance from the optimizations by @djwatson; 2) reduced virtual memory fragmentation and metadata usage; and 3) bug fixes on setting the number of background threads. In addition, peak / spike memory usage is improved with certain allocation patterns. As usual, the release and prior dev versions have gone through large-scale production testing. New features: - Implement oversize_threshold, which uses a dedicated arena for allocations crossing the specified threshold to reduce fragmentation. (@interwq) - Add extents usage information to stats. (@tyleretzel) - Log time information for sampled allocations. (@tyleretzel) - Support 0 size in sdallocx. (@djwatson) - Output rate for certain counters in malloc_stats. (@zinoale) - Add configure option --enable-readlinkat, which allows the use of readlinkat over readlink. (@davidtgoldblatt) - Add configure options --{enable,disable}-{static,shared} to allow not building unwanted libraries. (@Ericson2314) - Add configure option --disable-libdl to enable fully static builds. (@interwq) - Add mallctl interfaces: + opt.oversize_threshold (@interwq) + stats.arenas..extent_avail (@tyleretzel) + stats.arenas..extents..n{dirty,muzzy,retained} (@tyleretzel) + stats.arenas..extents..{dirty,muzzy,retained}_bytes (@tyleretzel) Portability improvements: - Update MSVC builds. (@maksqwe, @rustyx) - Workaround a compiler optimizer bug on s390x. (@rkmisra) - Make use of pthread_set_name_np(3) on FreeBSD. (@trasz) - Implement malloc_getcpu() to enable percpu_arena for windows. (@santagada) - Link against -pthread instead of -lpthread. (@paravoid) - Make background_thread not dependent on libdl. (@interwq) - Add stringify to fix a linker directive issue on MSVC. (@daverigby) - Detect and fall back when 8-bit atomics are unavailable. (@interwq) - Fall back to the default pthread_create if dlsym(3) fails. (@interwq) Optimizations and refactors: - Refactor the TSD module. (@davidtgoldblatt) - Avoid taking extents_muzzy mutex when muzzy is disabled. (@interwq) - Avoid taking large_mtx for auto arenas on the tcache flush path. (@interwq) - Optimize ixalloc by avoiding a size lookup. (@interwq) - Implement opt.oversize_threshold which uses a dedicated arena for requests crossing the threshold, also eagerly purges the oversize extents. Default the threshold to 8 MiB. (@interwq) - Clean compilation with -Wextra. (@gnzlbg, @jasone) - Refactor the size class module. (@davidtgoldblatt) - Refactor the stats emitter. (@tyleretzel) - Optimize pow2_ceil. (@rkmisra) - Avoid runtime detection of lazy purging on FreeBSD. (@trasz) - Optimize mmap(2) alignment handling on FreeBSD. (@trasz) - Improve error handling for THP state initialization. (@jsteemann) - Rework the malloc() fast path. (@djwatson) - Rework the free() fast path. (@djwatson) - Refactor and optimize the tcache fill / flush paths. (@djwatson) - Optimize sync / lwsync on PowerPC. (@chmeeedalf) - Bypass extent_dalloc() when retain is enabled. (@interwq) - Optimize the locking on large deallocation. (@interwq) - Reduce the number of pages committed from sanity checking in debug build. (@trasz, @interwq) - Deprecate OSSpinLock. (@interwq) - Lower the default number of background threads to 4 (when the feature is enabled). (@interwq) - Optimize the trylock spin wait. (@djwatson) - Use arena index for arena-matching checks. (@interwq) - Avoid forced decay on thread termination when using background threads. (@interwq) - Disable muzzy decay by default. (@djwatson, @interwq) - Only initialize libgcc unwinder when profiling is enabled. (@paravoid, @interwq) Bug fixes (all only relevant to jemalloc 5.x): - Fix background thread index issues with max_background_threads. (@djwatson, @interwq) - Fix stats output for opt.lg_extent_max_active_fit. (@interwq) - Fix opt.prof_prefix initialization. (@davidtgoldblatt) - Properly trigger decay on tcache destroy. (@interwq, @amosbird) - Fix tcache.flush. (@interwq) - Detect whether explicit extent zero out is necessary with huge pages or custom extent hooks, which may change the purge semantics. (@interwq) - Fix a side effect caused by extent_max_active_fit combined with decay-based purging, where freed extents can accumulate and not be reused for an extended period of time. (@interwq, @mpghf) - Fix a missing unlock on extent register error handling. (@zoulasc) Testing: - Simplify the Travis script output. (@gnzlbg) - Update the test scripts for FreeBSD. (@devnexen) - Add unit tests for the producer-consumer pattern. (@interwq) - Add Cirrus-CI config for FreeBSD builds. (@jasone) - Add size-matching sanity checks on tcache flush. (@davidtgoldblatt, @interwq) Incompatible changes: - Remove --with-lg-page-sizes. (@davidtgoldblatt) Documentation: - Attempt to build docs by default, however skip doc building when xsltproc is missing. (@interwq, @cmuellner) * 5.1.0 (May 4, 2018) This release is primarily about fine-tuning, ranging from several new features to numerous notable performance and portability enhancements. The release and prior dev versions have been running in multiple large scale applications for months, and the cumulative improvements are substantial in many cases. Given the long and successful production runs, this release is likely a good candidate for applications to upgrade, from both jemalloc 5.0 and before. For performance-critical applications, the newly added TUNING.md provides guidelines on jemalloc tuning. New features: - Implement transparent huge page support for internal metadata. (@interwq) - Add opt.thp to allow enabling / disabling transparent huge pages for all mappings. (@interwq) - Add maximum background thread count option. (@djwatson) - Allow prof_active to control opt.lg_prof_interval and prof.gdump. (@interwq) - Allow arena index lookup based on allocation addresses via mallctl. (@lionkov) - Allow disabling initial-exec TLS model. (@davidtgoldblatt, @KenMacD) - Add opt.lg_extent_max_active_fit to set the max ratio between the size of the active extent selected (to split off from) and the size of the requested allocation. (@interwq, @davidtgoldblatt) - Add retain_grow_limit to set the max size when growing virtual address space. (@interwq) - Add mallctl interfaces: + arena..retain_grow_limit (@interwq) + arenas.lookup (@lionkov) + max_background_threads (@djwatson) + opt.lg_extent_max_active_fit (@interwq) + opt.max_background_threads (@djwatson) + opt.metadata_thp (@interwq) + opt.thp (@interwq) + stats.metadata_thp (@interwq) Portability improvements: - Support GNU/kFreeBSD configuration. (@paravoid) - Support m68k, nios2 and SH3 architectures. (@paravoid) - Fall back to FD_CLOEXEC when O_CLOEXEC is unavailable. (@zonyitoo) - Fix symbol listing for cross-compiling. (@tamird) - Fix high bits computation on ARM. (@davidtgoldblatt, @paravoid) - Disable the CPU_SPINWAIT macro for Power. (@davidtgoldblatt, @marxin) - Fix MSVC 2015 & 2017 builds. (@rustyx) - Improve RISC-V support. (@EdSchouten) - Set name mangling script in strict mode. (@nicolov) - Avoid MADV_HUGEPAGE on ARM. (@marxin) - Modify configure to determine return value of strerror_r. (@davidtgoldblatt, @cferris1000) - Make sure CXXFLAGS is tested with CPP compiler. (@nehaljwani) - Fix 32-bit build on MSVC. (@rustyx) - Fix external symbol on MSVC. (@maksqwe) - Avoid a printf format specifier warning. (@jasone) - Add configure option --disable-initial-exec-tls which can allow jemalloc to be dynamically loaded after program startup. (@davidtgoldblatt, @KenMacD) - AArch64: Add ILP32 support. (@cmuellner) - Add --with-lg-vaddr configure option to support cross compiling. (@cmuellner, @davidtgoldblatt) Optimizations and refactors: - Improve active extent fit with extent_max_active_fit. This considerably reduces fragmentation over time and improves virtual memory and metadata usage. (@davidtgoldblatt, @interwq) - Eagerly coalesce large extents to reduce fragmentation. (@interwq) - sdallocx: only read size info when page aligned (i.e. possibly sampled), which speeds up the sized deallocation path significantly. (@interwq) - Avoid attempting new mappings for in place expansion with retain, since it rarely succeeds in practice and causes high overhead. (@interwq) - Refactor OOM handling in newImpl. (@wqfish) - Add internal fine-grained logging functionality for debugging use. (@davidtgoldblatt) - Refactor arena / tcache interactions. (@davidtgoldblatt) - Refactor extent management with dumpable flag. (@davidtgoldblatt) - Add runtime detection of lazy purging. (@interwq) - Use pairing heap instead of red-black tree for extents_avail. (@djwatson) - Use sysctl on startup in FreeBSD. (@trasz) - Use thread local prng state instead of atomic. (@djwatson) - Make decay to always purge one more extent than before, because in practice large extents are usually the ones that cross the decay threshold. Purging the additional extent helps save memory as well as reduce VM fragmentation. (@interwq) - Fast division by dynamic values. (@davidtgoldblatt) - Improve the fit for aligned allocation. (@interwq, @edwinsmith) - Refactor extent_t bitpacking. (@rkmisra) - Optimize the generated assembly for ticker operations. (@davidtgoldblatt) - Convert stats printing to use a structured text emitter. (@davidtgoldblatt) - Remove preserve_lru feature for extents management. (@djwatson) - Consolidate two memory loads into one on the fast deallocation path. (@davidtgoldblatt, @interwq) Bug fixes (most of the issues are only relevant to jemalloc 5.0): - Fix deadlock with multithreaded fork in OS X. (@davidtgoldblatt) - Validate returned file descriptor before use. (@zonyitoo) - Fix a few background thread initialization and shutdown issues. (@interwq) - Fix an extent coalesce + decay race by taking both coalescing extents off the LRU list. (@interwq) - Fix potentially unbound increase during decay, caused by one thread keep stashing memory to purge while other threads generating new pages. The number of pages to purge is checked to prevent this. (@interwq) - Fix a FreeBSD bootstrap assertion. (@strejda, @interwq) - Handle 32 bit mutex counters. (@rkmisra) - Fix a indexing bug when creating background threads. (@davidtgoldblatt, @binliu19) - Fix arguments passed to extent_init. (@yuleniwo, @interwq) - Fix addresses used for ordering mutexes. (@rkmisra) - Fix abort_conf processing during bootstrap. (@interwq) - Fix include path order for out-of-tree builds. (@cmuellner) Incompatible changes: - Remove --disable-thp. (@interwq) - Remove mallctl interfaces: + config.thp (@interwq) Documentation: - Add TUNING.md. (@interwq, @davidtgoldblatt, @djwatson) * 5.0.1 (July 1, 2017) This bugfix release fixes several issues, most of which are obscure enough that typical applications are not impacted. Bug fixes: - Update decay->nunpurged before purging, in order to avoid potential update races and subsequent incorrect purging volume. (@interwq) - Only abort on dlsym(3) error if the failure impacts an enabled feature (lazy locking and/or background threads). This mitigates an initialization failure bug for which we still do not have a clear reproduction test case. (@interwq) - Modify tsd management so that it neither crashes nor leaks if a thread's only allocation activity is to call free() after TLS destructors have been executed. This behavior was observed when operating with GNU libc, and is unlikely to be an issue with other libc implementations. (@interwq) - Mask signals during background thread creation. This prevents signals from being inadvertently delivered to background threads. (@jasone, @davidtgoldblatt, @interwq) - Avoid inactivity checks within background threads, in order to prevent recursive mutex acquisition. (@interwq) - Fix extent_grow_retained() to use the specified hooks when the arena..extent_hooks mallctl is used to override the default hooks. (@interwq) - Add missing reentrancy support for custom extent hooks which allocate. (@interwq) - Post-fork(2), re-initialize the list of tcaches associated with each arena to contain no tcaches except the forking thread's. (@interwq) - Add missing post-fork(2) mutex reinitialization for extent_grow_mtx. This fixes potential deadlocks after fork(2). (@interwq) - Enforce minimum autoconf version (currently 2.68), since 2.63 is known to generate corrupt configure scripts. (@jasone) - Ensure that the configured page size (--with-lg-page) is no larger than the configured huge page size (--with-lg-hugepage). (@jasone) * 5.0.0 (June 13, 2017) Unlike all previous jemalloc releases, this release does not use naturally aligned "chunks" for virtual memory management, and instead uses page-aligned "extents". This change has few externally visible effects, but the internal impacts are... extensive. Many other internal changes combine to make this the most cohesively designed version of jemalloc so far, with ample opportunity for further enhancements. Continuous integration is now an integral aspect of development thanks to the efforts of @davidtgoldblatt, and the dev branch tends to remain reasonably stable on the tested platforms (Linux, FreeBSD, macOS, and Windows). As a side effect the official release frequency may decrease over time. New features: - Implement optional per-CPU arena support; threads choose which arena to use based on current CPU rather than on fixed thread-->arena associations. (@interwq) - Implement two-phase decay of unused dirty pages. Pages transition from dirty-->muzzy-->clean, where the first phase transition relies on madvise(... MADV_FREE) semantics, and the second phase transition discards pages such that they are replaced with demand-zeroed pages on next access. (@jasone) - Increase decay time resolution from seconds to milliseconds. (@jasone) - Implement opt-in per CPU background threads, and use them for asynchronous decay-driven unused dirty page purging. (@interwq) - Add mutex profiling, which collects a variety of statistics useful for diagnosing overhead/contention issues. (@interwq) - Add C++ new/delete operator bindings. (@djwatson) - Support manually created arena destruction, such that all data and metadata are discarded. Add MALLCTL_ARENAS_DESTROYED for accessing merged stats associated with destroyed arenas. (@jasone) - Add MALLCTL_ARENAS_ALL as a fixed index for use in accessing merged/destroyed arena statistics via mallctl. (@jasone) - Add opt.abort_conf to optionally abort if invalid configuration options are detected during initialization. (@interwq) - Add opt.stats_print_opts, so that e.g. JSON output can be selected for the stats dumped during exit if opt.stats_print is true. (@jasone) - Add --with-version=VERSION for use when embedding jemalloc into another project's git repository. (@jasone) - Add --disable-thp to support cross compiling. (@jasone) - Add --with-lg-hugepage to support cross compiling. (@jasone) - Add mallctl interfaces (various authors): + background_thread + opt.abort_conf + opt.retain + opt.percpu_arena + opt.background_thread + opt.{dirty,muzzy}_decay_ms + opt.stats_print_opts + arena..initialized + arena..destroy + arena..{dirty,muzzy}_decay_ms + arena..extent_hooks + arenas.{dirty,muzzy}_decay_ms + arenas.bin..slab_size + arenas.nlextents + arenas.lextent..size + arenas.create + stats.background_thread.{num_threads,num_runs,run_interval} + stats.mutexes.{ctl,background_thread,prof,reset}. {num_ops,num_spin_acq,num_wait,max_wait_time,total_wait_time,max_num_thds, num_owner_switch} + stats.arenas..{dirty,muzzy}_decay_ms + stats.arenas..uptime + stats.arenas..{pmuzzy,base,internal,resident} + stats.arenas..{dirty,muzzy}_{npurge,nmadvise,purged} + stats.arenas..bins..{nslabs,reslabs,curslabs} + stats.arenas..bins..mutex. {num_ops,num_spin_acq,num_wait,max_wait_time,total_wait_time,max_num_thds, num_owner_switch} + stats.arenas..lextents..{nmalloc,ndalloc,nrequests,curlextents} + stats.arenas.i.mutexes.{large,extent_avail,extents_dirty,extents_muzzy, extents_retained,decay_dirty,decay_muzzy,base,tcache_list}. {num_ops,num_spin_acq,num_wait,max_wait_time,total_wait_time,max_num_thds, num_owner_switch} Portability improvements: - Improve reentrant allocation support, such that deadlock is less likely if e.g. a system library call in turn allocates memory. (@davidtgoldblatt, @interwq) - Support static linking of jemalloc with glibc. (@djwatson) Optimizations and refactors: - Organize virtual memory as "extents" of virtual memory pages, rather than as naturally aligned "chunks", and store all metadata in arbitrarily distant locations. This reduces virtual memory external fragmentation, and will interact better with huge pages (not yet explicitly supported). (@jasone) - Fold large and huge size classes together; only small and large size classes remain. (@jasone) - Unify the allocation paths, and merge most fast-path branching decisions. (@davidtgoldblatt, @interwq) - Embed per thread automatic tcache into thread-specific data, which reduces conditional branches and dereferences. Also reorganize tcache to increase fast-path data locality. (@interwq) - Rewrite atomics to closely model the C11 API, convert various synchronization from mutex-based to atomic, and use the explicit memory ordering control to resolve various hypothetical races without increasing synchronization overhead. (@davidtgoldblatt) - Extensively optimize rtree via various methods: + Add multiple layers of rtree lookup caching, since rtree lookups are now part of fast-path deallocation. (@interwq) + Determine rtree layout at compile time. (@jasone) + Make the tree shallower for common configurations. (@jasone) + Embed the root node in the top-level rtree data structure, thus avoiding one level of indirection. (@jasone) + Further specialize leaf elements as compared to internal node elements, and directly embed extent metadata needed for fast-path deallocation. (@jasone) + Ignore leading always-zero address bits (architecture-specific). (@jasone) - Reorganize headers (ongoing work) to make them hermetic, and disentangle various module dependencies. (@davidtgoldblatt) - Convert various internal data structures such as size class metadata from boot-time-initialized to compile-time-initialized. Propagate resulting data structure simplifications, such as making arena metadata fixed-size. (@jasone) - Simplify size class lookups when constrained to size classes that are multiples of the page size. This speeds lookups, but the primary benefit is complexity reduction in code that was the source of numerous regressions. (@jasone) - Lock individual extents when possible for localized extent operations, rather than relying on a top-level arena lock. (@davidtgoldblatt, @jasone) - Use first fit layout policy instead of best fit, in order to improve packing. (@jasone) - If munmap(2) is not in use, use an exponential series to grow each arena's virtual memory, so that the number of disjoint virtual memory mappings remains low. (@jasone) - Implement per arena base allocators, so that arenas never share any virtual memory pages. (@jasone) - Automatically generate private symbol name mangling macros. (@jasone) Incompatible changes: - Replace chunk hooks with an expanded/normalized set of extent hooks. (@jasone) - Remove ratio-based purging. (@jasone) - Remove --disable-tcache. (@jasone) - Remove --disable-tls. (@jasone) - Remove --enable-ivsalloc. (@jasone) - Remove --with-lg-size-class-group. (@jasone) - Remove --with-lg-tiny-min. (@jasone) - Remove --disable-cc-silence. (@jasone) - Remove --enable-code-coverage. (@jasone) - Remove --disable-munmap (replaced by opt.retain). (@jasone) - Remove Valgrind support. (@jasone) - Remove quarantine support. (@jasone) - Remove redzone support. (@jasone) - Remove mallctl interfaces (various authors): + config.munmap + config.tcache + config.tls + config.valgrind + opt.lg_chunk + opt.purge + opt.lg_dirty_mult + opt.decay_time + opt.quarantine + opt.redzone + opt.thp + arena..lg_dirty_mult + arena..decay_time + arena..chunk_hooks + arenas.initialized + arenas.lg_dirty_mult + arenas.decay_time + arenas.bin..run_size + arenas.nlruns + arenas.lrun..size + arenas.nhchunks + arenas.hchunk..size + arenas.extend + stats.cactive + stats.arenas..lg_dirty_mult + stats.arenas..decay_time + stats.arenas..metadata.{mapped,allocated} + stats.arenas..{npurge,nmadvise,purged} + stats.arenas..huge.{allocated,nmalloc,ndalloc,nrequests} + stats.arenas..bins..{nruns,reruns,curruns} + stats.arenas..lruns..{nmalloc,ndalloc,nrequests,curruns} + stats.arenas..hchunks..{nmalloc,ndalloc,nrequests,curhchunks} Bug fixes: - Improve interval-based profile dump triggering to dump only one profile when a single allocation's size exceeds the interval. (@jasone) - Use prefixed function names (as controlled by --with-jemalloc-prefix) when pruning backtrace frames in jeprof. (@jasone) * 4.5.0 (February 28, 2017) This is the first release to benefit from much broader continuous integration testing, thanks to @davidtgoldblatt. Had we had this testing infrastructure in place for prior releases, it would have caught all of the most serious regressions fixed by this release. New features: - Add --disable-thp and the opt.thp mallctl to provide opt-out mechanisms for transparent huge page integration. (@jasone) - Update zone allocator integration to work with macOS 10.12. (@glandium) - Restructure *CFLAGS configuration, so that CFLAGS behaves typically, and EXTRA_CFLAGS provides a way to specify e.g. -Werror during building, but not during configuration. (@jasone, @ronawho) Bug fixes: - Fix DSS (sbrk(2)-based) allocation. This regression was first released in 4.3.0. (@jasone) - Handle race in per size class utilization computation. This functionality was first released in 4.0.0. (@interwq) - Fix lock order reversal during gdump. (@jasone) - Fix/refactor tcache synchronization. This regression was first released in 4.0.0. (@jasone) - Fix various JSON-formatted malloc_stats_print() bugs. This functionality was first released in 4.3.0. (@jasone) - Fix huge-aligned allocation. This regression was first released in 4.4.0. (@jasone) - When transparent huge page integration is enabled, detect what state pages start in according to the kernel's current operating mode, and only convert arena chunks to non-huge during purging if that is not their initial state. This functionality was first released in 4.4.0. (@jasone) - Fix lg_chunk clamping for the --enable-cache-oblivious --disable-fill case. This regression was first released in 4.0.0. (@jasone, @428desmo) - Properly detect sparc64 when building for Linux. (@glaubitz) * 4.4.0 (December 3, 2016) New features: - Add configure support for *-*-linux-android. (@cferris1000, @jasone) - Add the --disable-syscall configure option, for use on systems that place security-motivated limitations on syscall(2). (@jasone) - Add support for Debian GNU/kFreeBSD. (@thesam) Optimizations: - Add extent serial numbers and use them where appropriate as a sort key that is higher priority than address, so that the allocation policy prefers older extents. This tends to improve locality (decrease fragmentation) when memory grows downward. (@jasone) - Refactor madvise(2) configuration so that MADV_FREE is detected and utilized on Linux 4.5 and newer. (@jasone) - Mark partially purged arena chunks as non-huge-page. This improves interaction with Linux's transparent huge page functionality. (@jasone) Bug fixes: - Fix size class computations for edge conditions involving extremely large allocations. This regression was first released in 4.0.0. (@jasone, @ingvarha) - Remove overly restrictive assertions related to the cactive statistic. This regression was first released in 4.1.0. (@jasone) - Implement a more reliable detection scheme for os_unfair_lock on macOS. (@jszakmeister) * 4.3.1 (November 7, 2016) Bug fixes: - Fix a severe virtual memory leak. This regression was first released in 4.3.0. (@interwq, @jasone) - Refactor atomic and prng APIs to restore support for 32-bit platforms that use pre-C11 toolchains, e.g. FreeBSD's mips. (@jasone) * 4.3.0 (November 4, 2016) This is the first release that passes the test suite for multiple Windows configurations, thanks in large part to @glandium setting up continuous integration via AppVeyor (and Travis CI for Linux and OS X). New features: - Add "J" (JSON) support to malloc_stats_print(). (@jasone) - Add Cray compiler support. (@ronawho) Optimizations: - Add/use adaptive spinning for bootstrapping and radix tree node initialization. (@jasone) Bug fixes: - Fix large allocation to search starting in the optimal size class heap, which can substantially reduce virtual memory churn and fragmentation. This regression was first released in 4.0.0. (@mjp41, @jasone) - Fix stats.arenas..nthreads accounting. (@interwq) - Fix and simplify decay-based purging. (@jasone) - Make DSS (sbrk(2)-related) operations lockless, which resolves potential deadlocks during thread exit. (@jasone) - Fix over-sized allocation of radix tree leaf nodes. (@mjp41, @ogaun, @jasone) - Fix over-sized allocation of arena_t (plus associated stats) data structures. (@jasone, @interwq) - Fix EXTRA_CFLAGS to not affect configuration. (@jasone) - Fix a Valgrind integration bug. (@ronawho) - Disallow 0x5a junk filling when running in Valgrind. (@jasone) - Fix a file descriptor leak on Linux. This regression was first released in 4.2.0. (@vsarunas, @jasone) - Fix static linking of jemalloc with glibc. (@djwatson) - Use syscall(2) rather than {open,read,close}(2) during boot on Linux. This works around other libraries' system call wrappers performing reentrant allocation. (@kspinka, @Whissi, @jasone) - Fix OS X default zone replacement to work with OS X 10.12. (@glandium, @jasone) - Fix cached memory management to avoid needless commit/decommit operations during purging, which resolves permanent virtual memory map fragmentation issues on Windows. (@mjp41, @jasone) - Fix TSD fetches to avoid (recursive) allocation. This is relevant to non-TLS and Windows configurations. (@jasone) - Fix malloc_conf overriding to work on Windows. (@jasone) - Forcibly disable lazy-lock on Windows (was forcibly *enabled*). (@jasone) * 4.2.1 (June 8, 2016) Bug fixes: - Fix bootstrapping issues for configurations that require allocation during tsd initialization (e.g. --disable-tls). (@cferris1000, @jasone) - Fix gettimeofday() version of nstime_update(). (@ronawho) - Fix Valgrind regressions in calloc() and chunk_alloc_wrapper(). (@ronawho) - Fix potential VM map fragmentation regression. (@jasone) - Fix opt_zero-triggered in-place huge reallocation zeroing. (@jasone) - Fix heap profiling context leaks in reallocation edge cases. (@jasone) * 4.2.0 (May 12, 2016) New features: - Add the arena..reset mallctl, which makes it possible to discard all of an arena's allocations in a single operation. (@jasone) - Add the stats.retained and stats.arenas..retained statistics. (@jasone) - Add the --with-version configure option. (@jasone) - Support --with-lg-page values larger than actual page size. (@jasone) Optimizations: - Use pairing heaps rather than red-black trees for various hot data structures. (@djwatson, @jasone) - Streamline fast paths of rtree operations. (@jasone) - Optimize the fast paths of calloc() and [m,d,sd]allocx(). (@jasone) - Decommit unused virtual memory if the OS does not overcommit. (@jasone) - Specify MAP_NORESERVE on Linux if [heuristic] overcommit is active, in order to avoid unfortunate interactions during fork(2). (@jasone) Bug fixes: - Fix chunk accounting related to triggering gdump profiles. (@jasone) - Link against librt for clock_gettime(2) if glibc < 2.17. (@jasone) - Scale leak report summary according to sampling probability. (@jasone) * 4.1.1 (May 3, 2016) This bugfix release resolves a variety of mostly minor issues, though the bitmap fix is critical for 64-bit Windows. Bug fixes: - Fix the linear scan version of bitmap_sfu() to shift by the proper amount even when sizeof(long) is not the same as sizeof(void *), as on 64-bit Windows. (@jasone) - Fix hashing functions to avoid unaligned memory accesses (and resulting crashes). This is relevant at least to some ARM-based platforms. (@rkmisra) - Fix fork()-related lock rank ordering reversals. These reversals were unlikely to cause deadlocks in practice except when heap profiling was enabled and active. (@jasone) - Fix various chunk leaks in OOM code paths. (@jasone) - Fix malloc_stats_print() to print opt.narenas correctly. (@jasone) - Fix MSVC-specific build/test issues. (@rustyx, @yuslepukhin) - Fix a variety of test failures that were due to test fragility rather than core bugs. (@jasone) * 4.1.0 (February 28, 2016) This release is primarily about optimizations, but it also incorporates a lot of portability-motivated refactoring and enhancements. Many people worked on this release, to an extent that even with the omission here of minor changes (see git revision history), and of the people who reported and diagnosed issues, so much of the work was contributed that starting with this release, changes are annotated with author credits to help reflect the collaborative effort involved. New features: - Implement decay-based unused dirty page purging, a major optimization with mallctl API impact. This is an alternative to the existing ratio-based unused dirty page purging, and is intended to eventually become the sole purging mechanism. New mallctls: + opt.purge + opt.decay_time + arena..decay + arena..decay_time + arenas.decay_time + stats.arenas..decay_time (@jasone, @cevans87) - Add --with-malloc-conf, which makes it possible to embed a default options string during configuration. This was motivated by the desire to specify --with-malloc-conf=purge:decay , since the default must remain purge:ratio until the 5.0.0 release. (@jasone) - Add MS Visual Studio 2015 support. (@rustyx, @yuslepukhin) - Make *allocx() size class overflow behavior defined. The maximum size class is now less than PTRDIFF_MAX to protect applications against numerical overflow, and all allocation functions are guaranteed to indicate errors rather than potentially crashing if the request size exceeds the maximum size class. (@jasone) - jeprof: + Add raw heap profile support. (@jasone) + Add --retain and --exclude for backtrace symbol filtering. (@jasone) Optimizations: - Optimize the fast path to combine various bootstrapping and configuration checks and execute more streamlined code in the common case. (@interwq) - Use linear scan for small bitmaps (used for small object tracking). In addition to speeding up bitmap operations on 64-bit systems, this reduces allocator metadata overhead by approximately 0.2%. (@djwatson) - Separate arena_avail trees, which substantially speeds up run tree operations. (@djwatson) - Use memoization (boot-time-computed table) for run quantization. Separate arena_avail trees reduced the importance of this optimization. (@jasone) - Attempt mmap-based in-place huge reallocation. This can dramatically speed up incremental huge reallocation. (@jasone) Incompatible changes: - Make opt.narenas unsigned rather than size_t. (@jasone) Bug fixes: - Fix stats.cactive accounting regression. (@rustyx, @jasone) - Handle unaligned keys in hash(). This caused problems for some ARM systems. (@jasone, @cferris1000) - Refactor arenas array. In addition to fixing a fork-related deadlock, this makes arena lookups faster and simpler. (@jasone) - Move retained memory allocation out of the default chunk allocation function, to a location that gets executed even if the application installs a custom chunk allocation function. This resolves a virtual memory leak. (@buchgr) - Fix a potential tsd cleanup leak. (@cferris1000, @jasone) - Fix run quantization. In practice this bug had no impact unless applications requested memory with alignment exceeding one page. (@jasone, @djwatson) - Fix LinuxThreads-specific bootstrapping deadlock. (Cosmin Paraschiv) - jeprof: + Don't discard curl options if timeout is not defined. (@djwatson) + Detect failed profile fetches. (@djwatson) - Fix stats.arenas..{dss,lg_dirty_mult,decay_time,pactive,pdirty} for --disable-stats case. (@jasone) * 4.0.4 (October 24, 2015) This bugfix release fixes another xallocx() regression. No other regressions have come to light in over a month, so this is likely a good starting point for people who prefer to wait for "dot one" releases with all the major issues shaken out. Bug fixes: - Fix xallocx(..., MALLOCX_ZERO to zero the last full trailing page of large allocations that have been randomly assigned an offset of 0 when --enable-cache-oblivious configure option is enabled. * 4.0.3 (September 24, 2015) This bugfix release continues the trend of xallocx() and heap profiling fixes. Bug fixes: - Fix xallocx(..., MALLOCX_ZERO) to zero all trailing bytes of large allocations when --enable-cache-oblivious configure option is enabled. - Fix xallocx(..., MALLOCX_ZERO) to zero trailing bytes of huge allocations when resizing from/to a size class that is not a multiple of the chunk size. - Fix prof_tctx_dump_iter() to filter out nodes that were created after heap profile dumping started. - Work around a potentially bad thread-specific data initialization interaction with NPTL (glibc's pthreads implementation). * 4.0.2 (September 21, 2015) This bugfix release addresses a few bugs specific to heap profiling. Bug fixes: - Fix ixallocx_prof_sample() to never modify nor create sampled small allocations. xallocx() is in general incapable of moving small allocations, so this fix removes buggy code without loss of generality. - Fix irallocx_prof_sample() to always allocate large regions, even when alignment is non-zero. - Fix prof_alloc_rollback() to read tdata from thread-specific data rather than dereferencing a potentially invalid tctx. * 4.0.1 (September 15, 2015) This is a bugfix release that is somewhat high risk due to the amount of refactoring required to address deep xallocx() problems. As a side effect of these fixes, xallocx() now tries harder to partially fulfill requests for optional extra space. Note that a couple of minor heap profiling optimizations are included, but these are better thought of as performance fixes that were integral to discovering most of the other bugs. Optimizations: - Avoid a chunk metadata read in arena_prof_tctx_set(), since it is in the fast path when heap profiling is enabled. Additionally, split a special case out into arena_prof_tctx_reset(), which also avoids chunk metadata reads. - Optimize irallocx_prof() to optimistically update the sampler state. The prior implementation appears to have been a holdover from when rallocx()/xallocx() functionality was combined as rallocm(). Bug fixes: - Fix TLS configuration such that it is enabled by default for platforms on which it works correctly. - Fix arenas_cache_cleanup() and arena_get_hard() to handle allocation/deallocation within the application's thread-specific data cleanup functions even after arenas_cache is torn down. - Fix xallocx() bugs related to size+extra exceeding HUGE_MAXCLASS. - Fix chunk purge hook calls for in-place huge shrinking reallocation to specify the old chunk size rather than the new chunk size. This bug caused no correctness issues for the default chunk purge function, but was visible to custom functions set via the "arena..chunk_hooks" mallctl. - Fix heap profiling bugs: + Fix heap profiling to distinguish among otherwise identical sample sites with interposed resets (triggered via the "prof.reset" mallctl). This bug could cause data structure corruption that would most likely result in a segfault. + Fix irealloc_prof() to prof_alloc_rollback() on OOM. + Make one call to prof_active_get_unlocked() per allocation event, and use the result throughout the relevant functions that handle an allocation event. Also add a missing check in prof_realloc(). These fixes protect allocation events against concurrent prof_active changes. + Fix ixallocx_prof() to pass usize_max and zero to ixallocx_prof_sample() in the correct order. + Fix prof_realloc() to call prof_free_sampled_object() after calling prof_malloc_sample_object(). Prior to this fix, if tctx and old_tctx were the same, the tctx could have been prematurely destroyed. - Fix portability bugs: + Don't bitshift by negative amounts when encoding/decoding run sizes in chunk header maps. This affected systems with page sizes greater than 8 KiB. + Rename index_t to szind_t to avoid an existing type on Solaris. + Add JEMALLOC_CXX_THROW to the memalign() function prototype, in order to match glibc and avoid compilation errors when including both jemalloc/jemalloc.h and malloc.h in C++ code. + Don't assume that /bin/sh is appropriate when running size_classes.sh during configuration. + Consider __sparcv9 a synonym for __sparc64__ when defining LG_QUANTUM. + Link tests to librt if it contains clock_gettime(2). * 4.0.0 (August 17, 2015) This version contains many speed and space optimizations, both minor and major. The major themes are generalization, unification, and simplification. Although many of these optimizations cause no visible behavior change, their cumulative effect is substantial. New features: - Normalize size class spacing to be consistent across the complete size range. By default there are four size classes per size doubling, but this is now configurable via the --with-lg-size-class-group option. Also add the --with-lg-page, --with-lg-page-sizes, --with-lg-quantum, and --with-lg-tiny-min options, which can be used to tweak page and size class settings. Impacts: + Worst case performance for incrementally growing/shrinking reallocation is improved because there are far fewer size classes, and therefore copying happens less often. + Internal fragmentation is limited to 20% for all but the smallest size classes (those less than four times the quantum). (1B + 4 KiB) and (1B + 4 MiB) previously suffered nearly 50% internal fragmentation. + Chunk fragmentation tends to be lower because there are fewer distinct run sizes to pack. - Add support for explicit tcaches. The "tcache.create", "tcache.flush", and "tcache.destroy" mallctls control tcache lifetime and flushing, and the MALLOCX_TCACHE(tc) and MALLOCX_TCACHE_NONE flags to the *allocx() API control which tcache is used for each operation. - Implement per thread heap profiling, as well as the ability to enable/disable heap profiling on a per thread basis. Add the "prof.reset", "prof.lg_sample", "thread.prof.name", "thread.prof.active", "opt.prof_thread_active_init", "prof.thread_active_init", and "thread.prof.active" mallctls. - Add support for per arena application-specified chunk allocators, configured via the "arena..chunk_hooks" mallctl. - Refactor huge allocation to be managed by arenas, so that arenas now function as general purpose independent allocators. This is important in the context of user-specified chunk allocators, aside from the scalability benefits. Related new statistics: + The "stats.arenas..huge.allocated", "stats.arenas..huge.nmalloc", "stats.arenas..huge.ndalloc", and "stats.arenas..huge.nrequests" mallctls provide high level per arena huge allocation statistics. + The "arenas.nhchunks", "arenas.hchunk..size", "stats.arenas..hchunks..nmalloc", "stats.arenas..hchunks..ndalloc", "stats.arenas..hchunks..nrequests", and "stats.arenas..hchunks..curhchunks" mallctls provide per size class statistics. - Add the 'util' column to malloc_stats_print() output, which reports the proportion of available regions that are currently in use for each small size class. - Add "alloc" and "free" modes for for junk filling (see the "opt.junk" mallctl), so that it is possible to separately enable junk filling for allocation versus deallocation. - Add the jemalloc-config script, which provides information about how jemalloc was configured, and how to integrate it into application builds. - Add metadata statistics, which are accessible via the "stats.metadata", "stats.arenas..metadata.mapped", and "stats.arenas..metadata.allocated" mallctls. - Add the "stats.resident" mallctl, which reports the upper limit of physically resident memory mapped by the allocator. - Add per arena control over unused dirty page purging, via the "arenas.lg_dirty_mult", "arena..lg_dirty_mult", and "stats.arenas..lg_dirty_mult" mallctls. - Add the "prof.gdump" mallctl, which makes it possible to toggle the gdump feature on/off during program execution. - Add sdallocx(), which implements sized deallocation. The primary optimization over dallocx() is the removal of a metadata read, which often suffers an L1 cache miss. - Add missing header includes in jemalloc/jemalloc.h, so that applications only have to #include . - Add support for additional platforms: + Bitrig + Cygwin + DragonFlyBSD + iOS + OpenBSD + OpenRISC/or1k Optimizations: - Maintain dirty runs in per arena LRUs rather than in per arena trees of dirty-run-containing chunks. In practice this change significantly reduces dirty page purging volume. - Integrate whole chunks into the unused dirty page purging machinery. This reduces the cost of repeated huge allocation/deallocation, because it effectively introduces a cache of chunks. - Split the arena chunk map into two separate arrays, in order to increase cache locality for the frequently accessed bits. - Move small run metadata out of runs, into arena chunk headers. This reduces run fragmentation, smaller runs reduce external fragmentation for small size classes, and packed (less uniformly aligned) metadata layout improves CPU cache set distribution. - Randomly distribute large allocation base pointer alignment relative to page boundaries in order to more uniformly utilize CPU cache sets. This can be disabled via the --disable-cache-oblivious configure option, and queried via the "config.cache_oblivious" mallctl. - Micro-optimize the fast paths for the public API functions. - Refactor thread-specific data to reside in a single structure. This assures that only a single TLS read is necessary per call into the public API. - Implement in-place huge allocation growing and shrinking. - Refactor rtree (radix tree for chunk lookups) to be lock-free, and make additional optimizations that reduce maximum lookup depth to one or two levels. This resolves what was a concurrency bottleneck for per arena huge allocation, because a global data structure is critical for determining which arenas own which huge allocations. Incompatible changes: - Replace --enable-cc-silence with --disable-cc-silence to suppress spurious warnings by default. - Assure that the constness of malloc_usable_size()'s return type matches that of the system implementation. - Change the heap profile dump format to support per thread heap profiling, rename pprof to jeprof, and enhance it with the --thread= option. As a result, the bundled jeprof must now be used rather than the upstream (gperftools) pprof. - Disable "opt.prof_final" by default, in order to avoid atexit(3), which can internally deadlock on some platforms. - Change the "arenas.nlruns" mallctl type from size_t to unsigned. - Replace the "stats.arenas..bins..allocated" mallctl with "stats.arenas..bins..curregs". - Ignore MALLOC_CONF in set{uid,gid,cap} binaries. - Ignore MALLOCX_ARENA(a) in dallocx(), in favor of using the MALLOCX_TCACHE(tc) and MALLOCX_TCACHE_NONE flags to control tcache usage. Removed features: - Remove the *allocm() API, which is superseded by the *allocx() API. - Remove the --enable-dss options, and make dss non-optional on all platforms which support sbrk(2). - Remove the "arenas.purge" mallctl, which was obsoleted by the "arena..purge" mallctl in 3.1.0. - Remove the unnecessary "opt.valgrind" mallctl; jemalloc automatically detects whether it is running inside Valgrind. - Remove the "stats.huge.allocated", "stats.huge.nmalloc", and "stats.huge.ndalloc" mallctls. - Remove the --enable-mremap option. - Remove the "stats.chunks.current", "stats.chunks.total", and "stats.chunks.high" mallctls. Bug fixes: - Fix the cactive statistic to decrease (rather than increase) when active memory decreases. This regression was first released in 3.5.0. - Fix OOM handling in memalign() and valloc(). A variant of this bug existed in all releases since 2.0.0, which introduced these functions. - Fix an OOM-related regression in arena_tcache_fill_small(), which could cause cache corruption on OOM. This regression was present in all releases from 2.2.0 through 3.6.0. - Fix size class overflow handling for malloc(), posix_memalign(), memalign(), calloc(), and realloc() when profiling is enabled. - Fix the "arena..dss" mallctl to return an error if "primary" or "secondary" precedence is specified, but sbrk(2) is not supported. - Fix fallback lg_floor() implementations to handle extremely large inputs. - Ensure the default purgeable zone is after the default zone on OS X. - Fix latent bugs in atomic_*(). - Fix the "arena..dss" mallctl to handle read-only calls. - Fix tls_model configuration to enable the initial-exec model when possible. - Mark malloc_conf as a weak symbol so that the application can override it. - Correctly detect glibc's adaptive pthread mutexes. - Fix the --without-export configure option. * 3.6.0 (March 31, 2014) This version contains a critical bug fix for a regression present in 3.5.0 and 3.5.1. Bug fixes: - Fix a regression in arena_chunk_alloc() that caused crashes during small/large allocation if chunk allocation failed. In the absence of this bug, chunk allocation failure would result in allocation failure, e.g. NULL return from malloc(). This regression was introduced in 3.5.0. - Fix backtracing for gcc intrinsics-based backtracing by specifying -fno-omit-frame-pointer to gcc. Note that the application (and all the libraries it links to) must also be compiled with this option for backtracing to be reliable. - Use dss allocation precedence for huge allocations as well as small/large allocations. - Fix test assertion failure message formatting. This bug did not manifest on x86_64 systems because of implementation subtleties in va_list. - Fix inconsequential test failures for hash and SFMT code. New features: - Support heap profiling on FreeBSD. This feature depends on the proc filesystem being mounted during heap profile dumping. * 3.5.1 (February 25, 2014) This version primarily addresses minor bugs in test code. Bug fixes: - Configure Solaris/Illumos to use MADV_FREE. - Fix junk filling for mremap(2)-based huge reallocation. This is only relevant if configuring with the --enable-mremap option specified. - Avoid compilation failure if 'restrict' C99 keyword is not supported by the compiler. - Add a configure test for SSE2 rather than assuming it is usable on i686 systems. This fixes test compilation errors, especially on 32-bit Linux systems. - Fix mallctl argument size mismatches (size_t vs. uint64_t) in the stats unit test. - Fix/remove flawed alignment-related overflow tests. - Prevent compiler optimizations that could change backtraces in the prof_accum unit test. * 3.5.0 (January 22, 2014) This version focuses on refactoring and automated testing, though it also includes some non-trivial heap profiling optimizations not mentioned below. New features: - Add the *allocx() API, which is a successor to the experimental *allocm() API. The *allocx() functions are slightly simpler to use because they have fewer parameters, they directly return the results of primary interest, and mallocx()/rallocx() avoid the strict aliasing pitfall that allocm()/rallocm() share with posix_memalign(). Note that *allocm() is slated for removal in the next non-bugfix release. - Add support for LinuxThreads. Bug fixes: - Unless heap profiling is enabled, disable floating point code and don't link with libm. This, in combination with e.g. EXTRA_CFLAGS=-mno-sse on x64 systems, makes it possible to completely disable floating point register use. Some versions of glibc neglect to save/restore caller-saved floating point registers during dynamic lazy symbol loading, and the symbol loading code uses whatever malloc the application happens to have linked/loaded with, the result being potential floating point register corruption. - Report ENOMEM rather than EINVAL if an OOM occurs during heap profiling backtrace creation in imemalign(). This bug impacted posix_memalign() and aligned_alloc(). - Fix a file descriptor leak in a prof_dump_maps() error path. - Fix prof_dump() to close the dump file descriptor for all relevant error paths. - Fix rallocm() to use the arena specified by the ALLOCM_ARENA(s) flag for allocation, not just deallocation. - Fix a data race for large allocation stats counters. - Fix a potential infinite loop during thread exit. This bug occurred on Solaris, and could affect other platforms with similar pthreads TSD implementations. - Don't junk-fill reallocations unless usable size changes. This fixes a violation of the *allocx()/*allocm() semantics. - Fix growing large reallocation to junk fill new space. - Fix huge deallocation to junk fill when munmap is disabled. - Change the default private namespace prefix from empty to je_, and change --with-private-namespace-prefix so that it prepends an additional prefix rather than replacing je_. This reduces the likelihood of applications which statically link jemalloc experiencing symbol name collisions. - Add missing private namespace mangling (relevant when --with-private-namespace is specified). - Add and use JEMALLOC_INLINE_C so that static inline functions are marked as static even for debug builds. - Add a missing mutex unlock in a malloc_init_hard() error path. In practice this error path is never executed. - Fix numerous bugs in malloc_strotumax() error handling/reporting. These bugs had no impact except for malformed inputs. - Fix numerous bugs in malloc_snprintf(). These bugs were not exercised by existing calls, so they had no impact. * 3.4.1 (October 20, 2013) Bug fixes: - Fix a race in the "arenas.extend" mallctl that could cause memory corruption of internal data structures and subsequent crashes. - Fix Valgrind integration flaws that caused Valgrind warnings about reads of uninitialized memory in: + arena chunk headers + internal zero-initialized data structures (relevant to tcache and prof code) - Preserve errno during the first allocation. A readlink(2) call during initialization fails unless /etc/malloc.conf exists, so errno was typically set during the first allocation prior to this fix. - Fix compilation warnings reported by gcc 4.8.1. * 3.4.0 (June 2, 2013) This version is essentially a small bugfix release, but the addition of aarch64 support requires that the minor version be incremented. Bug fixes: - Fix race-triggered deadlocks in chunk_record(). These deadlocks were typically triggered by multiple threads concurrently deallocating huge objects. New features: - Add support for the aarch64 architecture. * 3.3.1 (March 6, 2013) This version fixes bugs that are typically encountered only when utilizing custom run-time options. Bug fixes: - Fix a locking order bug that could cause deadlock during fork if heap profiling were enabled. - Fix a chunk recycling bug that could cause the allocator to lose track of whether a chunk was zeroed. On FreeBSD, NetBSD, and OS X, it could cause corruption if allocating via sbrk(2) (unlikely unless running with the "dss:primary" option specified). This was completely harmless on Linux unless using mlockall(2) (and unlikely even then, unless the --disable-munmap configure option or the "dss:primary" option was specified). This regression was introduced in 3.1.0 by the mlockall(2)/madvise(2) interaction fix. - Fix TLS-related memory corruption that could occur during thread exit if the thread never allocated memory. Only the quarantine and prof facilities were susceptible. - Fix two quarantine bugs: + Internal reallocation of the quarantined object array leaked the old array. + Reallocation failure for internal reallocation of the quarantined object array (very unlikely) resulted in memory corruption. - Fix Valgrind integration to annotate all internally allocated memory in a way that keeps Valgrind happy about internal data structure access. - Fix building for s390 systems. * 3.3.0 (January 23, 2013) This version includes a few minor performance improvements in addition to the listed new features and bug fixes. New features: - Add clipping support to lg_chunk option processing. - Add the --enable-ivsalloc option. - Add the --without-export option. - Add the --disable-zone-allocator option. Bug fixes: - Fix "arenas.extend" mallctl to output the number of arenas. - Fix chunk_recycle() to unconditionally inform Valgrind that returned memory is undefined. - Fix build break on FreeBSD related to alloca.h. * 3.2.0 (November 9, 2012) In addition to a couple of bug fixes, this version modifies page run allocation and dirty page purging algorithms in order to better control page-level virtual memory fragmentation. Incompatible changes: - Change the "opt.lg_dirty_mult" default from 5 to 3 (32:1 to 8:1). Bug fixes: - Fix dss/mmap allocation precedence code to use recyclable mmap memory only after primary dss allocation fails. - Fix deadlock in the "arenas.purge" mallctl. This regression was introduced in 3.1.0 by the addition of the "arena..purge" mallctl. * 3.1.0 (October 16, 2012) New features: - Auto-detect whether running inside Valgrind, thus removing the need to manually specify MALLOC_CONF=valgrind:true. - Add the "arenas.extend" mallctl, which allows applications to create manually managed arenas. - Add the ALLOCM_ARENA() flag for {,r,d}allocm(). - Add the "opt.dss", "arena..dss", and "stats.arenas..dss" mallctls, which provide control over dss/mmap precedence. - Add the "arena..purge" mallctl, which obsoletes "arenas.purge". - Define LG_QUANTUM for hppa. Incompatible changes: - Disable tcache by default if running inside Valgrind, in order to avoid making unallocated objects appear reachable to Valgrind. - Drop const from malloc_usable_size() argument on Linux. Bug fixes: - Fix heap profiling crash if sampled object is freed via realloc(p, 0). - Remove const from __*_hook variable declarations, so that glibc can modify them during process forking. - Fix mlockall(2)/madvise(2) interaction. - Fix fork(2)-related deadlocks. - Fix error return value for "thread.tcache.enabled" mallctl. * 3.0.0 (May 11, 2012) Although this version adds some major new features, the primary focus is on internal code cleanup that facilitates maintainability and portability, most of which is not reflected in the ChangeLog. This is the first release to incorporate substantial contributions from numerous other developers, and the result is a more broadly useful allocator (see the git revision history for contribution details). Note that the license has been unified, thanks to Facebook granting a license under the same terms as the other copyright holders (see COPYING). New features: - Implement Valgrind support, redzones, and quarantine. - Add support for additional platforms: + FreeBSD + Mac OS X Lion + MinGW + Windows (no support yet for replacing the system malloc) - Add support for additional architectures: + MIPS + SH4 + Tilera - Add support for cross compiling. - Add nallocm(), which rounds a request size up to the nearest size class without actually allocating. - Implement aligned_alloc() (blame C11). - Add the "thread.tcache.enabled" mallctl. - Add the "opt.prof_final" mallctl. - Update pprof (from gperftools 2.0). - Add the --with-mangling option. - Add the --disable-experimental option. - Add the --disable-munmap option, and make it the default on Linux. - Add the --enable-mremap option, which disables use of mremap(2) by default. Incompatible changes: - Enable stats by default. - Enable fill by default. - Disable lazy locking by default. - Rename the "tcache.flush" mallctl to "thread.tcache.flush". - Rename the "arenas.pagesize" mallctl to "arenas.page". - Change the "opt.lg_prof_sample" default from 0 to 19 (1 B to 512 KiB). - Change the "opt.prof_accum" default from true to false. Removed features: - Remove the swap feature, including the "config.swap", "swap.avail", "swap.prezeroed", "swap.nfds", and "swap.fds" mallctls. - Remove highruns statistics, including the "stats.arenas..bins..highruns" and "stats.arenas..lruns..highruns" mallctls. - As part of small size class refactoring, remove the "opt.lg_[qc]space_max", "arenas.cacheline", "arenas.subpage", "arenas.[tqcs]space_{min,max}", and "arenas.[tqcs]bins" mallctls. - Remove the "arenas.chunksize" mallctl. - Remove the "opt.lg_prof_tcmax" option. - Remove the "opt.lg_prof_bt_max" option. - Remove the "opt.lg_tcache_gc_sweep" option. - Remove the --disable-tiny option, including the "config.tiny" mallctl. - Remove the --enable-dynamic-page-shift configure option. - Remove the --enable-sysv configure option. Bug fixes: - Fix a statistics-related bug in the "thread.arena" mallctl that could cause invalid statistics and crashes. - Work around TLS deallocation via free() on Linux. This bug could cause write-after-free memory corruption. - Fix a potential deadlock that could occur during interval- and growth-triggered heap profile dumps. - Fix large calloc() zeroing bugs due to dropping chunk map unzeroed flags. - Fix chunk_alloc_dss() to stop claiming memory is zeroed. This bug could cause memory corruption and crashes with --enable-dss specified. - Fix fork-related bugs that could cause deadlock in children between fork and exec. - Fix malloc_stats_print() to honor 'b' and 'l' in the opts parameter. - Fix realloc(p, 0) to act like free(p). - Do not enforce minimum alignment in memalign(). - Check for NULL pointer in malloc_usable_size(). - Fix an off-by-one heap profile statistics bug that could be observed in interval- and growth-triggered heap profiles. - Fix the "epoch" mallctl to update cached stats even if the passed in epoch is 0. - Fix bin->runcur management to fix a layout policy bug. This bug did not affect correctness. - Fix a bug in choose_arena_hard() that potentially caused more arenas to be initialized than necessary. - Add missing "opt.lg_tcache_max" mallctl implementation. - Use glibc allocator hooks to make mixed allocator usage less likely. - Fix build issues for --disable-tcache. - Don't mangle pthread_create() when --with-private-namespace is specified. * 2.2.5 (November 14, 2011) Bug fixes: - Fix huge_ralloc() race when using mremap(2). This is a serious bug that could cause memory corruption and/or crashes. - Fix huge_ralloc() to maintain chunk statistics. - Fix malloc_stats_print(..., "a") output. * 2.2.4 (November 5, 2011) Bug fixes: - Initialize arenas_tsd before using it. This bug existed for 2.2.[0-3], as well as for --disable-tls builds in earlier releases. - Do not assume a 4 KiB page size in test/rallocm.c. * 2.2.3 (August 31, 2011) This version fixes numerous bugs related to heap profiling. Bug fixes: - Fix a prof-related race condition. This bug could cause memory corruption, but only occurred in non-default configurations (prof_accum:false). - Fix off-by-one backtracing issues (make sure that prof_alloc_prep() is excluded from backtraces). - Fix a prof-related bug in realloc() (only triggered by OOM errors). - Fix prof-related bugs in allocm() and rallocm(). - Fix prof_tdata_cleanup() for --disable-tls builds. - Fix a relative include path, to fix objdir builds. * 2.2.2 (July 30, 2011) Bug fixes: - Fix a build error for --disable-tcache. - Fix assertions in arena_purge() (for real this time). - Add the --with-private-namespace option. This is a workaround for symbol conflicts that can inadvertently arise when using static libraries. * 2.2.1 (March 30, 2011) Bug fixes: - Implement atomic operations for x86/x64. This fixes compilation failures for versions of gcc that are still in wide use. - Fix an assertion in arena_purge(). * 2.2.0 (March 22, 2011) This version incorporates several improvements to algorithms and data structures that tend to reduce fragmentation and increase speed. New features: - Add the "stats.cactive" mallctl. - Update pprof (from google-perftools 1.7). - Improve backtracing-related configuration logic, and add the --disable-prof-libgcc option. Bug fixes: - Change default symbol visibility from "internal", to "hidden", which decreases the overhead of library-internal function calls. - Fix symbol visibility so that it is also set on OS X. - Fix a build dependency regression caused by the introduction of the .pic.o suffix for PIC object files. - Add missing checks for mutex initialization failures. - Don't use libgcc-based backtracing except on x64, where it is known to work. - Fix deadlocks on OS X that were due to memory allocation in pthread_mutex_lock(). - Heap profiling-specific fixes: + Fix memory corruption due to integer overflow in small region index computation, when using a small enough sample interval that profiling context pointers are stored in small run headers. + Fix a bootstrap ordering bug that only occurred with TLS disabled. + Fix a rallocm() rsize bug. + Fix error detection bugs for aligned memory allocation. * 2.1.3 (March 14, 2011) Bug fixes: - Fix a cpp logic regression (due to the "thread.{de,}allocatedp" mallctl fix for OS X in 2.1.2). - Fix a "thread.arena" mallctl bug. - Fix a thread cache stats merging bug. * 2.1.2 (March 2, 2011) Bug fixes: - Fix "thread.{de,}allocatedp" mallctl for OS X. - Add missing jemalloc.a to build system. * 2.1.1 (January 31, 2011) Bug fixes: - Fix aligned huge reallocation (affected allocm()). - Fix the ALLOCM_LG_ALIGN macro definition. - Fix a heap dumping deadlock. - Fix a "thread.arena" mallctl bug. * 2.1.0 (December 3, 2010) This version incorporates some optimizations that can't quite be considered bug fixes. New features: - Use Linux's mremap(2) for huge object reallocation when possible. - Avoid locking in mallctl*() when possible. - Add the "thread.[de]allocatedp" mallctl's. - Convert the manual page source from roff to DocBook, and generate both roff and HTML manuals. Bug fixes: - Fix a crash due to incorrect bootstrap ordering. This only impacted --enable-debug --enable-dss configurations. - Fix a minor statistics bug for mallctl("swap.avail", ...). * 2.0.1 (October 29, 2010) Bug fixes: - Fix a race condition in heap profiling that could cause undefined behavior if "opt.prof_accum" were disabled. - Add missing mutex unlocks for some OOM error paths in the heap profiling code. - Fix a compilation error for non-C99 builds. * 2.0.0 (October 24, 2010) This version focuses on the experimental *allocm() API, and on improved run-time configuration/introspection. Nonetheless, numerous performance improvements are also included. New features: - Implement the experimental {,r,s,d}allocm() API, which provides a superset of the functionality available via malloc(), calloc(), posix_memalign(), realloc(), malloc_usable_size(), and free(). These functions can be used to allocate/reallocate aligned zeroed memory, ask for optional extra memory during reallocation, prevent object movement during reallocation, etc. - Replace JEMALLOC_OPTIONS/JEMALLOC_PROF_PREFIX with MALLOC_CONF, which is more human-readable, and more flexible. For example: JEMALLOC_OPTIONS=AJP is now: MALLOC_CONF=abort:true,fill:true,stats_print:true - Port to Apple OS X. Sponsored by Mozilla. - Make it possible for the application to control thread-->arena mappings via the "thread.arena" mallctl. - Add compile-time support for all TLS-related functionality via pthreads TSD. This is mainly of interest for OS X, which does not support TLS, but has a TSD implementation with similar performance. - Override memalign() and valloc() if they are provided by the system. - Add the "arenas.purge" mallctl, which can be used to synchronously purge all dirty unused pages. - Make cumulative heap profiling data optional, so that it is possible to limit the amount of memory consumed by heap profiling data structures. - Add per thread allocation counters that can be accessed via the "thread.allocated" and "thread.deallocated" mallctls. Incompatible changes: - Remove JEMALLOC_OPTIONS and malloc_options (see MALLOC_CONF above). - Increase default backtrace depth from 4 to 128 for heap profiling. - Disable interval-based profile dumps by default. Bug fixes: - Remove bad assertions in fork handler functions. These assertions could cause aborts for some combinations of configure settings. - Fix strerror_r() usage to deal with non-standard semantics in GNU libc. - Fix leak context reporting. This bug tended to cause the number of contexts to be underreported (though the reported number of objects and bytes were correct). - Fix a realloc() bug for large in-place growing reallocation. This bug could cause memory corruption, but it was hard to trigger. - Fix an allocation bug for small allocations that could be triggered if multiple threads raced to create a new run of backing pages. - Enhance the heap profiler to trigger samples based on usable size, rather than request size. - Fix a heap profiling bug due to sometimes losing track of requested object size for sampled objects. * 1.0.3 (August 12, 2010) Bug fixes: - Fix the libunwind-based implementation of stack backtracing (used for heap profiling). This bug could cause zero-length backtraces to be reported. - Add a missing mutex unlock in library initialization code. If multiple threads raced to initialize malloc, some of them could end up permanently blocked. * 1.0.2 (May 11, 2010) Bug fixes: - Fix junk filling of large objects, which could cause memory corruption. - Add MAP_NORESERVE support for chunk mapping, because otherwise virtual memory limits could cause swap file configuration to fail. Contributed by Jordan DeLong. * 1.0.1 (April 14, 2010) Bug fixes: - Fix compilation when --enable-fill is specified. - Fix threads-related profiling bugs that affected accuracy and caused memory to be leaked during thread exit. - Fix dirty page purging race conditions that could cause crashes. - Fix crash in tcache flushing code during thread destruction. * 1.0.0 (April 11, 2010) This release focuses on speed and run-time introspection. Numerous algorithmic improvements make this release substantially faster than its predecessors. New features: - Implement autoconf-based configuration system. - Add mallctl*(), for the purposes of introspection and run-time configuration. - Make it possible for the application to manually flush a thread's cache, via the "tcache.flush" mallctl. - Base maximum dirty page count on proportion of active memory. - Compute various additional run-time statistics, including per size class statistics for large objects. - Expose malloc_stats_print(), which can be called repeatedly by the application. - Simplify the malloc_message() signature to only take one string argument, and incorporate an opaque data pointer argument for use by the application in combination with malloc_stats_print(). - Add support for allocation backed by one or more swap files, and allow the application to disable over-commit if swap files are in use. - Implement allocation profiling and leak checking. Removed features: - Remove the dynamic arena rebalancing code, since thread-specific caching reduces its utility. Bug fixes: - Modify chunk allocation to work when address space layout randomization (ASLR) is in use. - Fix thread cleanup bugs related to TLS destruction. - Handle 0-size allocation requests in posix_memalign(). - Fix a chunk leak. The leaked chunks were never touched, so this impacted virtual memory usage, but not physical memory usage. * linux_2008082[78]a (August 27/28, 2008) These snapshot releases are the simple result of incorporating Linux-specific support into the FreeBSD malloc sources. -------------------------------------------------------------------------------- vim:filetype=text:textwidth=80 redis-8.0.2/deps/jemalloc/INSTALL.md000066400000000000000000000347151501533116600170130ustar00rootroot00000000000000Building and installing a packaged release of jemalloc can be as simple as typing the following while in the root directory of the source tree: ./configure make make install If building from unpackaged developer sources, the simplest command sequence that might work is: ./autogen.sh make make install You can uninstall the installed build artifacts like this: make uninstall Notes: - "autoconf" needs to be installed - Documentation is built by the default target only when xsltproc is available. Build will warn but not stop if the dependency is missing. ## Advanced configuration The 'configure' script supports numerous options that allow control of which functionality is enabled, where jemalloc is installed, etc. Optionally, pass any of the following arguments (not a definitive list) to 'configure': * `--help` Print a definitive list of options. * `--prefix=` Set the base directory in which to install. For example: ./configure --prefix=/usr/local will cause files to be installed into /usr/local/include, /usr/local/lib, and /usr/local/man. * `--with-version=(..--g|VERSION)` The VERSION file is mandatory for successful configuration, and the following steps are taken to assure its presence: 1) If --with-version=..--g is specified, generate VERSION using the specified value. 2) If --with-version is not specified in either form and the source directory is inside a git repository, try to generate VERSION via 'git describe' invocations that pattern-match release tags. 3) If VERSION is missing, generate it with a bogus version: 0.0.0-0-g0000000000000000000000000000000000000000 Note that --with-version=VERSION bypasses (1) and (2), which simplifies VERSION configuration when embedding a jemalloc release into another project's git repository. * `--with-rpath=` Embed one or more library paths, so that libjemalloc can find the libraries it is linked to. This works only on ELF-based systems. * `--with-mangling=` Mangle public symbols specified in which is a comma-separated list of name:mangled pairs. For example, to use ld's --wrap option as an alternative method for overriding libc's malloc implementation, specify something like: --with-mangling=malloc:__wrap_malloc,free:__wrap_free[...] Note that mangling happens prior to application of the prefix specified by --with-jemalloc-prefix, and mangled symbols are then ignored when applying the prefix. * `--with-jemalloc-prefix=` Prefix all public APIs with . For example, if is "prefix_", API changes like the following occur: malloc() --> prefix_malloc() malloc_conf --> prefix_malloc_conf /etc/malloc.conf --> /etc/prefix_malloc.conf MALLOC_CONF --> PREFIX_MALLOC_CONF This makes it possible to use jemalloc at the same time as the system allocator, or even to use multiple copies of jemalloc simultaneously. By default, the prefix is "", except on OS X, where it is "je_". On OS X, jemalloc overlays the default malloc zone, but makes no attempt to actually replace the "malloc", "calloc", etc. symbols. * `--without-export` Don't export public APIs. This can be useful when building jemalloc as a static library, or to avoid exporting public APIs when using the zone allocator on OSX. * `--with-private-namespace=` Prefix all library-private APIs with je_. For shared libraries, symbol visibility mechanisms prevent these symbols from being exported, but for static libraries, naming collisions are a real possibility. By default, is empty, which results in a symbol prefix of je_ . * `--with-install-suffix=` Append to the base name of all installed files, such that multiple versions of jemalloc can coexist in the same installation directory. For example, libjemalloc.so.0 becomes libjemalloc.so.0. * `--with-malloc-conf=` Embed `` as a run-time options string that is processed prior to the malloc_conf global variable, the /etc/malloc.conf symlink, and the MALLOC_CONF environment variable. For example, to change the default decay time to 30 seconds: --with-malloc-conf=decay_ms:30000 * `--enable-debug` Enable assertions and validation code. This incurs a substantial performance hit, but is very useful during application development. * `--disable-stats` Disable statistics gathering functionality. See the "opt.stats_print" option documentation for usage details. * `--enable-prof` Enable heap profiling and leak detection functionality. See the "opt.prof" option documentation for usage details. When enabled, there are several approaches to backtracing, and the configure script chooses the first one in the following list that appears to function correctly: + libunwind (requires --enable-prof-libunwind) + libgcc (unless --disable-prof-libgcc) + gcc intrinsics (unless --disable-prof-gcc) * `--enable-prof-libunwind` Use the libunwind library (http://www.nongnu.org/libunwind/) for stack backtracing. * `--disable-prof-libgcc` Disable the use of libgcc's backtracing functionality. * `--disable-prof-gcc` Disable the use of gcc intrinsics for backtracing. * `--with-static-libunwind=` Statically link against the specified libunwind.a rather than dynamically linking with -lunwind. * `--disable-fill` Disable support for junk/zero filling of memory. See the "opt.junk" and "opt.zero" option documentation for usage details. * `--disable-zone-allocator` Disable zone allocator for Darwin. This means jemalloc won't be hooked as the default allocator on OSX/iOS. * `--enable-utrace` Enable utrace(2)-based allocation tracing. This feature is not broadly portable (FreeBSD has it, but Linux and OS X do not). * `--enable-xmalloc` Enable support for optional immediate termination due to out-of-memory errors, as is commonly implemented by "xmalloc" wrapper function for malloc. See the "opt.xmalloc" option documentation for usage details. * `--enable-lazy-lock` Enable code that wraps pthread_create() to detect when an application switches from single-threaded to multi-threaded mode, so that it can avoid mutex locking/unlocking operations while in single-threaded mode. In practice, this feature usually has little impact on performance unless thread-specific caching is disabled. * `--disable-cache-oblivious` Disable cache-oblivious large allocation alignment by default, for large allocation requests with no alignment constraints. If this feature is disabled, all large allocations are page-aligned as an implementation artifact, which can severely harm CPU cache utilization. However, the cache-oblivious layout comes at the cost of one extra page per large allocation, which in the most extreme case increases physical memory usage for the 16 KiB size class to 20 KiB. * `--disable-syscall` Disable use of syscall(2) rather than {open,read,write,close}(2). This is intended as a workaround for systems that place security limitations on syscall(2). * `--disable-cxx` Disable C++ integration. This will cause new and delete operator implementations to be omitted. * `--with-xslroot=` Specify where to find DocBook XSL stylesheets when building the documentation. * `--with-lg-page=` Specify the base 2 log of the allocator page size, which must in turn be at least as large as the system page size. By default the configure script determines the host's page size and sets the allocator page size equal to the system page size, so this option need not be specified unless the system page size may change between configuration and execution, e.g. when cross compiling. * `--with-lg-hugepage=` Specify the base 2 log of the system huge page size. This option is useful when cross compiling, or when overriding the default for systems that do not explicitly support huge pages. * `--with-lg-quantum=` Specify the base 2 log of the minimum allocation alignment. jemalloc needs to know the minimum alignment that meets the following C standard requirement (quoted from the April 12, 2011 draft of the C11 standard): > The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated [...] This setting is architecture-specific, and although jemalloc includes known safe values for the most commonly used modern architectures, there is a wrinkle related to GNU libc (glibc) that may impact your choice of . On most modern architectures, this mandates 16-byte alignment (=4), but the glibc developers chose not to meet this requirement for performance reasons. An old discussion can be found at . Unlike glibc, jemalloc does follow the C standard by default (caveat: jemalloc technically cheats for size classes smaller than the quantum), but the fact that Linux systems already work around this allocator noncompliance means that it is generally safe in practice to let jemalloc's minimum alignment follow glibc's lead. If you specify `--with-lg-quantum=3` during configuration, jemalloc will provide additional size classes that are not 16-byte-aligned (24, 40, and 56). * `--with-lg-vaddr=` Specify the number of significant virtual address bits. By default, the configure script attempts to detect virtual address size on those platforms where it knows how, and picks a default otherwise. This option may be useful when cross-compiling. * `--disable-initial-exec-tls` Disable the initial-exec TLS model for jemalloc's internal thread-local storage (on those platforms that support explicit settings). This can allow jemalloc to be dynamically loaded after program startup (e.g. using dlopen). Note that in this case, there will be two malloc implementations operating in the same process, which will almost certainly result in confusing runtime crashes if pointers leak from one implementation to the other. * `--disable-libdl` Disable the usage of libdl, namely dlsym(3) which is required by the lazy lock option. This can allow building static binaries. The following environment variables (not a definitive list) impact configure's behavior: * `CFLAGS="?"` * `CXXFLAGS="?"` Pass these flags to the C/C++ compiler. Any flags set by the configure script are prepended, which means explicitly set flags generally take precedence. Take care when specifying flags such as -Werror, because configure tests may be affected in undesirable ways. * `EXTRA_CFLAGS="?"` * `EXTRA_CXXFLAGS="?"` Append these flags to CFLAGS/CXXFLAGS, without passing them to the compiler(s) during configuration. This makes it possible to add flags such as -Werror, while allowing the configure script to determine what other flags are appropriate for the specified configuration. * `CPPFLAGS="?"` Pass these flags to the C preprocessor. Note that CFLAGS is not passed to 'cpp' when 'configure' is looking for include files, so you must use CPPFLAGS instead if you need to help 'configure' find header files. * `LD_LIBRARY_PATH="?"` 'ld' uses this colon-separated list to find libraries. * `LDFLAGS="?"` Pass these flags when linking. * `PATH="?"` 'configure' uses this to find programs. In some cases it may be necessary to work around configuration results that do not match reality. For example, Linux 4.5 added support for the MADV_FREE flag to madvise(2), which can cause problems if building on a host with MADV_FREE support and deploying to a target without. To work around this, use a cache file to override the relevant configuration variable defined in configure.ac, e.g.: echo "je_cv_madv_free=no" > config.cache && ./configure -C ## Advanced compilation To build only parts of jemalloc, use the following targets: build_lib_shared build_lib_static build_lib build_doc_html build_doc_man build_doc To install only parts of jemalloc, use the following targets: install_bin install_include install_lib_shared install_lib_static install_lib_pc install_lib install_doc_html install_doc_man install_doc To clean up build results to varying degrees, use the following make targets: clean distclean relclean ## Advanced installation Optionally, define make variables when invoking make, including (not exclusively): * `INCLUDEDIR="?"` Use this as the installation prefix for header files. * `LIBDIR="?"` Use this as the installation prefix for libraries. * `MANDIR="?"` Use this as the installation prefix for man pages. * `DESTDIR="?"` Prepend DESTDIR to INCLUDEDIR, LIBDIR, DATADIR, and MANDIR. This is useful when installing to a different path than was specified via --prefix. * `CC="?"` Use this to invoke the C compiler. * `CFLAGS="?"` Pass these flags to the compiler. * `CPPFLAGS="?"` Pass these flags to the C preprocessor. * `LDFLAGS="?"` Pass these flags when linking. * `PATH="?"` Use this to search for programs used during configuration and building. ## Development If you intend to make non-trivial changes to jemalloc, use the 'autogen.sh' script rather than 'configure'. This re-generates 'configure', enables configuration dependency rules, and enables re-generation of automatically generated source files. The build system supports using an object directory separate from the source tree. For example, you can create an 'obj' directory, and from within that directory, issue configuration and build commands: autoconf mkdir obj cd obj ../configure --enable-autogen make ## Documentation The manual page is generated in both html and roff formats. Any web browser can be used to view the html manual. The roff manual page can be formatted prior to installation via the following command: nroff -man -t doc/jemalloc.3 redis-8.0.2/deps/jemalloc/Makefile.in000066400000000000000000000653161501533116600174310ustar00rootroot00000000000000# Clear out all vpaths, then set just one (default vpath) for the main build # directory. vpath vpath % . # Clear the default suffixes, so that built-in rules are not used. .SUFFIXES : SHELL := /bin/sh CC := @CC@ CXX := @CXX@ # Configuration parameters. DESTDIR = BINDIR := $(DESTDIR)@BINDIR@ INCLUDEDIR := $(DESTDIR)@INCLUDEDIR@ LIBDIR := $(DESTDIR)@LIBDIR@ DATADIR := $(DESTDIR)@DATADIR@ MANDIR := $(DESTDIR)@MANDIR@ srcroot := @srcroot@ objroot := @objroot@ abs_srcroot := @abs_srcroot@ abs_objroot := @abs_objroot@ # Build parameters. CPPFLAGS := @CPPFLAGS@ -I$(objroot)include -I$(srcroot)include CONFIGURE_CFLAGS := @CONFIGURE_CFLAGS@ SPECIFIED_CFLAGS := @SPECIFIED_CFLAGS@ EXTRA_CFLAGS := @EXTRA_CFLAGS@ CFLAGS := $(strip $(CONFIGURE_CFLAGS) $(SPECIFIED_CFLAGS) $(EXTRA_CFLAGS)) CONFIGURE_CXXFLAGS := @CONFIGURE_CXXFLAGS@ SPECIFIED_CXXFLAGS := @SPECIFIED_CXXFLAGS@ EXTRA_CXXFLAGS := @EXTRA_CXXFLAGS@ CXXFLAGS := $(strip $(CONFIGURE_CXXFLAGS) $(SPECIFIED_CXXFLAGS) $(EXTRA_CXXFLAGS)) LDFLAGS := @LDFLAGS@ EXTRA_LDFLAGS := @EXTRA_LDFLAGS@ LIBS := @LIBS@ RPATH_EXTRA := @RPATH_EXTRA@ SO := @so@ IMPORTLIB := @importlib@ O := @o@ A := @a@ EXE := @exe@ LIBPREFIX := @libprefix@ REV := @rev@ install_suffix := @install_suffix@ ABI := @abi@ XSLTPROC := @XSLTPROC@ XSLROOT := @XSLROOT@ AUTOCONF := @AUTOCONF@ _RPATH = @RPATH@ RPATH = $(if $(1),$(call _RPATH,$(1))) cfghdrs_in := $(addprefix $(srcroot),@cfghdrs_in@) cfghdrs_out := @cfghdrs_out@ cfgoutputs_in := $(addprefix $(srcroot),@cfgoutputs_in@) cfgoutputs_out := @cfgoutputs_out@ enable_autogen := @enable_autogen@ enable_doc := @enable_doc@ enable_shared := @enable_shared@ enable_static := @enable_static@ enable_prof := @enable_prof@ enable_zone_allocator := @enable_zone_allocator@ enable_experimental_smallocx := @enable_experimental_smallocx@ MALLOC_CONF := @JEMALLOC_CPREFIX@MALLOC_CONF link_whole_archive := @link_whole_archive@ DSO_LDFLAGS = @DSO_LDFLAGS@ SOREV = @SOREV@ PIC_CFLAGS = @PIC_CFLAGS@ CTARGET = @CTARGET@ LDTARGET = @LDTARGET@ TEST_LD_MODE = @TEST_LD_MODE@ MKLIB = @MKLIB@ AR = @AR@ ARFLAGS = @ARFLAGS@ DUMP_SYMS = @DUMP_SYMS@ AWK := @AWK@ CC_MM = @CC_MM@ LM := @LM@ INSTALL = @INSTALL@ ifeq (macho, $(ABI)) TEST_LIBRARY_PATH := DYLD_FALLBACK_LIBRARY_PATH="$(objroot)lib" else ifeq (pecoff, $(ABI)) TEST_LIBRARY_PATH := PATH="$(PATH):$(objroot)lib" else TEST_LIBRARY_PATH := endif endif LIBJEMALLOC := $(LIBPREFIX)jemalloc$(install_suffix) # Lists of files. BINS := $(objroot)bin/jemalloc-config $(objroot)bin/jemalloc.sh $(objroot)bin/jeprof C_HDRS := $(objroot)include/jemalloc/jemalloc$(install_suffix).h C_SRCS := $(srcroot)src/jemalloc.c \ $(srcroot)src/arena.c \ $(srcroot)src/background_thread.c \ $(srcroot)src/base.c \ $(srcroot)src/bin.c \ $(srcroot)src/bin_info.c \ $(srcroot)src/bitmap.c \ $(srcroot)src/buf_writer.c \ $(srcroot)src/cache_bin.c \ $(srcroot)src/ckh.c \ $(srcroot)src/counter.c \ $(srcroot)src/ctl.c \ $(srcroot)src/decay.c \ $(srcroot)src/div.c \ $(srcroot)src/ecache.c \ $(srcroot)src/edata.c \ $(srcroot)src/edata_cache.c \ $(srcroot)src/ehooks.c \ $(srcroot)src/emap.c \ $(srcroot)src/eset.c \ $(srcroot)src/exp_grow.c \ $(srcroot)src/extent.c \ $(srcroot)src/extent_dss.c \ $(srcroot)src/extent_mmap.c \ $(srcroot)src/fxp.c \ $(srcroot)src/san.c \ $(srcroot)src/san_bump.c \ $(srcroot)src/hook.c \ $(srcroot)src/hpa.c \ $(srcroot)src/hpa_hooks.c \ $(srcroot)src/hpdata.c \ $(srcroot)src/inspect.c \ $(srcroot)src/large.c \ $(srcroot)src/log.c \ $(srcroot)src/malloc_io.c \ $(srcroot)src/mutex.c \ $(srcroot)src/nstime.c \ $(srcroot)src/pa.c \ $(srcroot)src/pa_extra.c \ $(srcroot)src/pai.c \ $(srcroot)src/pac.c \ $(srcroot)src/pages.c \ $(srcroot)src/peak_event.c \ $(srcroot)src/prof.c \ $(srcroot)src/prof_data.c \ $(srcroot)src/prof_log.c \ $(srcroot)src/prof_recent.c \ $(srcroot)src/prof_stats.c \ $(srcroot)src/prof_sys.c \ $(srcroot)src/psset.c \ $(srcroot)src/rtree.c \ $(srcroot)src/safety_check.c \ $(srcroot)src/sc.c \ $(srcroot)src/sec.c \ $(srcroot)src/stats.c \ $(srcroot)src/sz.c \ $(srcroot)src/tcache.c \ $(srcroot)src/test_hooks.c \ $(srcroot)src/thread_event.c \ $(srcroot)src/ticker.c \ $(srcroot)src/tsd.c \ $(srcroot)src/witness.c ifeq ($(enable_zone_allocator), 1) C_SRCS += $(srcroot)src/zone.c endif ifeq ($(IMPORTLIB),$(SO)) STATIC_LIBS := $(objroot)lib/$(LIBJEMALLOC).$(A) endif ifdef PIC_CFLAGS STATIC_LIBS += $(objroot)lib/$(LIBJEMALLOC)_pic.$(A) else STATIC_LIBS += $(objroot)lib/$(LIBJEMALLOC)_s.$(A) endif DSOS := $(objroot)lib/$(LIBJEMALLOC).$(SOREV) ifneq ($(SOREV),$(SO)) DSOS += $(objroot)lib/$(LIBJEMALLOC).$(SO) endif ifeq (1, $(link_whole_archive)) LJEMALLOC := -Wl,--whole-archive -L$(objroot)lib -l$(LIBJEMALLOC) -Wl,--no-whole-archive else LJEMALLOC := $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB) endif PC := $(objroot)jemalloc.pc DOCS_XML := $(objroot)doc/jemalloc$(install_suffix).xml DOCS_HTML := $(DOCS_XML:$(objroot)%.xml=$(objroot)%.html) DOCS_MAN3 := $(DOCS_XML:$(objroot)%.xml=$(objroot)%.3) DOCS := $(DOCS_HTML) $(DOCS_MAN3) C_TESTLIB_SRCS := $(srcroot)test/src/btalloc.c $(srcroot)test/src/btalloc_0.c \ $(srcroot)test/src/btalloc_1.c $(srcroot)test/src/math.c \ $(srcroot)test/src/mtx.c $(srcroot)test/src/sleep.c \ $(srcroot)test/src/SFMT.c $(srcroot)test/src/test.c \ $(srcroot)test/src/thd.c $(srcroot)test/src/timer.c ifeq (1, $(link_whole_archive)) C_UTIL_INTEGRATION_SRCS := C_UTIL_CPP_SRCS := else C_UTIL_INTEGRATION_SRCS := $(srcroot)src/nstime.c $(srcroot)src/malloc_io.c \ $(srcroot)src/ticker.c C_UTIL_CPP_SRCS := $(srcroot)src/nstime.c $(srcroot)src/malloc_io.c endif TESTS_UNIT := \ $(srcroot)test/unit/a0.c \ $(srcroot)test/unit/arena_decay.c \ $(srcroot)test/unit/arena_reset.c \ $(srcroot)test/unit/atomic.c \ $(srcroot)test/unit/background_thread.c \ $(srcroot)test/unit/background_thread_enable.c \ $(srcroot)test/unit/base.c \ $(srcroot)test/unit/batch_alloc.c \ $(srcroot)test/unit/binshard.c \ $(srcroot)test/unit/bitmap.c \ $(srcroot)test/unit/bit_util.c \ $(srcroot)test/unit/buf_writer.c \ $(srcroot)test/unit/cache_bin.c \ $(srcroot)test/unit/ckh.c \ $(srcroot)test/unit/counter.c \ $(srcroot)test/unit/decay.c \ $(srcroot)test/unit/div.c \ $(srcroot)test/unit/double_free.c \ $(srcroot)test/unit/edata_cache.c \ $(srcroot)test/unit/emitter.c \ $(srcroot)test/unit/extent_quantize.c \ ${srcroot}test/unit/fb.c \ $(srcroot)test/unit/fork.c \ ${srcroot}test/unit/fxp.c \ ${srcroot}test/unit/san.c \ ${srcroot}test/unit/san_bump.c \ $(srcroot)test/unit/hash.c \ $(srcroot)test/unit/hook.c \ $(srcroot)test/unit/hpa.c \ $(srcroot)test/unit/hpa_background_thread.c \ $(srcroot)test/unit/hpdata.c \ $(srcroot)test/unit/huge.c \ $(srcroot)test/unit/inspect.c \ $(srcroot)test/unit/junk.c \ $(srcroot)test/unit/junk_alloc.c \ $(srcroot)test/unit/junk_free.c \ $(srcroot)test/unit/log.c \ $(srcroot)test/unit/mallctl.c \ $(srcroot)test/unit/malloc_conf_2.c \ $(srcroot)test/unit/malloc_io.c \ $(srcroot)test/unit/math.c \ $(srcroot)test/unit/mpsc_queue.c \ $(srcroot)test/unit/mq.c \ $(srcroot)test/unit/mtx.c \ $(srcroot)test/unit/nstime.c \ $(srcroot)test/unit/oversize_threshold.c \ $(srcroot)test/unit/pa.c \ $(srcroot)test/unit/pack.c \ $(srcroot)test/unit/pages.c \ $(srcroot)test/unit/peak.c \ $(srcroot)test/unit/ph.c \ $(srcroot)test/unit/prng.c \ $(srcroot)test/unit/prof_accum.c \ $(srcroot)test/unit/prof_active.c \ $(srcroot)test/unit/prof_gdump.c \ $(srcroot)test/unit/prof_hook.c \ $(srcroot)test/unit/prof_idump.c \ $(srcroot)test/unit/prof_log.c \ $(srcroot)test/unit/prof_mdump.c \ $(srcroot)test/unit/prof_recent.c \ $(srcroot)test/unit/prof_reset.c \ $(srcroot)test/unit/prof_stats.c \ $(srcroot)test/unit/prof_tctx.c \ $(srcroot)test/unit/prof_thread_name.c \ $(srcroot)test/unit/prof_sys_thread_name.c \ $(srcroot)test/unit/psset.c \ $(srcroot)test/unit/ql.c \ $(srcroot)test/unit/qr.c \ $(srcroot)test/unit/rb.c \ $(srcroot)test/unit/retained.c \ $(srcroot)test/unit/rtree.c \ $(srcroot)test/unit/safety_check.c \ $(srcroot)test/unit/sc.c \ $(srcroot)test/unit/sec.c \ $(srcroot)test/unit/seq.c \ $(srcroot)test/unit/SFMT.c \ $(srcroot)test/unit/size_check.c \ $(srcroot)test/unit/size_classes.c \ $(srcroot)test/unit/slab.c \ $(srcroot)test/unit/smoothstep.c \ $(srcroot)test/unit/spin.c \ $(srcroot)test/unit/stats.c \ $(srcroot)test/unit/stats_print.c \ $(srcroot)test/unit/sz.c \ $(srcroot)test/unit/tcache_max.c \ $(srcroot)test/unit/test_hooks.c \ $(srcroot)test/unit/thread_event.c \ $(srcroot)test/unit/ticker.c \ $(srcroot)test/unit/tsd.c \ $(srcroot)test/unit/uaf.c \ $(srcroot)test/unit/witness.c \ $(srcroot)test/unit/zero.c \ $(srcroot)test/unit/zero_realloc_abort.c \ $(srcroot)test/unit/zero_realloc_free.c \ $(srcroot)test/unit/zero_realloc_alloc.c \ $(srcroot)test/unit/zero_reallocs.c ifeq (@enable_prof@, 1) TESTS_UNIT += \ $(srcroot)test/unit/arena_reset_prof.c \ $(srcroot)test/unit/batch_alloc_prof.c endif TESTS_INTEGRATION := $(srcroot)test/integration/aligned_alloc.c \ $(srcroot)test/integration/allocated.c \ $(srcroot)test/integration/extent.c \ $(srcroot)test/integration/malloc.c \ $(srcroot)test/integration/mallocx.c \ $(srcroot)test/integration/MALLOCX_ARENA.c \ $(srcroot)test/integration/overflow.c \ $(srcroot)test/integration/posix_memalign.c \ $(srcroot)test/integration/rallocx.c \ $(srcroot)test/integration/sdallocx.c \ $(srcroot)test/integration/slab_sizes.c \ $(srcroot)test/integration/thread_arena.c \ $(srcroot)test/integration/thread_tcache_enabled.c \ $(srcroot)test/integration/xallocx.c ifeq (@enable_experimental_smallocx@, 1) TESTS_INTEGRATION += \ $(srcroot)test/integration/smallocx.c endif ifeq (@enable_cxx@, 1) CPP_SRCS := $(srcroot)src/jemalloc_cpp.cpp TESTS_INTEGRATION_CPP := $(srcroot)test/integration/cpp/basic.cpp \ $(srcroot)test/integration/cpp/infallible_new_true.cpp \ $(srcroot)test/integration/cpp/infallible_new_false.cpp else CPP_SRCS := TESTS_INTEGRATION_CPP := endif TESTS_ANALYZE := $(srcroot)test/analyze/prof_bias.c \ $(srcroot)test/analyze/rand.c \ $(srcroot)test/analyze/sizes.c TESTS_STRESS := $(srcroot)test/stress/batch_alloc.c \ $(srcroot)test/stress/fill_flush.c \ $(srcroot)test/stress/hookbench.c \ $(srcroot)test/stress/large_microbench.c \ $(srcroot)test/stress/mallctl.c \ $(srcroot)test/stress/microbench.c TESTS := $(TESTS_UNIT) $(TESTS_INTEGRATION) $(TESTS_INTEGRATION_CPP) \ $(TESTS_ANALYZE) $(TESTS_STRESS) PRIVATE_NAMESPACE_HDRS := $(objroot)include/jemalloc/internal/private_namespace.h $(objroot)include/jemalloc/internal/private_namespace_jet.h PRIVATE_NAMESPACE_GEN_HDRS := $(PRIVATE_NAMESPACE_HDRS:%.h=%.gen.h) C_SYM_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.sym.$(O)) C_SYMS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.sym) C_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.$(O)) CPP_OBJS := $(CPP_SRCS:$(srcroot)%.cpp=$(objroot)%.$(O)) C_PIC_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.pic.$(O)) CPP_PIC_OBJS := $(CPP_SRCS:$(srcroot)%.cpp=$(objroot)%.pic.$(O)) C_JET_SYM_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.jet.sym.$(O)) C_JET_SYMS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.jet.sym) C_JET_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.jet.$(O)) C_TESTLIB_UNIT_OBJS := $(C_TESTLIB_SRCS:$(srcroot)%.c=$(objroot)%.unit.$(O)) C_TESTLIB_INTEGRATION_OBJS := $(C_TESTLIB_SRCS:$(srcroot)%.c=$(objroot)%.integration.$(O)) C_UTIL_INTEGRATION_OBJS := $(C_UTIL_INTEGRATION_SRCS:$(srcroot)%.c=$(objroot)%.integration.$(O)) C_TESTLIB_ANALYZE_OBJS := $(C_TESTLIB_SRCS:$(srcroot)%.c=$(objroot)%.analyze.$(O)) C_TESTLIB_STRESS_OBJS := $(C_TESTLIB_SRCS:$(srcroot)%.c=$(objroot)%.stress.$(O)) C_TESTLIB_OBJS := $(C_TESTLIB_UNIT_OBJS) $(C_TESTLIB_INTEGRATION_OBJS) \ $(C_UTIL_INTEGRATION_OBJS) $(C_TESTLIB_ANALYZE_OBJS) \ $(C_TESTLIB_STRESS_OBJS) TESTS_UNIT_OBJS := $(TESTS_UNIT:$(srcroot)%.c=$(objroot)%.$(O)) TESTS_INTEGRATION_OBJS := $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%.$(O)) TESTS_INTEGRATION_CPP_OBJS := $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%.$(O)) TESTS_ANALYZE_OBJS := $(TESTS_ANALYZE:$(srcroot)%.c=$(objroot)%.$(O)) TESTS_STRESS_OBJS := $(TESTS_STRESS:$(srcroot)%.c=$(objroot)%.$(O)) TESTS_OBJS := $(TESTS_UNIT_OBJS) $(TESTS_INTEGRATION_OBJS) $(TESTS_ANALYZE_OBJS) \ $(TESTS_STRESS_OBJS) TESTS_CPP_OBJS := $(TESTS_INTEGRATION_CPP_OBJS) .PHONY: all dist build_doc_html build_doc_man build_doc .PHONY: install_bin install_include install_lib .PHONY: install_doc_html install_doc_man install_doc install .PHONY: tests check clean distclean relclean .SECONDARY : $(PRIVATE_NAMESPACE_GEN_HDRS) $(TESTS_OBJS) $(TESTS_CPP_OBJS) # Default target. all: build_lib dist: build_doc $(objroot)doc/%$(install_suffix).html : $(objroot)doc/%.xml $(srcroot)doc/stylesheet.xsl $(objroot)doc/html.xsl ifneq ($(XSLROOT),) $(XSLTPROC) -o $@ $(objroot)doc/html.xsl $< else ifeq ($(wildcard $(DOCS_HTML)),) @echo "

Missing xsltproc. Doc not built.

" > $@ endif @echo "Missing xsltproc. "$@" not (re)built." endif $(objroot)doc/%$(install_suffix).3 : $(objroot)doc/%.xml $(srcroot)doc/stylesheet.xsl $(objroot)doc/manpages.xsl ifneq ($(XSLROOT),) $(XSLTPROC) -o $@ $(objroot)doc/manpages.xsl $< # The -o option (output filename) of xsltproc may not work (it uses the # in the .xml file). Manually add the suffix if so. ifneq ($(install_suffix),) @if [ -f $(objroot)doc/jemalloc.3 ]; then \ mv $(objroot)doc/jemalloc.3 $(objroot)doc/jemalloc$(install_suffix).3 ; \ fi endif else ifeq ($(wildcard $(DOCS_MAN3)),) @echo "Missing xsltproc. Doc not built." > $@ endif @echo "Missing xsltproc. "$@" not (re)built." endif build_doc_html: $(DOCS_HTML) build_doc_man: $(DOCS_MAN3) build_doc: $(DOCS) # # Include generated dependency files. # ifdef CC_MM -include $(C_SYM_OBJS:%.$(O)=%.d) -include $(C_OBJS:%.$(O)=%.d) -include $(CPP_OBJS:%.$(O)=%.d) -include $(C_PIC_OBJS:%.$(O)=%.d) -include $(CPP_PIC_OBJS:%.$(O)=%.d) -include $(C_JET_SYM_OBJS:%.$(O)=%.d) -include $(C_JET_OBJS:%.$(O)=%.d) -include $(C_TESTLIB_OBJS:%.$(O)=%.d) -include $(TESTS_OBJS:%.$(O)=%.d) -include $(TESTS_CPP_OBJS:%.$(O)=%.d) endif $(C_SYM_OBJS): $(objroot)src/%.sym.$(O): $(srcroot)src/%.c $(C_SYM_OBJS): CPPFLAGS += -DJEMALLOC_NO_PRIVATE_NAMESPACE $(C_SYMS): $(objroot)src/%.sym: $(objroot)src/%.sym.$(O) $(C_OBJS): $(objroot)src/%.$(O): $(srcroot)src/%.c $(CPP_OBJS): $(objroot)src/%.$(O): $(srcroot)src/%.cpp $(C_PIC_OBJS): $(objroot)src/%.pic.$(O): $(srcroot)src/%.c $(C_PIC_OBJS): CFLAGS += $(PIC_CFLAGS) $(CPP_PIC_OBJS): $(objroot)src/%.pic.$(O): $(srcroot)src/%.cpp $(CPP_PIC_OBJS): CXXFLAGS += $(PIC_CFLAGS) $(C_JET_SYM_OBJS): $(objroot)src/%.jet.sym.$(O): $(srcroot)src/%.c $(C_JET_SYM_OBJS): CPPFLAGS += -DJEMALLOC_JET -DJEMALLOC_NO_PRIVATE_NAMESPACE $(C_JET_SYMS): $(objroot)src/%.jet.sym: $(objroot)src/%.jet.sym.$(O) $(C_JET_OBJS): $(objroot)src/%.jet.$(O): $(srcroot)src/%.c $(C_JET_OBJS): CPPFLAGS += -DJEMALLOC_JET $(C_TESTLIB_UNIT_OBJS): $(objroot)test/src/%.unit.$(O): $(srcroot)test/src/%.c $(C_TESTLIB_UNIT_OBJS): CPPFLAGS += -DJEMALLOC_UNIT_TEST $(C_TESTLIB_INTEGRATION_OBJS): $(objroot)test/src/%.integration.$(O): $(srcroot)test/src/%.c $(C_TESTLIB_INTEGRATION_OBJS): CPPFLAGS += -DJEMALLOC_INTEGRATION_TEST $(C_UTIL_INTEGRATION_OBJS): $(objroot)src/%.integration.$(O): $(srcroot)src/%.c $(C_TESTLIB_ANALYZE_OBJS): $(objroot)test/src/%.analyze.$(O): $(srcroot)test/src/%.c $(C_TESTLIB_ANALYZE_OBJS): CPPFLAGS += -DJEMALLOC_ANALYZE_TEST $(C_TESTLIB_STRESS_OBJS): $(objroot)test/src/%.stress.$(O): $(srcroot)test/src/%.c $(C_TESTLIB_STRESS_OBJS): CPPFLAGS += -DJEMALLOC_STRESS_TEST -DJEMALLOC_STRESS_TESTLIB $(C_TESTLIB_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include $(TESTS_UNIT_OBJS): CPPFLAGS += -DJEMALLOC_UNIT_TEST $(TESTS_INTEGRATION_OBJS): CPPFLAGS += -DJEMALLOC_INTEGRATION_TEST $(TESTS_INTEGRATION_CPP_OBJS): CPPFLAGS += -DJEMALLOC_INTEGRATION_CPP_TEST $(TESTS_ANALYZE_OBJS): CPPFLAGS += -DJEMALLOC_ANALYZE_TEST $(TESTS_STRESS_OBJS): CPPFLAGS += -DJEMALLOC_STRESS_TEST $(TESTS_OBJS): $(objroot)test/%.$(O): $(srcroot)test/%.c $(TESTS_CPP_OBJS): $(objroot)test/%.$(O): $(srcroot)test/%.cpp $(TESTS_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include $(TESTS_CPP_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include ifneq ($(IMPORTLIB),$(SO)) $(CPP_OBJS) $(C_SYM_OBJS) $(C_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS): CPPFLAGS += -DDLLEXPORT endif # Dependencies. ifndef CC_MM HEADER_DIRS = $(srcroot)include/jemalloc/internal \ $(objroot)include/jemalloc $(objroot)include/jemalloc/internal HEADERS = $(filter-out $(PRIVATE_NAMESPACE_HDRS),$(wildcard $(foreach dir,$(HEADER_DIRS),$(dir)/*.h))) $(C_SYM_OBJS) $(C_OBJS) $(CPP_OBJS) $(C_PIC_OBJS) $(CPP_PIC_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS) $(C_TESTLIB_OBJS) $(TESTS_OBJS) $(TESTS_CPP_OBJS): $(HEADERS) $(TESTS_OBJS) $(TESTS_CPP_OBJS): $(objroot)test/include/test/jemalloc_test.h endif $(C_OBJS) $(CPP_OBJS) $(C_PIC_OBJS) $(CPP_PIC_OBJS) $(C_TESTLIB_INTEGRATION_OBJS) $(C_UTIL_INTEGRATION_OBJS) $(TESTS_INTEGRATION_OBJS) $(TESTS_INTEGRATION_CPP_OBJS): $(objroot)include/jemalloc/internal/private_namespace.h $(C_JET_OBJS) $(C_TESTLIB_UNIT_OBJS) $(C_TESTLIB_ANALYZE_OBJS) $(C_TESTLIB_STRESS_OBJS) $(TESTS_UNIT_OBJS) $(TESTS_ANALYZE_OBJS) $(TESTS_STRESS_OBJS): $(objroot)include/jemalloc/internal/private_namespace_jet.h $(C_SYM_OBJS) $(C_OBJS) $(C_PIC_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS) $(C_TESTLIB_OBJS) $(TESTS_OBJS): %.$(O): @mkdir -p $(@D) $(CC) $(CFLAGS) -c $(CPPFLAGS) $(CTARGET) $< ifdef CC_MM @$(CC) -MM $(CPPFLAGS) -MT $@ -o $(@:%.$(O)=%.d) $< endif $(C_SYMS): %.sym: @mkdir -p $(@D) $(DUMP_SYMS) $< | $(AWK) -f $(objroot)include/jemalloc/internal/private_symbols.awk > $@ $(C_JET_SYMS): %.sym: @mkdir -p $(@D) $(DUMP_SYMS) $< | $(AWK) -f $(objroot)include/jemalloc/internal/private_symbols_jet.awk > $@ $(objroot)include/jemalloc/internal/private_namespace.gen.h: $(C_SYMS) $(SHELL) $(srcroot)include/jemalloc/internal/private_namespace.sh $^ > $@ $(objroot)include/jemalloc/internal/private_namespace_jet.gen.h: $(C_JET_SYMS) $(SHELL) $(srcroot)include/jemalloc/internal/private_namespace.sh $^ > $@ %.h: %.gen.h @if ! `cmp -s $< $@` ; then echo "cp $< $@"; cp $< $@ ; fi $(CPP_OBJS) $(CPP_PIC_OBJS) $(TESTS_CPP_OBJS): %.$(O): @mkdir -p $(@D) $(CXX) $(CXXFLAGS) -c $(CPPFLAGS) $(CTARGET) $< ifdef CC_MM @$(CXX) -MM $(CPPFLAGS) -MT $@ -o $(@:%.$(O)=%.d) $< endif ifneq ($(SOREV),$(SO)) %.$(SO) : %.$(SOREV) @mkdir -p $(@D) ln -sf $( $(srcroot)config.stamp.in $(objroot)config.stamp : $(cfgoutputs_in) $(cfghdrs_in) $(srcroot)configure ./$(objroot)config.status @touch $@ # There must be some action in order for make to re-read Makefile when it is # out of date. $(cfgoutputs_out) $(cfghdrs_out) : $(objroot)config.stamp @true endif redis-8.0.2/deps/jemalloc/README000066400000000000000000000020271501533116600162320ustar00rootroot00000000000000jemalloc is a general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support. jemalloc first came into use as the FreeBSD libc allocator in 2005, and since then it has found its way into numerous applications that rely on its predictable behavior. In 2010 jemalloc development efforts broadened to include developer support features such as heap profiling and extensive monitoring/tuning hooks. Modern jemalloc releases continue to be integrated back into FreeBSD, and therefore versatility remains critical. Ongoing development efforts trend toward making jemalloc among the best allocators for a broad range of demanding applications, and eliminating/mitigating weaknesses that have practical repercussions for real world applications. The COPYING file contains copyright and licensing information. The INSTALL file contains information on how to configure, build, and install jemalloc. The ChangeLog file contains a brief summary of changes for each release. URL: http://jemalloc.net/ redis-8.0.2/deps/jemalloc/TUNING.md000066400000000000000000000134571501533116600167110ustar00rootroot00000000000000This document summarizes the common approaches for performance fine tuning with jemalloc (as of 5.3.0). The default configuration of jemalloc tends to work reasonably well in practice, and most applications should not have to tune any options. However, in order to cover a wide range of applications and avoid pathological cases, the default setting is sometimes kept conservative and suboptimal, even for many common workloads. When jemalloc is properly tuned for a specific application / workload, it is common to improve system level metrics by a few percent, or make favorable trade-offs. ## Notable runtime options for performance tuning Runtime options can be set via [malloc_conf](http://jemalloc.net/jemalloc.3.html#tuning). * [background_thread](http://jemalloc.net/jemalloc.3.html#background_thread) Enabling jemalloc background threads generally improves the tail latency for application threads, since unused memory purging is shifted to the dedicated background threads. In addition, unintended purging delay caused by application inactivity is avoided with background threads. Suggested: `background_thread:true` when jemalloc managed threads can be allowed. * [metadata_thp](http://jemalloc.net/jemalloc.3.html#opt.metadata_thp) Allowing jemalloc to utilize transparent huge pages for its internal metadata usually reduces TLB misses significantly, especially for programs with large memory footprint and frequent allocation / deallocation activities. Metadata memory usage may increase due to the use of huge pages. Suggested for allocation intensive programs: `metadata_thp:auto` or `metadata_thp:always`, which is expected to improve CPU utilization at a small memory cost. * [dirty_decay_ms](http://jemalloc.net/jemalloc.3.html#opt.dirty_decay_ms) and [muzzy_decay_ms](http://jemalloc.net/jemalloc.3.html#opt.muzzy_decay_ms) Decay time determines how fast jemalloc returns unused pages back to the operating system, and therefore provides a fairly straightforward trade-off between CPU and memory usage. Shorter decay time purges unused pages faster to reduces memory usage (usually at the cost of more CPU cycles spent on purging), and vice versa. Suggested: tune the values based on the desired trade-offs. * [narenas](http://jemalloc.net/jemalloc.3.html#opt.narenas) By default jemalloc uses multiple arenas to reduce internal lock contention. However high arena count may also increase overall memory fragmentation, since arenas manage memory independently. When high degree of parallelism is not expected at the allocator level, lower number of arenas often improves memory usage. Suggested: if low parallelism is expected, try lower arena count while monitoring CPU and memory usage. * [percpu_arena](http://jemalloc.net/jemalloc.3.html#opt.percpu_arena) Enable dynamic thread to arena association based on running CPU. This has the potential to improve locality, e.g. when thread to CPU affinity is present. Suggested: try `percpu_arena:percpu` or `percpu_arena:phycpu` if thread migration between processors is expected to be infrequent. Examples: * High resource consumption application, prioritizing CPU utilization: `background_thread:true,metadata_thp:auto` combined with relaxed decay time (increased `dirty_decay_ms` and / or `muzzy_decay_ms`, e.g. `dirty_decay_ms:30000,muzzy_decay_ms:30000`). * High resource consumption application, prioritizing memory usage: `background_thread:true,tcache_max:4096` combined with shorter decay time (decreased `dirty_decay_ms` and / or `muzzy_decay_ms`, e.g. `dirty_decay_ms:5000,muzzy_decay_ms:5000`), and lower arena count (e.g. number of CPUs). * Low resource consumption application: `narenas:1,tcache_max:1024` combined with shorter decay time (decreased `dirty_decay_ms` and / or `muzzy_decay_ms`,e.g. `dirty_decay_ms:1000,muzzy_decay_ms:0`). * Extremely conservative -- minimize memory usage at all costs, only suitable when allocation activity is very rare: `narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0` Note that it is recommended to combine the options with `abort_conf:true` which aborts immediately on illegal options. ## Beyond runtime options In addition to the runtime options, there are a number of programmatic ways to improve application performance with jemalloc. * [Explicit arenas](http://jemalloc.net/jemalloc.3.html#arenas.create) Manually created arenas can help performance in various ways, e.g. by managing locality and contention for specific usages. For example, applications can explicitly allocate frequently accessed objects from a dedicated arena with [mallocx()](http://jemalloc.net/jemalloc.3.html#MALLOCX_ARENA) to improve locality. In addition, explicit arenas often benefit from individually tuned options, e.g. relaxed [decay time](http://jemalloc.net/jemalloc.3.html#arena.i.dirty_decay_ms) if frequent reuse is expected. * [Extent hooks](http://jemalloc.net/jemalloc.3.html#arena.i.extent_hooks) Extent hooks allow customization for managing underlying memory. One use case for performance purpose is to utilize huge pages -- for example, [HHVM](https://github.com/facebook/hhvm/blob/master/hphp/util/alloc.cpp) uses explicit arenas with customized extent hooks to manage 1GB huge pages for frequently accessed data, which reduces TLB misses significantly. * [Explicit thread-to-arena binding](http://jemalloc.net/jemalloc.3.html#thread.arena) It is common for some threads in an application to have different memory access / allocation patterns. Threads with heavy workloads often benefit from explicit binding, e.g. binding very active threads to dedicated arenas may reduce contention at the allocator level. redis-8.0.2/deps/jemalloc/VERSION000066400000000000000000000000131501533116600164130ustar00rootroot000000000000005.3.0-0-g0 redis-8.0.2/deps/jemalloc/autogen.sh000077500000000000000000000004121501533116600173470ustar00rootroot00000000000000#!/bin/sh for i in autoconf; do echo "$i" $i if [ $? -ne 0 ]; then echo "Error $? in $i" exit 1 fi done echo "./configure --enable-autogen $@" ./configure --enable-autogen $@ if [ $? -ne 0 ]; then echo "Error $? in ./configure" exit 1 fi redis-8.0.2/deps/jemalloc/bin/000077500000000000000000000000001501533116600161215ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/bin/jemalloc-config.in000066400000000000000000000030761501533116600215100ustar00rootroot00000000000000#!/bin/sh usage() { cat < Options: --help | -h : Print usage. --version : Print jemalloc version. --revision : Print shared library revision number. --config : Print configure options used to build jemalloc. --prefix : Print installation directory prefix. --bindir : Print binary installation directory. --datadir : Print data installation directory. --includedir : Print include installation directory. --libdir : Print library installation directory. --mandir : Print manual page installation directory. --cc : Print compiler used to build jemalloc. --cflags : Print compiler flags used to build jemalloc. --cppflags : Print preprocessor flags used to build jemalloc. --cxxflags : Print C++ compiler flags used to build jemalloc. --ldflags : Print library flags used to build jemalloc. --libs : Print libraries jemalloc was linked against. EOF } prefix="@prefix@" exec_prefix="@exec_prefix@" case "$1" in --help | -h) usage exit 0 ;; --version) echo "@jemalloc_version@" ;; --revision) echo "@rev@" ;; --config) echo "@CONFIG@" ;; --prefix) echo "@PREFIX@" ;; --bindir) echo "@BINDIR@" ;; --datadir) echo "@DATADIR@" ;; --includedir) echo "@INCLUDEDIR@" ;; --libdir) echo "@LIBDIR@" ;; --mandir) echo "@MANDIR@" ;; --cc) echo "@CC@" ;; --cflags) echo "@CFLAGS@" ;; --cppflags) echo "@CPPFLAGS@" ;; --cxxflags) echo "@CXXFLAGS@" ;; --ldflags) echo "@LDFLAGS@ @EXTRA_LDFLAGS@" ;; --libs) echo "@LIBS@" ;; *) usage exit 1 esac redis-8.0.2/deps/jemalloc/bin/jemalloc.sh.in000066400000000000000000000002271501533116600206510ustar00rootroot00000000000000#!/bin/sh prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ @LD_PRELOAD_VAR@=${libdir}/libjemalloc.@SOREV@ export @LD_PRELOAD_VAR@ exec "$@" redis-8.0.2/deps/jemalloc/bin/jeprof.in000066400000000000000000005446131501533116600177530ustar00rootroot00000000000000#! /usr/bin/env perl # Copyright (c) 1998-2007, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * 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. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 # OWNER 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. # --- # Program for printing the profile generated by common/profiler.cc, # or by the heap profiler (common/debugallocation.cc) # # The profile contains a sequence of entries of the form: # # This program parses the profile, and generates user-readable # output. # # Examples: # # % tools/jeprof "program" "profile" # Enters "interactive" mode # # % tools/jeprof --text "program" "profile" # Generates one line per procedure # # % tools/jeprof --gv "program" "profile" # Generates annotated call-graph and displays via "gv" # # % tools/jeprof --gv --focus=Mutex "program" "profile" # Restrict to code paths that involve an entry that matches "Mutex" # # % tools/jeprof --gv --focus=Mutex --ignore=string "program" "profile" # Restrict to code paths that involve an entry that matches "Mutex" # and does not match "string" # # % tools/jeprof --list=IBF_CheckDocid "program" "profile" # Generates disassembly listing of all routines with at least one # sample that match the --list= pattern. The listing is # annotated with the flat and cumulative sample counts at each line. # # % tools/jeprof --disasm=IBF_CheckDocid "program" "profile" # Generates disassembly listing of all routines with at least one # sample that match the --disasm= pattern. The listing is # annotated with the flat and cumulative sample counts at each PC value. # # TODO: Use color to indicate files? use strict; use warnings; use Getopt::Long; use Cwd; my $JEPROF_VERSION = "@jemalloc_version@"; my $PPROF_VERSION = "2.0"; # These are the object tools we use which can come from a # user-specified location using --tools, from the JEPROF_TOOLS # environment variable, or from the environment. my %obj_tool_map = ( "objdump" => "objdump", "nm" => "nm", "addr2line" => "addr2line", "c++filt" => "c++filt", ## ConfigureObjTools may add architecture-specific entries: #"nm_pdb" => "nm-pdb", # for reading windows (PDB-format) executables #"addr2line_pdb" => "addr2line-pdb", # ditto #"otool" => "otool", # equivalent of objdump on OS X ); # NOTE: these are lists, so you can put in commandline flags if you want. my @DOT = ("dot"); # leave non-absolute, since it may be in /usr/local my @GV = ("gv"); my @EVINCE = ("evince"); # could also be xpdf or perhaps acroread my @KCACHEGRIND = ("kcachegrind"); my @PS2PDF = ("ps2pdf"); # These are used for dynamic profiles my @URL_FETCHER = ("curl", "-s", "--fail"); # These are the web pages that servers need to support for dynamic profiles my $HEAP_PAGE = "/pprof/heap"; my $PROFILE_PAGE = "/pprof/profile"; # must support cgi-param "?seconds=#" my $PMUPROFILE_PAGE = "/pprof/pmuprofile(?:\\?.*)?"; # must support cgi-param # ?seconds=#&event=x&period=n my $GROWTH_PAGE = "/pprof/growth"; my $CONTENTION_PAGE = "/pprof/contention"; my $WALL_PAGE = "/pprof/wall(?:\\?.*)?"; # accepts options like namefilter my $FILTEREDPROFILE_PAGE = "/pprof/filteredprofile(?:\\?.*)?"; my $CENSUSPROFILE_PAGE = "/pprof/censusprofile(?:\\?.*)?"; # must support cgi-param # "?seconds=#", # "?tags_regexp=#" and # "?type=#". my $SYMBOL_PAGE = "/pprof/symbol"; # must support symbol lookup via POST my $PROGRAM_NAME_PAGE = "/pprof/cmdline"; # These are the web pages that can be named on the command line. # All the alternatives must begin with /. my $PROFILES = "($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|" . "$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|" . "$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)"; # default binary name my $UNKNOWN_BINARY = "(unknown)"; # There is a pervasive dependency on the length (in hex characters, # i.e., nibbles) of an address, distinguishing between 32-bit and # 64-bit profiles. To err on the safe size, default to 64-bit here: my $address_length = 16; my $dev_null = "/dev/null"; if (! -e $dev_null && $^O =~ /MSWin/) { # $^O is the OS perl was built for $dev_null = "nul"; } # A list of paths to search for shared object files my @prefix_list = (); # Special routine name that should not have any symbols. # Used as separator to parse "addr2line -i" output. my $sep_symbol = '_fini'; my $sep_address = undef; ##### Argument parsing ##### sub usage_string { return < is a space separated list of profile names. jeprof [options] is a list of profile files where each file contains the necessary symbol mappings as well as profile data (likely generated with --raw). jeprof [options] is a remote form. Symbols are obtained from host:port$SYMBOL_PAGE Each name can be: /path/to/profile - a path to a profile file host:port[/] - a location of a service to get profile from The / can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile, $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall, $CENSUSPROFILE_PAGE, or /pprof/filteredprofile. For instance: jeprof http://myserver.com:80$HEAP_PAGE If / is omitted, the service defaults to $PROFILE_PAGE (cpu profiling). jeprof --symbols Maps addresses to symbol names. In this mode, stdin should be a list of library mappings, in the same format as is found in the heap- and cpu-profile files (this loosely matches that of /proc/self/maps on linux), followed by a list of hex addresses to map, one per line. For more help with querying remote servers, including how to add the necessary server-side support code, see this filename (or one like it): /usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html Options: --cum Sort by cumulative data --base= Subtract from before display --interactive Run in interactive mode (interactive "help" gives help) [default] --seconds= Length of time for dynamic profiles [default=30 secs] --add_lib= Read additional symbols and line info from the given library --lib_prefix= Comma separated list of library path prefixes Reporting Granularity: --addresses Report at address level --lines Report at source line level --functions Report at function level [default] --files Report at source file level Output type: --text Generate text report --callgrind Generate callgrind format to stdout --gv Generate Postscript and display --evince Generate PDF and display --web Generate SVG and display --list= Generate source listing of matching routines --disasm= Generate disassembly of matching routines --symbols Print demangled symbol names found at given addresses --dot Generate DOT file to stdout --ps Generate Postcript to stdout --pdf Generate PDF to stdout --svg Generate SVG to stdout --gif Generate GIF to stdout --raw Generate symbolized jeprof data (useful with remote fetch) --collapsed Generate collapsed stacks for building flame graphs (see http://www.brendangregg.com/flamegraphs.html) Heap-Profile Options: --inuse_space Display in-use (mega)bytes [default] --inuse_objects Display in-use objects --alloc_space Display allocated (mega)bytes --alloc_objects Display allocated objects --show_bytes Display space in bytes --drop_negative Ignore negative differences Contention-profile options: --total_delay Display total delay at each region [default] --contentions Display number of delays at each region --mean_delay Display mean delay at each region Call-graph Options: --nodecount= Show at most so many nodes [default=80] --nodefraction= Hide nodes below *total [default=.005] --edgefraction= Hide edges below *total [default=.001] --maxdegree= Max incoming/outgoing edges per node [default=8] --focus= Focus on backtraces with nodes matching --thread= Show profile for thread --ignore= Ignore backtraces with nodes matching --scale= Set GV scaling [default=0] --heapcheck Make nodes with non-0 object counts (i.e. direct leak generators) more visible --retain= Retain only nodes that match --exclude= Exclude all nodes that match Miscellaneous: --tools=[,...] \$PATH for object tool pathnames --test Run unit tests --help This message --version Version information --debug-syms-by-id (Linux only) Find debug symbol files by build ID as well as by name Environment Variables: JEPROF_TMPDIR Profiles directory. Defaults to \$HOME/jeprof JEPROF_TOOLS Prefix for object tools pathnames Examples: jeprof /bin/ls ls.prof Enters "interactive" mode jeprof --text /bin/ls ls.prof Outputs one line per procedure jeprof --web /bin/ls ls.prof Displays annotated call-graph in web browser jeprof --gv /bin/ls ls.prof Displays annotated call-graph via 'gv' jeprof --gv --focus=Mutex /bin/ls ls.prof Restricts to code paths including a .*Mutex.* entry jeprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof Code paths including Mutex but not string jeprof --list=getdir /bin/ls ls.prof (Per-line) annotated source listing for getdir() jeprof --disasm=getdir /bin/ls ls.prof (Per-PC) annotated disassembly for getdir() jeprof http://localhost:1234/ Enters "interactive" mode jeprof --text localhost:1234 Outputs one line per procedure for localhost:1234 jeprof --raw localhost:1234 > ./local.raw jeprof --text ./local.raw Fetches a remote profile for later analysis and then analyzes it in text mode. EOF } sub version_string { return < \$main::opt_help, "version!" => \$main::opt_version, "cum!" => \$main::opt_cum, "base=s" => \$main::opt_base, "seconds=i" => \$main::opt_seconds, "add_lib=s" => \$main::opt_lib, "lib_prefix=s" => \$main::opt_lib_prefix, "functions!" => \$main::opt_functions, "lines!" => \$main::opt_lines, "addresses!" => \$main::opt_addresses, "files!" => \$main::opt_files, "text!" => \$main::opt_text, "callgrind!" => \$main::opt_callgrind, "list=s" => \$main::opt_list, "disasm=s" => \$main::opt_disasm, "symbols!" => \$main::opt_symbols, "gv!" => \$main::opt_gv, "evince!" => \$main::opt_evince, "web!" => \$main::opt_web, "dot!" => \$main::opt_dot, "ps!" => \$main::opt_ps, "pdf!" => \$main::opt_pdf, "svg!" => \$main::opt_svg, "gif!" => \$main::opt_gif, "raw!" => \$main::opt_raw, "collapsed!" => \$main::opt_collapsed, "interactive!" => \$main::opt_interactive, "nodecount=i" => \$main::opt_nodecount, "nodefraction=f" => \$main::opt_nodefraction, "edgefraction=f" => \$main::opt_edgefraction, "maxdegree=i" => \$main::opt_maxdegree, "focus=s" => \$main::opt_focus, "thread=s" => \$main::opt_thread, "ignore=s" => \$main::opt_ignore, "scale=i" => \$main::opt_scale, "heapcheck" => \$main::opt_heapcheck, "retain=s" => \$main::opt_retain, "exclude=s" => \$main::opt_exclude, "inuse_space!" => \$main::opt_inuse_space, "inuse_objects!" => \$main::opt_inuse_objects, "alloc_space!" => \$main::opt_alloc_space, "alloc_objects!" => \$main::opt_alloc_objects, "show_bytes!" => \$main::opt_show_bytes, "drop_negative!" => \$main::opt_drop_negative, "total_delay!" => \$main::opt_total_delay, "contentions!" => \$main::opt_contentions, "mean_delay!" => \$main::opt_mean_delay, "tools=s" => \$main::opt_tools, "test!" => \$main::opt_test, "debug!" => \$main::opt_debug, "debug-syms-by-id!" => \$main::opt_debug_syms_by_id, # Undocumented flags used only by unittests: "test_stride=i" => \$main::opt_test_stride, ) || usage("Invalid option(s)"); # Deal with the standard --help and --version if ($main::opt_help) { print usage_string(); exit(0); } if ($main::opt_version) { print version_string(); exit(0); } # Disassembly/listing/symbols mode requires address-level info if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) { $main::opt_functions = 0; $main::opt_lines = 0; $main::opt_addresses = 1; $main::opt_files = 0; } # Check heap-profiling flags if ($main::opt_inuse_space + $main::opt_inuse_objects + $main::opt_alloc_space + $main::opt_alloc_objects > 1) { usage("Specify at most on of --inuse/--alloc options"); } # Check output granularities my $grains = $main::opt_functions + $main::opt_lines + $main::opt_addresses + $main::opt_files + 0; if ($grains > 1) { usage("Only specify one output granularity option"); } if ($grains == 0) { $main::opt_functions = 1; } # Check output modes my $modes = $main::opt_text + $main::opt_callgrind + ($main::opt_list eq '' ? 0 : 1) + ($main::opt_disasm eq '' ? 0 : 1) + ($main::opt_symbols == 0 ? 0 : 1) + $main::opt_gv + $main::opt_evince + $main::opt_web + $main::opt_dot + $main::opt_ps + $main::opt_pdf + $main::opt_svg + $main::opt_gif + $main::opt_raw + $main::opt_collapsed + $main::opt_interactive + 0; if ($modes > 1) { usage("Only specify one output mode"); } if ($modes == 0) { if (-t STDOUT) { # If STDOUT is a tty, activate interactive mode $main::opt_interactive = 1; } else { $main::opt_text = 1; } } if ($main::opt_test) { RunUnitTests(); # Should not return exit(1); } # Binary name and profile arguments list $main::prog = ""; @main::pfile_args = (); # Remote profiling without a binary (using $SYMBOL_PAGE instead) if (@ARGV > 0) { if (IsProfileURL($ARGV[0])) { $main::use_symbol_page = 1; } elsif (IsSymbolizedProfileFile($ARGV[0])) { $main::use_symbolized_profile = 1; $main::prog = $UNKNOWN_BINARY; # will be set later from the profile file } } if ($main::use_symbol_page || $main::use_symbolized_profile) { # We don't need a binary! my %disabled = ('--lines' => $main::opt_lines, '--disasm' => $main::opt_disasm); for my $option (keys %disabled) { usage("$option cannot be used without a binary") if $disabled{$option}; } # Set $main::prog later... scalar(@ARGV) || usage("Did not specify profile file"); } elsif ($main::opt_symbols) { # --symbols needs a binary-name (to run nm on, etc) but not profiles $main::prog = shift(@ARGV) || usage("Did not specify program"); } else { $main::prog = shift(@ARGV) || usage("Did not specify program"); scalar(@ARGV) || usage("Did not specify profile file"); } # Parse profile file/location arguments foreach my $farg (@ARGV) { if ($farg =~ m/(.*)\@([0-9]+)(|\/.*)$/ ) { my $machine = $1; my $num_machines = $2; my $path = $3; for (my $i = 0; $i < $num_machines; $i++) { unshift(@main::pfile_args, "$i.$machine$path"); } } else { unshift(@main::pfile_args, $farg); } } if ($main::use_symbol_page) { unless (IsProfileURL($main::pfile_args[0])) { error("The first profile should be a remote form to use $SYMBOL_PAGE\n"); } CheckSymbolPage(); $main::prog = FetchProgramName(); } elsif (!$main::use_symbolized_profile) { # may not need objtools! ConfigureObjTools($main::prog) } # Break the opt_lib_prefix into the prefix_list array @prefix_list = split (',', $main::opt_lib_prefix); # Remove trailing / from the prefixes, in the list to prevent # searching things like /my/path//lib/mylib.so foreach (@prefix_list) { s|/+$||; } # Flag to prevent us from trying over and over to use # elfutils if it's not installed (used only with # --debug-syms-by-id option). $main::gave_up_on_elfutils = 0; } sub FilterAndPrint { my ($profile, $symbols, $libs, $thread) = @_; # Get total data in profile my $total = TotalProfile($profile); # Remove uniniteresting stack items $profile = RemoveUninterestingFrames($symbols, $profile); # Focus? if ($main::opt_focus ne '') { $profile = FocusProfile($symbols, $profile, $main::opt_focus); } # Ignore? if ($main::opt_ignore ne '') { $profile = IgnoreProfile($symbols, $profile, $main::opt_ignore); } my $calls = ExtractCalls($symbols, $profile); # Reduce profiles to required output granularity, and also clean # each stack trace so a given entry exists at most once. my $reduced = ReduceProfile($symbols, $profile); # Get derived profiles my $flat = FlatProfile($reduced); my $cumulative = CumulativeProfile($reduced); # Print if (!$main::opt_interactive) { if ($main::opt_disasm) { PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm); } elsif ($main::opt_list) { PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0); } elsif ($main::opt_text) { # Make sure the output is empty when have nothing to report # (only matters when --heapcheck is given but we must be # compatible with old branches that did not pass --heapcheck always): if ($total != 0) { printf("Total%s: %s %s\n", (defined($thread) ? " (t$thread)" : ""), Unparse($total), Units()); } PrintText($symbols, $flat, $cumulative, -1); } elsif ($main::opt_raw) { PrintSymbolizedProfile($symbols, $profile, $main::prog); } elsif ($main::opt_collapsed) { PrintCollapsedStacks($symbols, $profile); } elsif ($main::opt_callgrind) { PrintCallgrind($calls); } else { if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) { if ($main::opt_gv) { RunGV(TempName($main::next_tmpfile, "ps"), ""); } elsif ($main::opt_evince) { RunEvince(TempName($main::next_tmpfile, "pdf"), ""); } elsif ($main::opt_web) { my $tmp = TempName($main::next_tmpfile, "svg"); RunWeb($tmp); # The command we run might hand the file name off # to an already running browser instance and then exit. # Normally, we'd remove $tmp on exit (right now), # but fork a child to remove $tmp a little later, so that the # browser has time to load it first. delete $main::tempnames{$tmp}; if (fork() == 0) { sleep 5; unlink($tmp); exit(0); } } } else { cleanup(); exit(1); } } } else { InteractiveMode($profile, $symbols, $libs, $total); } } sub Main() { Init(); $main::collected_profile = undef; @main::profile_files = (); $main::op_time = time(); # Printing symbols is special and requires a lot less info that most. if ($main::opt_symbols) { PrintSymbols(*STDIN); # Get /proc/maps and symbols output from stdin return; } # Fetch all profile data FetchDynamicProfiles(); # this will hold symbols that we read from the profile files my $symbol_map = {}; # Read one profile, pick the last item on the list my $data = ReadProfile($main::prog, pop(@main::profile_files)); my $profile = $data->{profile}; my $pcs = $data->{pcs}; my $libs = $data->{libs}; # Info about main program and shared libraries $symbol_map = MergeSymbols($symbol_map, $data->{symbols}); # Add additional profiles, if available. if (scalar(@main::profile_files) > 0) { foreach my $pname (@main::profile_files) { my $data2 = ReadProfile($main::prog, $pname); $profile = AddProfile($profile, $data2->{profile}); $pcs = AddPcs($pcs, $data2->{pcs}); $symbol_map = MergeSymbols($symbol_map, $data2->{symbols}); } } # Subtract base from profile, if specified if ($main::opt_base ne '') { my $base = ReadProfile($main::prog, $main::opt_base); $profile = SubtractProfile($profile, $base->{profile}); $pcs = AddPcs($pcs, $base->{pcs}); $symbol_map = MergeSymbols($symbol_map, $base->{symbols}); } # Collect symbols my $symbols; if ($main::use_symbolized_profile) { $symbols = FetchSymbols($pcs, $symbol_map); } elsif ($main::use_symbol_page) { $symbols = FetchSymbols($pcs); } else { # TODO(csilvers): $libs uses the /proc/self/maps data from profile1, # which may differ from the data from subsequent profiles, especially # if they were run on different machines. Use appropriate libs for # each pc somehow. $symbols = ExtractSymbols($libs, $pcs); } if (!defined($main::opt_thread)) { FilterAndPrint($profile, $symbols, $libs); } if (defined($data->{threads})) { foreach my $thread (sort { $a <=> $b } keys(%{$data->{threads}})) { if (defined($main::opt_thread) && ($main::opt_thread eq '*' || $main::opt_thread == $thread)) { my $thread_profile = $data->{threads}{$thread}; FilterAndPrint($thread_profile, $symbols, $libs, $thread); } } } cleanup(); exit(0); } ##### Entry Point ##### Main(); # Temporary code to detect if we're running on a Goobuntu system. # These systems don't have the right stuff installed for the special # Readline libraries to work, so as a temporary workaround, we default # to using the normal stdio code, rather than the fancier readline-based # code sub ReadlineMightFail { if (-e '/lib/libtermcap.so.2') { return 0; # libtermcap exists, so readline should be okay } else { return 1; } } sub RunGV { my $fname = shift; my $bg = shift; # "" or " &" if we should run in background if (!system(ShellEscape(@GV, "--version") . " >$dev_null 2>&1")) { # Options using double dash are supported by this gv version. # Also, turn on noantialias to better handle bug in gv for # postscript files with large dimensions. # TODO: Maybe we should not pass the --noantialias flag # if the gv version is known to work properly without the flag. system(ShellEscape(@GV, "--scale=$main::opt_scale", "--noantialias", $fname) . $bg); } else { # Old gv version - only supports options that use single dash. print STDERR ShellEscape(@GV, "-scale", $main::opt_scale) . "\n"; system(ShellEscape(@GV, "-scale", "$main::opt_scale", $fname) . $bg); } } sub RunEvince { my $fname = shift; my $bg = shift; # "" or " &" if we should run in background system(ShellEscape(@EVINCE, $fname) . $bg); } sub RunWeb { my $fname = shift; print STDERR "Loading web page file:///$fname\n"; if (`uname` =~ /Darwin/) { # OS X: open will use standard preference for SVG files. system("/usr/bin/open", $fname); return; } # Some kind of Unix; try generic symlinks, then specific browsers. # (Stop once we find one.) # Works best if the browser is already running. my @alt = ( "/etc/alternatives/gnome-www-browser", "/etc/alternatives/x-www-browser", "google-chrome", "firefox", ); foreach my $b (@alt) { if (system($b, $fname) == 0) { return; } } print STDERR "Could not load web browser.\n"; } sub RunKcachegrind { my $fname = shift; my $bg = shift; # "" or " &" if we should run in background print STDERR "Starting '@KCACHEGRIND " . $fname . $bg . "'\n"; system(ShellEscape(@KCACHEGRIND, $fname) . $bg); } ##### Interactive helper routines ##### sub InteractiveMode { $| = 1; # Make output unbuffered for interactive mode my ($orig_profile, $symbols, $libs, $total) = @_; print STDERR "Welcome to jeprof! For help, type 'help'.\n"; # Use ReadLine if it's installed and input comes from a console. if ( -t STDIN && !ReadlineMightFail() && defined(eval {require Term::ReadLine}) ) { my $term = new Term::ReadLine 'jeprof'; while ( defined ($_ = $term->readline('(jeprof) '))) { $term->addhistory($_) if /\S/; if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) { last; # exit when we get an interactive command to quit } } } else { # don't have readline while (1) { print STDERR "(jeprof) "; $_ = ; last if ! defined $_ ; s/\r//g; # turn windows-looking lines into unix-looking lines # Save some flags that might be reset by InteractiveCommand() my $save_opt_lines = $main::opt_lines; if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) { last; # exit when we get an interactive command to quit } # Restore flags $main::opt_lines = $save_opt_lines; } } } # Takes two args: orig profile, and command to run. # Returns 1 if we should keep going, or 0 if we were asked to quit sub InteractiveCommand { my($orig_profile, $symbols, $libs, $total, $command) = @_; $_ = $command; # just to make future m//'s easier if (!defined($_)) { print STDERR "\n"; return 0; } if (m/^\s*quit/) { return 0; } if (m/^\s*help/) { InteractiveHelpMessage(); return 1; } # Clear all the mode options -- mode is controlled by "$command" $main::opt_text = 0; $main::opt_callgrind = 0; $main::opt_disasm = 0; $main::opt_list = 0; $main::opt_gv = 0; $main::opt_evince = 0; $main::opt_cum = 0; if (m/^\s*(text|top)(\d*)\s*(.*)/) { $main::opt_text = 1; my $line_limit = ($2 ne "") ? int($2) : 10; my $routine; my $ignore; ($routine, $ignore) = ParseInteractiveArgs($3); my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore); my $reduced = ReduceProfile($symbols, $profile); # Get derived profiles my $flat = FlatProfile($reduced); my $cumulative = CumulativeProfile($reduced); PrintText($symbols, $flat, $cumulative, $line_limit); return 1; } if (m/^\s*callgrind\s*([^ \n]*)/) { $main::opt_callgrind = 1; # Get derived profiles my $calls = ExtractCalls($symbols, $orig_profile); my $filename = $1; if ( $1 eq '' ) { $filename = TempName($main::next_tmpfile, "callgrind"); } PrintCallgrind($calls, $filename); if ( $1 eq '' ) { RunKcachegrind($filename, " & "); $main::next_tmpfile++; } return 1; } if (m/^\s*(web)?list\s*(.+)/) { my $html = (defined($1) && ($1 eq "web")); $main::opt_list = 1; my $routine; my $ignore; ($routine, $ignore) = ParseInteractiveArgs($2); my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore); my $reduced = ReduceProfile($symbols, $profile); # Get derived profiles my $flat = FlatProfile($reduced); my $cumulative = CumulativeProfile($reduced); PrintListing($total, $libs, $flat, $cumulative, $routine, $html); return 1; } if (m/^\s*disasm\s*(.+)/) { $main::opt_disasm = 1; my $routine; my $ignore; ($routine, $ignore) = ParseInteractiveArgs($1); # Process current profile to account for various settings my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore); my $reduced = ReduceProfile($symbols, $profile); # Get derived profiles my $flat = FlatProfile($reduced); my $cumulative = CumulativeProfile($reduced); PrintDisassembly($libs, $flat, $cumulative, $routine); return 1; } if (m/^\s*(gv|web|evince)\s*(.*)/) { $main::opt_gv = 0; $main::opt_evince = 0; $main::opt_web = 0; if ($1 eq "gv") { $main::opt_gv = 1; } elsif ($1 eq "evince") { $main::opt_evince = 1; } elsif ($1 eq "web") { $main::opt_web = 1; } my $focus; my $ignore; ($focus, $ignore) = ParseInteractiveArgs($2); # Process current profile to account for various settings my $profile = ProcessProfile($total, $orig_profile, $symbols, $focus, $ignore); my $reduced = ReduceProfile($symbols, $profile); # Get derived profiles my $flat = FlatProfile($reduced); my $cumulative = CumulativeProfile($reduced); if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) { if ($main::opt_gv) { RunGV(TempName($main::next_tmpfile, "ps"), " &"); } elsif ($main::opt_evince) { RunEvince(TempName($main::next_tmpfile, "pdf"), " &"); } elsif ($main::opt_web) { RunWeb(TempName($main::next_tmpfile, "svg")); } $main::next_tmpfile++; } return 1; } if (m/^\s*$/) { return 1; } print STDERR "Unknown command: try 'help'.\n"; return 1; } sub ProcessProfile { my $total_count = shift; my $orig_profile = shift; my $symbols = shift; my $focus = shift; my $ignore = shift; # Process current profile to account for various settings my $profile = $orig_profile; printf("Total: %s %s\n", Unparse($total_count), Units()); if ($focus ne '') { $profile = FocusProfile($symbols, $profile, $focus); my $focus_count = TotalProfile($profile); printf("After focusing on '%s': %s %s of %s (%0.1f%%)\n", $focus, Unparse($focus_count), Units(), Unparse($total_count), ($focus_count*100.0) / $total_count); } if ($ignore ne '') { $profile = IgnoreProfile($symbols, $profile, $ignore); my $ignore_count = TotalProfile($profile); printf("After ignoring '%s': %s %s of %s (%0.1f%%)\n", $ignore, Unparse($ignore_count), Units(), Unparse($total_count), ($ignore_count*100.0) / $total_count); } return $profile; } sub InteractiveHelpMessage { print STDERR <{$k}; my @addrs = split(/\n/, $k); if ($#addrs >= 0) { my $depth = $#addrs + 1; # int(foo / 2**32) is the only reliable way to get rid of bottom # 32 bits on both 32- and 64-bit systems. print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32)); print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32)); foreach my $full_addr (@addrs) { my $addr = $full_addr; $addr =~ s/0x0*//; # strip off leading 0x, zeroes if (length($addr) > 16) { print STDERR "Invalid address in profile: $full_addr\n"; next; } my $low_addr = substr($addr, -8); # get last 8 hex chars my $high_addr = substr($addr, -16, 8); # get up to 8 more hex chars print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr)); } } } } # Print symbols and profile data sub PrintSymbolizedProfile { my $symbols = shift; my $profile = shift; my $prog = shift; $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $symbol_marker = $&; print '--- ', $symbol_marker, "\n"; if (defined($prog)) { print 'binary=', $prog, "\n"; } while (my ($pc, $name) = each(%{$symbols})) { my $sep = ' '; print '0x', $pc; # We have a list of function names, which include the inlined # calls. They are separated (and terminated) by --, which is # illegal in function names. for (my $j = 2; $j <= $#{$name}; $j += 3) { print $sep, $name->[$j]; $sep = '--'; } print "\n"; } print '---', "\n"; my $profile_marker; if ($main::profile_type eq 'heap') { $HEAP_PAGE =~ m,[^/]+$,; # matches everything after the last slash $profile_marker = $&; } elsif ($main::profile_type eq 'growth') { $GROWTH_PAGE =~ m,[^/]+$,; # matches everything after the last slash $profile_marker = $&; } elsif ($main::profile_type eq 'contention') { $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash $profile_marker = $&; } else { # elsif ($main::profile_type eq 'cpu') $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash $profile_marker = $&; } print '--- ', $profile_marker, "\n"; if (defined($main::collected_profile)) { # if used with remote fetch, simply dump the collected profile to output. open(SRC, "<$main::collected_profile"); while () { print $_; } close(SRC); } else { # --raw/http: For everything to work correctly for non-remote profiles, we # would need to extend PrintProfileData() to handle all possible profile # types, re-enable the code that is currently disabled in ReadCPUProfile() # and FixCallerAddresses(), and remove the remote profile dumping code in # the block above. die "--raw/http: jeprof can only dump remote profiles for --raw\n"; # dump a cpu-format profile to standard out PrintProfileData($profile); } } # Print text output sub PrintText { my $symbols = shift; my $flat = shift; my $cumulative = shift; my $line_limit = shift; my $total = TotalProfile($flat); # Which profile to sort by? my $s = $main::opt_cum ? $cumulative : $flat; my $running_sum = 0; my $lines = 0; foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b } keys(%{$cumulative})) { my $f = GetEntry($flat, $k); my $c = GetEntry($cumulative, $k); $running_sum += $f; my $sym = $k; if (exists($symbols->{$k})) { $sym = $symbols->{$k}->[0] . " " . $symbols->{$k}->[1]; if ($main::opt_addresses) { $sym = $k . " " . $sym; } } if ($f != 0 || $c != 0) { printf("%8s %6s %6s %8s %6s %s\n", Unparse($f), Percent($f, $total), Percent($running_sum, $total), Unparse($c), Percent($c, $total), $sym); } $lines++; last if ($line_limit >= 0 && $lines >= $line_limit); } } # Callgrind format has a compression for repeated function and file # names. You show the name the first time, and just use its number # subsequently. This can cut down the file to about a third or a # quarter of its uncompressed size. $key and $val are the key/value # pair that would normally be printed by callgrind; $map is a map from # value to number. sub CompressedCGName { my($key, $val, $map) = @_; my $idx = $map->{$val}; # For very short keys, providing an index hurts rather than helps. if (length($val) <= 3) { return "$key=$val\n"; } elsif (defined($idx)) { return "$key=($idx)\n"; } else { # scalar(keys $map) gives the number of items in the map. $idx = scalar(keys(%{$map})) + 1; $map->{$val} = $idx; return "$key=($idx) $val\n"; } } # Print the call graph in a way that's suiteable for callgrind. sub PrintCallgrind { my $calls = shift; my $filename; my %filename_to_index_map; my %fnname_to_index_map; if ($main::opt_interactive) { $filename = shift; print STDERR "Writing callgrind file to '$filename'.\n" } else { $filename = "&STDOUT"; } open(CG, ">$filename"); printf CG ("events: Hits\n\n"); foreach my $call ( map { $_->[0] } sort { $a->[1] cmp $b ->[1] || $a->[2] <=> $b->[2] } map { /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/; [$_, $1, $2] } keys %$calls ) { my $count = int($calls->{$call}); $call =~ /([^:]+):(\d+):([^ ]+)( -> ([^:]+):(\d+):(.+))?/; my ( $caller_file, $caller_line, $caller_function, $callee_file, $callee_line, $callee_function ) = ( $1, $2, $3, $5, $6, $7 ); # TODO(csilvers): for better compression, collect all the # caller/callee_files and functions first, before printing # anything, and only compress those referenced more than once. printf CG CompressedCGName("fl", $caller_file, \%filename_to_index_map); printf CG CompressedCGName("fn", $caller_function, \%fnname_to_index_map); if (defined $6) { printf CG CompressedCGName("cfl", $callee_file, \%filename_to_index_map); printf CG CompressedCGName("cfn", $callee_function, \%fnname_to_index_map); printf CG ("calls=$count $callee_line\n"); } printf CG ("$caller_line $count\n\n"); } } # Print disassembly for all all routines that match $main::opt_disasm sub PrintDisassembly { my $libs = shift; my $flat = shift; my $cumulative = shift; my $disasm_opts = shift; my $total = TotalProfile($flat); foreach my $lib (@{$libs}) { my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts); my $offset = AddressSub($lib->[1], $lib->[3]); foreach my $routine (sort ByName keys(%{$symbol_table})) { my $start_addr = $symbol_table->{$routine}->[0]; my $end_addr = $symbol_table->{$routine}->[1]; # See if there are any samples in this routine my $length = hex(AddressSub($end_addr, $start_addr)); my $addr = AddressAdd($start_addr, $offset); for (my $i = 0; $i < $length; $i++) { if (defined($cumulative->{$addr})) { PrintDisassembledFunction($lib->[0], $offset, $routine, $flat, $cumulative, $start_addr, $end_addr, $total); last; } $addr = AddressInc($addr); } } } } # Return reference to array of tuples of the form: # [start_address, filename, linenumber, instruction, limit_address] # E.g., # ["0x806c43d", "/foo/bar.cc", 131, "ret", "0x806c440"] sub Disassemble { my $prog = shift; my $offset = shift; my $start_addr = shift; my $end_addr = shift; my $objdump = $obj_tool_map{"objdump"}; my $cmd = ShellEscape($objdump, "-C", "-d", "-l", "--no-show-raw-insn", "--start-address=0x$start_addr", "--stop-address=0x$end_addr", $prog); open(OBJDUMP, "$cmd |") || error("$cmd: $!\n"); my @result = (); my $filename = ""; my $linenumber = -1; my $last = ["", "", "", ""]; while () { s/\r//g; # turn windows-looking lines into unix-looking lines chop; if (m|\s*([^:\s]+):(\d+)\s*$|) { # Location line of the form: # : $filename = $1; $linenumber = $2; } elsif (m/^ +([0-9a-f]+):\s*(.*)/) { # Disassembly line -- zero-extend address to full length my $addr = HexExtend($1); my $k = AddressAdd($addr, $offset); $last->[4] = $k; # Store ending address for previous instruction $last = [$k, $filename, $linenumber, $2, $end_addr]; push(@result, $last); } } close(OBJDUMP); return @result; } # The input file should contain lines of the form /proc/maps-like # output (same format as expected from the profiles) or that looks # like hex addresses (like "0xDEADBEEF"). We will parse all # /proc/maps output, and for all the hex addresses, we will output # "short" symbol names, one per line, in the same order as the input. sub PrintSymbols { my $maps_and_symbols_file = shift; # ParseLibraries expects pcs to be in a set. Fine by us... my @pclist = (); # pcs in sorted order my $pcs = {}; my $map = ""; foreach my $line (<$maps_and_symbols_file>) { $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines if ($line =~ /\b(0x[0-9a-f]+)\b/i) { push(@pclist, HexExtend($1)); $pcs->{$pclist[-1]} = 1; } else { $map .= $line; } } my $libs = ParseLibraries($main::prog, $map, $pcs); my $symbols = ExtractSymbols($libs, $pcs); foreach my $pc (@pclist) { # ->[0] is the shortname, ->[2] is the full name print(($symbols->{$pc}->[0] || "??") . "\n"); } } # For sorting functions by name sub ByName { return ShortFunctionName($a) cmp ShortFunctionName($b); } # Print source-listing for all all routines that match $list_opts sub PrintListing { my $total = shift; my $libs = shift; my $flat = shift; my $cumulative = shift; my $list_opts = shift; my $html = shift; my $output = \*STDOUT; my $fname = ""; if ($html) { # Arrange to write the output to a temporary file $fname = TempName($main::next_tmpfile, "html"); $main::next_tmpfile++; if (!open(TEMP, ">$fname")) { print STDERR "$fname: $!\n"; return; } $output = \*TEMP; print $output HtmlListingHeader(); printf $output ("
%s
Total: %s %s
\n", $main::prog, Unparse($total), Units()); } my $listed = 0; foreach my $lib (@{$libs}) { my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts); my $offset = AddressSub($lib->[1], $lib->[3]); foreach my $routine (sort ByName keys(%{$symbol_table})) { # Print if there are any samples in this routine my $start_addr = $symbol_table->{$routine}->[0]; my $end_addr = $symbol_table->{$routine}->[1]; my $length = hex(AddressSub($end_addr, $start_addr)); my $addr = AddressAdd($start_addr, $offset); for (my $i = 0; $i < $length; $i++) { if (defined($cumulative->{$addr})) { $listed += PrintSource( $lib->[0], $offset, $routine, $flat, $cumulative, $start_addr, $end_addr, $html, $output); last; } $addr = AddressInc($addr); } } } if ($html) { if ($listed > 0) { print $output HtmlListingFooter(); close($output); RunWeb($fname); } else { close($output); unlink($fname); } } } sub HtmlListingHeader { return <<'EOF'; Pprof listing EOF } sub HtmlListingFooter { return <<'EOF'; EOF } sub HtmlEscape { my $text = shift; $text =~ s/&/&/g; $text =~ s//>/g; return $text; } # Returns the indentation of the line, if it has any non-whitespace # characters. Otherwise, returns -1. sub Indentation { my $line = shift; if (m/^(\s*)\S/) { return length($1); } else { return -1; } } # If the symbol table contains inlining info, Disassemble() may tag an # instruction with a location inside an inlined function. But for # source listings, we prefer to use the location in the function we # are listing. So use MapToSymbols() to fetch full location # information for each instruction and then pick out the first # location from a location list (location list contains callers before # callees in case of inlining). # # After this routine has run, each entry in $instructions contains: # [0] start address # [1] filename for function we are listing # [2] line number for function we are listing # [3] disassembly # [4] limit address # [5] most specific filename (may be different from [1] due to inlining) # [6] most specific line number (may be different from [2] due to inlining) sub GetTopLevelLineNumbers { my ($lib, $offset, $instructions) = @_; my $pcs = []; for (my $i = 0; $i <= $#{$instructions}; $i++) { push(@{$pcs}, $instructions->[$i]->[0]); } my $symbols = {}; MapToSymbols($lib, $offset, $pcs, $symbols); for (my $i = 0; $i <= $#{$instructions}; $i++) { my $e = $instructions->[$i]; push(@{$e}, $e->[1]); push(@{$e}, $e->[2]); my $addr = $e->[0]; my $sym = $symbols->{$addr}; if (defined($sym)) { if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\d+)$/) { $e->[1] = $1; # File name $e->[2] = $2; # Line number } } } } # Print source-listing for one routine sub PrintSource { my $prog = shift; my $offset = shift; my $routine = shift; my $flat = shift; my $cumulative = shift; my $start_addr = shift; my $end_addr = shift; my $html = shift; my $output = shift; # Disassemble all instructions (just to get line numbers) my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr); GetTopLevelLineNumbers($prog, $offset, \@instructions); # Hack 1: assume that the first source file encountered in the # disassembly contains the routine my $filename = undef; for (my $i = 0; $i <= $#instructions; $i++) { if ($instructions[$i]->[2] >= 0) { $filename = $instructions[$i]->[1]; last; } } if (!defined($filename)) { print STDERR "no filename found in $routine\n"; return 0; } # Hack 2: assume that the largest line number from $filename is the # end of the procedure. This is typically safe since if P1 contains # an inlined call to P2, then P2 usually occurs earlier in the # source file. If this does not work, we might have to compute a # density profile or just print all regions we find. my $lastline = 0; for (my $i = 0; $i <= $#instructions; $i++) { my $f = $instructions[$i]->[1]; my $l = $instructions[$i]->[2]; if (($f eq $filename) && ($l > $lastline)) { $lastline = $l; } } # Hack 3: assume the first source location from "filename" is the start of # the source code. my $firstline = 1; for (my $i = 0; $i <= $#instructions; $i++) { if ($instructions[$i]->[1] eq $filename) { $firstline = $instructions[$i]->[2]; last; } } # Hack 4: Extend last line forward until its indentation is less than # the indentation we saw on $firstline my $oldlastline = $lastline; { if (!open(FILE, "<$filename")) { print STDERR "$filename: $!\n"; return 0; } my $l = 0; my $first_indentation = -1; while () { s/\r//g; # turn windows-looking lines into unix-looking lines $l++; my $indent = Indentation($_); if ($l >= $firstline) { if ($first_indentation < 0 && $indent >= 0) { $first_indentation = $indent; last if ($first_indentation == 0); } } if ($l >= $lastline && $indent >= 0) { if ($indent >= $first_indentation) { $lastline = $l+1; } else { last; } } } close(FILE); } # Assign all samples to the range $firstline,$lastline, # Hack 4: If an instruction does not occur in the range, its samples # are moved to the next instruction that occurs in the range. my $samples1 = {}; # Map from line number to flat count my $samples2 = {}; # Map from line number to cumulative count my $running1 = 0; # Unassigned flat counts my $running2 = 0; # Unassigned cumulative counts my $total1 = 0; # Total flat counts my $total2 = 0; # Total cumulative counts my %disasm = (); # Map from line number to disassembly my $running_disasm = ""; # Unassigned disassembly my $skip_marker = "---\n"; if ($html) { $skip_marker = ""; for (my $l = $firstline; $l <= $lastline; $l++) { $disasm{$l} = ""; } } my $last_dis_filename = ''; my $last_dis_linenum = -1; my $last_touched_line = -1; # To detect gaps in disassembly for a line foreach my $e (@instructions) { # Add up counts for all address that fall inside this instruction my $c1 = 0; my $c2 = 0; for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) { $c1 += GetEntry($flat, $a); $c2 += GetEntry($cumulative, $a); } if ($html) { my $dis = sprintf(" %6s %6s \t\t%8s: %s ", HtmlPrintNumber($c1), HtmlPrintNumber($c2), UnparseAddress($offset, $e->[0]), CleanDisassembly($e->[3])); # Append the most specific source line associated with this instruction if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) }; $dis = HtmlEscape($dis); my $f = $e->[5]; my $l = $e->[6]; if ($f ne $last_dis_filename) { $dis .= sprintf("%s:%d", HtmlEscape(CleanFileName($f)), $l); } elsif ($l ne $last_dis_linenum) { # De-emphasize the unchanged file name portion $dis .= sprintf("%s" . ":%d", HtmlEscape(CleanFileName($f)), $l); } else { # De-emphasize the entire location $dis .= sprintf("%s:%d", HtmlEscape(CleanFileName($f)), $l); } $last_dis_filename = $f; $last_dis_linenum = $l; $running_disasm .= $dis; $running_disasm .= "\n"; } $running1 += $c1; $running2 += $c2; $total1 += $c1; $total2 += $c2; my $file = $e->[1]; my $line = $e->[2]; if (($file eq $filename) && ($line >= $firstline) && ($line <= $lastline)) { # Assign all accumulated samples to this line AddEntry($samples1, $line, $running1); AddEntry($samples2, $line, $running2); $running1 = 0; $running2 = 0; if ($html) { if ($line != $last_touched_line && $disasm{$line} ne '') { $disasm{$line} .= "\n"; } $disasm{$line} .= $running_disasm; $running_disasm = ''; $last_touched_line = $line; } } } # Assign any leftover samples to $lastline AddEntry($samples1, $lastline, $running1); AddEntry($samples2, $lastline, $running2); if ($html) { if ($lastline != $last_touched_line && $disasm{$lastline} ne '') { $disasm{$lastline} .= "\n"; } $disasm{$lastline} .= $running_disasm; } if ($html) { printf $output ( "

%s

%s\n
\n" .
      "Total:%6s %6s (flat / cumulative %s)\n",
      HtmlEscape(ShortFunctionName($routine)),
      HtmlEscape(CleanFileName($filename)),
      Unparse($total1),
      Unparse($total2),
      Units());
  } else {
    printf $output (
      "ROUTINE ====================== %s in %s\n" .
      "%6s %6s Total %s (flat / cumulative)\n",
      ShortFunctionName($routine),
      CleanFileName($filename),
      Unparse($total1),
      Unparse($total2),
      Units());
  }
  if (!open(FILE, "<$filename")) {
    print STDERR "$filename: $!\n";
    return 0;
  }
  my $l = 0;
  while () {
    s/\r//g;         # turn windows-looking lines into unix-looking lines
    $l++;
    if ($l >= $firstline - 5 &&
        (($l <= $oldlastline + 5) || ($l <= $lastline))) {
      chop;
      my $text = $_;
      if ($l == $firstline) { print $output $skip_marker; }
      my $n1 = GetEntry($samples1, $l);
      my $n2 = GetEntry($samples2, $l);
      if ($html) {
        # Emit a span that has one of the following classes:
        #    livesrc -- has samples
        #    deadsrc -- has disassembly, but with no samples
        #    nop     -- has no matching disasembly
        # Also emit an optional span containing disassembly.
        my $dis = $disasm{$l};
        my $asm = "";
        if (defined($dis) && $dis ne '') {
          $asm = "" . $dis . "";
        }
        my $source_class = (($n1 + $n2 > 0)
                            ? "livesrc"
                            : (($asm ne "") ? "deadsrc" : "nop"));
        printf $output (
          "%5d " .
          "%6s %6s %s%s\n",
          $l, $source_class,
          HtmlPrintNumber($n1),
          HtmlPrintNumber($n2),
          HtmlEscape($text),
          $asm);
      } else {
        printf $output(
          "%6s %6s %4d: %s\n",
          UnparseAlt($n1),
          UnparseAlt($n2),
          $l,
          $text);
      }
      if ($l == $lastline)  { print $output $skip_marker; }
    };
  }
  close(FILE);
  if ($html) {
    print $output "
\n"; } return 1; } # Return the source line for the specified file/linenumber. # Returns undef if not found. sub SourceLine { my $file = shift; my $line = shift; # Look in cache if (!defined($main::source_cache{$file})) { if (100 < scalar keys(%main::source_cache)) { # Clear the cache when it gets too big $main::source_cache = (); } # Read all lines from the file if (!open(FILE, "<$file")) { print STDERR "$file: $!\n"; $main::source_cache{$file} = []; # Cache the negative result return undef; } my $lines = []; push(@{$lines}, ""); # So we can use 1-based line numbers as indices while () { push(@{$lines}, $_); } close(FILE); # Save the lines in the cache $main::source_cache{$file} = $lines; } my $lines = $main::source_cache{$file}; if (($line < 0) || ($line > $#{$lines})) { return undef; } else { return $lines->[$line]; } } # Print disassembly for one routine with interspersed source if available sub PrintDisassembledFunction { my $prog = shift; my $offset = shift; my $routine = shift; my $flat = shift; my $cumulative = shift; my $start_addr = shift; my $end_addr = shift; my $total = shift; # Disassemble all instructions my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr); # Make array of counts per instruction my @flat_count = (); my @cum_count = (); my $flat_total = 0; my $cum_total = 0; foreach my $e (@instructions) { # Add up counts for all address that fall inside this instruction my $c1 = 0; my $c2 = 0; for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) { $c1 += GetEntry($flat, $a); $c2 += GetEntry($cumulative, $a); } push(@flat_count, $c1); push(@cum_count, $c2); $flat_total += $c1; $cum_total += $c2; } # Print header with total counts printf("ROUTINE ====================== %s\n" . "%6s %6s %s (flat, cumulative) %.1f%% of total\n", ShortFunctionName($routine), Unparse($flat_total), Unparse($cum_total), Units(), ($cum_total * 100.0) / $total); # Process instructions in order my $current_file = ""; for (my $i = 0; $i <= $#instructions; ) { my $e = $instructions[$i]; # Print the new file name whenever we switch files if ($e->[1] ne $current_file) { $current_file = $e->[1]; my $fname = $current_file; $fname =~ s|^\./||; # Trim leading "./" # Shorten long file names if (length($fname) >= 58) { $fname = "..." . substr($fname, -55); } printf("-------------------- %s\n", $fname); } # TODO: Compute range of lines to print together to deal with # small reorderings. my $first_line = $e->[2]; my $last_line = $first_line; my %flat_sum = (); my %cum_sum = (); for (my $l = $first_line; $l <= $last_line; $l++) { $flat_sum{$l} = 0; $cum_sum{$l} = 0; } # Find run of instructions for this range of source lines my $first_inst = $i; while (($i <= $#instructions) && ($instructions[$i]->[2] >= $first_line) && ($instructions[$i]->[2] <= $last_line)) { $e = $instructions[$i]; $flat_sum{$e->[2]} += $flat_count[$i]; $cum_sum{$e->[2]} += $cum_count[$i]; $i++; } my $last_inst = $i - 1; # Print source lines for (my $l = $first_line; $l <= $last_line; $l++) { my $line = SourceLine($current_file, $l); if (!defined($line)) { $line = "?\n"; next; } else { $line =~ s/^\s+//; } printf("%6s %6s %5d: %s", UnparseAlt($flat_sum{$l}), UnparseAlt($cum_sum{$l}), $l, $line); } # Print disassembly for (my $x = $first_inst; $x <= $last_inst; $x++) { my $e = $instructions[$x]; printf("%6s %6s %8s: %6s\n", UnparseAlt($flat_count[$x]), UnparseAlt($cum_count[$x]), UnparseAddress($offset, $e->[0]), CleanDisassembly($e->[3])); } } } # Print DOT graph sub PrintDot { my $prog = shift; my $symbols = shift; my $raw = shift; my $flat = shift; my $cumulative = shift; my $overall_total = shift; # Get total my $local_total = TotalProfile($flat); my $nodelimit = int($main::opt_nodefraction * $local_total); my $edgelimit = int($main::opt_edgefraction * $local_total); my $nodecount = $main::opt_nodecount; # Find nodes to include my @list = (sort { abs(GetEntry($cumulative, $b)) <=> abs(GetEntry($cumulative, $a)) || $a cmp $b } keys(%{$cumulative})); my $last = $nodecount - 1; if ($last > $#list) { $last = $#list; } while (($last >= 0) && (abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) { $last--; } if ($last < 0) { print STDERR "No nodes to print\n"; return 0; } if ($nodelimit > 0 || $edgelimit > 0) { printf STDERR ("Dropping nodes with <= %s %s; edges with <= %s abs(%s)\n", Unparse($nodelimit), Units(), Unparse($edgelimit), Units()); } # Open DOT output file my $output; my $escaped_dot = ShellEscape(@DOT); my $escaped_ps2pdf = ShellEscape(@PS2PDF); if ($main::opt_gv) { my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "ps")); $output = "| $escaped_dot -Tps2 >$escaped_outfile"; } elsif ($main::opt_evince) { my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "pdf")); $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile"; } elsif ($main::opt_ps) { $output = "| $escaped_dot -Tps2"; } elsif ($main::opt_pdf) { $output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - -"; } elsif ($main::opt_web || $main::opt_svg) { # We need to post-process the SVG, so write to a temporary file always. my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "svg")); $output = "| $escaped_dot -Tsvg >$escaped_outfile"; } elsif ($main::opt_gif) { $output = "| $escaped_dot -Tgif"; } else { $output = ">&STDOUT"; } open(DOT, $output) || error("$output: $!\n"); # Title printf DOT ("digraph \"%s; %s %s\" {\n", $prog, Unparse($overall_total), Units()); if ($main::opt_pdf) { # The output is more printable if we set the page size for dot. printf DOT ("size=\"8,11\"\n"); } printf DOT ("node [width=0.375,height=0.25];\n"); # Print legend printf DOT ("Legend [shape=box,fontsize=24,shape=plaintext," . "label=\"%s\\l%s\\l%s\\l%s\\l%s\\l\"];\n", $prog, sprintf("Total %s: %s", Units(), Unparse($overall_total)), sprintf("Focusing on: %s", Unparse($local_total)), sprintf("Dropped nodes with <= %s abs(%s)", Unparse($nodelimit), Units()), sprintf("Dropped edges with <= %s %s", Unparse($edgelimit), Units()) ); # Print nodes my %node = (); my $nextnode = 1; foreach my $a (@list[0..$last]) { # Pick font size my $f = GetEntry($flat, $a); my $c = GetEntry($cumulative, $a); my $fs = 8; if ($local_total > 0) { $fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total))); } $node{$a} = $nextnode++; my $sym = $a; $sym =~ s/\s+/\\n/g; $sym =~ s/::/\\n/g; # Extra cumulative info to print for non-leaves my $extra = ""; if ($f != $c) { $extra = sprintf("\\rof %s (%s)", Unparse($c), Percent($c, $local_total)); } my $style = ""; if ($main::opt_heapcheck) { if ($f > 0) { # make leak-causing nodes more visible (add a background) $style = ",style=filled,fillcolor=gray" } elsif ($f < 0) { # make anti-leak-causing nodes (which almost never occur) # stand out as well (triple border) $style = ",peripheries=3" } } printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" . "\",shape=box,fontsize=%.1f%s];\n", $node{$a}, $sym, Unparse($f), Percent($f, $local_total), $extra, $fs, $style, ); } # Get edges and counts per edge my %edge = (); my $n; my $fullname_to_shortname_map = {}; FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map); foreach my $k (keys(%{$raw})) { # TODO: omit low %age edges $n = $raw->{$k}; my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k); for (my $i = 1; $i <= $#translated; $i++) { my $src = $translated[$i]; my $dst = $translated[$i-1]; #next if ($src eq $dst); # Avoid self-edges? if (exists($node{$src}) && exists($node{$dst})) { my $edge_label = "$src\001$dst"; if (!exists($edge{$edge_label})) { $edge{$edge_label} = 0; } $edge{$edge_label} += $n; } } } # Print edges (process in order of decreasing counts) my %indegree = (); # Number of incoming edges added per node so far my %outdegree = (); # Number of outgoing edges added per node so far foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) { my @x = split(/\001/, $e); $n = $edge{$e}; # Initialize degree of kept incoming and outgoing edges if necessary my $src = $x[0]; my $dst = $x[1]; if (!exists($outdegree{$src})) { $outdegree{$src} = 0; } if (!exists($indegree{$dst})) { $indegree{$dst} = 0; } my $keep; if ($indegree{$dst} == 0) { # Keep edge if needed for reachability $keep = 1; } elsif (abs($n) <= $edgelimit) { # Drop if we are below --edgefraction $keep = 0; } elsif ($outdegree{$src} >= $main::opt_maxdegree || $indegree{$dst} >= $main::opt_maxdegree) { # Keep limited number of in/out edges per node $keep = 0; } else { $keep = 1; } if ($keep) { $outdegree{$src}++; $indegree{$dst}++; # Compute line width based on edge count my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0); if ($fraction > 1) { $fraction = 1; } my $w = $fraction * 2; if ($w < 1 && ($main::opt_web || $main::opt_svg)) { # SVG output treats line widths < 1 poorly. $w = 1; } # Dot sometimes segfaults if given edge weights that are too large, so # we cap the weights at a large value my $edgeweight = abs($n) ** 0.7; if ($edgeweight > 100000) { $edgeweight = 100000; } $edgeweight = int($edgeweight); my $style = sprintf("setlinewidth(%f)", $w); if ($x[1] =~ m/\(inline\)/) { $style .= ",dashed"; } # Use a slightly squashed function of the edge count as the weight printf DOT ("N%s -> N%s [label=%s, weight=%d, style=\"%s\"];\n", $node{$x[0]}, $node{$x[1]}, Unparse($n), $edgeweight, $style); } } print DOT ("}\n"); close(DOT); if ($main::opt_web || $main::opt_svg) { # Rewrite SVG to be more usable inside web browser. RewriteSvg(TempName($main::next_tmpfile, "svg")); } return 1; } sub RewriteSvg { my $svgfile = shift; open(SVG, $svgfile) || die "open temp svg: $!"; my @svg = ; close(SVG); unlink $svgfile; my $svg = join('', @svg); # Dot's SVG output is # # # # ... # # # # Change it to # # # $svg_javascript # # # ... # # # # Fix width, height; drop viewBox. $svg =~ s/(?s) above first my $svg_javascript = SvgJavascript(); my $viewport = "\n"; $svg =~ s/ above . $svg =~ s/(.*)(<\/svg>)/$1<\/g>$2/; $svg =~ s/$svgfile") || die "open $svgfile: $!"; print SVG $svg; close(SVG); } } sub SvgJavascript { return <<'EOF'; EOF } # Provides a map from fullname to shortname for cases where the # shortname is ambiguous. The symlist has both the fullname and # shortname for all symbols, which is usually fine, but sometimes -- # such as overloaded functions -- two different fullnames can map to # the same shortname. In that case, we use the address of the # function to disambiguate the two. This function fills in a map that # maps fullnames to modified shortnames in such cases. If a fullname # is not present in the map, the 'normal' shortname provided by the # symlist is the appropriate one to use. sub FillFullnameToShortnameMap { my $symbols = shift; my $fullname_to_shortname_map = shift; my $shortnames_seen_once = {}; my $shortnames_seen_more_than_once = {}; foreach my $symlist (values(%{$symbols})) { # TODO(csilvers): deal with inlined symbols too. my $shortname = $symlist->[0]; my $fullname = $symlist->[2]; if ($fullname !~ /<[0-9a-fA-F]+>$/) { # fullname doesn't end in an address next; # the only collisions we care about are when addresses differ } if (defined($shortnames_seen_once->{$shortname}) && $shortnames_seen_once->{$shortname} ne $fullname) { $shortnames_seen_more_than_once->{$shortname} = 1; } else { $shortnames_seen_once->{$shortname} = $fullname; } } foreach my $symlist (values(%{$symbols})) { my $shortname = $symlist->[0]; my $fullname = $symlist->[2]; # TODO(csilvers): take in a list of addresses we care about, and only # store in the map if $symlist->[1] is in that list. Saves space. next if defined($fullname_to_shortname_map->{$fullname}); if (defined($shortnames_seen_more_than_once->{$shortname})) { if ($fullname =~ /<0*([^>]*)>$/) { # fullname has address at end of it $fullname_to_shortname_map->{$fullname} = "$shortname\@$1"; } } } } # Return a small number that identifies the argument. # Multiple calls with the same argument will return the same number. # Calls with different arguments will return different numbers. sub ShortIdFor { my $key = shift; my $id = $main::uniqueid{$key}; if (!defined($id)) { $id = keys(%main::uniqueid) + 1; $main::uniqueid{$key} = $id; } return $id; } # Translate a stack of addresses into a stack of symbols sub TranslateStack { my $symbols = shift; my $fullname_to_shortname_map = shift; my $k = shift; my @addrs = split(/\n/, $k); my @result = (); for (my $i = 0; $i <= $#addrs; $i++) { my $a = $addrs[$i]; # Skip large addresses since they sometimes show up as fake entries on RH9 if (length($a) > 8 && $a gt "7fffffffffffffff") { next; } if ($main::opt_disasm || $main::opt_list) { # We want just the address for the key push(@result, $a); next; } my $symlist = $symbols->{$a}; if (!defined($symlist)) { $symlist = [$a, "", $a]; } # We can have a sequence of symbols for a particular entry # (more than one symbol in the case of inlining). Callers # come before callees in symlist, so walk backwards since # the translated stack should contain callees before callers. for (my $j = $#{$symlist}; $j >= 2; $j -= 3) { my $func = $symlist->[$j-2]; my $fileline = $symlist->[$j-1]; my $fullfunc = $symlist->[$j]; if (defined($fullname_to_shortname_map->{$fullfunc})) { $func = $fullname_to_shortname_map->{$fullfunc}; } if ($j > 2) { $func = "$func (inline)"; } # Do not merge nodes corresponding to Callback::Run since that # causes confusing cycles in dot display. Instead, we synthesize # a unique name for this frame per caller. if ($func =~ m/Callback.*::Run$/) { my $caller = ($i > 0) ? $addrs[$i-1] : 0; $func = "Run#" . ShortIdFor($caller); } if ($main::opt_addresses) { push(@result, "$a $func $fileline"); } elsif ($main::opt_lines) { if ($func eq '??' && $fileline eq '??:0') { push(@result, "$a"); } else { push(@result, "$func $fileline"); } } elsif ($main::opt_functions) { if ($func eq '??') { push(@result, "$a"); } else { push(@result, $func); } } elsif ($main::opt_files) { if ($fileline eq '??:0' || $fileline eq '') { push(@result, "$a"); } else { my $f = $fileline; $f =~ s/:\d+$//; push(@result, $f); } } else { push(@result, $a); last; # Do not print inlined info } } } # print join(",", @addrs), " => ", join(",", @result), "\n"; return @result; } # Generate percent string for a number and a total sub Percent { my $num = shift; my $tot = shift; if ($tot != 0) { return sprintf("%.1f%%", $num * 100.0 / $tot); } else { return ($num == 0) ? "nan" : (($num > 0) ? "+inf" : "-inf"); } } # Generate pretty-printed form of number sub Unparse { my $num = shift; if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') { if ($main::opt_inuse_objects || $main::opt_alloc_objects) { return sprintf("%d", $num); } else { if ($main::opt_show_bytes) { return sprintf("%d", $num); } else { return sprintf("%.1f", $num / 1048576.0); } } } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) { return sprintf("%.3f", $num / 1e9); # Convert nanoseconds to seconds } else { return sprintf("%d", $num); } } # Alternate pretty-printed form: 0 maps to "." sub UnparseAlt { my $num = shift; if ($num == 0) { return "."; } else { return Unparse($num); } } # Alternate pretty-printed form: 0 maps to "" sub HtmlPrintNumber { my $num = shift; if ($num == 0) { return ""; } else { return Unparse($num); } } # Return output units sub Units { if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') { if ($main::opt_inuse_objects || $main::opt_alloc_objects) { return "objects"; } else { if ($main::opt_show_bytes) { return "B"; } else { return "MB"; } } } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) { return "seconds"; } else { return "samples"; } } ##### Profile manipulation code ##### # Generate flattened profile: # If count is charged to stack [a,b,c,d], in generated profile, # it will be charged to [a] sub FlatProfile { my $profile = shift; my $result = {}; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); if ($#addrs >= 0) { AddEntry($result, $addrs[0], $count); } } return $result; } # Generate cumulative profile: # If count is charged to stack [a,b,c,d], in generated profile, # it will be charged to [a], [b], [c], [d] sub CumulativeProfile { my $profile = shift; my $result = {}; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); foreach my $a (@addrs) { AddEntry($result, $a, $count); } } return $result; } # If the second-youngest PC on the stack is always the same, returns # that pc. Otherwise, returns undef. sub IsSecondPcAlwaysTheSame { my $profile = shift; my $second_pc = undef; foreach my $k (keys(%{$profile})) { my @addrs = split(/\n/, $k); if ($#addrs < 1) { return undef; } if (not defined $second_pc) { $second_pc = $addrs[1]; } else { if ($second_pc ne $addrs[1]) { return undef; } } } return $second_pc; } sub ExtractSymbolNameInlineStack { my $symbols = shift; my $address = shift; my @stack = (); if (exists $symbols->{$address}) { my @localinlinestack = @{$symbols->{$address}}; for (my $i = $#localinlinestack; $i > 0; $i-=3) { my $file = $localinlinestack[$i-1]; my $fn = $localinlinestack[$i-0]; if ($file eq "?" || $file eq ":0") { $file = "??:0"; } if ($fn eq '??') { # If we can't get the symbol name, at least use the file information. $fn = $file; } my $suffix = "[inline]"; if ($i == 2) { $suffix = ""; } push (@stack, $fn.$suffix); } } else { # If we can't get a symbol name, at least fill in the address. push (@stack, $address); } return @stack; } sub ExtractSymbolLocation { my $symbols = shift; my $address = shift; # 'addr2line' outputs "??:0" for unknown locations; we do the # same to be consistent. my $location = "??:0:unknown"; if (exists $symbols->{$address}) { my $file = $symbols->{$address}->[1]; if ($file eq "?") { $file = "??:0" } $location = $file . ":" . $symbols->{$address}->[0]; } return $location; } # Extracts a graph of calls. sub ExtractCalls { my $symbols = shift; my $profile = shift; my $calls = {}; while( my ($stack_trace, $count) = each %$profile ) { my @address = split(/\n/, $stack_trace); my $destination = ExtractSymbolLocation($symbols, $address[0]); AddEntry($calls, $destination, $count); for (my $i = 1; $i <= $#address; $i++) { my $source = ExtractSymbolLocation($symbols, $address[$i]); my $call = "$source -> $destination"; AddEntry($calls, $call, $count); $destination = $source; } } return $calls; } sub FilterFrames { my $symbols = shift; my $profile = shift; if ($main::opt_retain eq '' && $main::opt_exclude eq '') { return $profile; } my $result = {}; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); my @path = (); foreach my $a (@addrs) { my $sym; if (exists($symbols->{$a})) { $sym = $symbols->{$a}->[0]; } else { $sym = $a; } if ($main::opt_retain ne '' && $sym !~ m/$main::opt_retain/) { next; } if ($main::opt_exclude ne '' && $sym =~ m/$main::opt_exclude/) { next; } push(@path, $a); } if (scalar(@path) > 0) { my $reduced_path = join("\n", @path); AddEntry($result, $reduced_path, $count); } } return $result; } sub PrintCollapsedStacks { my $symbols = shift; my $profile = shift; while (my ($stack_trace, $count) = each %$profile) { my @address = split(/\n/, $stack_trace); my @names = reverse ( map { ExtractSymbolNameInlineStack($symbols, $_) } @address ); printf("%s %d\n", join(";", @names), $count); } } sub RemoveUninterestingFrames { my $symbols = shift; my $profile = shift; # List of function names to skip my %skip = (); my $skip_regexp = 'NOMATCH'; if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') { foreach my $name ('@JEMALLOC_PREFIX@calloc', 'cfree', '@JEMALLOC_PREFIX@malloc', 'newImpl', 'void* newImpl', '@JEMALLOC_PREFIX@free', '@JEMALLOC_PREFIX@memalign', '@JEMALLOC_PREFIX@posix_memalign', '@JEMALLOC_PREFIX@aligned_alloc', 'pvalloc', '@JEMALLOC_PREFIX@valloc', '@JEMALLOC_PREFIX@realloc', '@JEMALLOC_PREFIX@mallocx', '@JEMALLOC_PREFIX@rallocx', '@JEMALLOC_PREFIX@xallocx', '@JEMALLOC_PREFIX@dallocx', '@JEMALLOC_PREFIX@sdallocx', '@JEMALLOC_PREFIX@sdallocx_noflags', 'tc_calloc', 'tc_cfree', 'tc_malloc', 'tc_free', 'tc_memalign', 'tc_posix_memalign', 'tc_pvalloc', 'tc_valloc', 'tc_realloc', 'tc_new', 'tc_delete', 'tc_newarray', 'tc_deletearray', 'tc_new_nothrow', 'tc_newarray_nothrow', 'do_malloc', '::do_malloc', # new name -- got moved to an unnamed ns '::do_malloc_or_cpp_alloc', 'DoSampledAllocation', 'simple_alloc::allocate', '__malloc_alloc_template::allocate', '__builtin_delete', '__builtin_new', '__builtin_vec_delete', '__builtin_vec_new', 'operator new', 'operator new[]', # The entry to our memory-allocation routines on OS X 'malloc_zone_malloc', 'malloc_zone_calloc', 'malloc_zone_valloc', 'malloc_zone_realloc', 'malloc_zone_memalign', 'malloc_zone_free', # These mark the beginning/end of our custom sections '__start_google_malloc', '__stop_google_malloc', '__start_malloc_hook', '__stop_malloc_hook') { $skip{$name} = 1; $skip{"_" . $name} = 1; # Mach (OS X) adds a _ prefix to everything } # TODO: Remove TCMalloc once everything has been # moved into the tcmalloc:: namespace and we have flushed # old code out of the system. $skip_regexp = "TCMalloc|^tcmalloc::"; } elsif ($main::profile_type eq 'contention') { foreach my $vname ('base::RecordLockProfileData', 'base::SubmitMutexProfileData', 'base::SubmitSpinLockProfileData', 'Mutex::Unlock', 'Mutex::UnlockSlow', 'Mutex::ReaderUnlock', 'MutexLock::~MutexLock', 'SpinLock::Unlock', 'SpinLock::SlowUnlock', 'SpinLockHolder::~SpinLockHolder') { $skip{$vname} = 1; } } elsif ($main::profile_type eq 'cpu') { # Drop signal handlers used for CPU profile collection # TODO(dpeng): this should not be necessary; it's taken # care of by the general 2nd-pc mechanism below. foreach my $name ('ProfileData::Add', # historical 'ProfileData::prof_handler', # historical 'CpuProfiler::prof_handler', '__FRAME_END__', '__pthread_sighandler', '__restore') { $skip{$name} = 1; } } else { # Nothing skipped for unknown types } if ($main::profile_type eq 'cpu') { # If all the second-youngest program counters are the same, # this STRONGLY suggests that it is an artifact of measurement, # i.e., stack frames pushed by the CPU profiler signal handler. # Hence, we delete them. # (The topmost PC is read from the signal structure, not from # the stack, so it does not get involved.) while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) { my $result = {}; my $func = ''; if (exists($symbols->{$second_pc})) { $second_pc = $symbols->{$second_pc}->[0]; } print STDERR "Removing $second_pc from all stack traces.\n"; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); splice @addrs, 1, 1; my $reduced_path = join("\n", @addrs); AddEntry($result, $reduced_path, $count); } $profile = $result; } } my $result = {}; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); my @path = (); foreach my $a (@addrs) { if (exists($symbols->{$a})) { my $func = $symbols->{$a}->[0]; if ($skip{$func} || ($func =~ m/$skip_regexp/)) { # Throw away the portion of the backtrace seen so far, under the # assumption that previous frames were for functions internal to the # allocator. @path = (); next; } } push(@path, $a); } my $reduced_path = join("\n", @path); AddEntry($result, $reduced_path, $count); } $result = FilterFrames($symbols, $result); return $result; } # Reduce profile to granularity given by user sub ReduceProfile { my $symbols = shift; my $profile = shift; my $result = {}; my $fullname_to_shortname_map = {}; FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map); foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k); my @path = (); my %seen = (); $seen{''} = 1; # So that empty keys are skipped foreach my $e (@translated) { # To avoid double-counting due to recursion, skip a stack-trace # entry if it has already been seen if (!$seen{$e}) { $seen{$e} = 1; push(@path, $e); } } my $reduced_path = join("\n", @path); AddEntry($result, $reduced_path, $count); } return $result; } # Does the specified symbol array match the regexp? sub SymbolMatches { my $sym = shift; my $re = shift; if (defined($sym)) { for (my $i = 0; $i < $#{$sym}; $i += 3) { if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) { return 1; } } } return 0; } # Focus only on paths involving specified regexps sub FocusProfile { my $symbols = shift; my $profile = shift; my $focus = shift; my $result = {}; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); foreach my $a (@addrs) { # Reply if it matches either the address/shortname/fileline if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) { AddEntry($result, $k, $count); last; } } } return $result; } # Focus only on paths not involving specified regexps sub IgnoreProfile { my $symbols = shift; my $profile = shift; my $ignore = shift; my $result = {}; foreach my $k (keys(%{$profile})) { my $count = $profile->{$k}; my @addrs = split(/\n/, $k); my $matched = 0; foreach my $a (@addrs) { # Reply if it matches either the address/shortname/fileline if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) { $matched = 1; last; } } if (!$matched) { AddEntry($result, $k, $count); } } return $result; } # Get total count in profile sub TotalProfile { my $profile = shift; my $result = 0; foreach my $k (keys(%{$profile})) { $result += $profile->{$k}; } return $result; } # Add A to B sub AddProfile { my $A = shift; my $B = shift; my $R = {}; # add all keys in A foreach my $k (keys(%{$A})) { my $v = $A->{$k}; AddEntry($R, $k, $v); } # add all keys in B foreach my $k (keys(%{$B})) { my $v = $B->{$k}; AddEntry($R, $k, $v); } return $R; } # Merges symbol maps sub MergeSymbols { my $A = shift; my $B = shift; my $R = {}; foreach my $k (keys(%{$A})) { $R->{$k} = $A->{$k}; } if (defined($B)) { foreach my $k (keys(%{$B})) { $R->{$k} = $B->{$k}; } } return $R; } # Add A to B sub AddPcs { my $A = shift; my $B = shift; my $R = {}; # add all keys in A foreach my $k (keys(%{$A})) { $R->{$k} = 1 } # add all keys in B foreach my $k (keys(%{$B})) { $R->{$k} = 1 } return $R; } # Subtract B from A sub SubtractProfile { my $A = shift; my $B = shift; my $R = {}; foreach my $k (keys(%{$A})) { my $v = $A->{$k} - GetEntry($B, $k); if ($v < 0 && $main::opt_drop_negative) { $v = 0; } AddEntry($R, $k, $v); } if (!$main::opt_drop_negative) { # Take care of when subtracted profile has more entries foreach my $k (keys(%{$B})) { if (!exists($A->{$k})) { AddEntry($R, $k, 0 - $B->{$k}); } } } return $R; } # Get entry from profile; zero if not present sub GetEntry { my $profile = shift; my $k = shift; if (exists($profile->{$k})) { return $profile->{$k}; } else { return 0; } } # Add entry to specified profile sub AddEntry { my $profile = shift; my $k = shift; my $n = shift; if (!exists($profile->{$k})) { $profile->{$k} = 0; } $profile->{$k} += $n; } # Add a stack of entries to specified profile, and add them to the $pcs # list. sub AddEntries { my $profile = shift; my $pcs = shift; my $stack = shift; my $count = shift; my @k = (); foreach my $e (split(/\s+/, $stack)) { my $pc = HexExtend($e); $pcs->{$pc} = 1; push @k, $pc; } AddEntry($profile, (join "\n", @k), $count); } ##### Code to profile a server dynamically ##### sub CheckSymbolPage { my $url = SymbolPageURL(); my $command = ShellEscape(@URL_FETCHER, $url); open(SYMBOL, "$command |") or error($command); my $line = ; $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines close(SYMBOL); unless (defined($line)) { error("$url doesn't exist\n"); } if ($line =~ /^num_symbols:\s+(\d+)$/) { if ($1 == 0) { error("Stripped binary. No symbols available.\n"); } } else { error("Failed to get the number of symbols from $url\n"); } } sub IsProfileURL { my $profile_name = shift; if (-f $profile_name) { printf STDERR "Using local file $profile_name.\n"; return 0; } return 1; } sub ParseProfileURL { my $profile_name = shift; if (!defined($profile_name) || $profile_name eq "") { return (); } # Split profile URL - matches all non-empty strings, so no test. $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,; my $proto = $1 || "http://"; my $hostport = $2; my $prefix = $3; my $profile = $4 || "/"; my $host = $hostport; $host =~ s/:.*//; my $baseurl = "$proto$hostport$prefix"; return ($host, $baseurl, $profile); } # We fetch symbols from the first profile argument. sub SymbolPageURL { my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]); return "$baseURL$SYMBOL_PAGE"; } sub FetchProgramName() { my ($host, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]); my $url = "$baseURL$PROGRAM_NAME_PAGE"; my $command_line = ShellEscape(@URL_FETCHER, $url); open(CMDLINE, "$command_line |") or error($command_line); my $cmdline = ; $cmdline =~ s/\r//g; # turn windows-looking lines into unix-looking lines close(CMDLINE); error("Failed to get program name from $url\n") unless defined($cmdline); $cmdline =~ s/\x00.+//; # Remove argv[1] and latters. $cmdline =~ s!\n!!g; # Remove LFs. return $cmdline; } # Gee, curl's -L (--location) option isn't reliable at least # with its 7.12.3 version. Curl will forget to post data if # there is a redirection. This function is a workaround for # curl. Redirection happens on borg hosts. sub ResolveRedirectionForCurl { my $url = shift; my $command_line = ShellEscape(@URL_FETCHER, "--head", $url); open(CMDLINE, "$command_line |") or error($command_line); while () { s/\r//g; # turn windows-looking lines into unix-looking lines if (/^Location: (.*)/) { $url = $1; } } close(CMDLINE); return $url; } # Add a timeout flat to URL_FETCHER. Returns a new list. sub AddFetchTimeout { my $timeout = shift; my @fetcher = @_; if (defined($timeout)) { if (join(" ", @fetcher) =~ m/\bcurl -s/) { push(@fetcher, "--max-time", sprintf("%d", $timeout)); } elsif (join(" ", @fetcher) =~ m/\brpcget\b/) { push(@fetcher, sprintf("--deadline=%d", $timeout)); } } return @fetcher; } # Reads a symbol map from the file handle name given as $1, returning # the resulting symbol map. Also processes variables relating to symbols. # Currently, the only variable processed is 'binary=' which updates # $main::prog to have the correct program name. sub ReadSymbols { my $in = shift; my $map = {}; while (<$in>) { s/\r//g; # turn windows-looking lines into unix-looking lines # Removes all the leading zeroes from the symbols, see comment below. if (m/^0x0*([0-9a-f]+)\s+(.+)/) { $map->{$1} = $2; } elsif (m/^---/) { last; } elsif (m/^([a-z][^=]*)=(.*)$/ ) { my ($variable, $value) = ($1, $2); for ($variable, $value) { s/^\s+//; s/\s+$//; } if ($variable eq "binary") { if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) { printf STDERR ("Warning: Mismatched binary name '%s', using '%s'.\n", $main::prog, $value); } $main::prog = $value; } else { printf STDERR ("Ignoring unknown variable in symbols list: " . "'%s' = '%s'\n", $variable, $value); } } } return $map; } sub URLEncode { my $str = shift; $str =~ s/([^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%02x", ord $1 /eg; return $str; } sub AppendSymbolFilterParams { my $url = shift; my @params = (); if ($main::opt_retain ne '') { push(@params, sprintf("retain=%s", URLEncode($main::opt_retain))); } if ($main::opt_exclude ne '') { push(@params, sprintf("exclude=%s", URLEncode($main::opt_exclude))); } if (scalar @params > 0) { $url = sprintf("%s?%s", $url, join("&", @params)); } return $url; } # Fetches and processes symbols to prepare them for use in the profile output # code. If the optional 'symbol_map' arg is not given, fetches symbols from # $SYMBOL_PAGE for all PC values found in profile. Otherwise, the raw symbols # are assumed to have already been fetched into 'symbol_map' and are simply # extracted and processed. sub FetchSymbols { my $pcset = shift; my $symbol_map = shift; my %seen = (); my @pcs = grep { !$seen{$_}++ } keys(%$pcset); # uniq if (!defined($symbol_map)) { my $post_data = join("+", sort((map {"0x" . "$_"} @pcs))); open(POSTFILE, ">$main::tmpfile_sym"); print POSTFILE $post_data; close(POSTFILE); my $url = SymbolPageURL(); my $command_line; if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) { $url = ResolveRedirectionForCurl($url); $url = AppendSymbolFilterParams($url); $command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym", $url); } else { $url = AppendSymbolFilterParams($url); $command_line = (ShellEscape(@URL_FETCHER, "--post", $url) . " < " . ShellEscape($main::tmpfile_sym)); } # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols. my $escaped_cppfilt = ShellEscape($obj_tool_map{"c++filt"}); open(SYMBOL, "$command_line | $escaped_cppfilt |") or error($command_line); $symbol_map = ReadSymbols(*SYMBOL{IO}); close(SYMBOL); } my $symbols = {}; foreach my $pc (@pcs) { my $fullname; # For 64 bits binaries, symbols are extracted with 8 leading zeroes. # Then /symbol reads the long symbols in as uint64, and outputs # the result with a "0x%08llx" format which get rid of the zeroes. # By removing all the leading zeroes in both $pc and the symbols from # /symbol, the symbols match and are retrievable from the map. my $shortpc = $pc; $shortpc =~ s/^0*//; # Each line may have a list of names, which includes the function # and also other functions it has inlined. They are separated (in # PrintSymbolizedProfile), by --, which is illegal in function names. my $fullnames; if (defined($symbol_map->{$shortpc})) { $fullnames = $symbol_map->{$shortpc}; } else { $fullnames = "0x" . $pc; # Just use addresses } my $sym = []; $symbols->{$pc} = $sym; foreach my $fullname (split("--", $fullnames)) { my $name = ShortFunctionName($fullname); push(@{$sym}, $name, "?", $fullname); } } return $symbols; } sub BaseName { my $file_name = shift; $file_name =~ s!^.*/!!; # Remove directory name return $file_name; } sub MakeProfileBaseName { my ($binary_name, $profile_name) = @_; my ($host, $baseURL, $path) = ParseProfileURL($profile_name); my $binary_shortname = BaseName($binary_name); return sprintf("%s.%s.%s", $binary_shortname, $main::op_time, $host); } sub FetchDynamicProfile { my $binary_name = shift; my $profile_name = shift; my $fetch_name_only = shift; my $encourage_patience = shift; if (!IsProfileURL($profile_name)) { return $profile_name; } else { my ($host, $baseURL, $path) = ParseProfileURL($profile_name); if ($path eq "" || $path eq "/") { # Missing type specifier defaults to cpu-profile $path = $PROFILE_PAGE; } my $profile_file = MakeProfileBaseName($binary_name, $profile_name); my $url = "$baseURL$path"; my $fetch_timeout = undef; if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) { if ($path =~ m/[?]/) { $url .= "&"; } else { $url .= "?"; } $url .= sprintf("seconds=%d", $main::opt_seconds); $fetch_timeout = $main::opt_seconds * 1.01 + 60; # Set $profile_type for consumption by PrintSymbolizedProfile. $main::profile_type = 'cpu'; } else { # For non-CPU profiles, we add a type-extension to # the target profile file name. my $suffix = $path; $suffix =~ s,/,.,g; $profile_file .= $suffix; # Set $profile_type for consumption by PrintSymbolizedProfile. if ($path =~ m/$HEAP_PAGE/) { $main::profile_type = 'heap'; } elsif ($path =~ m/$GROWTH_PAGE/) { $main::profile_type = 'growth'; } elsif ($path =~ m/$CONTENTION_PAGE/) { $main::profile_type = 'contention'; } } my $profile_dir = $ENV{"JEPROF_TMPDIR"} || ($ENV{HOME} . "/jeprof"); if (! -d $profile_dir) { mkdir($profile_dir) || die("Unable to create profile directory $profile_dir: $!\n"); } my $tmp_profile = "$profile_dir/.tmp.$profile_file"; my $real_profile = "$profile_dir/$profile_file"; if ($fetch_name_only > 0) { return $real_profile; } my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER); my $cmd = ShellEscape(@fetcher, $url) . " > " . ShellEscape($tmp_profile); if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){ print STDERR "Gathering CPU profile from $url for $main::opt_seconds seconds to\n ${real_profile}\n"; if ($encourage_patience) { print STDERR "Be patient...\n"; } } else { print STDERR "Fetching $path profile from $url to\n ${real_profile}\n"; } (system($cmd) == 0) || error("Failed to get profile: $cmd: $!\n"); (system("mv", $tmp_profile, $real_profile) == 0) || error("Unable to rename profile\n"); print STDERR "Wrote profile to $real_profile\n"; $main::collected_profile = $real_profile; return $main::collected_profile; } } # Collect profiles in parallel sub FetchDynamicProfiles { my $items = scalar(@main::pfile_args); my $levels = log($items) / log(2); if ($items == 1) { $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1); } else { # math rounding issues if ((2 ** $levels) < $items) { $levels++; } my $count = scalar(@main::pfile_args); for (my $i = 0; $i < $count; $i++) { $main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0); } print STDERR "Fetching $count profiles, Be patient...\n"; FetchDynamicProfilesRecurse($levels, 0, 0); $main::collected_profile = join(" \\\n ", @main::profile_files); } } # Recursively fork a process to get enough processes # collecting profiles sub FetchDynamicProfilesRecurse { my $maxlevel = shift; my $level = shift; my $position = shift; if (my $pid = fork()) { $position = 0 | ($position << 1); TryCollectProfile($maxlevel, $level, $position); wait; } else { $position = 1 | ($position << 1); TryCollectProfile($maxlevel, $level, $position); cleanup(); exit(0); } } # Collect a single profile sub TryCollectProfile { my $maxlevel = shift; my $level = shift; my $position = shift; if ($level >= ($maxlevel - 1)) { if ($position < scalar(@main::pfile_args)) { FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0); } } else { FetchDynamicProfilesRecurse($maxlevel, $level+1, $position); } } ##### Parsing code ##### # Provide a small streaming-read module to handle very large # cpu-profile files. Stream in chunks along a sliding window. # Provides an interface to get one 'slot', correctly handling # endian-ness differences. A slot is one 32-bit or 64-bit word # (depending on the input profile). We tell endianness and bit-size # for the profile by looking at the first 8 bytes: in cpu profiles, # the second slot is always 3 (we'll accept anything that's not 0). BEGIN { package CpuProfileStream; sub new { my ($class, $file, $fname) = @_; my $self = { file => $file, base => 0, stride => 512 * 1024, # must be a multiple of bitsize/8 slots => [], unpack_code => "", # N for big-endian, V for little perl_is_64bit => 1, # matters if profile is 64-bit }; bless $self, $class; # Let unittests adjust the stride if ($main::opt_test_stride > 0) { $self->{stride} = $main::opt_test_stride; } # Read the first two slots to figure out bitsize and endianness. my $slots = $self->{slots}; my $str; read($self->{file}, $str, 8); # Set the global $address_length based on what we see here. # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars). $address_length = ($str eq (chr(0)x8)) ? 16 : 8; if ($address_length == 8) { if (substr($str, 6, 2) eq chr(0)x2) { $self->{unpack_code} = 'V'; # Little-endian. } elsif (substr($str, 4, 2) eq chr(0)x2) { $self->{unpack_code} = 'N'; # Big-endian } else { ::error("$fname: header size >= 2**16\n"); } @$slots = unpack($self->{unpack_code} . "*", $str); } else { # If we're a 64-bit profile, check if we're a 64-bit-capable # perl. Otherwise, each slot will be represented as a float # instead of an int64, losing precision and making all the # 64-bit addresses wrong. We won't complain yet, but will # later if we ever see a value that doesn't fit in 32 bits. my $has_q = 0; eval { $has_q = pack("Q", "1") ? 1 : 1; }; if (!$has_q) { $self->{perl_is_64bit} = 0; } read($self->{file}, $str, 8); if (substr($str, 4, 4) eq chr(0)x4) { # We'd love to use 'Q', but it's a) not universal, b) not endian-proof. $self->{unpack_code} = 'V'; # Little-endian. } elsif (substr($str, 0, 4) eq chr(0)x4) { $self->{unpack_code} = 'N'; # Big-endian } else { ::error("$fname: header size >= 2**32\n"); } my @pair = unpack($self->{unpack_code} . "*", $str); # Since we know one of the pair is 0, it's fine to just add them. @$slots = (0, $pair[0] + $pair[1]); } return $self; } # Load more data when we access slots->get(X) which is not yet in memory. sub overflow { my ($self) = @_; my $slots = $self->{slots}; $self->{base} += $#$slots + 1; # skip over data we're replacing my $str; read($self->{file}, $str, $self->{stride}); if ($address_length == 8) { # the 32-bit case # This is the easy case: unpack provides 32-bit unpacking primitives. @$slots = unpack($self->{unpack_code} . "*", $str); } else { # We need to unpack 32 bits at a time and combine. my @b32_values = unpack($self->{unpack_code} . "*", $str); my @b64_values = (); for (my $i = 0; $i < $#b32_values; $i += 2) { # TODO(csilvers): if this is a 32-bit perl, the math below # could end up in a too-large int, which perl will promote # to a double, losing necessary precision. Deal with that. # Right now, we just die. my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]); if ($self->{unpack_code} eq 'N') { # big-endian ($lo, $hi) = ($hi, $lo); } my $value = $lo + $hi * (2**32); if (!$self->{perl_is_64bit} && # check value is exactly represented (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) { ::error("Need a 64-bit perl to process this 64-bit profile.\n"); } push(@b64_values, $value); } @$slots = @b64_values; } } # Access the i-th long in the file (logically), or -1 at EOF. sub get { my ($self, $idx) = @_; my $slots = $self->{slots}; while ($#$slots >= 0) { if ($idx < $self->{base}) { # The only time we expect a reference to $slots[$i - something] # after referencing $slots[$i] is reading the very first header. # Since $stride > |header|, that shouldn't cause any lookback # errors. And everything after the header is sequential. print STDERR "Unexpected look-back reading CPU profile"; return -1; # shrug, don't know what better to return } elsif ($idx > $self->{base} + $#$slots) { $self->overflow(); } else { return $slots->[$idx - $self->{base}]; } } # If we get here, $slots is [], which means we've reached EOF return -1; # unique since slots is supposed to hold unsigned numbers } } # Reads the top, 'header' section of a profile, and returns the last # line of the header, commonly called a 'header line'. The header # section of a profile consists of zero or more 'command' lines that # are instructions to jeprof, which jeprof executes when reading the # header. All 'command' lines start with a %. After the command # lines is the 'header line', which is a profile-specific line that # indicates what type of profile it is, and perhaps other global # information about the profile. For instance, here's a header line # for a heap profile: # heap profile: 53: 38236 [ 5525: 1284029] @ heapprofile # For historical reasons, the CPU profile does not contain a text- # readable header line. If the profile looks like a CPU profile, # this function returns "". If no header line could be found, this # function returns undef. # # The following commands are recognized: # %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:' # # The input file should be in binmode. sub ReadProfileHeader { local *PROFILE = shift; my $firstchar = ""; my $line = ""; read(PROFILE, $firstchar, 1); seek(PROFILE, -1, 1); # unread the firstchar if ($firstchar !~ /[[:print:]]/) { # is not a text character return ""; } while (defined($line = )) { $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines if ($line =~ /^%warn\s+(.*)/) { # 'warn' command # Note this matches both '%warn blah\n' and '%warn\n'. print STDERR "WARNING: $1\n"; # print the rest of the line } elsif ($line =~ /^%/) { print STDERR "Ignoring unknown command from profile header: $line"; } else { # End of commands, must be the header line. return $line; } } return undef; # got to EOF without seeing a header line } sub IsSymbolizedProfileFile { my $file_name = shift; if (!(-e $file_name) || !(-r $file_name)) { return 0; } # Check if the file contains a symbol-section marker. open(TFILE, "<$file_name"); binmode TFILE; my $firstline = ReadProfileHeader(*TFILE); close(TFILE); if (!$firstline) { return 0; } $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $symbol_marker = $&; return $firstline =~ /^--- *$symbol_marker/; } # Parse profile generated by common/profiler.cc and return a reference # to a map: # $result->{version} Version number of profile file # $result->{period} Sampling period (in microseconds) # $result->{profile} Profile object # $result->{threads} Map of thread IDs to profile objects # $result->{map} Memory map info from profile # $result->{pcs} Hash of all PC values seen, key is hex address sub ReadProfile { my $prog = shift; my $fname = shift; my $result; # return value $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $contention_marker = $&; $GROWTH_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $growth_marker = $&; $SYMBOL_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $symbol_marker = $&; $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $profile_marker = $&; $HEAP_PAGE =~ m,[^/]+$,; # matches everything after the last slash my $heap_marker = $&; # Look at first line to see if it is a heap or a CPU profile. # CPU profile may start with no header at all, and just binary data # (starting with \0\0\0\0) -- in that case, don't try to read the # whole firstline, since it may be gigabytes(!) of data. open(PROFILE, "<$fname") || error("$fname: $!\n"); binmode PROFILE; # New perls do UTF-8 processing my $header = ReadProfileHeader(*PROFILE); if (!defined($header)) { # means "at EOF" error("Profile is empty.\n"); } my $symbols; if ($header =~ m/^--- *$symbol_marker/o) { # Verify that the user asked for a symbolized profile if (!$main::use_symbolized_profile) { # we have both a binary and symbolized profiles, abort error("FATAL ERROR: Symbolized profile\n $fname\ncannot be used with " . "a binary arg. Try again without passing\n $prog\n"); } # Read the symbol section of the symbolized profile file. $symbols = ReadSymbols(*PROFILE{IO}); # Read the next line to get the header for the remaining profile. $header = ReadProfileHeader(*PROFILE) || ""; } if ($header =~ m/^--- *($heap_marker|$growth_marker)/o) { # Skip "--- ..." line for profile types that have their own headers. $header = ReadProfileHeader(*PROFILE) || ""; } $main::profile_type = ''; if ($header =~ m/^heap profile:.*$growth_marker/o) { $main::profile_type = 'growth'; $result = ReadHeapProfile($prog, *PROFILE, $header); } elsif ($header =~ m/^heap profile:/) { $main::profile_type = 'heap'; $result = ReadHeapProfile($prog, *PROFILE, $header); } elsif ($header =~ m/^heap/) { $main::profile_type = 'heap'; $result = ReadThreadedHeapProfile($prog, $fname, $header); } elsif ($header =~ m/^--- *$contention_marker/o) { $main::profile_type = 'contention'; $result = ReadSynchProfile($prog, *PROFILE); } elsif ($header =~ m/^--- *Stacks:/) { print STDERR "Old format contention profile: mistakenly reports " . "condition variable signals as lock contentions.\n"; $main::profile_type = 'contention'; $result = ReadSynchProfile($prog, *PROFILE); } elsif ($header =~ m/^--- *$profile_marker/) { # the binary cpu profile data starts immediately after this line $main::profile_type = 'cpu'; $result = ReadCPUProfile($prog, $fname, *PROFILE); } else { if (defined($symbols)) { # a symbolized profile contains a format we don't recognize, bail out error("$fname: Cannot recognize profile section after symbols.\n"); } # no ascii header present -- must be a CPU profile $main::profile_type = 'cpu'; $result = ReadCPUProfile($prog, $fname, *PROFILE); } close(PROFILE); # if we got symbols along with the profile, return those as well if (defined($symbols)) { $result->{symbols} = $symbols; } return $result; } # Subtract one from caller pc so we map back to call instr. # However, don't do this if we're reading a symbolized profile # file, in which case the subtract-one was done when the file # was written. # # We apply the same logic to all readers, though ReadCPUProfile uses an # independent implementation. sub FixCallerAddresses { my $stack = shift; # --raw/http: Always subtract one from pc's, because PrintSymbolizedProfile() # dumps unadjusted profiles. { $stack =~ /(\s)/; my $delimiter = $1; my @addrs = split(' ', $stack); my @fixedaddrs; $#fixedaddrs = $#addrs; if ($#addrs >= 0) { $fixedaddrs[0] = $addrs[0]; } for (my $i = 1; $i <= $#addrs; $i++) { $fixedaddrs[$i] = AddressSub($addrs[$i], "0x1"); } return join $delimiter, @fixedaddrs; } } # CPU profile reader sub ReadCPUProfile { my $prog = shift; my $fname = shift; # just used for logging local *PROFILE = shift; my $version; my $period; my $i; my $profile = {}; my $pcs = {}; # Parse string into array of slots. my $slots = CpuProfileStream->new(*PROFILE, $fname); # Read header. The current header version is a 5-element structure # containing: # 0: header count (always 0) # 1: header "words" (after this one: 3) # 2: format version (0) # 3: sampling period (usec) # 4: unused padding (always 0) if ($slots->get(0) != 0 ) { error("$fname: not a profile file, or old format profile file\n"); } $i = 2 + $slots->get(1); $version = $slots->get(2); $period = $slots->get(3); # Do some sanity checking on these header values. if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) { error("$fname: not a profile file, or corrupted profile file\n"); } # Parse profile while ($slots->get($i) != -1) { my $n = $slots->get($i++); my $d = $slots->get($i++); if ($d > (2**16)) { # TODO(csilvers): what's a reasonable max-stack-depth? my $addr = sprintf("0%o", $i * ($address_length == 8 ? 4 : 8)); print STDERR "At index $i (address $addr):\n"; error("$fname: stack trace depth >= 2**32\n"); } if ($slots->get($i) == 0) { # End of profile data marker $i += $d; last; } # Make key out of the stack entries my @k = (); for (my $j = 0; $j < $d; $j++) { my $pc = $slots->get($i+$j); # Subtract one from caller pc so we map back to call instr. $pc--; $pc = sprintf("%0*x", $address_length, $pc); $pcs->{$pc} = 1; push @k, $pc; } AddEntry($profile, (join "\n", @k), $n); $i += $d; } # Parse map my $map = ''; seek(PROFILE, $i * 4, 0); read(PROFILE, $map, (stat PROFILE)[7]); my $r = {}; $r->{version} = $version; $r->{period} = $period; $r->{profile} = $profile; $r->{libs} = ParseLibraries($prog, $map, $pcs); $r->{pcs} = $pcs; return $r; } sub HeapProfileIndex { my $index = 1; if ($main::opt_inuse_space) { $index = 1; } elsif ($main::opt_inuse_objects) { $index = 0; } elsif ($main::opt_alloc_space) { $index = 3; } elsif ($main::opt_alloc_objects) { $index = 2; } return $index; } sub ReadMappedLibraries { my $fh = shift; my $map = ""; # Read the /proc/self/maps data while (<$fh>) { s/\r//g; # turn windows-looking lines into unix-looking lines $map .= $_; } return $map; } sub ReadMemoryMap { my $fh = shift; my $map = ""; # Read /proc/self/maps data as formatted by DumpAddressMap() my $buildvar = ""; while () { s/\r//g; # turn windows-looking lines into unix-looking lines # Parse "build=" specification if supplied if (m/^\s*build=(.*)\n/) { $buildvar = $1; } # Expand "$build" variable if available $_ =~ s/\$build\b/$buildvar/g; $map .= $_; } return $map; } sub AdjustSamples { my ($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2) = @_; if ($sample_adjustment) { if ($sampling_algorithm == 2) { # Remote-heap version 2 # The sampling frequency is the rate of a Poisson process. # This means that the probability of sampling an allocation of # size X with sampling rate Y is 1 - exp(-X/Y) if ($n1 != 0) { my $ratio = (($s1*1.0)/$n1)/($sample_adjustment); my $scale_factor = 1/(1 - exp(-$ratio)); $n1 *= $scale_factor; $s1 *= $scale_factor; } if ($n2 != 0) { my $ratio = (($s2*1.0)/$n2)/($sample_adjustment); my $scale_factor = 1/(1 - exp(-$ratio)); $n2 *= $scale_factor; $s2 *= $scale_factor; } } else { # Remote-heap version 1 my $ratio; $ratio = (($s1*1.0)/$n1)/($sample_adjustment); if ($ratio < 1) { $n1 /= $ratio; $s1 /= $ratio; } $ratio = (($s2*1.0)/$n2)/($sample_adjustment); if ($ratio < 1) { $n2 /= $ratio; $s2 /= $ratio; } } } return ($n1, $s1, $n2, $s2); } sub ReadHeapProfile { my $prog = shift; local *PROFILE = shift; my $header = shift; my $index = HeapProfileIndex(); # Find the type of this profile. The header line looks like: # heap profile: 1246: 8800744 [ 1246: 8800744] @ /266053 # There are two pairs , the first inuse objects/space, and the # second allocated objects/space. This is followed optionally by a profile # type, and if that is present, optionally by a sampling frequency. # For remote heap profiles (v1): # The interpretation of the sampling frequency is that the profiler, for # each sample, calculates a uniformly distributed random integer less than # the given value, and records the next sample after that many bytes have # been allocated. Therefore, the expected sample interval is half of the # given frequency. By default, if not specified, the expected sample # interval is 128KB. Only remote-heap-page profiles are adjusted for # sample size. # For remote heap profiles (v2): # The sampling frequency is the rate of a Poisson process. This means that # the probability of sampling an allocation of size X with sampling rate Y # is 1 - exp(-X/Y) # For version 2, a typical header line might look like this: # heap profile: 1922: 127792360 [ 1922: 127792360] @ _v2/524288 # the trailing number (524288) is the sampling rate. (Version 1 showed # double the 'rate' here) my $sampling_algorithm = 0; my $sample_adjustment = 0; chomp($header); my $type = "unknown"; if ($header =~ m"^heap profile:\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\](\s*@\s*([^/]*)(/(\d+))?)?") { if (defined($6) && ($6 ne '')) { $type = $6; my $sample_period = $8; # $type is "heapprofile" for profiles generated by the # heap-profiler, and either "heap" or "heap_v2" for profiles # generated by sampling directly within tcmalloc. It can also # be "growth" for heap-growth profiles. The first is typically # found for profiles generated locally, and the others for # remote profiles. if (($type eq "heapprofile") || ($type !~ /heap/) ) { # No need to adjust for the sampling rate with heap-profiler-derived data $sampling_algorithm = 0; } elsif ($type =~ /_v2/) { $sampling_algorithm = 2; # version 2 sampling if (defined($sample_period) && ($sample_period ne '')) { $sample_adjustment = int($sample_period); } } else { $sampling_algorithm = 1; # version 1 sampling if (defined($sample_period) && ($sample_period ne '')) { $sample_adjustment = int($sample_period)/2; } } } else { # We detect whether or not this is a remote-heap profile by checking # that the total-allocated stats ($n2,$s2) are exactly the # same as the in-use stats ($n1,$s1). It is remotely conceivable # that a non-remote-heap profile may pass this check, but it is hard # to imagine how that could happen. # In this case it's so old it's guaranteed to be remote-heap version 1. my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4); if (($n1 == $n2) && ($s1 == $s2)) { # This is likely to be a remote-heap based sample profile $sampling_algorithm = 1; } } } if ($sampling_algorithm > 0) { # For remote-heap generated profiles, adjust the counts and sizes to # account for the sample rate (we sample once every 128KB by default). if ($sample_adjustment == 0) { # Turn on profile adjustment. $sample_adjustment = 128*1024; print STDERR "Adjusting heap profiles for 1-in-128KB sampling rate\n"; } else { printf STDERR ("Adjusting heap profiles for 1-in-%d sampling rate\n", $sample_adjustment); } if ($sampling_algorithm > 1) { # We don't bother printing anything for the original version (version 1) printf STDERR "Heap version $sampling_algorithm\n"; } } my $profile = {}; my $pcs = {}; my $map = ""; while () { s/\r//g; # turn windows-looking lines into unix-looking lines if (/^MAPPED_LIBRARIES:/) { $map .= ReadMappedLibraries(*PROFILE); last; } if (/^--- Memory map:/) { $map .= ReadMemoryMap(*PROFILE); last; } # Read entry of the form: # : [: ] @ a1 a2 a3 ... an s/^\s*//; s/\s*$//; if (m/^\s*(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]\s+@\s+(.*)$/) { my $stack = $5; my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4); my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2); AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]); } } my $r = {}; $r->{version} = "heap"; $r->{period} = 1; $r->{profile} = $profile; $r->{libs} = ParseLibraries($prog, $map, $pcs); $r->{pcs} = $pcs; return $r; } sub ReadThreadedHeapProfile { my ($prog, $fname, $header) = @_; my $index = HeapProfileIndex(); my $sampling_algorithm = 0; my $sample_adjustment = 0; chomp($header); my $type = "unknown"; # Assuming a very specific type of header for now. if ($header =~ m"^heap_v2/(\d+)") { $type = "_v2"; $sampling_algorithm = 2; $sample_adjustment = int($1); } if ($type ne "_v2" || !defined($sample_adjustment)) { die "Threaded heap profiles require v2 sampling with a sample rate\n"; } my $profile = {}; my $thread_profiles = {}; my $pcs = {}; my $map = ""; my $stack = ""; while () { s/\r//g; if (/^MAPPED_LIBRARIES:/) { $map .= ReadMappedLibraries(*PROFILE); last; } if (/^--- Memory map:/) { $map .= ReadMemoryMap(*PROFILE); last; } # Read entry of the form: # @ a1 a2 ... an # t*: : [: ] # t1: : [: ] # ... # tn: : [: ] s/^\s*//; s/\s*$//; if (m/^@\s+(.*)$/) { $stack = $1; } elsif (m/^\s*(t(\*|\d+)):\s+(\d+):\s+(\d+)\s+\[\s*(\d+):\s+(\d+)\]$/) { if ($stack eq "") { # Still in the header, so this is just a per-thread summary. next; } my $thread = $2; my ($n1, $s1, $n2, $s2) = ($3, $4, $5, $6); my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2); if ($thread eq "*") { AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]); } else { if (!exists($thread_profiles->{$thread})) { $thread_profiles->{$thread} = {}; } AddEntries($thread_profiles->{$thread}, $pcs, FixCallerAddresses($stack), $counts[$index]); } } } my $r = {}; $r->{version} = "heap"; $r->{period} = 1; $r->{profile} = $profile; $r->{threads} = $thread_profiles; $r->{libs} = ParseLibraries($prog, $map, $pcs); $r->{pcs} = $pcs; return $r; } sub ReadSynchProfile { my $prog = shift; local *PROFILE = shift; my $header = shift; my $map = ''; my $profile = {}; my $pcs = {}; my $sampling_period = 1; my $cyclespernanosec = 2.8; # Default assumption for old binaries my $seen_clockrate = 0; my $line; my $index = 0; if ($main::opt_total_delay) { $index = 0; } elsif ($main::opt_contentions) { $index = 1; } elsif ($main::opt_mean_delay) { $index = 2; } while ( $line = ) { $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines if ( $line =~ /^\s*(\d+)\s+(\d+) \@\s*(.*?)\s*$/ ) { my ($cycles, $count, $stack) = ($1, $2, $3); # Convert cycles to nanoseconds $cycles /= $cyclespernanosec; # Adjust for sampling done by application $cycles *= $sampling_period; $count *= $sampling_period; my @values = ($cycles, $count, $cycles / $count); AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]); } elsif ( $line =~ /^(slow release).*thread \d+ \@\s*(.*?)\s*$/ || $line =~ /^\s*(\d+) \@\s*(.*?)\s*$/ ) { my ($cycles, $stack) = ($1, $2); if ($cycles !~ /^\d+$/) { next; } # Convert cycles to nanoseconds $cycles /= $cyclespernanosec; # Adjust for sampling done by application $cycles *= $sampling_period; AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles); } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) { my ($variable, $value) = ($1,$2); for ($variable, $value) { s/^\s+//; s/\s+$//; } if ($variable eq "cycles/second") { $cyclespernanosec = $value / 1e9; $seen_clockrate = 1; } elsif ($variable eq "sampling period") { $sampling_period = $value; } elsif ($variable eq "ms since reset") { # Currently nothing is done with this value in jeprof # So we just silently ignore it for now } elsif ($variable eq "discarded samples") { # Currently nothing is done with this value in jeprof # So we just silently ignore it for now } else { printf STDERR ("Ignoring unnknown variable in /contention output: " . "'%s' = '%s'\n",$variable,$value); } } else { # Memory map entry $map .= $line; } } if (!$seen_clockrate) { printf STDERR ("No cycles/second entry in profile; Guessing %.1f GHz\n", $cyclespernanosec); } my $r = {}; $r->{version} = 0; $r->{period} = $sampling_period; $r->{profile} = $profile; $r->{libs} = ParseLibraries($prog, $map, $pcs); $r->{pcs} = $pcs; return $r; } # Given a hex value in the form "0x1abcd" or "1abcd", return either # "0001abcd" or "000000000001abcd", depending on the current (global) # address length. sub HexExtend { my $addr = shift; $addr =~ s/^(0x)?0*//; my $zeros_needed = $address_length - length($addr); if ($zeros_needed < 0) { printf STDERR "Warning: address $addr is longer than address length $address_length\n"; return $addr; } return ("0" x $zeros_needed) . $addr; } ##### Symbol extraction ##### # Aggressively search the lib_prefix values for the given library # If all else fails, just return the name of the library unmodified. # If the lib_prefix is "/my/path,/other/path" and $file is "/lib/dir/mylib.so" # it will search the following locations in this order, until it finds a file: # /my/path/lib/dir/mylib.so # /other/path/lib/dir/mylib.so # /my/path/dir/mylib.so # /other/path/dir/mylib.so # /my/path/mylib.so # /other/path/mylib.so # /lib/dir/mylib.so (returned as last resort) sub FindLibrary { my $file = shift; my $suffix = $file; # Search for the library as described above do { foreach my $prefix (@prefix_list) { my $fullpath = $prefix . $suffix; if (-e $fullpath) { return $fullpath; } } } while ($suffix =~ s|^/[^/]+/|/|); return $file; } # Return path to library with debugging symbols. # For libc libraries, the copy in /usr/lib/debug contains debugging symbols sub DebuggingLibrary { my $file = shift; if ($file !~ m|^/|) { return undef; } # Find debug symbol file if it's named after the library's name. if (-f "/usr/lib/debug$file") { if($main::opt_debug) { print STDERR "found debug info for $file in /usr/lib/debug$file\n"; } return "/usr/lib/debug$file"; } elsif (-f "/usr/lib/debug$file.debug") { if($main::opt_debug) { print STDERR "found debug info for $file in /usr/lib/debug$file.debug\n"; } return "/usr/lib/debug$file.debug"; } if(!$main::opt_debug_syms_by_id) { if($main::opt_debug) { print STDERR "no debug symbols found for $file\n" }; return undef; } # Find debug file if it's named after the library's build ID. my $readelf = ''; if (!$main::gave_up_on_elfutils) { $readelf = qx/eu-readelf -n ${file}/; if ($?) { print STDERR "Cannot run eu-readelf. To use --debug-syms-by-id you must be on Linux, with elfutils installed.\n"; $main::gave_up_on_elfutils = 1; return undef; } my $buildID = $1 if $readelf =~ /Build ID: ([A-Fa-f0-9]+)/s; if (defined $buildID && length $buildID > 0) { my $symbolFile = '/usr/lib/debug/.build-id/' . substr($buildID, 0, 2) . '/' . substr($buildID, 2) . '.debug'; if (-e $symbolFile) { if($main::opt_debug) { print STDERR "found debug symbol file $symbolFile for $file\n" }; return $symbolFile; } else { if($main::opt_debug) { print STDERR "no debug symbol file found for $file, build ID: $buildID\n" }; return undef; } } } if($main::opt_debug) { print STDERR "no debug symbols found for $file, build ID unknown\n" }; return undef; } # Parse text section header of a library using objdump sub ParseTextSectionHeaderFromObjdump { my $lib = shift; my $size = undef; my $vma; my $file_offset; # Get objdump output from the library file to figure out how to # map between mapped addresses and addresses in the library. my $cmd = ShellEscape($obj_tool_map{"objdump"}, "-h", $lib); open(OBJDUMP, "$cmd |") || error("$cmd: $!\n"); while () { s/\r//g; # turn windows-looking lines into unix-looking lines # Idx Name Size VMA LMA File off Algn # 10 .text 00104b2c 420156f0 420156f0 000156f0 2**4 # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file # offset may still be 8. But AddressSub below will still handle that. my @x = split; if (($#x >= 6) && ($x[1] eq '.text')) { $size = $x[2]; $vma = $x[3]; $file_offset = $x[5]; last; } } close(OBJDUMP); if (!defined($size)) { return undef; } my $r = {}; $r->{size} = $size; $r->{vma} = $vma; $r->{file_offset} = $file_offset; return $r; } # Parse text section header of a library using otool (on OS X) sub ParseTextSectionHeaderFromOtool { my $lib = shift; my $size = undef; my $vma = undef; my $file_offset = undef; # Get otool output from the library file to figure out how to # map between mapped addresses and addresses in the library. my $command = ShellEscape($obj_tool_map{"otool"}, "-l", $lib); open(OTOOL, "$command |") || error("$command: $!\n"); my $cmd = ""; my $sectname = ""; my $segname = ""; foreach my $line () { $line =~ s/\r//g; # turn windows-looking lines into unix-looking lines # Load command <#> # cmd LC_SEGMENT # [...] # Section # sectname __text # segname __TEXT # addr 0x000009f8 # size 0x00018b9e # offset 2552 # align 2^2 (4) # We will need to strip off the leading 0x from the hex addresses, # and convert the offset into hex. if ($line =~ /Load command/) { $cmd = ""; $sectname = ""; $segname = ""; } elsif ($line =~ /Section/) { $sectname = ""; $segname = ""; } elsif ($line =~ /cmd (\w+)/) { $cmd = $1; } elsif ($line =~ /sectname (\w+)/) { $sectname = $1; } elsif ($line =~ /segname (\w+)/) { $segname = $1; } elsif (!(($cmd eq "LC_SEGMENT" || $cmd eq "LC_SEGMENT_64") && $sectname eq "__text" && $segname eq "__TEXT")) { next; } elsif ($line =~ /\baddr 0x([0-9a-fA-F]+)/) { $vma = $1; } elsif ($line =~ /\bsize 0x([0-9a-fA-F]+)/) { $size = $1; } elsif ($line =~ /\boffset ([0-9]+)/) { $file_offset = sprintf("%016x", $1); } if (defined($vma) && defined($size) && defined($file_offset)) { last; } } close(OTOOL); if (!defined($vma) || !defined($size) || !defined($file_offset)) { return undef; } my $r = {}; $r->{size} = $size; $r->{vma} = $vma; $r->{file_offset} = $file_offset; return $r; } sub ParseTextSectionHeader { # obj_tool_map("otool") is only defined if we're in a Mach-O environment if (defined($obj_tool_map{"otool"})) { my $r = ParseTextSectionHeaderFromOtool(@_); if (defined($r)){ return $r; } } # If otool doesn't work, or we don't have it, fall back to objdump return ParseTextSectionHeaderFromObjdump(@_); } # Split /proc/pid/maps dump into a list of libraries sub ParseLibraries { return if $main::use_symbol_page; # We don't need libraries info. my $prog = Cwd::abs_path(shift); my $map = shift; my $pcs = shift; my $result = []; my $h = "[a-f0-9]+"; my $zero_offset = HexExtend("0"); my $buildvar = ""; foreach my $l (split("\n", $map)) { if ($l =~ m/^\s*build=(.*)$/) { $buildvar = $1; } my $start; my $finish; my $offset; my $lib; if ($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+\.(so|dll|dylib|bundle)((\.\d+)+\w*(\.\d+){0,3})?)$/i) { # Full line from /proc/self/maps. Example: # 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so $start = HexExtend($1); $finish = HexExtend($2); $offset = HexExtend($3); $lib = $4; $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths } elsif ($l =~ /^\s*($h)-($h):\s*(\S+\.so(\.\d+)*)/) { # Cooked line from DumpAddressMap. Example: # 40000000-40015000: /lib/ld-2.3.2.so $start = HexExtend($1); $finish = HexExtend($2); $offset = $zero_offset; $lib = $3; } elsif (($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+)$/i) && ($4 eq $prog)) { # PIEs and address space randomization do not play well with our # default assumption that main executable is at lowest # addresses. So we're detecting main executable in # /proc/self/maps as well. $start = HexExtend($1); $finish = HexExtend($2); $offset = HexExtend($3); $lib = $4; $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths } # FreeBSD 10.0 virtual memory map /proc/curproc/map as defined in # function procfs_doprocmap (sys/fs/procfs/procfs_map.c) # # Example: # 0x800600000 0x80061a000 26 0 0xfffff800035a0000 r-x 75 33 0x1004 COW NC vnode /libexec/ld-elf.s # o.1 NCH -1 elsif ($l =~ /^(0x$h)\s(0x$h)\s\d+\s\d+\s0x$h\sr-x\s\d+\s\d+\s0x\d+\s(COW|NCO)\s(NC|NNC)\svnode\s(\S+\.so(\.\d+)*)/) { $start = HexExtend($1); $finish = HexExtend($2); $offset = $zero_offset; $lib = FindLibrary($5); } else { next; } # Expand "$build" variable if available $lib =~ s/\$build\b/$buildvar/g; $lib = FindLibrary($lib); # Check for pre-relocated libraries, which use pre-relocated symbol tables # and thus require adjusting the offset that we'll use to translate # VM addresses into symbol table addresses. # Only do this if we're not going to fetch the symbol table from a # debugging copy of the library. if (!DebuggingLibrary($lib)) { my $text = ParseTextSectionHeader($lib); if (defined($text)) { my $vma_offset = AddressSub($text->{vma}, $text->{file_offset}); $offset = AddressAdd($offset, $vma_offset); } } if($main::opt_debug) { printf STDERR "$start:$finish ($offset) $lib\n"; } push(@{$result}, [$lib, $start, $finish, $offset]); } # Append special entry for additional library (not relocated) if ($main::opt_lib ne "") { my $text = ParseTextSectionHeader($main::opt_lib); if (defined($text)) { my $start = $text->{vma}; my $finish = AddressAdd($start, $text->{size}); push(@{$result}, [$main::opt_lib, $start, $finish, $start]); } } # Append special entry for the main program. This covers # 0..max_pc_value_seen, so that we assume pc values not found in one # of the library ranges will be treated as coming from the main # program binary. my $min_pc = HexExtend("0"); my $max_pc = $min_pc; # find the maximal PC value in any sample foreach my $pc (keys(%{$pcs})) { if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); } } push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]); return $result; } # Add two hex addresses of length $address_length. # Run jeprof --test for unit test if this is changed. sub AddressAdd { my $addr1 = shift; my $addr2 = shift; my $sum; if ($address_length == 8) { # Perl doesn't cope with wraparound arithmetic, so do it explicitly: $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16); return sprintf("%08x", $sum); } else { # Do the addition in 7-nibble chunks to trivialize carry handling. if ($main::opt_debug and $main::opt_test) { print STDERR "AddressAdd $addr1 + $addr2 = "; } my $a1 = substr($addr1,-7); $addr1 = substr($addr1,0,-7); my $a2 = substr($addr2,-7); $addr2 = substr($addr2,0,-7); $sum = hex($a1) + hex($a2); my $c = 0; if ($sum > 0xfffffff) { $c = 1; $sum -= 0x10000000; } my $r = sprintf("%07x", $sum); $a1 = substr($addr1,-7); $addr1 = substr($addr1,0,-7); $a2 = substr($addr2,-7); $addr2 = substr($addr2,0,-7); $sum = hex($a1) + hex($a2) + $c; $c = 0; if ($sum > 0xfffffff) { $c = 1; $sum -= 0x10000000; } $r = sprintf("%07x", $sum) . $r; $sum = hex($addr1) + hex($addr2) + $c; if ($sum > 0xff) { $sum -= 0x100; } $r = sprintf("%02x", $sum) . $r; if ($main::opt_debug and $main::opt_test) { print STDERR "$r\n"; } return $r; } } # Subtract two hex addresses of length $address_length. # Run jeprof --test for unit test if this is changed. sub AddressSub { my $addr1 = shift; my $addr2 = shift; my $diff; if ($address_length == 8) { # Perl doesn't cope with wraparound arithmetic, so do it explicitly: $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16); return sprintf("%08x", $diff); } else { # Do the addition in 7-nibble chunks to trivialize borrow handling. # if ($main::opt_debug) { print STDERR "AddressSub $addr1 - $addr2 = "; } my $a1 = hex(substr($addr1,-7)); $addr1 = substr($addr1,0,-7); my $a2 = hex(substr($addr2,-7)); $addr2 = substr($addr2,0,-7); my $b = 0; if ($a2 > $a1) { $b = 1; $a1 += 0x10000000; } $diff = $a1 - $a2; my $r = sprintf("%07x", $diff); $a1 = hex(substr($addr1,-7)); $addr1 = substr($addr1,0,-7); $a2 = hex(substr($addr2,-7)) + $b; $addr2 = substr($addr2,0,-7); $b = 0; if ($a2 > $a1) { $b = 1; $a1 += 0x10000000; } $diff = $a1 - $a2; $r = sprintf("%07x", $diff) . $r; $a1 = hex($addr1); $a2 = hex($addr2) + $b; if ($a2 > $a1) { $a1 += 0x100; } $diff = $a1 - $a2; $r = sprintf("%02x", $diff) . $r; # if ($main::opt_debug) { print STDERR "$r\n"; } return $r; } } # Increment a hex addresses of length $address_length. # Run jeprof --test for unit test if this is changed. sub AddressInc { my $addr = shift; my $sum; if ($address_length == 8) { # Perl doesn't cope with wraparound arithmetic, so do it explicitly: $sum = (hex($addr)+1) % (0x10000000 * 16); return sprintf("%08x", $sum); } else { # Do the addition in 7-nibble chunks to trivialize carry handling. # We are always doing this to step through the addresses in a function, # and will almost never overflow the first chunk, so we check for this # case and exit early. # if ($main::opt_debug) { print STDERR "AddressInc $addr1 = "; } my $a1 = substr($addr,-7); $addr = substr($addr,0,-7); $sum = hex($a1) + 1; my $r = sprintf("%07x", $sum); if ($sum <= 0xfffffff) { $r = $addr . $r; # if ($main::opt_debug) { print STDERR "$r\n"; } return HexExtend($r); } else { $r = "0000000"; } $a1 = substr($addr,-7); $addr = substr($addr,0,-7); $sum = hex($a1) + 1; $r = sprintf("%07x", $sum) . $r; if ($sum <= 0xfffffff) { $r = $addr . $r; # if ($main::opt_debug) { print STDERR "$r\n"; } return HexExtend($r); } else { $r = "00000000000000"; } $sum = hex($addr) + 1; if ($sum > 0xff) { $sum -= 0x100; } $r = sprintf("%02x", $sum) . $r; # if ($main::opt_debug) { print STDERR "$r\n"; } return $r; } } # Extract symbols for all PC values found in profile sub ExtractSymbols { my $libs = shift; my $pcset = shift; my $symbols = {}; # Map each PC value to the containing library. To make this faster, # we sort libraries by their starting pc value (highest first), and # advance through the libraries as we advance the pc. Sometimes the # addresses of libraries may overlap with the addresses of the main # binary, so to make sure the libraries 'win', we iterate over the # libraries in reverse order (which assumes the binary doesn't start # in the middle of a library, which seems a fair assumption). my @pcs = (sort { $a cmp $b } keys(%{$pcset})); # pcset is 0-extended strings foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) { my $libname = $lib->[0]; my $start = $lib->[1]; my $finish = $lib->[2]; my $offset = $lib->[3]; # Use debug library if it exists my $debug_libname = DebuggingLibrary($libname); if ($debug_libname) { $libname = $debug_libname; } # Get list of pcs that belong in this library. my $contained = []; my ($start_pc_index, $finish_pc_index); # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index]. for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0; $finish_pc_index--) { last if $pcs[$finish_pc_index - 1] le $finish; } # Find smallest start_pc_index such that $start <= $pc[$start_pc_index]. for ($start_pc_index = $finish_pc_index; $start_pc_index > 0; $start_pc_index--) { last if $pcs[$start_pc_index - 1] lt $start; } # This keeps PC values higher than $pc[$finish_pc_index] in @pcs, # in case there are overlaps in libraries and the main binary. @{$contained} = splice(@pcs, $start_pc_index, $finish_pc_index - $start_pc_index); # Map to symbols MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols); } return $symbols; } # Map list of PC values to symbols for a given image sub MapToSymbols { my $image = shift; my $offset = shift; my $pclist = shift; my $symbols = shift; my $debug = 0; # Ignore empty binaries if ($#{$pclist} < 0) { return; } # Figure out the addr2line command to use my $addr2line = $obj_tool_map{"addr2line"}; my $cmd = ShellEscape($addr2line, "-f", "-C", "-e", $image); if (exists $obj_tool_map{"addr2line_pdb"}) { $addr2line = $obj_tool_map{"addr2line_pdb"}; $cmd = ShellEscape($addr2line, "--demangle", "-f", "-C", "-e", $image); } # If "addr2line" isn't installed on the system at all, just use # nm to get what info we can (function names, but not line numbers). if (system(ShellEscape($addr2line, "--help") . " >$dev_null 2>&1") != 0) { MapSymbolsWithNM($image, $offset, $pclist, $symbols); return; } # "addr2line -i" can produce a variable number of lines per input # address, with no separator that allows us to tell when data for # the next address starts. So we find the address for a special # symbol (_fini) and interleave this address between all real # addresses passed to addr2line. The name of this special symbol # can then be used as a separator. $sep_address = undef; # May be filled in by MapSymbolsWithNM() my $nm_symbols = {}; MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols); if (defined($sep_address)) { # Only add " -i" to addr2line if the binary supports it. # addr2line --help returns 0, but not if it sees an unknown flag first. if (system("$cmd -i --help >$dev_null 2>&1") == 0) { $cmd .= " -i"; } else { $sep_address = undef; # no need for sep_address if we don't support -i } } # Make file with all PC values with intervening 'sep_address' so # that we can reliably detect the end of inlined function list open(ADDRESSES, ">$main::tmpfile_sym") || error("$main::tmpfile_sym: $!\n"); if ($debug) { print("---- $image ---\n"); } for (my $i = 0; $i <= $#{$pclist}; $i++) { # addr2line always reads hex addresses, and does not need '0x' prefix. if ($debug) { printf STDERR ("%s\n", $pclist->[$i]); } printf ADDRESSES ("%s\n", AddressSub($pclist->[$i], $offset)); if (defined($sep_address)) { printf ADDRESSES ("%s\n", $sep_address); } } close(ADDRESSES); if ($debug) { print("----\n"); system("cat", $main::tmpfile_sym); print("----\n"); system("$cmd < " . ShellEscape($main::tmpfile_sym)); print("----\n"); } open(SYMBOLS, "$cmd <" . ShellEscape($main::tmpfile_sym) . " |") || error("$cmd: $!\n"); my $count = 0; # Index in pclist while () { # Read fullfunction and filelineinfo from next pair of lines s/\r?\n$//g; my $fullfunction = $_; $_ = ; s/\r?\n$//g; my $filelinenum = $_; if (defined($sep_address) && $fullfunction eq $sep_symbol) { # Terminating marker for data for this address $count++; next; } $filelinenum =~ s|\\|/|g; # turn windows-style paths into unix-style paths my $pcstr = $pclist->[$count]; my $function = ShortFunctionName($fullfunction); my $nms = $nm_symbols->{$pcstr}; if (defined($nms)) { if ($fullfunction eq '??') { # nm found a symbol for us. $function = $nms->[0]; $fullfunction = $nms->[2]; } else { # MapSymbolsWithNM tags each routine with its starting address, # useful in case the image has multiple occurrences of this # routine. (It uses a syntax that resembles template parameters, # that are automatically stripped out by ShortFunctionName().) # addr2line does not provide the same information. So we check # if nm disambiguated our symbol, and if so take the annotated # (nm) version of the routine-name. TODO(csilvers): this won't # catch overloaded, inlined symbols, which nm doesn't see. # Better would be to do a check similar to nm's, in this fn. if ($nms->[2] =~ m/^\Q$function\E/) { # sanity check it's the right fn $function = $nms->[0]; $fullfunction = $nms->[2]; } } } # Prepend to accumulated symbols for pcstr # (so that caller comes before callee) my $sym = $symbols->{$pcstr}; if (!defined($sym)) { $sym = []; $symbols->{$pcstr} = $sym; } unshift(@{$sym}, $function, $filelinenum, $fullfunction); if ($debug) { printf STDERR ("%s => [%s]\n", $pcstr, join(" ", @{$sym})); } if (!defined($sep_address)) { # Inlining is off, so this entry ends immediately $count++; } } close(SYMBOLS); } # Use nm to map the list of referenced PCs to symbols. Return true iff we # are able to read procedure information via nm. sub MapSymbolsWithNM { my $image = shift; my $offset = shift; my $pclist = shift; my $symbols = shift; # Get nm output sorted by increasing address my $symbol_table = GetProcedureBoundaries($image, "."); if (!%{$symbol_table}) { return 0; } # Start addresses are already the right length (8 or 16 hex digits). my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] } keys(%{$symbol_table}); if ($#names < 0) { # No symbols: just use addresses foreach my $pc (@{$pclist}) { my $pcstr = "0x" . $pc; $symbols->{$pc} = [$pcstr, "?", $pcstr]; } return 0; } # Sort addresses so we can do a join against nm output my $index = 0; my $fullname = $names[0]; my $name = ShortFunctionName($fullname); foreach my $pc (sort { $a cmp $b } @{$pclist}) { # Adjust for mapped offset my $mpc = AddressSub($pc, $offset); while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){ $index++; $fullname = $names[$index]; $name = ShortFunctionName($fullname); } if ($mpc lt $symbol_table->{$fullname}->[1]) { $symbols->{$pc} = [$name, "?", $fullname]; } else { my $pcstr = "0x" . $pc; $symbols->{$pc} = [$pcstr, "?", $pcstr]; } } return 1; } sub ShortFunctionName { my $function = shift; while ($function =~ s/\([^()]*\)(\s*const)?//g) { } # Argument types while ($function =~ s/<[^<>]*>//g) { } # Remove template arguments $function =~ s/^.*\s+(\w+::)/$1/; # Remove leading type return $function; } # Trim overly long symbols found in disassembler output sub CleanDisassembly { my $d = shift; while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax) while ($d =~ s/(\w+)<[^<>]*>/$1/g) { } # Remove template arguments return $d; } # Clean file name for display sub CleanFileName { my ($f) = @_; $f =~ s|^/proc/self/cwd/||; $f =~ s|^\./||; return $f; } # Make address relative to section and clean up for display sub UnparseAddress { my ($offset, $address) = @_; $address = AddressSub($address, $offset); $address =~ s/^0x//; $address =~ s/^0*//; return $address; } ##### Miscellaneous ##### # Find the right versions of the above object tools to use. The # argument is the program file being analyzed, and should be an ELF # 32-bit or ELF 64-bit executable file. The location of the tools # is determined by considering the following options in this order: # 1) --tools option, if set # 2) JEPROF_TOOLS environment variable, if set # 3) the environment sub ConfigureObjTools { my $prog_file = shift; # Check for the existence of $prog_file because /usr/bin/file does not # predictably return error status in prod. (-e $prog_file) || error("$prog_file does not exist.\n"); my $file_type = undef; if (-e "/usr/bin/file") { # Follow symlinks (at least for systems where "file" supports that). my $escaped_prog_file = ShellEscape($prog_file); $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null || /usr/bin/file $escaped_prog_file`; } elsif ($^O == "MSWin32") { $file_type = "MS Windows"; } else { print STDERR "WARNING: Can't determine the file type of $prog_file"; } if ($file_type =~ /64-bit/) { # Change $address_length to 16 if the program file is ELF 64-bit. # We can't detect this from many (most?) heap or lock contention # profiles, since the actual addresses referenced are generally in low # memory even for 64-bit programs. $address_length = 16; } if ($file_type =~ /MS Windows/) { # For windows, we provide a version of nm and addr2line as part of # the opensource release, which is capable of parsing # Windows-style PDB executables. It should live in the path, or # in the same directory as jeprof. $obj_tool_map{"nm_pdb"} = "nm-pdb"; $obj_tool_map{"addr2line_pdb"} = "addr2line-pdb"; } if ($file_type =~ /Mach-O/) { # OS X uses otool to examine Mach-O files, rather than objdump. $obj_tool_map{"otool"} = "otool"; $obj_tool_map{"addr2line"} = "false"; # no addr2line $obj_tool_map{"objdump"} = "false"; # no objdump } # Go fill in %obj_tool_map with the pathnames to use: foreach my $tool (keys %obj_tool_map) { $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool}); } } # Returns the path of a caller-specified object tool. If --tools or # JEPROF_TOOLS are specified, then returns the full path to the tool # with that prefix. Otherwise, returns the path unmodified (which # means we will look for it on PATH). sub ConfigureTool { my $tool = shift; my $path; # --tools (or $JEPROF_TOOLS) is a comma separated list, where each # item is either a) a pathname prefix, or b) a map of the form # :. First we look for an entry of type (b) for our # tool. If one is found, we use it. Otherwise, we consider all the # pathname prefixes in turn, until one yields an existing file. If # none does, we use a default path. my $tools = $main::opt_tools || $ENV{"JEPROF_TOOLS"} || ""; if ($tools =~ m/(,|^)\Q$tool\E:([^,]*)/) { $path = $2; # TODO(csilvers): sanity-check that $path exists? Hard if it's relative. } elsif ($tools ne '') { foreach my $prefix (split(',', $tools)) { next if ($prefix =~ /:/); # ignore "tool:fullpath" entries in the list if (-x $prefix . $tool) { $path = $prefix . $tool; last; } } if (!$path) { error("No '$tool' found with prefix specified by " . "--tools (or \$JEPROF_TOOLS) '$tools'\n"); } } else { # ... otherwise use the version that exists in the same directory as # jeprof. If there's nothing there, use $PATH. $0 =~ m,[^/]*$,; # this is everything after the last slash my $dirname = $`; # this is everything up to and including the last slash if (-x "$dirname$tool") { $path = "$dirname$tool"; } else { $path = $tool; } } if ($main::opt_debug) { print STDERR "Using '$path' for '$tool'.\n"; } return $path; } sub ShellEscape { my @escaped_words = (); foreach my $word (@_) { my $escaped_word = $word; if ($word =~ m![^a-zA-Z0-9/.,_=-]!) { # check for anything not in whitelist $escaped_word =~ s/'/'\\''/; $escaped_word = "'$escaped_word'"; } push(@escaped_words, $escaped_word); } return join(" ", @escaped_words); } sub cleanup { unlink($main::tmpfile_sym); unlink(keys %main::tempnames); # We leave any collected profiles in $HOME/jeprof in case the user wants # to look at them later. We print a message informing them of this. if ((scalar(@main::profile_files) > 0) && defined($main::collected_profile)) { if (scalar(@main::profile_files) == 1) { print STDERR "Dynamically gathered profile is in $main::collected_profile\n"; } print STDERR "If you want to investigate this profile further, you can do:\n"; print STDERR "\n"; print STDERR " jeprof \\\n"; print STDERR " $main::prog \\\n"; print STDERR " $main::collected_profile\n"; print STDERR "\n"; } } sub sighandler { cleanup(); exit(1); } sub error { my $msg = shift; print STDERR $msg; cleanup(); exit(1); } # Run $nm_command and get all the resulting procedure boundaries whose # names match "$regexp" and returns them in a hashtable mapping from # procedure name to a two-element vector of [start address, end address] sub GetProcedureBoundariesViaNm { my $escaped_nm_command = shift; # shell-escaped my $regexp = shift; my $symbol_table = {}; open(NM, "$escaped_nm_command |") || error("$escaped_nm_command: $!\n"); my $last_start = "0"; my $routine = ""; while () { s/\r//g; # turn windows-looking lines into unix-looking lines if (m/^\s*([0-9a-f]+) (.) (..*)/) { my $start_val = $1; my $type = $2; my $this_routine = $3; # It's possible for two symbols to share the same address, if # one is a zero-length variable (like __start_google_malloc) or # one symbol is a weak alias to another (like __libc_malloc). # In such cases, we want to ignore all values except for the # actual symbol, which in nm-speak has type "T". The logic # below does this, though it's a bit tricky: what happens when # we have a series of lines with the same address, is the first # one gets queued up to be processed. However, it won't # *actually* be processed until later, when we read a line with # a different address. That means that as long as we're reading # lines with the same address, we have a chance to replace that # item in the queue, which we do whenever we see a 'T' entry -- # that is, a line with type 'T'. If we never see a 'T' entry, # we'll just go ahead and process the first entry (which never # got touched in the queue), and ignore the others. if ($start_val eq $last_start && $type =~ /t/i) { # We are the 'T' symbol at this address, replace previous symbol. $routine = $this_routine; next; } elsif ($start_val eq $last_start) { # We're not the 'T' symbol at this address, so ignore us. next; } if ($this_routine eq $sep_symbol) { $sep_address = HexExtend($start_val); } # Tag this routine with the starting address in case the image # has multiple occurrences of this routine. We use a syntax # that resembles template parameters that are automatically # stripped out by ShortFunctionName() $this_routine .= "<$start_val>"; if (defined($routine) && $routine =~ m/$regexp/) { $symbol_table->{$routine} = [HexExtend($last_start), HexExtend($start_val)]; } $last_start = $start_val; $routine = $this_routine; } elsif (m/^Loaded image name: (.+)/) { # The win32 nm workalike emits information about the binary it is using. if ($main::opt_debug) { print STDERR "Using Image $1\n"; } } elsif (m/^PDB file name: (.+)/) { # The win32 nm workalike emits information about the pdb it is using. if ($main::opt_debug) { print STDERR "Using PDB $1\n"; } } } close(NM); # Handle the last line in the nm output. Unfortunately, we don't know # how big this last symbol is, because we don't know how big the file # is. For now, we just give it a size of 0. # TODO(csilvers): do better here. if (defined($routine) && $routine =~ m/$regexp/) { $symbol_table->{$routine} = [HexExtend($last_start), HexExtend($last_start)]; } return $symbol_table; } # Gets the procedure boundaries for all routines in "$image" whose names # match "$regexp" and returns them in a hashtable mapping from procedure # name to a two-element vector of [start address, end address]. # Will return an empty map if nm is not installed or not working properly. sub GetProcedureBoundaries { my $image = shift; my $regexp = shift; # If $image doesn't start with /, then put ./ in front of it. This works # around an obnoxious bug in our probing of nm -f behavior. # "nm -f $image" is supposed to fail on GNU nm, but if: # # a. $image starts with [BbSsPp] (for example, bin/foo/bar), AND # b. you have a.out in your current directory (a not uncommon occurrence) # # then "nm -f $image" succeeds because -f only looks at the first letter of # the argument, which looks valid because it's [BbSsPp], and then since # there's no image provided, it looks for a.out and finds it. # # This regex makes sure that $image starts with . or /, forcing the -f # parsing to fail since . and / are not valid formats. $image =~ s#^[^/]#./$&#; # For libc libraries, the copy in /usr/lib/debug contains debugging symbols my $debugging = DebuggingLibrary($image); if ($debugging) { $image = $debugging; } my $nm = $obj_tool_map{"nm"}; my $cppfilt = $obj_tool_map{"c++filt"}; # nm can fail for two reasons: 1) $image isn't a debug library; 2) nm # binary doesn't support --demangle. In addition, for OS X we need # to use the -f flag to get 'flat' nm output (otherwise we don't sort # properly and get incorrect results). Unfortunately, GNU nm uses -f # in an incompatible way. So first we test whether our nm supports # --demangle and -f. my $demangle_flag = ""; my $cppfilt_flag = ""; my $to_devnull = ">$dev_null 2>&1"; if (system(ShellEscape($nm, "--demangle", $image) . $to_devnull) == 0) { # In this mode, we do "nm --demangle " $demangle_flag = "--demangle"; $cppfilt_flag = ""; } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) { # In this mode, we do "nm | c++filt" $cppfilt_flag = " | " . ShellEscape($cppfilt); }; my $flatten_flag = ""; if (system(ShellEscape($nm, "-f", $image) . $to_devnull) == 0) { $flatten_flag = "-f"; } # Finally, in the case $imagie isn't a debug library, we try again with # -D to at least get *exported* symbols. If we can't use --demangle, # we use c++filt instead, if it exists on this system. my @nm_commands = (ShellEscape($nm, "-n", $flatten_flag, $demangle_flag, $image) . " 2>$dev_null $cppfilt_flag", ShellEscape($nm, "-D", "-n", $flatten_flag, $demangle_flag, $image) . " 2>$dev_null $cppfilt_flag", # 6nm is for Go binaries ShellEscape("6nm", "$image") . " 2>$dev_null | sort", ); # If the executable is an MS Windows PDB-format executable, we'll # have set up obj_tool_map("nm_pdb"). In this case, we actually # want to use both unix nm and windows-specific nm_pdb, since # PDB-format executables can apparently include dwarf .o files. if (exists $obj_tool_map{"nm_pdb"}) { push(@nm_commands, ShellEscape($obj_tool_map{"nm_pdb"}, "--demangle", $image) . " 2>$dev_null"); } foreach my $nm_command (@nm_commands) { my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp); return $symbol_table if (%{$symbol_table}); } my $symbol_table = {}; return $symbol_table; } # The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings. # To make them more readable, we add underscores at interesting places. # This routine removes the underscores, producing the canonical representation # used by jeprof to represent addresses, particularly in the tested routines. sub CanonicalHex { my $arg = shift; return join '', (split '_',$arg); } # Unit test for AddressAdd: sub AddressAddUnitTest { my $test_data_8 = shift; my $test_data_16 = shift; my $error_count = 0; my $fail_count = 0; my $pass_count = 0; # print STDERR "AddressAddUnitTest: ", 1+$#{$test_data_8}, " tests\n"; # First a few 8-nibble addresses. Note that this implementation uses # plain old arithmetic, so a quick sanity check along with verifying what # happens to overflow (we want it to wrap): $address_length = 8; foreach my $row (@{$test_data_8}) { if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } my $sum = AddressAdd ($row->[0], $row->[1]); if ($sum ne $row->[2]) { printf STDERR "ERROR: %s != %s + %s = %s\n", $sum, $row->[0], $row->[1], $row->[2]; ++$fail_count; } else { ++$pass_count; } } printf STDERR "AddressAdd 32-bit tests: %d passes, %d failures\n", $pass_count, $fail_count; $error_count = $fail_count; $fail_count = 0; $pass_count = 0; # Now 16-nibble addresses. $address_length = 16; foreach my $row (@{$test_data_16}) { if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1])); my $expected = join '', (split '_',$row->[2]); if ($sum ne CanonicalHex($row->[2])) { printf STDERR "ERROR: %s != %s + %s = %s\n", $sum, $row->[0], $row->[1], $row->[2]; ++$fail_count; } else { ++$pass_count; } } printf STDERR "AddressAdd 64-bit tests: %d passes, %d failures\n", $pass_count, $fail_count; $error_count += $fail_count; return $error_count; } # Unit test for AddressSub: sub AddressSubUnitTest { my $test_data_8 = shift; my $test_data_16 = shift; my $error_count = 0; my $fail_count = 0; my $pass_count = 0; # print STDERR "AddressSubUnitTest: ", 1+$#{$test_data_8}, " tests\n"; # First a few 8-nibble addresses. Note that this implementation uses # plain old arithmetic, so a quick sanity check along with verifying what # happens to overflow (we want it to wrap): $address_length = 8; foreach my $row (@{$test_data_8}) { if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } my $sum = AddressSub ($row->[0], $row->[1]); if ($sum ne $row->[3]) { printf STDERR "ERROR: %s != %s - %s = %s\n", $sum, $row->[0], $row->[1], $row->[3]; ++$fail_count; } else { ++$pass_count; } } printf STDERR "AddressSub 32-bit tests: %d passes, %d failures\n", $pass_count, $fail_count; $error_count = $fail_count; $fail_count = 0; $pass_count = 0; # Now 16-nibble addresses. $address_length = 16; foreach my $row (@{$test_data_16}) { if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1])); if ($sum ne CanonicalHex($row->[3])) { printf STDERR "ERROR: %s != %s - %s = %s\n", $sum, $row->[0], $row->[1], $row->[3]; ++$fail_count; } else { ++$pass_count; } } printf STDERR "AddressSub 64-bit tests: %d passes, %d failures\n", $pass_count, $fail_count; $error_count += $fail_count; return $error_count; } # Unit test for AddressInc: sub AddressIncUnitTest { my $test_data_8 = shift; my $test_data_16 = shift; my $error_count = 0; my $fail_count = 0; my $pass_count = 0; # print STDERR "AddressIncUnitTest: ", 1+$#{$test_data_8}, " tests\n"; # First a few 8-nibble addresses. Note that this implementation uses # plain old arithmetic, so a quick sanity check along with verifying what # happens to overflow (we want it to wrap): $address_length = 8; foreach my $row (@{$test_data_8}) { if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } my $sum = AddressInc ($row->[0]); if ($sum ne $row->[4]) { printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum, $row->[0], $row->[4]; ++$fail_count; } else { ++$pass_count; } } printf STDERR "AddressInc 32-bit tests: %d passes, %d failures\n", $pass_count, $fail_count; $error_count = $fail_count; $fail_count = 0; $pass_count = 0; # Now 16-nibble addresses. $address_length = 16; foreach my $row (@{$test_data_16}) { if ($main::opt_debug and $main::opt_test) { print STDERR "@{$row}\n"; } my $sum = AddressInc (CanonicalHex($row->[0])); if ($sum ne CanonicalHex($row->[4])) { printf STDERR "ERROR: %s != %s + 1 = %s\n", $sum, $row->[0], $row->[4]; ++$fail_count; } else { ++$pass_count; } } printf STDERR "AddressInc 64-bit tests: %d passes, %d failures\n", $pass_count, $fail_count; $error_count += $fail_count; return $error_count; } # Driver for unit tests. # Currently just the address add/subtract/increment routines for 64-bit. sub RunUnitTests { my $error_count = 0; # This is a list of tuples [a, b, a+b, a-b, a+1] my $unit_test_data_8 = [ [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)], [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)], [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)], [qw(00000001 ffffffff 00000000 00000002 00000002)], [qw(00000001 fffffff0 fffffff1 00000011 00000002)], ]; my $unit_test_data_16 = [ # The implementation handles data in 7-nibble chunks, so those are the # interesting boundaries. [qw(aaaaaaaa 50505050 00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)], [qw(50505050 aaaaaaaa 00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)], [qw(ffffffff aaaaaaaa 00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)], [qw(00000001 ffffffff 00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)], [qw(00000001 fffffff0 00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)], [qw(00_a00000a_aaaaaaa 50505050 00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)], [qw(0f_fff0005_0505050 aaaaaaaa 0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)], [qw(00_000000f_fffffff 01_800000a_aaaaaaa 01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)], [qw(00_0000000_0000001 ff_fffffff_fffffff 00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)], [qw(00_0000000_0000001 ff_fffffff_ffffff0 ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)], ]; $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16); $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16); $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16); if ($error_count > 0) { print STDERR $error_count, " errors: FAILED\n"; } else { print STDERR "PASS\n"; } exit ($error_count); } ���������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/build-aux/����������������������������������������������������������������0000775�0000000�0000000�00000000000�15015331166�0017243�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/build-aux/config.guess����������������������������������������������������0000775�0000000�0000000�00000140450�15015331166�0021567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /bin/sh # Attempt to guess a canonical system name. # Copyright 1992-2021 Free Software Foundation, Inc. timestamp='2021-01-01' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # # Originally written by Per Bothner; maintained since 2000 by Ben Elliston. # # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.guess # # Please send patches to . me=$(echo "$0" | sed -e 's,.*/,,') usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright 1992-2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. tmp= # shellcheck disable=SC2172 trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 set_cc_for_build() { # prevent multiple calls if $tmp is already set test "$tmp" && return 0 : "${TMPDIR=/tmp}" # shellcheck disable=SC2039 { tmp=$( (umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null) && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } dummy=$tmp/dummy case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in ,,) echo "int x;" > "$dummy.c" for driver in cc gcc c89 c99 ; do if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then CC_FOR_BUILD="$driver" break fi done if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac } # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if test -f /.attbin/uname ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=$( (uname -m) 2>/dev/null) || UNAME_MACHINE=unknown UNAME_RELEASE=$( (uname -r) 2>/dev/null) || UNAME_RELEASE=unknown UNAME_SYSTEM=$( (uname -s) 2>/dev/null) || UNAME_SYSTEM=unknown UNAME_VERSION=$( (uname -v) 2>/dev/null) || UNAME_VERSION=unknown case "$UNAME_SYSTEM" in Linux|GNU|GNU/*) LIBC=unknown set_cc_for_build cat <<-EOF > "$dummy.c" #include #if defined(__UCLIBC__) LIBC=uclibc #elif defined(__dietlibc__) LIBC=dietlibc #elif defined(__GLIBC__) LIBC=gnu #else #include /* First heuristic to detect musl libc. */ #ifdef __DEFINED_va_list LIBC=musl #endif #endif EOF eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g')" # Second heuristic to detect musl libc. if [ "$LIBC" = unknown ] && command -v ldd >/dev/null && ldd --version 2>&1 | grep -q ^musl; then LIBC=musl fi # If the system lacks a compiler, then just pick glibc. # We could probably try harder. if [ "$LIBC" = unknown ]; then LIBC=gnu fi ;; esac # Note: order is significant - the case branches are not exclusive. case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=$( (uname -p 2>/dev/null || \ "/sbin/$sysctl" 2>/dev/null || \ "/usr/sbin/$sysctl" 2>/dev/null || \ echo unknown)) case "$UNAME_MACHINE_ARCH" in aarch64eb) machine=aarch64_be-unknown ;; armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; sh5el) machine=sh5le-unknown ;; earmv*) arch=$(echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,') endian=$(echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p') machine="${arch}${endian}"-unknown ;; *) machine="$UNAME_MACHINE_ARCH"-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently (or will in the future) and ABI. case "$UNAME_MACHINE_ARCH" in earm*) os=netbsdelf ;; arm*|i386|m68k|ns32k|sh3*|sparc|vax) set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ELF__ then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # Determine ABI tags. case "$UNAME_MACHINE_ARCH" in earm*) expr='s/^earmv[0-9]/-eabi/;s/eb$//' abi=$(echo "$UNAME_MACHINE_ARCH" | sed -e "$expr") ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "$UNAME_VERSION" in Debian*) release='-gnu' ;; *) release=$(echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2) ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "$machine-${os}${release}${abi-}" exit ;; *:Bitrig:*:*) UNAME_MACHINE_ARCH=$(arch | sed 's/Bitrig.//') echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=$(arch | sed 's/OpenBSD.//') echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" exit ;; *:LibertyBSD:*:*) UNAME_MACHINE_ARCH=$(arch | sed 's/^.*BSD\.//') echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" exit ;; *:MidnightBSD:*:*) echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" exit ;; *:ekkoBSD:*:*) echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" exit ;; *:SolidBSD:*:*) echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" exit ;; *:OS108:*:*) echo "$UNAME_MACHINE"-unknown-os108_"$UNAME_RELEASE" exit ;; macppc:MirBSD:*:*) echo powerpc-unknown-mirbsd"$UNAME_RELEASE" exit ;; *:MirBSD:*:*) echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" exit ;; *:Sortix:*:*) echo "$UNAME_MACHINE"-unknown-sortix exit ;; *:Twizzler:*:*) echo "$UNAME_MACHINE"-unknown-twizzler exit ;; *:Redox:*:*) echo "$UNAME_MACHINE"-unknown-redox exit ;; mips:OSF1:*.*) echo mips-dec-osf1 exit ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=$(/usr/sbin/sizer -v | awk '{print $3}') ;; *5.*) UNAME_RELEASE=$(/usr/sbin/sizer -v | awk '{print $4}') ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=$(/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1) case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE=alpha ;; "EV4.5 (21064)") UNAME_MACHINE=alpha ;; "LCA4 (21066/21068)") UNAME_MACHINE=alpha ;; "EV5 (21164)") UNAME_MACHINE=alphaev5 ;; "EV5.6 (21164A)") UNAME_MACHINE=alphaev56 ;; "EV5.6 (21164PC)") UNAME_MACHINE=alphapca56 ;; "EV5.7 (21164PC)") UNAME_MACHINE=alphapca57 ;; "EV6 (21264)") UNAME_MACHINE=alphaev6 ;; "EV6.7 (21264A)") UNAME_MACHINE=alphaev67 ;; "EV6.8CB (21264C)") UNAME_MACHINE=alphaev68 ;; "EV6.8AL (21264B)") UNAME_MACHINE=alphaev68 ;; "EV6.8CX (21264D)") UNAME_MACHINE=alphaev68 ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE=alphaev69 ;; "EV7 (21364)") UNAME_MACHINE=alphaev7 ;; "EV7.9 (21364A)") UNAME_MACHINE=alphaev79 ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo "$UNAME_MACHINE"-dec-osf"$(echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz)" # Reset EXIT trap before exiting to avoid spurious non-zero exit code. exitcode=$? trap '' 0 exit $exitcode ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit ;; *:[Aa]miga[Oo][Ss]:*:*) echo "$UNAME_MACHINE"-unknown-amigaos exit ;; *:[Mm]orph[Oo][Ss]:*:*) echo "$UNAME_MACHINE"-unknown-morphos exit ;; *:OS/390:*:*) echo i370-ibm-openedition exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix"$UNAME_RELEASE" exit ;; arm*:riscos:*:*|arm*:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "$( (/bin/universe) 2>/dev/null)" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case $(/usr/bin/uname -p) in sparc) echo sparc-icl-nx7; exit ;; esac ;; s390x:SunOS:*:*) echo "$UNAME_MACHINE"-ibm-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')" exit ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" exit ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')" exit ;; i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) echo i386-pc-auroraux"$UNAME_RELEASE" exit ;; i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) set_cc_for_build SUN_ARCH=i386 # If there is a compiler, see if it is configured for 64-bit objects. # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. # This test works for both compilers. if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then SUN_ARCH=x86_64 fi fi echo "$SUN_ARCH"-pc-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" exit ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" exit ;; sun4*:SunOS:*:*) case "$(/usr/bin/arch -k)" in Series*|S4*) UNAME_RELEASE=$(uname -v) ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/')" exit ;; sun3*:SunOS:*:*) echo m68k-sun-sunos"$UNAME_RELEASE" exit ;; sun*:*:4.2BSD:*) UNAME_RELEASE=$( (sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null) test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 case "$(/bin/arch)" in sun3) echo m68k-sun-sunos"$UNAME_RELEASE" ;; sun4) echo sparc-sun-sunos"$UNAME_RELEASE" ;; esac exit ;; aushp:SunOS:*:*) echo sparc-auspex-sunos"$UNAME_RELEASE" exit ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint"$UNAME_RELEASE" exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint"$UNAME_RELEASE" exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint"$UNAME_RELEASE" exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint"$UNAME_RELEASE" exit ;; m68k:machten:*:*) echo m68k-apple-machten"$UNAME_RELEASE" exit ;; powerpc:machten:*:*) echo powerpc-apple-machten"$UNAME_RELEASE" exit ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix"$UNAME_RELEASE" exit ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix"$UNAME_RELEASE" exit ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix"$UNAME_RELEASE" exit ;; mips:*:*:UMIPS | mips:*:*:RISCos) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && dummyarg=$(echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p') && SYSTEM_NAME=$("$dummy" "$dummyarg") && { echo "$SYSTEM_NAME"; exit; } echo mips-mips-riscos"$UNAME_RELEASE" exit ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=$(/usr/bin/uname -p) if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 then if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ test "$TARGET_BINARY_INTERFACE"x = x then echo m88k-dg-dgux"$UNAME_RELEASE" else echo m88k-dg-dguxbcs"$UNAME_RELEASE" fi else echo i586-dg-dgux"$UNAME_RELEASE" fi exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit ;; *:IRIX*:*:*) echo mips-sgi-irix"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/g')" exit ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit ;; # Note that: echo "'$(uname -s)'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit ;; ia64:AIX:*:*) if test -x /usr/bin/oslevel ; then IBM_REV=$(/usr/bin/oslevel) else IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" fi echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" exit ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=$("$dummy") then echo "$SYSTEM_NAME" else echo rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit ;; *:AIX:*:[4567]) IBM_CPU_ID=$(/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }') if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if test -x /usr/bin/lslpp ; then IBM_REV=$(/usr/bin/lslpp -Lqc bos.rte.libc | awk -F: '{ print $3 }' | sed s/[0-9]*$/0/) else IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" fi echo "$IBM_ARCH"-ibm-aix"$IBM_REV" exit ;; *:AIX:*:*) echo rs6000-ibm-aix exit ;; ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) echo romp-ibm-bsd4.4 exit ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to exit ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//') case "$UNAME_MACHINE" in 9000/31?) HP_ARCH=m68000 ;; 9000/[34]??) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if test -x /usr/bin/getconf; then sc_cpu_version=$(/usr/bin/getconf SC_CPU_VERSION 2>/dev/null) sc_kernel_bits=$(/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null) case "$sc_cpu_version" in 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "$sc_kernel_bits" in 32) HP_ARCH=hppa2.0n ;; 64) HP_ARCH=hppa2.0w ;; '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 esac ;; esac fi if test "$HP_ARCH" = ""; then set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=$("$dummy") test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if test "$HP_ARCH" = hppa2.0w then set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | grep -q __LP64__ then HP_ARCH=hppa2.0w else HP_ARCH=hppa64 fi fi echo "$HP_ARCH"-hp-hpux"$HPUX_REV" exit ;; ia64:HP-UX:*:*) HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//') echo ia64-hp-hpux"$HPUX_REV" exit ;; 3050*:HI-UX:*:*) set_cc_for_build sed 's/^ //' << EOF > "$dummy.c" #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=$("$dummy") && { echo "$SYSTEM_NAME"; exit; } echo unknown-hitachi-hiuxwe2 exit ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) echo hppa1.1-hp-bsd exit ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) echo hppa1.1-hp-osf exit ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit ;; i*86:OSF1:*:*) if test -x /usr/sbin/sysversion ; then echo "$UNAME_MACHINE"-unknown-osf1mk else echo "$UNAME_MACHINE"-unknown-osf1 fi exit ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*[A-Z]90:*:*:*) echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' exit ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=$(uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz) FUJITSU_SYS=$(uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///') FUJITSU_REL=$(echo "$UNAME_RELEASE" | sed -e 's/ /_/') echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=$(uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///') FUJITSU_REL=$(echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/') echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" exit ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi"$UNAME_RELEASE" exit ;; *:BSD/OS:*:*) echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" exit ;; arm:FreeBSD:*:*) UNAME_PROCESSOR=$(uname -p) set_cc_for_build if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabi else echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabihf fi exit ;; *:FreeBSD:*:*) UNAME_PROCESSOR=$(/usr/bin/uname -p) case "$UNAME_PROCESSOR" in amd64) UNAME_PROCESSOR=x86_64 ;; i386) UNAME_PROCESSOR=i586 ;; esac echo "$UNAME_PROCESSOR"-unknown-freebsd"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')" exit ;; i*:CYGWIN*:*) echo "$UNAME_MACHINE"-pc-cygwin exit ;; *:MINGW64*:*) echo "$UNAME_MACHINE"-pc-mingw64 exit ;; *:MINGW*:*) echo "$UNAME_MACHINE"-pc-mingw32 exit ;; *:MSYS*:*) echo "$UNAME_MACHINE"-pc-msys exit ;; i*:PW*:*) echo "$UNAME_MACHINE"-pc-pw32 exit ;; *:Interix*:*) case "$UNAME_MACHINE" in x86) echo i586-pc-interix"$UNAME_RELEASE" exit ;; authenticamd | genuineintel | EM64T) echo x86_64-unknown-interix"$UNAME_RELEASE" exit ;; IA64) echo ia64-unknown-interix"$UNAME_RELEASE" exit ;; esac ;; i*:UWIN*:*) echo "$UNAME_MACHINE"-pc-uwin exit ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) echo x86_64-pc-cygwin exit ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" exit ;; *:GNU:*:*) # the GNU system echo "$(echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,')-unknown-$LIBC$(echo "$UNAME_RELEASE"|sed -e 's,/.*$,,')" exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo "$UNAME_MACHINE-unknown-$(echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]")$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')-$LIBC" exit ;; *:Minix:*:*) echo "$UNAME_MACHINE"-unknown-minix exit ;; aarch64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; aarch64_be:Linux:*:*) UNAME_MACHINE=aarch64_be echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; alpha:Linux:*:*) case $(sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null) in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep -q ld.so.1 if test "$?" = 0 ; then LIBC=gnulibc1 ; fi echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; arc:Linux:*:* | arceb:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; arm*:Linux:*:*) set_cc_for_build if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_EABI__ then echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" else if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ | grep -q __ARM_PCS_VFP then echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi else echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf fi fi exit ;; avr32*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; cris:Linux:*:*) echo "$UNAME_MACHINE"-axis-linux-"$LIBC" exit ;; crisv32:Linux:*:*) echo "$UNAME_MACHINE"-axis-linux-"$LIBC" exit ;; e2k:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; frv:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; hexagon:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; i*86:Linux:*:*) echo "$UNAME_MACHINE"-pc-linux-"$LIBC" exit ;; ia64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; k1om:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; m32r*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; m68*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; mips:Linux:*:* | mips64:Linux:*:*) set_cc_for_build IS_GLIBC=0 test x"${LIBC}" = xgnu && IS_GLIBC=1 sed 's/^ //' << EOF > "$dummy.c" #undef CPU #undef mips #undef mipsel #undef mips64 #undef mips64el #if ${IS_GLIBC} && defined(_ABI64) LIBCABI=gnuabi64 #else #if ${IS_GLIBC} && defined(_ABIN32) LIBCABI=gnuabin32 #else LIBCABI=${LIBC} #endif #endif #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa64r6 #else #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 CPU=mipsisa32r6 #else #if defined(__mips64) CPU=mips64 #else CPU=mips #endif #endif #endif #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) MIPS_ENDIAN=el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) MIPS_ENDIAN= #else MIPS_ENDIAN= #endif #endif EOF eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI')" test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } ;; mips64el:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; openrisc*:Linux:*:*) echo or1k-unknown-linux-"$LIBC" exit ;; or32:Linux:*:* | or1k*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; padre:Linux:*:*) echo sparc-unknown-linux-"$LIBC" exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-"$LIBC" exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case $(grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2) in PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; *) echo hppa-unknown-linux-"$LIBC" ;; esac exit ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-"$LIBC" exit ;; ppc:Linux:*:*) echo powerpc-unknown-linux-"$LIBC" exit ;; ppc64le:Linux:*:*) echo powerpc64le-unknown-linux-"$LIBC" exit ;; ppcle:Linux:*:*) echo powerpcle-unknown-linux-"$LIBC" exit ;; riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; s390:Linux:*:* | s390x:Linux:*:*) echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" exit ;; sh64*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; sh*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; tile*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; vax:Linux:*:*) echo "$UNAME_MACHINE"-dec-linux-"$LIBC" exit ;; x86_64:Linux:*:*) set_cc_for_build LIBCABI=$LIBC if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_X32 >/dev/null then LIBCABI="$LIBC"x32 fi fi echo "$UNAME_MACHINE"-pc-linux-"$LIBCABI" exit ;; xtensa*:Linux:*:*) echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" exit ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo "$UNAME_MACHINE"-pc-os2-emx exit ;; i*86:XTS-300:*:STOP) echo "$UNAME_MACHINE"-unknown-stop exit ;; i*86:atheos:*:*) echo "$UNAME_MACHINE"-unknown-atheos exit ;; i*86:syllable:*:*) echo "$UNAME_MACHINE"-pc-syllable exit ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) echo i386-unknown-lynxos"$UNAME_RELEASE" exit ;; i*86:*DOS:*:*) echo "$UNAME_MACHINE"-pc-msdosdjgpp exit ;; i*86:*:4.*:*) UNAME_REL=$(echo "$UNAME_RELEASE" | sed 's/\/MP$//') if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" else echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" fi exit ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case $(/bin/uname -X | grep "^Machine") in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}" exit ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=$(sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=$( (/bin/uname -X|grep Release|sed -e 's/.*= //')) (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" else echo "$UNAME_MACHINE"-pc-sysv32 fi exit ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i586. # Note: whatever this is, it MUST be the same as what config.sub # prints for the "djgpp" host, or else GDB configure will decide that # this is a cross-build. echo i586-pc-msdosdjgpp exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 exit ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 fi exit ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.$(sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; NCR*:*:4.2:* | MPRAS*:*:4.2:*) OS_REL='.3' test -r /etc/.relid \ && OS_REL=.$(sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos"$UNAME_RELEASE" exit ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos"$UNAME_RELEASE" exit ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos"$UNAME_RELEASE" exit ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) echo powerpc-unknown-lynxos"$UNAME_RELEASE" exit ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv"$UNAME_RELEASE" exit ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=$( (uname -p) 2>/dev/null) echo "$UNAME_MACHINE"-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. echo "$UNAME_MACHINE"-stratus-vos exit ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit ;; mc68*:A/UX:*:*) echo m68k-apple-aux"$UNAME_RELEASE" exit ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if test -d /usr/nec; then echo mips-nec-sysv"$UNAME_RELEASE" else echo mips-unknown-sysv"$UNAME_RELEASE" fi exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit ;; BePC:Haiku:*:*) # Haiku running on Intel PC compatible. echo i586-pc-haiku exit ;; x86_64:Haiku:*:*) echo x86_64-unknown-haiku exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux"$UNAME_RELEASE" exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux"$UNAME_RELEASE" exit ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux"$UNAME_RELEASE" exit ;; SX-7:SUPER-UX:*:*) echo sx7-nec-superux"$UNAME_RELEASE" exit ;; SX-8:SUPER-UX:*:*) echo sx8-nec-superux"$UNAME_RELEASE" exit ;; SX-8R:SUPER-UX:*:*) echo sx8r-nec-superux"$UNAME_RELEASE" exit ;; SX-ACE:SUPER-UX:*:*) echo sxace-nec-superux"$UNAME_RELEASE" exit ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody"$UNAME_RELEASE" exit ;; *:Rhapsody:*:*) echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" exit ;; arm64:Darwin:*:*) echo aarch64-apple-darwin"$UNAME_RELEASE" exit ;; *:Darwin:*:*) UNAME_PROCESSOR=$(uname -p) case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac if command -v xcode-select > /dev/null 2> /dev/null && \ ! xcode-select --print-path > /dev/null 2> /dev/null ; then # Avoid executing cc if there is no toolchain installed as # cc will be a stub that puts up a graphical alert # prompting the user to install developer tools. CC_FOR_BUILD=no_compiler_found else set_cc_for_build fi if test "$CC_FOR_BUILD" != no_compiler_found; then if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_64BIT_ARCH >/dev/null then case $UNAME_PROCESSOR in i386) UNAME_PROCESSOR=x86_64 ;; powerpc) UNAME_PROCESSOR=powerpc64 ;; esac fi # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ grep IS_PPC >/dev/null then UNAME_PROCESSOR=powerpc fi elif test "$UNAME_PROCESSOR" = i386 ; then # uname -m returns i386 or x86_64 UNAME_PROCESSOR=$UNAME_MACHINE fi echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=$(uname -p) if test "$UNAME_PROCESSOR" = x86; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" exit ;; *:QNX:*:4*) echo i386-pc-qnx exit ;; NEO-*:NONSTOP_KERNEL:*:*) echo neo-tandem-nsk"$UNAME_RELEASE" exit ;; NSE-*:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk"$UNAME_RELEASE" exit ;; NSR-*:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk"$UNAME_RELEASE" exit ;; NSV-*:NONSTOP_KERNEL:*:*) echo nsv-tandem-nsk"$UNAME_RELEASE" exit ;; NSX-*:NONSTOP_KERNEL:*:*) echo nsx-tandem-nsk"$UNAME_RELEASE" exit ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit ;; DS/*:UNIX_System_V:*:*) echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" exit ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. # shellcheck disable=SC2154 if test "$cputype" = 386; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo "$UNAME_MACHINE"-unknown-plan9 exit ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) echo mips-sei-seiux"$UNAME_RELEASE" exit ;; *:DragonFly:*:*) echo "$UNAME_MACHINE"-unknown-dragonfly"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')" exit ;; *:*VMS:*:*) UNAME_MACHINE=$( (uname -p) 2>/dev/null) case "$UNAME_MACHINE" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; *:XENIX:*:SysV) echo i386-pc-xenix exit ;; i*86:skyos:*:*) echo "$UNAME_MACHINE"-pc-skyos"$(echo "$UNAME_RELEASE" | sed -e 's/ .*$//')" exit ;; i*86:rdos:*:*) echo "$UNAME_MACHINE"-pc-rdos exit ;; i*86:AROS:*:*) echo "$UNAME_MACHINE"-pc-aros exit ;; x86_64:VMkernel:*:*) echo "$UNAME_MACHINE"-unknown-esx exit ;; amd64:Isilon\ OneFS:*:*) echo x86_64-unknown-onefs exit ;; *:Unleashed:*:*) echo "$UNAME_MACHINE"-unknown-unleashed"$UNAME_RELEASE" exit ;; esac # No uname command or uname output not recognized. set_cc_for_build cat > "$dummy.c" < #include #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #include #if defined(_SIZE_T_) || defined(SIGLOST) #include #endif #endif #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=$( (hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null); if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) #if !defined (ultrix) #include #if defined (BSD) #if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); #else #if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); #else printf ("vax-dec-bsd\n"); exit (0); #endif #endif #else printf ("vax-dec-bsd\n"); exit (0); #endif #else #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname un; uname (&un); printf ("vax-dec-ultrix%s\n", un.release); exit (0); #else printf ("vax-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) #if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) #if defined(_SIZE_T_) || defined(SIGLOST) struct utsname *un; uname (&un); printf ("mips-dec-ultrix%s\n", un.release); exit (0); #else printf ("mips-dec-ultrix\n"); exit (0); #endif #endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=$($dummy) && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } echo "$0: unable to guess system type" >&2 case "$UNAME_MACHINE:$UNAME_SYSTEM" in mips:Linux | mips64:Linux) # If we got here on MIPS GNU/Linux, output extra information. cat >&2 <&2 <&2 </dev/null || echo unknown) uname -r = $( (uname -r) 2>/dev/null || echo unknown) uname -s = $( (uname -s) 2>/dev/null || echo unknown) uname -v = $( (uname -v) 2>/dev/null || echo unknown) /usr/bin/uname -p = $( (/usr/bin/uname -p) 2>/dev/null) /bin/uname -X = $( (/bin/uname -X) 2>/dev/null) hostinfo = $( (hostinfo) 2>/dev/null) /bin/universe = $( (/bin/universe) 2>/dev/null) /usr/bin/arch -k = $( (/usr/bin/arch -k) 2>/dev/null) /bin/arch = $( (/bin/arch) 2>/dev/null) /usr/bin/oslevel = $( (/usr/bin/oslevel) 2>/dev/null) /usr/convex/getsysinfo = $( (/usr/convex/getsysinfo) 2>/dev/null) UNAME_MACHINE = "$UNAME_MACHINE" UNAME_RELEASE = "$UNAME_RELEASE" UNAME_SYSTEM = "$UNAME_SYSTEM" UNAME_VERSION = "$UNAME_VERSION" EOF fi exit 1 # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/build-aux/config.sub������������������������������������������������������0000775�0000000�0000000�00000102733�15015331166�0021234�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /bin/sh # Configuration validation subroutine script. # Copyright 1992-2021 Free Software Foundation, Inc. timestamp='2021-01-07' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that # program. This Exception is an additional permission under section 7 # of the GNU General Public License, version 3 ("GPLv3"). # Please send patches to . # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # You can get the latest version of this script from: # https://git.savannah.gnu.org/cgit/config.git/plain/config.sub # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. me=$(echo "$0" | sed -e 's,.*/,,') usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS Canonicalize a configuration name. Options: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright 1992-2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; *local*) # First pass through any local machine types. echo "$1" exit ;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Split fields of configuration type # shellcheck disable=SC2162 IFS="-" read field1 field2 field3 field4 <&2 exit 1 ;; *-*-*-*) basic_machine=$field1-$field2 basic_os=$field3-$field4 ;; *-*-*) # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two # parts maybe_os=$field2-$field3 case $maybe_os in nto-qnx* | linux-* | uclinux-uclibc* \ | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ | storm-chaos* | os2-emx* | rtmk-nova*) basic_machine=$field1 basic_os=$maybe_os ;; android-linux) basic_machine=$field1-unknown basic_os=linux-android ;; *) basic_machine=$field1-$field2 basic_os=$field3 ;; esac ;; *-*) # A lone config we happen to match not fitting any pattern case $field1-$field2 in decstation-3100) basic_machine=mips-dec basic_os= ;; *-*) # Second component is usually, but not always the OS case $field2 in # Prevent following clause from handling this valid os sun*os*) basic_machine=$field1 basic_os=$field2 ;; # Manufacturers dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \ | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \ | unicom* | ibm* | next | hp | isi* | apollo | altos* \ | convergent* | ncr* | news | 32* | 3600* | 3100* \ | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \ | ultra | tti* | harris | dolphin | highlevel | gould \ | cbm | ns | masscomp | apple | axis | knuth | cray \ | microblaze* | sim | cisco \ | oki | wec | wrs | winbond) basic_machine=$field1-$field2 basic_os= ;; *) basic_machine=$field1 basic_os=$field2 ;; esac ;; esac ;; *) # Convert single-component short-hands not valid as part of # multi-component configurations. case $field1 in 386bsd) basic_machine=i386-pc basic_os=bsd ;; a29khif) basic_machine=a29k-amd basic_os=udi ;; adobe68k) basic_machine=m68010-adobe basic_os=scout ;; alliant) basic_machine=fx80-alliant basic_os= ;; altos | altos3068) basic_machine=m68k-altos basic_os= ;; am29k) basic_machine=a29k-none basic_os=bsd ;; amdahl) basic_machine=580-amdahl basic_os=sysv ;; amiga) basic_machine=m68k-unknown basic_os= ;; amigaos | amigados) basic_machine=m68k-unknown basic_os=amigaos ;; amigaunix | amix) basic_machine=m68k-unknown basic_os=sysv4 ;; apollo68) basic_machine=m68k-apollo basic_os=sysv ;; apollo68bsd) basic_machine=m68k-apollo basic_os=bsd ;; aros) basic_machine=i386-pc basic_os=aros ;; aux) basic_machine=m68k-apple basic_os=aux ;; balance) basic_machine=ns32k-sequent basic_os=dynix ;; blackfin) basic_machine=bfin-unknown basic_os=linux ;; cegcc) basic_machine=arm-unknown basic_os=cegcc ;; convex-c1) basic_machine=c1-convex basic_os=bsd ;; convex-c2) basic_machine=c2-convex basic_os=bsd ;; convex-c32) basic_machine=c32-convex basic_os=bsd ;; convex-c34) basic_machine=c34-convex basic_os=bsd ;; convex-c38) basic_machine=c38-convex basic_os=bsd ;; cray) basic_machine=j90-cray basic_os=unicos ;; crds | unos) basic_machine=m68k-crds basic_os= ;; da30) basic_machine=m68k-da30 basic_os= ;; decstation | pmax | pmin | dec3100 | decstatn) basic_machine=mips-dec basic_os= ;; delta88) basic_machine=m88k-motorola basic_os=sysv3 ;; dicos) basic_machine=i686-pc basic_os=dicos ;; djgpp) basic_machine=i586-pc basic_os=msdosdjgpp ;; ebmon29k) basic_machine=a29k-amd basic_os=ebmon ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson basic_os=ose ;; gmicro) basic_machine=tron-gmicro basic_os=sysv ;; go32) basic_machine=i386-pc basic_os=go32 ;; h8300hms) basic_machine=h8300-hitachi basic_os=hms ;; h8300xray) basic_machine=h8300-hitachi basic_os=xray ;; h8500hms) basic_machine=h8500-hitachi basic_os=hms ;; harris) basic_machine=m88k-harris basic_os=sysv3 ;; hp300 | hp300hpux) basic_machine=m68k-hp basic_os=hpux ;; hp300bsd) basic_machine=m68k-hp basic_os=bsd ;; hppaosf) basic_machine=hppa1.1-hp basic_os=osf ;; hppro) basic_machine=hppa1.1-hp basic_os=proelf ;; i386mach) basic_machine=i386-mach basic_os=mach ;; isi68 | isi) basic_machine=m68k-isi basic_os=sysv ;; m68knommu) basic_machine=m68k-unknown basic_os=linux ;; magnum | m3230) basic_machine=mips-mips basic_os=sysv ;; merlin) basic_machine=ns32k-utek basic_os=sysv ;; mingw64) basic_machine=x86_64-pc basic_os=mingw64 ;; mingw32) basic_machine=i686-pc basic_os=mingw32 ;; mingw32ce) basic_machine=arm-unknown basic_os=mingw32ce ;; monitor) basic_machine=m68k-rom68k basic_os=coff ;; morphos) basic_machine=powerpc-unknown basic_os=morphos ;; moxiebox) basic_machine=moxie-unknown basic_os=moxiebox ;; msdos) basic_machine=i386-pc basic_os=msdos ;; msys) basic_machine=i686-pc basic_os=msys ;; mvs) basic_machine=i370-ibm basic_os=mvs ;; nacl) basic_machine=le32-unknown basic_os=nacl ;; ncr3000) basic_machine=i486-ncr basic_os=sysv4 ;; netbsd386) basic_machine=i386-pc basic_os=netbsd ;; netwinder) basic_machine=armv4l-rebel basic_os=linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony basic_os=newsos ;; news1000) basic_machine=m68030-sony basic_os=newsos ;; necv70) basic_machine=v70-nec basic_os=sysv ;; nh3000) basic_machine=m68k-harris basic_os=cxux ;; nh[45]000) basic_machine=m88k-harris basic_os=cxux ;; nindy960) basic_machine=i960-intel basic_os=nindy ;; mon960) basic_machine=i960-intel basic_os=mon960 ;; nonstopux) basic_machine=mips-compaq basic_os=nonstopux ;; os400) basic_machine=powerpc-ibm basic_os=os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson basic_os=ose ;; os68k) basic_machine=m68k-none basic_os=os68k ;; paragon) basic_machine=i860-intel basic_os=osf ;; parisc) basic_machine=hppa-unknown basic_os=linux ;; psp) basic_machine=mipsallegrexel-sony basic_os=psp ;; pw32) basic_machine=i586-unknown basic_os=pw32 ;; rdos | rdos64) basic_machine=x86_64-pc basic_os=rdos ;; rdos32) basic_machine=i386-pc basic_os=rdos ;; rom68k) basic_machine=m68k-rom68k basic_os=coff ;; sa29200) basic_machine=a29k-amd basic_os=udi ;; sei) basic_machine=mips-sei basic_os=seiux ;; sequent) basic_machine=i386-sequent basic_os= ;; sps7) basic_machine=m68k-bull basic_os=sysv2 ;; st2000) basic_machine=m68k-tandem basic_os= ;; stratus) basic_machine=i860-stratus basic_os=sysv4 ;; sun2) basic_machine=m68000-sun basic_os= ;; sun2os3) basic_machine=m68000-sun basic_os=sunos3 ;; sun2os4) basic_machine=m68000-sun basic_os=sunos4 ;; sun3) basic_machine=m68k-sun basic_os= ;; sun3os3) basic_machine=m68k-sun basic_os=sunos3 ;; sun3os4) basic_machine=m68k-sun basic_os=sunos4 ;; sun4) basic_machine=sparc-sun basic_os= ;; sun4os3) basic_machine=sparc-sun basic_os=sunos3 ;; sun4os4) basic_machine=sparc-sun basic_os=sunos4 ;; sun4sol2) basic_machine=sparc-sun basic_os=solaris2 ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun basic_os= ;; sv1) basic_machine=sv1-cray basic_os=unicos ;; symmetry) basic_machine=i386-sequent basic_os=dynix ;; t3e) basic_machine=alphaev5-cray basic_os=unicos ;; t90) basic_machine=t90-cray basic_os=unicos ;; toad1) basic_machine=pdp10-xkl basic_os=tops20 ;; tpf) basic_machine=s390x-ibm basic_os=tpf ;; udi29k) basic_machine=a29k-amd basic_os=udi ;; ultra3) basic_machine=a29k-nyu basic_os=sym1 ;; v810 | necv810) basic_machine=v810-nec basic_os=none ;; vaxv) basic_machine=vax-dec basic_os=sysv ;; vms) basic_machine=vax-dec basic_os=vms ;; vsta) basic_machine=i386-pc basic_os=vsta ;; vxworks960) basic_machine=i960-wrs basic_os=vxworks ;; vxworks68) basic_machine=m68k-wrs basic_os=vxworks ;; vxworks29k) basic_machine=a29k-wrs basic_os=vxworks ;; xbox) basic_machine=i686-pc basic_os=mingw32 ;; ymp) basic_machine=ymp-cray basic_os=unicos ;; *) basic_machine=$1 basic_os= ;; esac ;; esac # Decode 1-component or ad-hoc basic machines case $basic_machine in # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) cpu=hppa1.1 vendor=winbond ;; op50n) cpu=hppa1.1 vendor=oki ;; op60c) cpu=hppa1.1 vendor=oki ;; ibm*) cpu=i370 vendor=ibm ;; orion105) cpu=clipper vendor=highlevel ;; mac | mpw | mac-mpw) cpu=m68k vendor=apple ;; pmac | pmac-mpw) cpu=powerpc vendor=apple ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) cpu=m68000 vendor=att ;; 3b*) cpu=we32k vendor=att ;; bluegene*) cpu=powerpc vendor=ibm basic_os=cnk ;; decsystem10* | dec10*) cpu=pdp10 vendor=dec basic_os=tops10 ;; decsystem20* | dec20*) cpu=pdp10 vendor=dec basic_os=tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) cpu=m68k vendor=motorola ;; dpx2*) cpu=m68k vendor=bull basic_os=sysv3 ;; encore | umax | mmax) cpu=ns32k vendor=encore ;; elxsi) cpu=elxsi vendor=elxsi basic_os=${basic_os:-bsd} ;; fx2800) cpu=i860 vendor=alliant ;; genix) cpu=ns32k vendor=ns ;; h3050r* | hiux*) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) cpu=m68000 vendor=hp ;; hp9k3[2-9][0-9]) cpu=m68k vendor=hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) cpu=hppa1.1 vendor=hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) cpu=hppa1.1 vendor=hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) cpu=hppa1.0 vendor=hp ;; i*86v32) cpu=$(echo "$1" | sed -e 's/86.*/86/') vendor=pc basic_os=sysv32 ;; i*86v4*) cpu=$(echo "$1" | sed -e 's/86.*/86/') vendor=pc basic_os=sysv4 ;; i*86v) cpu=$(echo "$1" | sed -e 's/86.*/86/') vendor=pc basic_os=sysv ;; i*86sol2) cpu=$(echo "$1" | sed -e 's/86.*/86/') vendor=pc basic_os=solaris2 ;; j90 | j90-cray) cpu=j90 vendor=cray basic_os=${basic_os:-unicos} ;; iris | iris4d) cpu=mips vendor=sgi case $basic_os in irix*) ;; *) basic_os=irix4 ;; esac ;; miniframe) cpu=m68000 vendor=convergent ;; *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) cpu=m68k vendor=atari basic_os=mint ;; news-3600 | risc-news) cpu=mips vendor=sony basic_os=newsos ;; next | m*-next) cpu=m68k vendor=next case $basic_os in openstep*) ;; nextstep*) ;; ns2*) basic_os=nextstep2 ;; *) basic_os=nextstep3 ;; esac ;; np1) cpu=np1 vendor=gould ;; op50n-* | op60c-*) cpu=hppa1.1 vendor=oki basic_os=proelf ;; pa-hitachi) cpu=hppa1.1 vendor=hitachi basic_os=hiuxwe2 ;; pbd) cpu=sparc vendor=tti ;; pbb) cpu=m68k vendor=tti ;; pc532) cpu=ns32k vendor=pc532 ;; pn) cpu=pn vendor=gould ;; power) cpu=power vendor=ibm ;; ps2) cpu=i386 vendor=ibm ;; rm[46]00) cpu=mips vendor=siemens ;; rtpc | rtpc-*) cpu=romp vendor=ibm ;; sde) cpu=mipsisa32 vendor=sde basic_os=${basic_os:-elf} ;; simso-wrs) cpu=sparclite vendor=wrs basic_os=vxworks ;; tower | tower-32) cpu=m68k vendor=ncr ;; vpp*|vx|vx-*) cpu=f301 vendor=fujitsu ;; w65) cpu=w65 vendor=wdc ;; w89k-*) cpu=hppa1.1 vendor=winbond basic_os=proelf ;; none) cpu=none vendor=none ;; leon|leon[3-9]) cpu=sparc vendor=$basic_machine ;; leon-*|leon[3-9]-*) cpu=sparc vendor=$(echo "$basic_machine" | sed 's/-.*//') ;; *-*) # shellcheck disable=SC2162 IFS="-" read cpu vendor <&2 exit 1 ;; esac ;; esac # Here we canonicalize certain aliases for manufacturers. case $vendor in digital*) vendor=dec ;; commodore*) vendor=cbm ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if test x$basic_os != x then # First recognize some ad-hoc caes, or perhaps split kernel-os, or else just # set os. case $basic_os in gnu/linux*) kernel=linux os=$(echo $basic_os | sed -e 's|gnu/linux|gnu|') ;; os2-emx) kernel=os2 os=$(echo $basic_os | sed -e 's|os2-emx|emx|') ;; nto-qnx*) kernel=nto os=$(echo $basic_os | sed -e 's|nto-qnx|qnx|') ;; *-*) # shellcheck disable=SC2162 IFS="-" read kernel os <&2 exit 1 ;; esac # As a final step for OS-related things, validate the OS-kernel combination # (given a valid OS), if there is a kernel. case $kernel-$os in linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* | linux-musl* | linux-uclibc* ) ;; uclinux-uclibc* ) ;; -dietlibc* | -newlib* | -musl* | -uclibc* ) # These are just libc implementations, not actual OSes, and thus # require a kernel. echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 exit 1 ;; kfreebsd*-gnu* | kopensolaris*-gnu*) ;; nto-qnx*) ;; os2-emx) ;; *-eabi* | *-gnueabi*) ;; -*) # Blank kernel with real OS is always fine. ;; *-*) echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 exit 1 ;; esac # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. case $vendor in unknown) case $cpu-$os in *-riscix*) vendor=acorn ;; *-sunos*) vendor=sun ;; *-cnk* | *-aix*) vendor=ibm ;; *-beos*) vendor=be ;; *-hpux*) vendor=hp ;; *-mpeix*) vendor=hp ;; *-hiux*) vendor=hitachi ;; *-unos*) vendor=crds ;; *-dgux*) vendor=dg ;; *-luna*) vendor=omron ;; *-genix*) vendor=ns ;; *-clix*) vendor=intergraph ;; *-mvs* | *-opened*) vendor=ibm ;; *-os400*) vendor=ibm ;; s390-* | s390x-*) vendor=ibm ;; *-ptx*) vendor=sequent ;; *-tpf*) vendor=ibm ;; *-vxsim* | *-vxworks* | *-windiss*) vendor=wrs ;; *-aux*) vendor=apple ;; *-hms*) vendor=hitachi ;; *-mpw* | *-macos*) vendor=apple ;; *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) vendor=atari ;; *-vos*) vendor=stratus ;; esac ;; esac echo "$cpu-$vendor-${kernel:+$kernel-}$os" exit # Local variables: # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: �������������������������������������redis-8.0.2/deps/jemalloc/build-aux/install-sh������������������������������������������������������0000775�0000000�0000000�00000012721�15015331166�0021252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /bin/sh # # install - install a program, script, or datafile # This comes from X11R5 (mit/util/scripts/install.sh). # # Copyright 1991 by the Massachusetts Institute of Technology # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of M.I.T. not be used in advertising or # publicity pertaining to distribution of the software without specific, # written prior permission. M.I.T. makes no representations about the # suitability of this software for any purpose. It is provided "as is" # without express or implied warranty. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. It can only install one file at a time, a restriction # shared with many OS's install programs. # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$mvprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd="$cpprog" shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd="$stripprog" shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "install: no input file specified" exit 1 else true fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d $dst ]; then instcmd=: else instcmd=mkdir fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f $src -o -d $src ] then true else echo "install: $src does not exist" exit 1 fi if [ x"$dst" = x ] then echo "install: no destination specified" exit 1 else true fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d $dst ] then dst="$dst"/`basename $src` else true fi fi ## this sed command emulates the dirname command dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` IFS="${oIFS}" pathcomp='' while [ $# -ne 0 ] ; do pathcomp="${pathcomp}${1}" shift if [ ! -d "${pathcomp}" ] ; then $mkdirprog "${pathcomp}" else true fi pathcomp="${pathcomp}/" done fi if [ x"$dir_arg" != x ] then $doit $instcmd $dst && if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename $dst` else dstfile=`basename $dst $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename $dst` else true fi # Make a temp file name in the proper directory. dsttmp=$dstdir/#inst.$$# # Move or copy the file name to the temp name $doit $instcmd $src $dsttmp && trap "rm -f ${dsttmp}" 0 && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && # Now rename the file to the real destination. $doit $rmcmd -f $dstdir/$dstfile && $doit $mvcmd $dsttmp $dstdir/$dstfile fi && exit 0 �����������������������������������������������redis-8.0.2/deps/jemalloc/config.stamp.in�����������������������������������������������������������0000664�0000000�0000000�00000000000�15015331166�0020257�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/configure�����������������������������������������������������������������0000775�0000000�0000000�00001534152�15015331166�0017273�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="as_nop=: if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else \$as_nop case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else \$as_nop exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else $as_nop as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else $as_nop if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf "%s\n" "$0: This script requires a shell more modern than all" printf "%s\n" "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_nop # --------- # Do nothing but, unlike ":", preserve the value of $?. as_fn_nop () { return $? } as_nop=as_fn_nop # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else $as_nop as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_nop # --------- # Do nothing but, unlike ":", preserve the value of $?. as_fn_nop () { return $? } as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='' PACKAGE_TARNAME='' PACKAGE_VERSION='' PACKAGE_STRING='' PACKAGE_BUGREPORT='' PACKAGE_URL='' ac_unique_file="Makefile.in" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= ac_subst_vars='LTLIBOBJS LIBOBJS cfgoutputs_out cfgoutputs_in cfghdrs_out cfghdrs_in enable_initial_exec_tls enable_zone_allocator enable_tls enable_lazy_lock libdl enable_uaf_detection enable_opt_size_checks enable_opt_safety_checks enable_readlinkat enable_log enable_cache_oblivious enable_xmalloc enable_utrace enable_fill enable_prof enable_experimental_smallocx enable_stats enable_debug je_ install_suffix private_namespace JEMALLOC_CPREFIX JEMALLOC_PREFIX enable_static enable_shared enable_doc AUTOCONF LD RANLIB INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM enable_autogen RPATH_EXTRA LM CC_MM DUMP_SYMS AROUT ARFLAGS MKLIB TEST_LD_MODE LDTARGET CTARGET PIC_CFLAGS SOREV EXTRA_LDFLAGS DSO_LDFLAGS link_whole_archive libprefix exe a o importlib so LD_PRELOAD_VAR RPATH abi jemalloc_version_gid jemalloc_version_nrev jemalloc_version_bugfix jemalloc_version_minor jemalloc_version_major jemalloc_version AWK NM AR host_os host_vendor host_cpu host build_os build_vendor build_cpu build EXTRA_CXXFLAGS SPECIFIED_CXXFLAGS CONFIGURE_CXXFLAGS enable_cxx HAVE_CXX14 HAVE_CXX17 ac_ct_CXX CXXFLAGS CXX CPP EXTRA_CFLAGS SPECIFIED_CFLAGS CONFIGURE_CFLAGS OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC XSLROOT XSLTPROC MANDIR DATADIR LIBDIR INCLUDEDIR BINDIR PREFIX abs_objroot objroot abs_srcroot srcroot rev CONFIG target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking with_xslroot enable_cxx with_lg_vaddr with_version with_rpath enable_autogen enable_doc enable_shared enable_static with_mangling with_jemalloc_prefix with_export with_private_namespace with_install_suffix with_malloc_conf enable_debug enable_stats enable_experimental_smallocx enable_prof enable_prof_libunwind with_static_libunwind enable_prof_libgcc enable_prof_gcc enable_fill enable_utrace enable_xmalloc enable_cache_oblivious enable_log enable_readlinkat enable_opt_safety_checks enable_opt_size_checks enable_uaf_detection with_lg_quantum with_lg_slab_maxregs with_lg_page with_lg_hugepage enable_libdl enable_syscall enable_lazy_lock enable_zone_allocator enable_initial_exec_tls ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS CPP CXX CXXFLAGS CCC' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --disable-cxx Disable C++ integration --enable-autogen Automatically regenerate configure output --enable-doc Build documentation --enable-shared Build shared libaries --enable-static Build static libaries --enable-debug Build debugging code --disable-stats Disable statistics calculation/reporting --enable-experimental-smallocx Enable experimental smallocx API --enable-prof Enable allocation profiling --enable-prof-libunwind Use libunwind for backtracing --disable-prof-libgcc Do not use libgcc for backtracing --disable-prof-gcc Do not use gcc intrinsics for backtracing --disable-fill Disable support for junk/zero filling --enable-utrace Enable utrace(2)-based tracing --enable-xmalloc Support xmalloc option --disable-cache-oblivious Disable support for cache-oblivious allocation alignment --enable-log Support debug logging --enable-readlinkat Use readlinkat over readlink --enable-opt-safety-checks Perform certain low-overhead checks, even in opt mode --enable-opt-size-checks Perform sized-deallocation argument checks, even in opt mode --enable-uaf-detection Allow sampled junk-filling on deallocation to detect use-after-free --disable-libdl Do not use libdl --disable-syscall Disable use of syscall(2) --enable-lazy-lock Enable lazy locking (only lock when multi-threaded) --disable-zone-allocator Disable zone allocator for Darwin --disable-initial-exec-tls Disable the initial-exec tls model Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-xslroot= XSL stylesheet root path --with-lg-vaddr= Number of significant virtual address bits --with-version=..--g Version string --with-rpath= Colon-separated rpath (ELF systems only) --with-mangling= Mangle symbols in --with-jemalloc-prefix= Prefix to prepend to all public APIs --without-export disable exporting jemalloc public APIs --with-private-namespace= Prefix to prepend to all library-private APIs --with-install-suffix= Suffix to append to all installed files --with-malloc-conf= config.malloc_conf options string --with-static-libunwind= Path to static libunwind library; use rather than dynamically linking --with-lg-quantum= Base 2 log of minimum allocation alignment --with-lg-slab-maxregs= Base 2 log of maximum number of regions in a slab (used with malloc_conf slab_sizes) --with-lg-page= Base 2 log of system page size --with-lg-hugepage= Base 2 log of system huge page size Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CPP C preprocessor CXX C++ compiler command CXXFLAGS C++ compiler flags Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err } then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_cxx_try_compile LINENO # ---------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_try_run LINENO # ---------------------- # Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that # executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; } then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: program exited with status $ac_status" >&5 printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else $as_nop eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_compute_int LINENO EXPR VAR INCLUDES # -------------------------------------------- # Tries to find the compile-time value of EXPR in a program that includes # INCLUDES, setting VAR accordingly. Returns whether the value could be # computed ac_fn_c_compute_int () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if test "$cross_compiling" = yes; then # Depending upon the size, compute the lo and hi bounds. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) >= 0)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_lo=0 ac_mid=0 while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) <= $ac_mid)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_hi=$ac_mid; break else $as_nop as_fn_arith $ac_mid + 1 && ac_lo=$as_val if test $ac_lo -le $ac_mid; then ac_lo= ac_hi= break fi as_fn_arith 2 '*' $ac_mid + 1 && ac_mid=$as_val fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) < 0)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_hi=-1 ac_mid=-1 while :; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) >= $ac_mid)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_lo=$ac_mid; break else $as_nop as_fn_arith '(' $ac_mid ')' - 1 && ac_hi=$as_val if test $ac_mid -le $ac_hi; then ac_lo= ac_hi= break fi as_fn_arith 2 '*' $ac_mid && ac_mid=$as_val fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done else $as_nop ac_lo= ac_hi= fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext # Binary search between lo and hi bounds. while test "x$ac_lo" != "x$ac_hi"; do as_fn_arith '(' $ac_hi - $ac_lo ')' / 2 + $ac_lo && ac_mid=$as_val cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { static int test_array [1 - 2 * !(($2) <= $ac_mid)]; test_array [0] = 0; return test_array [0]; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_hi=$ac_mid else $as_nop as_fn_arith '(' $ac_mid ')' + 1 && ac_lo=$as_val fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext done case $ac_lo in #(( ?*) eval "$3=\$ac_lo"; ac_retval=0 ;; '') ac_retval=1 ;; esac else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 static long int longval (void) { return $2; } static unsigned long int ulongval (void) { return $2; } #include #include int main (void) { FILE *f = fopen ("conftest.val", "w"); if (! f) return 1; if (($2) < 0) { long int i = longval (); if (i != ($2)) return 1; fprintf (f, "%ld", i); } else { unsigned long int i = ulongval (); if (i != ($2)) return 1; fprintf (f, "%lu", i); } /* Do not output a trailing newline, as this causes \r\n confusion on some platforms. */ return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : echo >>conftest.val; read $3 &5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else $as_nop eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache # variable VAR accordingly. ac_fn_c_check_type () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop eval "$3=no" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof ($2)) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 int main (void) { if (sizeof (($2))) return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else $as_nop eval "$3=yes" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_type ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf "%s\n" "$as_me: caught signal $ac_signal" printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Does the compiler advertise C89 conformance? Do not test the value of __STDC__, because some compilers set it to 0 while being otherwise adequately conformant. */ #if !defined __STDC__ # error "Compiler does not advertise C89 conformance" #endif #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' // Does the compiler advertise C99 conformance? #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[0] = argv[0][0]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || dynamic_array[ni.number - 1] != 543); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' // Does the compiler advertise C11 conformance? #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " # Test code for whether the C++ compiler supports C++98 (global declarations) ac_cxx_conftest_cxx98_globals=' // Does the compiler advertise C++98 conformance? #if !defined __cplusplus || __cplusplus < 199711L # error "Compiler does not advertise C++98 conformance" #endif // These inclusions are to reject old compilers that // lack the unsuffixed header files. #include #include // and are *not* freestanding headers in C++98. extern void assert (int); namespace std { extern int strcmp (const char *, const char *); } // Namespaces, exceptions, and templates were all added after "C++ 2.0". using std::exception; using std::strcmp; namespace { void test_exception_syntax() { try { throw "test"; } catch (const char *s) { // Extra parentheses suppress a warning when building autoconf itself, // due to lint rules shared with more typical C programs. assert (!(strcmp) (s, "test")); } } template struct test_template { T const val; explicit test_template(T t) : val(t) {} template T add(U u) { return static_cast(u) + val; } }; } // anonymous namespace ' # Test code for whether the C++ compiler supports C++98 (body of main) ac_cxx_conftest_cxx98_main=' assert (argc); assert (! argv[0]); { test_exception_syntax (); test_template tt (2.0); assert (tt.add (4) == 6.0); assert (true && !false); } ' # Test code for whether the C++ compiler supports C++11 (global declarations) ac_cxx_conftest_cxx11_globals=' // Does the compiler advertise C++ 2011 conformance? #if !defined __cplusplus || __cplusplus < 201103L # error "Compiler does not advertise C++11 conformance" #endif namespace cxx11test { constexpr int get_val() { return 20; } struct testinit { int i; double d; }; class delegate { public: delegate(int n) : n(n) {} delegate(): delegate(2354) {} virtual int getval() { return this->n; }; protected: int n; }; class overridden : public delegate { public: overridden(int n): delegate(n) {} virtual int getval() override final { return this->n * 2; } }; class nocopy { public: nocopy(int i): i(i) {} nocopy() = default; nocopy(const nocopy&) = delete; nocopy & operator=(const nocopy&) = delete; private: int i; }; // for testing lambda expressions template Ret eval(Fn f, Ret v) { return f(v); } // for testing variadic templates and trailing return types template auto sum(V first) -> V { return first; } template auto sum(V first, Args... rest) -> V { return first + sum(rest...); } } ' # Test code for whether the C++ compiler supports C++11 (body of main) ac_cxx_conftest_cxx11_main=' { // Test auto and decltype auto a1 = 6538; auto a2 = 48573953.4; auto a3 = "String literal"; int total = 0; for (auto i = a3; *i; ++i) { total += *i; } decltype(a2) a4 = 34895.034; } { // Test constexpr short sa[cxx11test::get_val()] = { 0 }; } { // Test initializer lists cxx11test::testinit il = { 4323, 435234.23544 }; } { // Test range-based for int array[] = {9, 7, 13, 15, 4, 18, 12, 10, 5, 3, 14, 19, 17, 8, 6, 20, 16, 2, 11, 1}; for (auto &x : array) { x += 23; } } { // Test lambda expressions using cxx11test::eval; assert (eval ([](int x) { return x*2; }, 21) == 42); double d = 2.0; assert (eval ([&](double x) { return d += x; }, 3.0) == 5.0); assert (d == 5.0); assert (eval ([=](double x) mutable { return d += x; }, 4.0) == 9.0); assert (d == 5.0); } { // Test use of variadic templates using cxx11test::sum; auto a = sum(1); auto b = sum(1, 2); auto c = sum(1.0, 2.0, 3.0); } { // Test constructor delegation cxx11test::delegate d1; cxx11test::delegate d2(); cxx11test::delegate d3(45); } { // Test override and final cxx11test::overridden o1(55464); } { // Test nullptr char *c = nullptr; } { // Test template brackets test_template<::test_template> v(test_template(12)); } { // Unicode literals char const *utf8 = u8"UTF-8 string \u2500"; char16_t const *utf16 = u"UTF-8 string \u2500"; char32_t const *utf32 = U"UTF-32 string \u2500"; } ' # Test code for whether the C compiler supports C++11 (complete). ac_cxx_conftest_cxx11_program="${ac_cxx_conftest_cxx98_globals} ${ac_cxx_conftest_cxx11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_cxx_conftest_cxx98_main} ${ac_cxx_conftest_cxx11_main} return ok; } " # Test code for whether the C compiler supports C++98 (complete). ac_cxx_conftest_cxx98_program="${ac_cxx_conftest_cxx98_globals} int main (int argc, char **argv) { int ok = 0; ${ac_cxx_conftest_cxx98_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" # Auxiliary files required by this configure script. ac_aux_files="install-sh config.guess config.sub" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}/build-aux" # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else $as_nop as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu CONFIGURE_CFLAGS= SPECIFIED_CFLAGS="${CFLAGS}" CONFIGURE_CXXFLAGS= SPECIFIED_CXXFLAGS="${CXXFLAGS}" CONFIG=`echo ${ac_configure_args} | sed -e 's#'"'"'\([^ ]*\)'"'"'#\1#g'` rev=2 srcroot=$srcdir if test "x${srcroot}" = "x." ; then srcroot="" else srcroot="${srcroot}/" fi abs_srcroot="`cd \"${srcdir}\"; pwd`/" objroot="" abs_objroot="`pwd`/" case "$prefix" in *\ * ) as_fn_error $? "Prefix should not contain spaces" "$LINENO" 5 ;; "NONE" ) prefix="/usr/local" ;; esac case "$exec_prefix" in *\ * ) as_fn_error $? "Exec prefix should not contain spaces" "$LINENO" 5 ;; "NONE" ) exec_prefix=$prefix ;; esac PREFIX=$prefix BINDIR=`eval echo $bindir` BINDIR=`eval echo $BINDIR` INCLUDEDIR=`eval echo $includedir` INCLUDEDIR=`eval echo $INCLUDEDIR` LIBDIR=`eval echo $libdir` LIBDIR=`eval echo $LIBDIR` DATADIR=`eval echo $datadir` DATADIR=`eval echo $DATADIR` MANDIR=`eval echo $mandir` MANDIR=`eval echo $MANDIR` # Extract the first word of "xsltproc", so it can be a program name with args. set dummy xsltproc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_XSLTPROC+y} then : printf %s "(cached) " >&6 else $as_nop case $XSLTPROC in [\\/]* | ?:[\\/]*) ac_cv_path_XSLTPROC="$XSLTPROC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_XSLTPROC="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_XSLTPROC" && ac_cv_path_XSLTPROC="false" ;; esac fi XSLTPROC=$ac_cv_path_XSLTPROC if test -n "$XSLTPROC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $XSLTPROC" >&5 printf "%s\n" "$XSLTPROC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test -d "/usr/share/xml/docbook/stylesheet/docbook-xsl" ; then DEFAULT_XSLROOT="/usr/share/xml/docbook/stylesheet/docbook-xsl" elif test -d "/usr/share/sgml/docbook/xsl-stylesheets" ; then DEFAULT_XSLROOT="/usr/share/sgml/docbook/xsl-stylesheets" else DEFAULT_XSLROOT="" fi # Check whether --with-xslroot was given. if test ${with_xslroot+y} then : withval=$with_xslroot; if test "x$with_xslroot" = "xno" ; then XSLROOT="${DEFAULT_XSLROOT}" else XSLROOT="${with_xslroot}" fi else $as_nop XSLROOT="${DEFAULT_XSLROOT}" fi if test "x$XSLTPROC" = "xfalse" ; then XSLROOT="" fi CFLAGS=$CFLAGS ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else $as_nop ac_file='' fi if test -z "$ac_file" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else $as_nop { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else $as_nop ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else $as_nop ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else $as_nop CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else $as_nop ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x$GCC" != "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler is MSVC" >&5 printf %s "checking whether compiler is MSVC... " >&6; } if test ${je_cv_msvc+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef _MSC_VER int fail-1; #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_msvc=yes else $as_nop je_cv_msvc=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_msvc" >&5 printf "%s\n" "$je_cv_msvc" >&6; } fi je_cv_cray_prgenv_wrapper="" if test "x${PE_ENV}" != "x" ; then case "${CC}" in CC|cc) je_cv_cray_prgenv_wrapper="yes" ;; *) ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler is cray" >&5 printf %s "checking whether compiler is cray... " >&6; } if test ${je_cv_cray+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef _CRAYC int fail-1; #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cray=yes else $as_nop je_cv_cray=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_cray" >&5 printf "%s\n" "$je_cv_cray" >&6; } if test "x${je_cv_cray}" = "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether cray compiler version is 8.4" >&5 printf %s "checking whether cray compiler version is 8.4... " >&6; } if test ${je_cv_cray_84+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #if !(_RELEASE_MAJOR == 8 && _RELEASE_MINOR == 4) int fail-1; #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cray_84=yes else $as_nop je_cv_cray_84=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_cray_84" >&5 printf "%s\n" "$je_cv_cray_84" >&6; } fi if test "x$GCC" = "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -std=gnu11" >&5 printf %s "checking whether compiler supports -std=gnu11... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-std=gnu11 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-std=gnu11 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x$je_cv_cflags_added" = "x-std=gnu11" ; then printf "%s\n" "#define JEMALLOC_HAS_RESTRICT " >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -std=gnu99" >&5 printf %s "checking whether compiler supports -std=gnu99... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-std=gnu99 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-std=gnu99 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x$je_cv_cflags_added" = "x-std=gnu99" ; then printf "%s\n" "#define JEMALLOC_HAS_RESTRICT " >>confdefs.h fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror=unknown-warning-option" >&5 printf %s "checking whether compiler supports -Werror=unknown-warning-option... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror=unknown-warning-option if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror=unknown-warning-option { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wall" >&5 printf %s "checking whether compiler supports -Wall... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wall if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wall { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wextra" >&5 printf %s "checking whether compiler supports -Wextra... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wextra if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wextra { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wshorten-64-to-32" >&5 printf %s "checking whether compiler supports -Wshorten-64-to-32... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wshorten-64-to-32 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wshorten-64-to-32 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wsign-compare" >&5 printf %s "checking whether compiler supports -Wsign-compare... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wsign-compare if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wsign-compare { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wundef" >&5 printf %s "checking whether compiler supports -Wundef... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wundef if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wundef { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wno-format-zero-length" >&5 printf %s "checking whether compiler supports -Wno-format-zero-length... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wno-format-zero-length if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wno-format-zero-length { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wpointer-arith" >&5 printf %s "checking whether compiler supports -Wpointer-arith... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wpointer-arith if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wpointer-arith { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wno-missing-braces" >&5 printf %s "checking whether compiler supports -Wno-missing-braces... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wno-missing-braces if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wno-missing-braces { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wno-missing-field-initializers" >&5 printf %s "checking whether compiler supports -Wno-missing-field-initializers... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wno-missing-field-initializers if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wno-missing-field-initializers { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wno-missing-attributes" >&5 printf %s "checking whether compiler supports -Wno-missing-attributes... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wno-missing-attributes if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wno-missing-attributes { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -pipe" >&5 printf %s "checking whether compiler supports -pipe... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-pipe if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-pipe { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -g3" >&5 printf %s "checking whether compiler supports -g3... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-g3 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-g3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi elif test "x$je_cv_msvc" = "xyes" ; then CC="$CC -nologo" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Zi" >&5 printf %s "checking whether compiler supports -Zi... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Zi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Zi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -MT" >&5 printf %s "checking whether compiler supports -MT... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-MT if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-MT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -W3" >&5 printf %s "checking whether compiler supports -W3... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-W3 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-W3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -FS" >&5 printf %s "checking whether compiler supports -FS... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-FS if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-FS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi T_APPEND_V=-I${srcdir}/include/msvc_compat if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi fi if test "x$je_cv_cray" = "xyes" ; then if test "x$je_cv_cray_84" = "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hipa2" >&5 printf %s "checking whether compiler supports -hipa2... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-hipa2 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-hipa2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hnognu" >&5 printf %s "checking whether compiler supports -hnognu... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-hnognu if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-hnognu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hnomessage=128" >&5 printf %s "checking whether compiler supports -hnomessage=128... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-hnomessage=128 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-hnomessage=128 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hnomessage=1357" >&5 printf %s "checking whether compiler supports -hnomessage=1357... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-hnomessage=1357 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-hnomessage=1357 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 printf %s "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if test ${ac_cv_prog_CPP+y} then : printf %s "(cached) " >&6 else $as_nop # Double quotes because $CC needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO" then : else $as_nop # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else $as_nop # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : break fi done ac_cv_prog_CPP=$CPP fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 printf "%s\n" "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO" then : else $as_nop # Broken: fails on valid input. continue fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else $as_nop # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : else $as_nop { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See \`config.log' for more details" "$LINENO" 5; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu # Check whether --enable-cxx was given. if test ${enable_cxx+y} then : enableval=$enable_cxx; if test "x$enable_cxx" = "xno" ; then enable_cxx="0" else enable_cxx="1" fi else $as_nop enable_cxx="1" fi if test "x$enable_cxx" = "x1" ; then # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) # or '14' (for the C++14 standard). # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test -z "$CXX"; then if test -n "$CCC"; then CXX=$CCC else if test -n "$ac_tool_prefix"; then for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CXX+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 printf "%s\n" "$CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CXX" && break done fi if test -z "$CXX"; then ac_ct_CXX=$CXX for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CXX+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CXX="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 printf "%s\n" "$ac_ct_CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CXX" && break done if test "x$ac_ct_CXX" = x; then CXX="g++" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX fi fi fi fi # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C++" >&5 printf %s "checking whether the compiler supports GNU C++... " >&6; } if test ${ac_cv_cxx_compiler_gnu+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_compiler_gnu=yes else $as_nop ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 printf "%s\n" "$ac_cv_cxx_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test $ac_compiler_gnu = yes; then GXX=yes else GXX= fi ac_test_CXXFLAGS=${CXXFLAGS+y} ac_save_CXXFLAGS=$CXXFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 printf %s "checking whether $CXX accepts -g... " >&6; } if test ${ac_cv_prog_cxx_g+y} then : printf %s "(cached) " >&6 else $as_nop ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_g=yes else $as_nop CXXFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : else $as_nop ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cxx_werror_flag=$ac_save_cxx_werror_flag fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 printf "%s\n" "$ac_cv_prog_cxx_g" >&6; } if test $ac_test_CXXFLAGS; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then CXXFLAGS="-g -O2" else CXXFLAGS="-g" fi else if test "$GXX" = yes; then CXXFLAGS="-O2" else CXXFLAGS= fi fi ac_prog_cxx_stdcxx=no if test x$ac_prog_cxx_stdcxx = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++11 features" >&5 printf %s "checking for $CXX option to enable C++11 features... " >&6; } if test ${ac_cv_prog_cxx_11+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cxx_11=no ac_save_CXX=$CXX cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_cxx_conftest_cxx11_program _ACEOF for ac_arg in '' -std=gnu++11 -std=gnu++0x -std=c++11 -std=c++0x -qlanglvl=extended0x -AA do CXX="$ac_save_CXX $ac_arg" if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_cxx11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cxx_cxx11" != "xno" && break done rm -f conftest.$ac_ext CXX=$ac_save_CXX fi if test "x$ac_cv_prog_cxx_cxx11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cxx_cxx11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx11" >&5 printf "%s\n" "$ac_cv_prog_cxx_cxx11" >&6; } CXX="$CXX $ac_cv_prog_cxx_cxx11" fi ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx11 ac_prog_cxx_stdcxx=cxx11 fi fi if test x$ac_prog_cxx_stdcxx = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++98 features" >&5 printf %s "checking for $CXX option to enable C++98 features... " >&6; } if test ${ac_cv_prog_cxx_98+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cxx_98=no ac_save_CXX=$CXX cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_cxx_conftest_cxx98_program _ACEOF for ac_arg in '' -std=gnu++98 -std=c++98 -qlanglvl=extended -AA do CXX="$ac_save_CXX $ac_arg" if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_cxx98=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cxx_cxx98" != "xno" && break done rm -f conftest.$ac_ext CXX=$ac_save_CXX fi if test "x$ac_cv_prog_cxx_cxx98" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cxx_cxx98" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx98" >&5 printf "%s\n" "$ac_cv_prog_cxx_cxx98" >&6; } CXX="$CXX $ac_cv_prog_cxx_cxx98" fi ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx98 ac_prog_cxx_stdcxx=cxx98 fi fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu ax_cxx_compile_alternatives="17 1z" ax_cxx_compile_cxx17_required=false ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu ac_success=no if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do cachevar=`printf "%s\n" "ax_cv_cxx_compile_cxx17_$switch" | $as_tr_sh` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++17 features with $switch" >&5 printf %s "checking whether $CXX supports C++17 features with $switch... " >&6; } if eval test \${$cachevar+y} then : printf %s "(cached) " >&6 else $as_nop ac_save_CXX="$CXX" CXX="$CXX $switch" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201103L #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { virtual ~Derived() override {} virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201703L #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // __cplusplus < 201703L _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : eval $cachevar=yes else $as_nop eval $cachevar=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CXX="$ac_save_CXX" fi eval ac_res=\$$cachevar { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test x$ax_cxx_compile_cxx17_required = xtrue; then if test x$ac_success = xno; then as_fn_error $? "*** A compiler with support for C++17 language features is required." "$LINENO" 5 fi fi if test x$ac_success = xno; then HAVE_CXX17=0 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: No compiler with C++17 support was found" >&5 printf "%s\n" "$as_me: No compiler with C++17 support was found" >&6;} else HAVE_CXX17=1 printf "%s\n" "#define HAVE_CXX17 1" >>confdefs.h fi if test "x${HAVE_CXX17}" != "x1"; then ax_cxx_compile_alternatives="14 1y" ax_cxx_compile_cxx14_required=false ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu ac_success=no if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do cachevar=`printf "%s\n" "ax_cv_cxx_compile_cxx14_$switch" | $as_tr_sh` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 printf %s "checking whether $CXX supports C++14 features with $switch... " >&6; } if eval test \${$cachevar+y} then : printf %s "(cached) " >&6 else $as_nop ac_save_CXX="$CXX" CXX="$CXX $switch" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201103L #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { virtual ~Derived() override {} virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : eval $cachevar=yes else $as_nop eval $cachevar=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext CXX="$ac_save_CXX" fi eval ac_res=\$$cachevar { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test x$ax_cxx_compile_cxx14_required = xtrue; then if test x$ac_success = xno; then as_fn_error $? "*** A compiler with support for C++14 language features is required." "$LINENO" 5 fi fi if test x$ac_success = xno; then HAVE_CXX14=0 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: No compiler with C++14 support was found" >&5 printf "%s\n" "$as_me: No compiler with C++14 support was found" >&6;} else HAVE_CXX14=1 printf "%s\n" "#define HAVE_CXX14 1" >>confdefs.h fi fi if test "x${HAVE_CXX14}" = "x1" -o "x${HAVE_CXX17}" = "x1"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wall" >&5 printf %s "checking whether compiler supports -Wall... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-Wall if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-Wall { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wextra" >&5 printf %s "checking whether compiler supports -Wextra... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-Wextra if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-Wextra { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -g3" >&5 printf %s "checking whether compiler supports -g3... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-g3 if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-g3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi SAVED_LIBS="${LIBS}" T_APPEND_V=-lstdc++ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether libstdc++ linkage is compilable" >&5 printf %s "checking whether libstdc++ linkage is compilable... " >&6; } if test ${je_cv_libstdcxx+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { int *arr = (int *)malloc(sizeof(int) * 42); if (arr == NULL) return 1; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_libstdcxx=yes else $as_nop je_cv_libstdcxx=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_libstdcxx" >&5 printf "%s\n" "$je_cv_libstdcxx" >&6; } if test "x${je_cv_libstdcxx}" = "xno" ; then LIBS="${SAVED_LIBS}" fi else enable_cxx="0" fi fi if test "x$enable_cxx" = "x1"; then printf "%s\n" "#define JEMALLOC_ENABLE_CXX " >>confdefs.h fi ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf "%s\n" "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5 printf %s "checking whether byte ordering is bigendian... " >&6; } if test ${ac_cv_c_bigendian+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_c_bigendian=unknown # See if we're dealing with a universal compiler. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifndef __APPLE_CC__ not a universal capable compiler #endif typedef int dummy; _ACEOF if ac_fn_c_try_compile "$LINENO" then : # Check for potential -arch flags. It is not universal unless # there are at least two -arch flags with different values. ac_arch= ac_prev= for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do if test -n "$ac_prev"; then case $ac_word in i?86 | x86_64 | ppc | ppc64) if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then ac_arch=$ac_word else ac_cv_c_bigendian=universal break fi ;; esac ac_prev= elif test "x$ac_word" = "x-arch"; then ac_prev=arch fi done fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test $ac_cv_c_bigendian = unknown; then # See if sys/param.h defines the BYTE_ORDER macro. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { #if ! (defined BYTE_ORDER && defined BIG_ENDIAN \ && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \ && LITTLE_ENDIAN) bogus endian macros #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : # It does; now see whether it defined to BIG_ENDIAN or not. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { #if BYTE_ORDER != BIG_ENDIAN not big endian #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_bigendian=yes else $as_nop ac_cv_c_bigendian=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi if test $ac_cv_c_bigendian = unknown; then # See if defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris). cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { #if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN) bogus endian macros #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : # It does; now see whether it defined to _BIG_ENDIAN or not. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { #ifndef _BIG_ENDIAN not big endian #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_bigendian=yes else $as_nop ac_cv_c_bigendian=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi if test $ac_cv_c_bigendian = unknown; then # Compile a test program. if test "$cross_compiling" = yes then : # Try to guess by grepping values from an object file. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ unsigned short int ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; unsigned short int ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; int use_ascii (int i) { return ascii_mm[i] + ascii_ii[i]; } unsigned short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; unsigned short int ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; int use_ebcdic (int i) { return ebcdic_mm[i] + ebcdic_ii[i]; } extern int foo; int main (void) { return use_ascii (foo) == use_ebcdic (foo); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then ac_cv_c_bigendian=yes fi if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then if test "$ac_cv_c_bigendian" = unknown; then ac_cv_c_bigendian=no else # finding both strings is unlikely to happen, but who knows? ac_cv_c_bigendian=unknown fi fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default int main (void) { /* Are we little or big endian? From Harbison&Steele. */ union { long int l; char c[sizeof (long int)]; } u; u.l = 1; return u.c[sizeof (long int) - 1] == 1; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : ac_cv_c_bigendian=no else $as_nop ac_cv_c_bigendian=yes fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5 printf "%s\n" "$ac_cv_c_bigendian" >&6; } case $ac_cv_c_bigendian in #( yes) ac_cv_big_endian=1;; #( no) ac_cv_big_endian=0 ;; #( universal) printf "%s\n" "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h ;; #( *) as_fn_error $? "unknown endianness presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;; esac if test "x${ac_cv_big_endian}" = "x1" ; then printf "%s\n" "#define JEMALLOC_BIG_ENDIAN " >>confdefs.h fi if test "x${je_cv_msvc}" = "xyes" -a "x${ac_cv_header_inttypes_h}" = "xno"; then T_APPEND_V=-I${srcdir}/include/msvc_compat/C99 if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi fi if test "x${je_cv_msvc}" = "xyes" ; then LG_SIZEOF_PTR=LG_SIZEOF_PTR_WIN { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Using a predefined value for sizeof(void *): 4 for 32-bit, 8 for 64-bit" >&5 printf "%s\n" "Using a predefined value for sizeof(void *): 4 for 32-bit, 8 for 64-bit" >&6; } else # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of void *" >&5 printf %s "checking size of void *... " >&6; } if test ${ac_cv_sizeof_void_p+y} then : printf %s "(cached) " >&6 else $as_nop if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (void *))" "ac_cv_sizeof_void_p" "$ac_includes_default" then : else $as_nop if test "$ac_cv_type_void_p" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (void *) See \`config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_void_p=0 fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_void_p" >&5 printf "%s\n" "$ac_cv_sizeof_void_p" >&6; } printf "%s\n" "#define SIZEOF_VOID_P $ac_cv_sizeof_void_p" >>confdefs.h if test "x${ac_cv_sizeof_void_p}" = "x8" ; then LG_SIZEOF_PTR=3 elif test "x${ac_cv_sizeof_void_p}" = "x4" ; then LG_SIZEOF_PTR=2 else as_fn_error $? "Unsupported pointer size: ${ac_cv_sizeof_void_p}" "$LINENO" 5 fi fi printf "%s\n" "#define LG_SIZEOF_PTR $LG_SIZEOF_PTR" >>confdefs.h # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of int" >&5 printf %s "checking size of int... " >&6; } if test ${ac_cv_sizeof_int+y} then : printf %s "(cached) " >&6 else $as_nop if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (int))" "ac_cv_sizeof_int" "$ac_includes_default" then : else $as_nop if test "$ac_cv_type_int" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (int) See \`config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_int=0 fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_int" >&5 printf "%s\n" "$ac_cv_sizeof_int" >&6; } printf "%s\n" "#define SIZEOF_INT $ac_cv_sizeof_int" >>confdefs.h if test "x${ac_cv_sizeof_int}" = "x8" ; then LG_SIZEOF_INT=3 elif test "x${ac_cv_sizeof_int}" = "x4" ; then LG_SIZEOF_INT=2 else as_fn_error $? "Unsupported int size: ${ac_cv_sizeof_int}" "$LINENO" 5 fi printf "%s\n" "#define LG_SIZEOF_INT $LG_SIZEOF_INT" >>confdefs.h # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of long" >&5 printf %s "checking size of long... " >&6; } if test ${ac_cv_sizeof_long+y} then : printf %s "(cached) " >&6 else $as_nop if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long))" "ac_cv_sizeof_long" "$ac_includes_default" then : else $as_nop if test "$ac_cv_type_long" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (long) See \`config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_long=0 fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long" >&5 printf "%s\n" "$ac_cv_sizeof_long" >&6; } printf "%s\n" "#define SIZEOF_LONG $ac_cv_sizeof_long" >>confdefs.h if test "x${ac_cv_sizeof_long}" = "x8" ; then LG_SIZEOF_LONG=3 elif test "x${ac_cv_sizeof_long}" = "x4" ; then LG_SIZEOF_LONG=2 else as_fn_error $? "Unsupported long size: ${ac_cv_sizeof_long}" "$LINENO" 5 fi printf "%s\n" "#define LG_SIZEOF_LONG $LG_SIZEOF_LONG" >>confdefs.h # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of long long" >&5 printf %s "checking size of long long... " >&6; } if test ${ac_cv_sizeof_long_long+y} then : printf %s "(cached) " >&6 else $as_nop if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long long))" "ac_cv_sizeof_long_long" "$ac_includes_default" then : else $as_nop if test "$ac_cv_type_long_long" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (long long) See \`config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_long_long=0 fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long_long" >&5 printf "%s\n" "$ac_cv_sizeof_long_long" >&6; } printf "%s\n" "#define SIZEOF_LONG_LONG $ac_cv_sizeof_long_long" >>confdefs.h if test "x${ac_cv_sizeof_long_long}" = "x8" ; then LG_SIZEOF_LONG_LONG=3 elif test "x${ac_cv_sizeof_long_long}" = "x4" ; then LG_SIZEOF_LONG_LONG=2 else as_fn_error $? "Unsupported long long size: ${ac_cv_sizeof_long_long}" "$LINENO" 5 fi printf "%s\n" "#define LG_SIZEOF_LONG_LONG $LG_SIZEOF_LONG_LONG" >>confdefs.h # The cast to long int works around a bug in the HP C Compiler # version HP92453-01 B.11.11.23709.GP, which incorrectly rejects # declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'. # This bug is HP SR number 8606223364. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking size of intmax_t" >&5 printf %s "checking size of intmax_t... " >&6; } if test ${ac_cv_sizeof_intmax_t+y} then : printf %s "(cached) " >&6 else $as_nop if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (intmax_t))" "ac_cv_sizeof_intmax_t" "$ac_includes_default" then : else $as_nop if test "$ac_cv_type_intmax_t" = yes; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot compute sizeof (intmax_t) See \`config.log' for more details" "$LINENO" 5; } else ac_cv_sizeof_intmax_t=0 fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_intmax_t" >&5 printf "%s\n" "$ac_cv_sizeof_intmax_t" >&6; } printf "%s\n" "#define SIZEOF_INTMAX_T $ac_cv_sizeof_intmax_t" >>confdefs.h if test "x${ac_cv_sizeof_intmax_t}" = "x16" ; then LG_SIZEOF_INTMAX_T=4 elif test "x${ac_cv_sizeof_intmax_t}" = "x8" ; then LG_SIZEOF_INTMAX_T=3 elif test "x${ac_cv_sizeof_intmax_t}" = "x4" ; then LG_SIZEOF_INTMAX_T=2 else as_fn_error $? "Unsupported intmax_t size: ${ac_cv_sizeof_intmax_t}" "$LINENO" 5 fi printf "%s\n" "#define LG_SIZEOF_INTMAX_T $LG_SIZEOF_INTMAX_T" >>confdefs.h # Make sure we can run config.sub. $SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 printf %s "checking build system type... " >&6; } if test ${ac_cv_build+y} then : printf %s "(cached) " >&6 else $as_nop ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5 fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 printf "%s\n" "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 printf %s "checking host system type... " >&6; } if test ${ac_cv_host+y} then : printf %s "(cached) " >&6 else $as_nop if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5 fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 printf "%s\n" "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac CPU_SPINWAIT="" case "${host_cpu}" in i686|x86_64) HAVE_CPU_SPINWAIT=1 if test "x${je_cv_msvc}" = "xyes" ; then if test ${je_cv_pause_msvc+y} then : printf %s "(cached) " >&6 else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pause instruction MSVC is compilable" >&5 printf %s "checking whether pause instruction MSVC is compilable... " >&6; } if test ${je_cv_pause_msvc+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { _mm_pause(); return 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pause_msvc=yes else $as_nop je_cv_pause_msvc=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pause_msvc" >&5 printf "%s\n" "$je_cv_pause_msvc" >&6; } fi if test "x${je_cv_pause_msvc}" = "xyes" ; then CPU_SPINWAIT='_mm_pause()' fi else if test ${je_cv_pause+y} then : printf %s "(cached) " >&6 else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pause instruction is compilable" >&5 printf %s "checking whether pause instruction is compilable... " >&6; } if test ${je_cv_pause+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { __asm__ volatile("pause"); return 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pause=yes else $as_nop je_cv_pause=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pause" >&5 printf "%s\n" "$je_cv_pause" >&6; } fi if test "x${je_cv_pause}" = "xyes" ; then CPU_SPINWAIT='__asm__ volatile("pause")' fi fi ;; aarch64|arm*) HAVE_CPU_SPINWAIT=1 if test ${je_cv_isb+y} then : printf %s "(cached) " >&6 else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether isb instruction is compilable" >&5 printf %s "checking whether isb instruction is compilable... " >&6; } if test ${je_cv_isb+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { __asm__ volatile("isb"); return 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_isb=yes else $as_nop je_cv_isb=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_isb" >&5 printf "%s\n" "$je_cv_isb" >&6; } fi if test "x${je_cv_isb}" = "xyes" ; then CPU_SPINWAIT='__asm__ volatile("isb")' fi ;; *) HAVE_CPU_SPINWAIT=0 ;; esac printf "%s\n" "#define HAVE_CPU_SPINWAIT $HAVE_CPU_SPINWAIT" >>confdefs.h printf "%s\n" "#define CPU_SPINWAIT $CPU_SPINWAIT" >>confdefs.h # Check whether --with-lg_vaddr was given. if test ${with_lg_vaddr+y} then : withval=$with_lg_vaddr; LG_VADDR="$with_lg_vaddr" else $as_nop LG_VADDR="detect" fi case "${host_cpu}" in aarch64) if test "x$LG_VADDR" = "xdetect"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking number of significant virtual address bits" >&5 printf %s "checking number of significant virtual address bits... " >&6; } if test "x${LG_SIZEOF_PTR}" = "x2" ; then #aarch64 ILP32 LG_VADDR=32 else #aarch64 LP64 LG_VADDR=48 fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LG_VADDR" >&5 printf "%s\n" "$LG_VADDR" >&6; } fi ;; x86_64) if test "x$LG_VADDR" = "xdetect"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking number of significant virtual address bits" >&5 printf %s "checking number of significant virtual address bits... " >&6; } if test ${je_cv_lg_vaddr+y} then : printf %s "(cached) " >&6 else $as_nop if test "$cross_compiling" = yes then : je_cv_lg_vaddr=57 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifdef _WIN32 #include #include typedef unsigned __int32 uint32_t; #else #include #endif int main (void) { uint32_t r[4]; uint32_t eax_in = 0x80000008U; #ifdef _WIN32 __cpuid((int *)r, (int)eax_in); #else asm volatile ("cpuid" : "=a" (r[0]), "=b" (r[1]), "=c" (r[2]), "=d" (r[3]) : "a" (eax_in), "c" (0) ); #endif uint32_t eax_out = r[0]; uint32_t vaddr = ((eax_out & 0x0000ff00U) >> 8); FILE *f = fopen("conftest.out", "w"); if (f == NULL) { return 1; } if (vaddr > (sizeof(void *) << 3)) { vaddr = sizeof(void *) << 3; } fprintf(f, "%u", vaddr); fclose(f); return 0; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : je_cv_lg_vaddr=`cat conftest.out` else $as_nop je_cv_lg_vaddr=error fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_lg_vaddr" >&5 printf "%s\n" "$je_cv_lg_vaddr" >&6; } if test "x${je_cv_lg_vaddr}" != "x" ; then LG_VADDR="${je_cv_lg_vaddr}" fi if test "x${LG_VADDR}" != "xerror" ; then printf "%s\n" "#define LG_VADDR $LG_VADDR" >>confdefs.h else as_fn_error $? "cannot determine number of significant virtual address bits" "$LINENO" 5 fi fi ;; *) if test "x$LG_VADDR" = "xdetect"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking number of significant virtual address bits" >&5 printf %s "checking number of significant virtual address bits... " >&6; } if test "x${LG_SIZEOF_PTR}" = "x3" ; then LG_VADDR=64 elif test "x${LG_SIZEOF_PTR}" = "x2" ; then LG_VADDR=32 elif test "x${LG_SIZEOF_PTR}" = "xLG_SIZEOF_PTR_WIN" ; then LG_VADDR="(1U << (LG_SIZEOF_PTR_WIN+3))" else as_fn_error $? "Unsupported lg(pointer size): ${LG_SIZEOF_PTR}" "$LINENO" 5 fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LG_VADDR" >&5 printf "%s\n" "$LG_VADDR" >&6; } fi ;; esac printf "%s\n" "#define LG_VADDR $LG_VADDR" >>confdefs.h LD_PRELOAD_VAR="LD_PRELOAD" so="so" importlib="${so}" o="$ac_objext" a="a" exe="$ac_exeext" libprefix="lib" link_whole_archive="0" DSO_LDFLAGS='-shared -Wl,-soname,$(@F)' RPATH='-Wl,-rpath,$(1)' SOREV="${so}.${rev}" PIC_CFLAGS='-fPIC -DPIC' CTARGET='-o $@' LDTARGET='-o $@' TEST_LD_MODE= EXTRA_LDFLAGS= ARFLAGS='crs' AROUT=' $@' CC_MM=1 if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then TEST_LD_MODE='-dynamic' fi if test "x${je_cv_cray}" = "xyes" ; then CC_MM= fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. set dummy ${ac_tool_prefix}ar; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AR+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AR="${ac_tool_prefix}ar" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AR=$ac_cv_prog_AR if test -n "$AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 printf "%s\n" "$AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_AR"; then ac_ct_AR=$AR # Extract the first word of "ar", so it can be a program name with args. set dummy ar; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_AR+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_AR"; then ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_AR="ar" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_AR=$ac_cv_prog_ac_ct_AR if test -n "$ac_ct_AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 printf "%s\n" "$ac_ct_AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_AR" = x; then AR=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac AR=$ac_ct_AR fi else AR="$ac_cv_prog_AR" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}nm", so it can be a program name with args. set dummy ${ac_tool_prefix}nm; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_NM+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$NM"; then ac_cv_prog_NM="$NM" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_NM="${ac_tool_prefix}nm" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi NM=$ac_cv_prog_NM if test -n "$NM"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NM" >&5 printf "%s\n" "$NM" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_NM"; then ac_ct_NM=$NM # Extract the first word of "nm", so it can be a program name with args. set dummy nm; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_NM+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_NM"; then ac_cv_prog_ac_ct_NM="$ac_ct_NM" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_NM="nm" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_NM=$ac_cv_prog_ac_ct_NM if test -n "$ac_ct_NM"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NM" >&5 printf "%s\n" "$ac_ct_NM" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_NM" = x; then NM=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac NM=$ac_ct_NM fi else NM="$ac_cv_prog_NM" fi for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AWK+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 printf "%s\n" "$AWK" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$AWK" && break done # Check whether --with-version was given. if test ${with_version+y} then : withval=$with_version; echo "${with_version}" | grep '^[0-9]\+\.[0-9]\+\.[0-9]\+-[0-9]\+-g[0-9a-f]\+$' 2>&1 1>/dev/null if test $? -eq 0 ; then echo "$with_version" > "${objroot}VERSION" else echo "${with_version}" | grep '^VERSION$' 2>&1 1>/dev/null if test $? -ne 0 ; then as_fn_error $? "${with_version} does not match ..--g or VERSION" "$LINENO" 5 fi fi else $as_nop if test "x`test ! \"${srcroot}\" && cd \"${srcroot}\"; git rev-parse --is-inside-work-tree 2>/dev/null`" = "xtrue" ; then for pattern in '[0-9].[0-9].[0-9]' '[0-9].[0-9].[0-9][0-9]' \ '[0-9].[0-9][0-9].[0-9]' '[0-9].[0-9][0-9].[0-9][0-9]' \ '[0-9][0-9].[0-9].[0-9]' '[0-9][0-9].[0-9].[0-9][0-9]' \ '[0-9][0-9].[0-9][0-9].[0-9]' \ '[0-9][0-9].[0-9][0-9].[0-9][0-9]'; do (test ! "${srcroot}" && cd "${srcroot}"; git describe --long --abbrev=40 --match="${pattern}") > "${objroot}VERSION.tmp" 2>/dev/null if test $? -eq 0 ; then mv "${objroot}VERSION.tmp" "${objroot}VERSION" break fi done fi rm -f "${objroot}VERSION.tmp" fi if test ! -e "${objroot}VERSION" ; then if test ! -e "${srcroot}VERSION" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Missing VERSION file, and unable to generate it; creating bogus VERSION" >&5 printf "%s\n" "Missing VERSION file, and unable to generate it; creating bogus VERSION" >&6; } echo "0.0.0-0-g000000missing_version_try_git_fetch_tags" > "${objroot}VERSION" else cp ${srcroot}VERSION ${objroot}VERSION fi fi jemalloc_version=`cat "${objroot}VERSION"` jemalloc_version_major=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print $1}'` jemalloc_version_minor=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print $2}'` jemalloc_version_bugfix=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print $3}'` jemalloc_version_nrev=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print $4}'` jemalloc_version_gid=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print $5}'` default_retain="0" zero_realloc_default_free="0" maps_coalesce="1" DUMP_SYMS="${NM} -a" SYM_PREFIX="" case "${host}" in *-*-darwin* | *-*-ios*) abi="macho" RPATH="" LD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES" so="dylib" importlib="${so}" force_tls="0" DSO_LDFLAGS='-shared -Wl,-install_name,$(LIBDIR)/$(@F)' SOREV="${rev}.${so}" sbrk_deprecated="1" SYM_PREFIX="_" ;; *-*-freebsd*) T_APPEND_V=-D_BSD_SOURCE if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi abi="elf" printf "%s\n" "#define JEMALLOC_SYSCTL_VM_OVERCOMMIT " >>confdefs.h force_lazy_lock="1" ;; *-*-dragonfly*) abi="elf" ;; *-*-openbsd*) abi="elf" force_tls="0" ;; *-*-bitrig*) abi="elf" ;; *-*-linux-android*) T_APPEND_V=-D_GNU_SOURCE if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi abi="elf" glibc="0" printf "%s\n" "#define JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS " >>confdefs.h printf "%s\n" "#define JEMALLOC_HAS_ALLOCA_H " >>confdefs.h printf "%s\n" "#define JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY " >>confdefs.h printf "%s\n" "#define JEMALLOC_THREADED_INIT " >>confdefs.h printf "%s\n" "#define JEMALLOC_C11_ATOMICS " >>confdefs.h force_tls="0" if test "${LG_SIZEOF_PTR}" = "3"; then default_retain="1" fi zero_realloc_default_free="1" ;; *-*-linux*) T_APPEND_V=-D_GNU_SOURCE if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi abi="elf" glibc="1" printf "%s\n" "#define JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS " >>confdefs.h printf "%s\n" "#define JEMALLOC_HAS_ALLOCA_H " >>confdefs.h printf "%s\n" "#define JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY " >>confdefs.h printf "%s\n" "#define JEMALLOC_THREADED_INIT " >>confdefs.h printf "%s\n" "#define JEMALLOC_USE_CXX_THROW " >>confdefs.h if test "${LG_SIZEOF_PTR}" = "3"; then default_retain="1" fi zero_realloc_default_free="1" ;; *-*-kfreebsd*) T_APPEND_V=-D_GNU_SOURCE if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi abi="elf" printf "%s\n" "#define JEMALLOC_HAS_ALLOCA_H " >>confdefs.h printf "%s\n" "#define JEMALLOC_SYSCTL_VM_OVERCOMMIT " >>confdefs.h printf "%s\n" "#define JEMALLOC_THREADED_INIT " >>confdefs.h printf "%s\n" "#define JEMALLOC_USE_CXX_THROW " >>confdefs.h ;; *-*-netbsd*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking ABI" >&5 printf %s "checking ABI... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #ifdef __ELF__ /* ELF */ #else #error aout #endif int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : abi="elf" else $as_nop abi="aout" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $abi" >&5 printf "%s\n" "$abi" >&6; } ;; *-*-solaris2*) abi="elf" RPATH='-Wl,-R,$(1)' T_APPEND_V=-D_POSIX_PTHREAD_SEMANTICS if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi T_APPEND_V=-lposix4 -lsocket -lnsl if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi ;; *-ibm-aix*) if test "${LG_SIZEOF_PTR}" = "3"; then LD_PRELOAD_VAR="LDR_PRELOAD64" else LD_PRELOAD_VAR="LDR_PRELOAD" fi abi="xcoff" ;; *-*-mingw* | *-*-cygwin*) abi="pecoff" force_tls="0" maps_coalesce="0" RPATH="" so="dll" if test "x$je_cv_msvc" = "xyes" ; then importlib="lib" DSO_LDFLAGS="-LD" EXTRA_LDFLAGS="-link -DEBUG" CTARGET='-Fo$@' LDTARGET='-Fe$@' AR='lib' ARFLAGS='-nologo -out:' AROUT='$@' CC_MM= else importlib="${so}" DSO_LDFLAGS="-shared" link_whole_archive="1" fi case "${host}" in *-*-cygwin*) DUMP_SYMS="dumpbin /SYMBOLS" ;; *) ;; esac a="lib" libprefix="" SOREV="${so}" PIC_CFLAGS="" if test "${LG_SIZEOF_PTR}" = "3"; then default_retain="1" fi zero_realloc_default_free="1" ;; *-*-nto-qnx) abi="elf" force_tls="0" printf "%s\n" "#define JEMALLOC_HAS_ALLOCA_H " >>confdefs.h ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Unsupported operating system: ${host}" >&5 printf "%s\n" "Unsupported operating system: ${host}" >&6; } abi="elf" ;; esac JEMALLOC_USABLE_SIZE_CONST=const for ac_header in malloc.h do : ac_fn_c_check_header_compile "$LINENO" "malloc.h" "ac_cv_header_malloc_h" "$ac_includes_default" if test "x$ac_cv_header_malloc_h" = xyes then : printf "%s\n" "#define HAVE_MALLOC_H 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether malloc_usable_size definition can use const argument" >&5 printf %s "checking whether malloc_usable_size definition can use const argument... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include size_t malloc_usable_size(const void *ptr); int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop JEMALLOC_USABLE_SIZE_CONST= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi done printf "%s\n" "#define JEMALLOC_USABLE_SIZE_CONST $JEMALLOC_USABLE_SIZE_CONST" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing log" >&5 printf %s "checking for library containing log... " >&6; } if test ${ac_cv_search_log+y} then : printf %s "(cached) " >&6 else $as_nop ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char log (); int main (void) { return log (); ; return 0; } _ACEOF for ac_lib in '' m do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_log=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_log+y} then : break fi done if test ${ac_cv_search_log+y} then : else $as_nop ac_cv_search_log=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_log" >&5 printf "%s\n" "$ac_cv_search_log" >&6; } ac_res=$ac_cv_search_log if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else $as_nop as_fn_error $? "Missing math functions" "$LINENO" 5 fi if test "x$ac_cv_search_log" != "xnone required" ; then LM="$ac_cv_search_log" else LM= fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether __attribute__ syntax is compilable" >&5 printf %s "checking whether __attribute__ syntax is compilable... " >&6; } if test ${je_cv_attribute+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ static __attribute__((unused)) void foo(void){} int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_attribute=yes else $as_nop je_cv_attribute=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_attribute" >&5 printf "%s\n" "$je_cv_attribute" >&6; } if test "x${je_cv_attribute}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR " >>confdefs.h if test "x${GCC}" = "xyes" -a "x${abi}" = "xelf"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fvisibility=hidden" >&5 printf %s "checking whether compiler supports -fvisibility=hidden... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-fvisibility=hidden if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-fvisibility=hidden { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fvisibility=hidden" >&5 printf %s "checking whether compiler supports -fvisibility=hidden... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-fvisibility=hidden if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-fvisibility=hidden { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi fi fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether tls_model attribute is compilable" >&5 printf %s "checking whether tls_model attribute is compilable... " >&6; } if test ${je_cv_tls_model+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { static __thread int __attribute__((tls_model("initial-exec"), unused)) foo; foo = 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_tls_model=yes else $as_nop je_cv_tls_model=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_tls_model" >&5 printf "%s\n" "$je_cv_tls_model" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether alloc_size attribute is compilable" >&5 printf %s "checking whether alloc_size attribute is compilable... " >&6; } if test ${je_cv_alloc_size+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { void *foo(size_t size) __attribute__((alloc_size(1))); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_alloc_size=yes else $as_nop je_cv_alloc_size=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_alloc_size" >&5 printf "%s\n" "$je_cv_alloc_size" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_alloc_size}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR_ALLOC_SIZE " >>confdefs.h fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether format(gnu_printf, ...) attribute is compilable" >&5 printf %s "checking whether format(gnu_printf, ...) attribute is compilable... " >&6; } if test ${je_cv_format_gnu_printf+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { void *foo(const char *format, ...) __attribute__((format(gnu_printf, 1, 2))); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_format_gnu_printf=yes else $as_nop je_cv_format_gnu_printf=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_format_gnu_printf" >&5 printf "%s\n" "$je_cv_format_gnu_printf" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_format_gnu_printf}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF " >>confdefs.h fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether format(printf, ...) attribute is compilable" >&5 printf %s "checking whether format(printf, ...) attribute is compilable... " >&6; } if test ${je_cv_format_printf+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { void *foo(const char *format, ...) __attribute__((format(printf, 1, 2))); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_format_printf=yes else $as_nop je_cv_format_printf=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_format_printf" >&5 printf "%s\n" "$je_cv_format_printf" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_format_printf}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR_FORMAT_PRINTF " >>confdefs.h fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether format(printf, ...) attribute is compilable" >&5 printf %s "checking whether format(printf, ...) attribute is compilable... " >&6; } if test ${je_cv_format_arg+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { const char * __attribute__((__format_arg__(1))) foo(const char *format); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_format_arg=yes else $as_nop je_cv_format_arg=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_format_arg" >&5 printf "%s\n" "$je_cv_format_arg" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_format_arg}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR_FORMAT_ARG " >>confdefs.h fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wimplicit-fallthrough" >&5 printf %s "checking whether compiler supports -Wimplicit-fallthrough... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wimplicit-fallthrough if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wimplicit-fallthrough { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether fallthrough attribute is compilable" >&5 printf %s "checking whether fallthrough attribute is compilable... " >&6; } if test ${je_cv_fallthrough+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #if !__has_attribute(fallthrough) #error "foo" #endif int main (void) { int x = 0; switch (x) { case 0: __attribute__((__fallthrough__)); case 1: return 1; } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_fallthrough=yes else $as_nop je_cv_fallthrough=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_fallthrough" >&5 printf "%s\n" "$je_cv_fallthrough" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_fallthrough}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR_FALLTHROUGH " >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wimplicit-fallthrough" >&5 printf %s "checking whether compiler supports -Wimplicit-fallthrough... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Wimplicit-fallthrough if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Wimplicit-fallthrough { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wimplicit-fallthrough" >&5 printf %s "checking whether compiler supports -Wimplicit-fallthrough... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-Wimplicit-fallthrough if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-Wimplicit-fallthrough { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether cold attribute is compilable" >&5 printf %s "checking whether cold attribute is compilable... " >&6; } if test ${je_cv_cold+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { __attribute__((__cold__)) void foo(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_cold=yes else $as_nop je_cv_cold=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_cold" >&5 printf "%s\n" "$je_cv_cold" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_cold}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_ATTR_COLD " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether vm_make_tag is compilable" >&5 printf %s "checking whether vm_make_tag is compilable... " >&6; } if test ${je_cv_vm_make_tag+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { void *p; p = mmap(0, 16, PROT_READ, MAP_ANON|MAP_PRIVATE, VM_MAKE_TAG(1), 0); munmap(p, 16); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_vm_make_tag=yes else $as_nop je_cv_vm_make_tag=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_vm_make_tag" >&5 printf "%s\n" "$je_cv_vm_make_tag" >&6; } if test "x${je_cv_vm_make_tag}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_VM_MAKE_TAG " >>confdefs.h fi # Check whether --with-rpath was given. if test ${with_rpath+y} then : withval=$with_rpath; if test "x$with_rpath" = "xno" ; then RPATH_EXTRA= else RPATH_EXTRA="`echo $with_rpath | tr \":\" \" \"`" fi else $as_nop RPATH_EXTRA= fi # Check whether --enable-autogen was given. if test ${enable_autogen+y} then : enableval=$enable_autogen; if test "x$enable_autogen" = "xno" ; then enable_autogen="0" else enable_autogen="1" fi else $as_nop enable_autogen="0" fi # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf "%s\n" "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RANLIB+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 printf "%s\n" "$RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_RANLIB+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 printf "%s\n" "$ac_ct_RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi # Extract the first word of "ld", so it can be a program name with args. set dummy ld; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_LD+y} then : printf %s "(cached) " >&6 else $as_nop case $LD in [\\/]* | ?:[\\/]*) ac_cv_path_LD="$LD" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_LD="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_LD" && ac_cv_path_LD="false" ;; esac fi LD=$ac_cv_path_LD if test -n "$LD"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LD" >&5 printf "%s\n" "$LD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi # Extract the first word of "autoconf", so it can be a program name with args. set dummy autoconf; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_AUTOCONF+y} then : printf %s "(cached) " >&6 else $as_nop case $AUTOCONF in [\\/]* | ?:[\\/]*) ac_cv_path_AUTOCONF="$AUTOCONF" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_AUTOCONF="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_AUTOCONF" && ac_cv_path_AUTOCONF="false" ;; esac fi AUTOCONF=$ac_cv_path_AUTOCONF if test -n "$AUTOCONF"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AUTOCONF" >&5 printf "%s\n" "$AUTOCONF" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi # Check whether --enable-doc was given. if test ${enable_doc+y} then : enableval=$enable_doc; if test "x$enable_doc" = "xno" ; then enable_doc="0" else enable_doc="1" fi else $as_nop enable_doc="1" fi # Check whether --enable-shared was given. if test ${enable_shared+y} then : enableval=$enable_shared; if test "x$enable_shared" = "xno" ; then enable_shared="0" else enable_shared="1" fi else $as_nop enable_shared="1" fi # Check whether --enable-static was given. if test ${enable_static+y} then : enableval=$enable_static; if test "x$enable_static" = "xno" ; then enable_static="0" else enable_static="1" fi else $as_nop enable_static="1" fi if test "$enable_shared$enable_static" = "00" ; then as_fn_error $? "Please enable one of shared or static builds" "$LINENO" 5 fi # Check whether --with-mangling was given. if test ${with_mangling+y} then : withval=$with_mangling; mangling_map="$with_mangling" else $as_nop mangling_map="" fi # Check whether --with-jemalloc_prefix was given. if test ${with_jemalloc_prefix+y} then : withval=$with_jemalloc_prefix; JEMALLOC_PREFIX="$with_jemalloc_prefix" else $as_nop if test "x$abi" != "xmacho" -a "x$abi" != "xpecoff"; then JEMALLOC_PREFIX="" else JEMALLOC_PREFIX="je_" fi fi if test "x$JEMALLOC_PREFIX" = "x" ; then printf "%s\n" "#define JEMALLOC_IS_MALLOC " >>confdefs.h else JEMALLOC_CPREFIX=`echo ${JEMALLOC_PREFIX} | tr "a-z" "A-Z"` printf "%s\n" "#define JEMALLOC_PREFIX \"$JEMALLOC_PREFIX\"" >>confdefs.h printf "%s\n" "#define JEMALLOC_CPREFIX \"$JEMALLOC_CPREFIX\"" >>confdefs.h fi # Check whether --with-export was given. if test ${with_export+y} then : withval=$with_export; if test "x$with_export" = "xno"; then printf "%s\n" "#define JEMALLOC_EXPORT /**/" >>confdefs.h fi fi public_syms="aligned_alloc calloc dallocx free mallctl mallctlbymib mallctlnametomib malloc malloc_conf malloc_conf_2_conf_harder malloc_message malloc_stats_print malloc_usable_size mallocx smallocx_${jemalloc_version_gid} nallocx posix_memalign rallocx realloc sallocx sdallocx xallocx" ac_fn_c_check_func "$LINENO" "memalign" "ac_cv_func_memalign" if test "x$ac_cv_func_memalign" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE_MEMALIGN " >>confdefs.h public_syms="${public_syms} memalign" fi ac_fn_c_check_func "$LINENO" "valloc" "ac_cv_func_valloc" if test "x$ac_cv_func_valloc" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE_VALLOC " >>confdefs.h public_syms="${public_syms} valloc" fi ac_fn_c_check_func "$LINENO" "malloc_size" "ac_cv_func_malloc_size" if test "x$ac_cv_func_malloc_size" = xyes then : printf "%s\n" "#define JEMALLOC_HAVE_MALLOC_SIZE " >>confdefs.h public_syms="${public_syms} malloc_size" fi wrap_syms= if test "x${JEMALLOC_PREFIX}" = "x" ; then ac_fn_c_check_func "$LINENO" "__libc_calloc" "ac_cv_func___libc_calloc" if test "x$ac_cv_func___libc_calloc" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___LIBC_CALLOC " >>confdefs.h wrap_syms="${wrap_syms} __libc_calloc" fi ac_fn_c_check_func "$LINENO" "__libc_free" "ac_cv_func___libc_free" if test "x$ac_cv_func___libc_free" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___LIBC_FREE " >>confdefs.h wrap_syms="${wrap_syms} __libc_free" fi ac_fn_c_check_func "$LINENO" "__libc_malloc" "ac_cv_func___libc_malloc" if test "x$ac_cv_func___libc_malloc" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___LIBC_MALLOC " >>confdefs.h wrap_syms="${wrap_syms} __libc_malloc" fi ac_fn_c_check_func "$LINENO" "__libc_memalign" "ac_cv_func___libc_memalign" if test "x$ac_cv_func___libc_memalign" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___LIBC_MEMALIGN " >>confdefs.h wrap_syms="${wrap_syms} __libc_memalign" fi ac_fn_c_check_func "$LINENO" "__libc_realloc" "ac_cv_func___libc_realloc" if test "x$ac_cv_func___libc_realloc" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___LIBC_REALLOC " >>confdefs.h wrap_syms="${wrap_syms} __libc_realloc" fi ac_fn_c_check_func "$LINENO" "__libc_valloc" "ac_cv_func___libc_valloc" if test "x$ac_cv_func___libc_valloc" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___LIBC_VALLOC " >>confdefs.h wrap_syms="${wrap_syms} __libc_valloc" fi ac_fn_c_check_func "$LINENO" "__posix_memalign" "ac_cv_func___posix_memalign" if test "x$ac_cv_func___posix_memalign" = xyes then : printf "%s\n" "#define JEMALLOC_OVERRIDE___POSIX_MEMALIGN " >>confdefs.h wrap_syms="${wrap_syms} __posix_memalign" fi fi case "${host}" in *-*-mingw* | *-*-cygwin*) wrap_syms="${wrap_syms} tls_callback" ;; *) ;; esac # Check whether --with-private_namespace was given. if test ${with_private_namespace+y} then : withval=$with_private_namespace; JEMALLOC_PRIVATE_NAMESPACE="${with_private_namespace}je_" else $as_nop JEMALLOC_PRIVATE_NAMESPACE="je_" fi printf "%s\n" "#define JEMALLOC_PRIVATE_NAMESPACE $JEMALLOC_PRIVATE_NAMESPACE" >>confdefs.h private_namespace="$JEMALLOC_PRIVATE_NAMESPACE" # Check whether --with-install_suffix was given. if test ${with_install_suffix+y} then : withval=$with_install_suffix; case "$with_install_suffix" in *\ * ) as_fn_error $? "Install suffix should not contain spaces" "$LINENO" 5 ;; * ) INSTALL_SUFFIX="$with_install_suffix" ;; esac else $as_nop INSTALL_SUFFIX= fi install_suffix="$INSTALL_SUFFIX" # Check whether --with-malloc_conf was given. if test ${with_malloc_conf+y} then : withval=$with_malloc_conf; JEMALLOC_CONFIG_MALLOC_CONF="$with_malloc_conf" else $as_nop JEMALLOC_CONFIG_MALLOC_CONF="" fi config_malloc_conf="$JEMALLOC_CONFIG_MALLOC_CONF" printf "%s\n" "#define JEMALLOC_CONFIG_MALLOC_CONF \"$config_malloc_conf\"" >>confdefs.h je_="je_" cfgoutputs_in="Makefile.in" cfgoutputs_in="${cfgoutputs_in} jemalloc.pc.in" cfgoutputs_in="${cfgoutputs_in} doc/html.xsl.in" cfgoutputs_in="${cfgoutputs_in} doc/manpages.xsl.in" cfgoutputs_in="${cfgoutputs_in} doc/jemalloc.xml.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_macros.h.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_protos.h.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_typedefs.h.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/internal/jemalloc_preamble.h.in" cfgoutputs_in="${cfgoutputs_in} test/test.sh.in" cfgoutputs_in="${cfgoutputs_in} test/include/test/jemalloc_test.h.in" cfgoutputs_out="Makefile" cfgoutputs_out="${cfgoutputs_out} jemalloc.pc" cfgoutputs_out="${cfgoutputs_out} doc/html.xsl" cfgoutputs_out="${cfgoutputs_out} doc/manpages.xsl" cfgoutputs_out="${cfgoutputs_out} doc/jemalloc.xml" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_macros.h" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_protos.h" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_typedefs.h" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/internal/jemalloc_preamble.h" cfgoutputs_out="${cfgoutputs_out} test/test.sh" cfgoutputs_out="${cfgoutputs_out} test/include/test/jemalloc_test.h" cfgoutputs_tup="Makefile" cfgoutputs_tup="${cfgoutputs_tup} jemalloc.pc:jemalloc.pc.in" cfgoutputs_tup="${cfgoutputs_tup} doc/html.xsl:doc/html.xsl.in" cfgoutputs_tup="${cfgoutputs_tup} doc/manpages.xsl:doc/manpages.xsl.in" cfgoutputs_tup="${cfgoutputs_tup} doc/jemalloc.xml:doc/jemalloc.xml.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_macros.h:include/jemalloc/jemalloc_macros.h.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_protos.h:include/jemalloc/jemalloc_protos.h.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_typedefs.h:include/jemalloc/jemalloc_typedefs.h.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/internal/jemalloc_preamble.h" cfgoutputs_tup="${cfgoutputs_tup} test/test.sh:test/test.sh.in" cfgoutputs_tup="${cfgoutputs_tup} test/include/test/jemalloc_test.h:test/include/test/jemalloc_test.h.in" cfghdrs_in="include/jemalloc/jemalloc_defs.h.in" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/jemalloc_internal_defs.h.in" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_symbols.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_namespace.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_namespace.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_unnamespace.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/jemalloc_rename.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/jemalloc_mangle.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/jemalloc.sh" cfghdrs_in="${cfghdrs_in} test/include/test/jemalloc_test_defs.h.in" cfghdrs_out="include/jemalloc/jemalloc_defs.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc${install_suffix}.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols.awk" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols_jet.awk" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_symbols.txt" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_namespace.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_unnamespace.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_protos_jet.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_rename.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_mangle.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_mangle_jet.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/jemalloc_internal_defs.h" cfghdrs_out="${cfghdrs_out} test/include/test/jemalloc_test_defs.h" cfghdrs_tup="include/jemalloc/jemalloc_defs.h:include/jemalloc/jemalloc_defs.h.in" cfghdrs_tup="${cfghdrs_tup} include/jemalloc/internal/jemalloc_internal_defs.h:include/jemalloc/internal/jemalloc_internal_defs.h.in" cfghdrs_tup="${cfghdrs_tup} test/include/test/jemalloc_test_defs.h:test/include/test/jemalloc_test_defs.h.in" # Check whether --enable-debug was given. if test ${enable_debug+y} then : enableval=$enable_debug; if test "x$enable_debug" = "xno" ; then enable_debug="0" else enable_debug="1" fi else $as_nop enable_debug="0" fi if test "x$enable_debug" = "x1" ; then printf "%s\n" "#define JEMALLOC_DEBUG " >>confdefs.h fi if test "x$enable_debug" = "x0" ; then if test "x$GCC" = "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O3" >&5 printf %s "checking whether compiler supports -O3... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-O3 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-O3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O3" >&5 printf %s "checking whether compiler supports -O3... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-O3 if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-O3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -funroll-loops" >&5 printf %s "checking whether compiler supports -funroll-loops... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-funroll-loops if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-funroll-loops { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi elif test "x$je_cv_msvc" = "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O2" >&5 printf %s "checking whether compiler supports -O2... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-O2 if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-O2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O2" >&5 printf %s "checking whether compiler supports -O2... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-O2 if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-O2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O" >&5 printf %s "checking whether compiler supports -O... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-O if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-O { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O" >&5 printf %s "checking whether compiler supports -O... " >&6; } T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" T_APPEND_V=-O if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}" else CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : je_cv_cxxflags_added=-O { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cxxflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}" else CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}" fi fi fi # Check whether --enable-stats was given. if test ${enable_stats+y} then : enableval=$enable_stats; if test "x$enable_stats" = "xno" ; then enable_stats="0" else enable_stats="1" fi else $as_nop enable_stats="1" fi if test "x$enable_stats" = "x1" ; then printf "%s\n" "#define JEMALLOC_STATS " >>confdefs.h fi # Check whether --enable-experimental_smallocx was given. if test ${enable_experimental_smallocx+y} then : enableval=$enable_experimental_smallocx; if test "x$enable_experimental_smallocx" = "xno" ; then enable_experimental_smallocx="0" else enable_experimental_smallocx="1" fi else $as_nop enable_experimental_smallocx="0" fi if test "x$enable_experimental_smallocx" = "x1" ; then printf "%s\n" "#define JEMALLOC_EXPERIMENTAL_SMALLOCX_API " >>confdefs.h fi # Check whether --enable-prof was given. if test ${enable_prof+y} then : enableval=$enable_prof; if test "x$enable_prof" = "xno" ; then enable_prof="0" else enable_prof="1" fi else $as_nop enable_prof="0" fi if test "x$enable_prof" = "x1" ; then backtrace_method="" else backtrace_method="N/A" fi # Check whether --enable-prof-libunwind was given. if test ${enable_prof_libunwind+y} then : enableval=$enable_prof_libunwind; if test "x$enable_prof_libunwind" = "xno" ; then enable_prof_libunwind="0" else enable_prof_libunwind="1" if test "x$enable_prof" = "x0" ; then as_fn_error $? "--enable-prof-libunwind should only be used with --enable-prof" "$LINENO" 5 fi fi else $as_nop enable_prof_libunwind="0" fi # Check whether --with-static_libunwind was given. if test ${with_static_libunwind+y} then : withval=$with_static_libunwind; if test "x$with_static_libunwind" = "xno" ; then LUNWIND="-lunwind" else if test ! -f "$with_static_libunwind" ; then as_fn_error $? "Static libunwind not found: $with_static_libunwind" "$LINENO" 5 fi LUNWIND="$with_static_libunwind" fi else $as_nop LUNWIND="-lunwind" fi if test "x$backtrace_method" = "x" -a "x$enable_prof_libunwind" = "x1" ; then for ac_header in libunwind.h do : ac_fn_c_check_header_compile "$LINENO" "libunwind.h" "ac_cv_header_libunwind_h" "$ac_includes_default" if test "x$ac_cv_header_libunwind_h" = xyes then : printf "%s\n" "#define HAVE_LIBUNWIND_H 1" >>confdefs.h else $as_nop enable_prof_libunwind="0" fi done if test "x$LUNWIND" = "x-lunwind" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for unw_backtrace in -lunwind" >&5 printf %s "checking for unw_backtrace in -lunwind... " >&6; } if test ${ac_cv_lib_unwind_unw_backtrace+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lunwind $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char unw_backtrace (); int main (void) { return unw_backtrace (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_unwind_unw_backtrace=yes else $as_nop ac_cv_lib_unwind_unw_backtrace=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_unwind_unw_backtrace" >&5 printf "%s\n" "$ac_cv_lib_unwind_unw_backtrace" >&6; } if test "x$ac_cv_lib_unwind_unw_backtrace" = xyes then : T_APPEND_V=$LUNWIND if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi else $as_nop enable_prof_libunwind="0" fi else T_APPEND_V=$LUNWIND if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi fi if test "x${enable_prof_libunwind}" = "x1" ; then backtrace_method="libunwind" printf "%s\n" "#define JEMALLOC_PROF_LIBUNWIND " >>confdefs.h fi fi # Check whether --enable-prof-libgcc was given. if test ${enable_prof_libgcc+y} then : enableval=$enable_prof_libgcc; if test "x$enable_prof_libgcc" = "xno" ; then enable_prof_libgcc="0" else enable_prof_libgcc="1" fi else $as_nop enable_prof_libgcc="1" fi if test "x$backtrace_method" = "x" -a "x$enable_prof_libgcc" = "x1" \ -a "x$GCC" = "xyes" ; then for ac_header in unwind.h do : ac_fn_c_check_header_compile "$LINENO" "unwind.h" "ac_cv_header_unwind_h" "$ac_includes_default" if test "x$ac_cv_header_unwind_h" = xyes then : printf "%s\n" "#define HAVE_UNWIND_H 1" >>confdefs.h else $as_nop enable_prof_libgcc="0" fi done if test "x${enable_prof_libgcc}" = "x1" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _Unwind_Backtrace in -lgcc" >&5 printf %s "checking for _Unwind_Backtrace in -lgcc... " >&6; } if test ${ac_cv_lib_gcc__Unwind_Backtrace+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lgcc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char _Unwind_Backtrace (); int main (void) { return _Unwind_Backtrace (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_gcc__Unwind_Backtrace=yes else $as_nop ac_cv_lib_gcc__Unwind_Backtrace=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gcc__Unwind_Backtrace" >&5 printf "%s\n" "$ac_cv_lib_gcc__Unwind_Backtrace" >&6; } if test "x$ac_cv_lib_gcc__Unwind_Backtrace" = xyes then : T_APPEND_V=-lgcc if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi else $as_nop enable_prof_libgcc="0" fi fi if test "x${enable_prof_libgcc}" = "x1" ; then backtrace_method="libgcc" printf "%s\n" "#define JEMALLOC_PROF_LIBGCC " >>confdefs.h fi else enable_prof_libgcc="0" fi # Check whether --enable-prof-gcc was given. if test ${enable_prof_gcc+y} then : enableval=$enable_prof_gcc; if test "x$enable_prof_gcc" = "xno" ; then enable_prof_gcc="0" else enable_prof_gcc="1" fi else $as_nop enable_prof_gcc="1" fi if test "x$backtrace_method" = "x" -a "x$enable_prof_gcc" = "x1" \ -a "x$GCC" = "xyes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fno-omit-frame-pointer" >&5 printf %s "checking whether compiler supports -fno-omit-frame-pointer... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-fno-omit-frame-pointer if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-fno-omit-frame-pointer { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi backtrace_method="gcc intrinsics" printf "%s\n" "#define JEMALLOC_PROF_GCC " >>confdefs.h else enable_prof_gcc="0" fi if test "x$backtrace_method" = "x" ; then backtrace_method="none (disabling profiling)" enable_prof="0" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking configured backtracing method" >&5 printf %s "checking configured backtracing method... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $backtrace_method" >&5 printf "%s\n" "$backtrace_method" >&6; } if test "x$enable_prof" = "x1" ; then T_APPEND_V=$LM if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi printf "%s\n" "#define JEMALLOC_PROF " >>confdefs.h fi if test "x${maps_coalesce}" = "x1" ; then printf "%s\n" "#define JEMALLOC_MAPS_COALESCE " >>confdefs.h fi if test "x$default_retain" = "x1" ; then printf "%s\n" "#define JEMALLOC_RETAIN " >>confdefs.h fi if test "x$zero_realloc_default_free" = "x1" ; then printf "%s\n" "#define JEMALLOC_ZERO_REALLOC_DEFAULT_FREE " >>confdefs.h fi have_dss="1" ac_fn_c_check_func "$LINENO" "sbrk" "ac_cv_func_sbrk" if test "x$ac_cv_func_sbrk" = xyes then : have_sbrk="1" else $as_nop have_sbrk="0" fi if test "x$have_sbrk" = "x1" ; then if test "x$sbrk_deprecated" = "x1" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Disabling dss allocation because sbrk is deprecated" >&5 printf "%s\n" "Disabling dss allocation because sbrk is deprecated" >&6; } have_dss="0" fi else have_dss="0" fi if test "x$have_dss" = "x1" ; then printf "%s\n" "#define JEMALLOC_DSS " >>confdefs.h fi # Check whether --enable-fill was given. if test ${enable_fill+y} then : enableval=$enable_fill; if test "x$enable_fill" = "xno" ; then enable_fill="0" else enable_fill="1" fi else $as_nop enable_fill="1" fi if test "x$enable_fill" = "x1" ; then printf "%s\n" "#define JEMALLOC_FILL " >>confdefs.h fi # Check whether --enable-utrace was given. if test ${enable_utrace+y} then : enableval=$enable_utrace; if test "x$enable_utrace" = "xno" ; then enable_utrace="0" else enable_utrace="1" fi else $as_nop enable_utrace="0" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether utrace(2) is compilable" >&5 printf %s "checking whether utrace(2) is compilable... " >&6; } if test ${je_cv_utrace+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include #include int main (void) { utrace((void *)0, 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_utrace=yes else $as_nop je_cv_utrace=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_utrace" >&5 printf "%s\n" "$je_cv_utrace" >&6; } if test "x${je_cv_utrace}" = "xno" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether utrace(2) with label is compilable" >&5 printf %s "checking whether utrace(2) with label is compilable... " >&6; } if test ${je_cv_utrace_label+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include #include int main (void) { utrace((void *)0, (void *)0, 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_utrace_label=yes else $as_nop je_cv_utrace_label=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_utrace_label" >&5 printf "%s\n" "$je_cv_utrace_label" >&6; } if test "x${je_cv_utrace_label}" = "xno"; then enable_utrace="0" fi if test "x$enable_utrace" = "x1" ; then printf "%s\n" "#define JEMALLOC_UTRACE_LABEL " >>confdefs.h fi else if test "x$enable_utrace" = "x1" ; then printf "%s\n" "#define JEMALLOC_UTRACE " >>confdefs.h fi fi # Check whether --enable-xmalloc was given. if test ${enable_xmalloc+y} then : enableval=$enable_xmalloc; if test "x$enable_xmalloc" = "xno" ; then enable_xmalloc="0" else enable_xmalloc="1" fi else $as_nop enable_xmalloc="0" fi if test "x$enable_xmalloc" = "x1" ; then printf "%s\n" "#define JEMALLOC_XMALLOC " >>confdefs.h fi # Check whether --enable-cache-oblivious was given. if test ${enable_cache_oblivious+y} then : enableval=$enable_cache_oblivious; if test "x$enable_cache_oblivious" = "xno" ; then enable_cache_oblivious="0" else enable_cache_oblivious="1" fi else $as_nop enable_cache_oblivious="1" fi if test "x$enable_cache_oblivious" = "x1" ; then printf "%s\n" "#define JEMALLOC_CACHE_OBLIVIOUS " >>confdefs.h fi # Check whether --enable-log was given. if test ${enable_log+y} then : enableval=$enable_log; if test "x$enable_log" = "xno" ; then enable_log="0" else enable_log="1" fi else $as_nop enable_log="0" fi if test "x$enable_log" = "x1" ; then printf "%s\n" "#define JEMALLOC_LOG " >>confdefs.h fi # Check whether --enable-readlinkat was given. if test ${enable_readlinkat+y} then : enableval=$enable_readlinkat; if test "x$enable_readlinkat" = "xno" ; then enable_readlinkat="0" else enable_readlinkat="1" fi else $as_nop enable_readlinkat="0" fi if test "x$enable_readlinkat" = "x1" ; then printf "%s\n" "#define JEMALLOC_READLINKAT " >>confdefs.h fi # Check whether --enable-opt-safety-checks was given. if test ${enable_opt_safety_checks+y} then : enableval=$enable_opt_safety_checks; if test "x$enable_opt_safety_checks" = "xno" ; then enable_opt_safety_checks="0" else enable_opt_safety_checks="1" fi else $as_nop enable_opt_safety_checks="0" fi if test "x$enable_opt_safety_checks" = "x1" ; then printf "%s\n" "#define JEMALLOC_OPT_SAFETY_CHECKS " >>confdefs.h fi # Check whether --enable-opt-size-checks was given. if test ${enable_opt_size_checks+y} then : enableval=$enable_opt_size_checks; if test "x$enable_opt_size_checks" = "xno" ; then enable_opt_size_checks="0" else enable_opt_size_checks="1" fi else $as_nop enable_opt_size_checks="0" fi if test "x$enable_opt_size_checks" = "x1" ; then printf "%s\n" "#define JEMALLOC_OPT_SIZE_CHECKS " >>confdefs.h fi # Check whether --enable-uaf-detection was given. if test ${enable_uaf_detection+y} then : enableval=$enable_uaf_detection; if test "x$enable_uaf_detection" = "xno" ; then enable_uaf_detection="0" else enable_uaf_detection="1" fi else $as_nop enable_uaf_detection="0" fi if test "x$enable_uaf_detection" = "x1" ; then printf "%s\n" "#define JEMALLOC_UAF_DETECTION " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a program using __builtin_unreachable is compilable" >&5 printf %s "checking whether a program using __builtin_unreachable is compilable... " >&6; } if test ${je_cv_gcc_builtin_unreachable+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ void foo (void) { __builtin_unreachable(); } int main (void) { { foo(); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_builtin_unreachable=yes else $as_nop je_cv_gcc_builtin_unreachable=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_builtin_unreachable" >&5 printf "%s\n" "$je_cv_gcc_builtin_unreachable" >&6; } if test "x${je_cv_gcc_builtin_unreachable}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_INTERNAL_UNREACHABLE __builtin_unreachable" >>confdefs.h else printf "%s\n" "#define JEMALLOC_INTERNAL_UNREACHABLE abort" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a program using __builtin_ffsl is compilable" >&5 printf %s "checking whether a program using __builtin_ffsl is compilable... " >&6; } if test ${je_cv_gcc_builtin_ffsl+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { { int rv = __builtin_ffsl(0x08); printf("%d\n", rv); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_builtin_ffsl=yes else $as_nop je_cv_gcc_builtin_ffsl=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_builtin_ffsl" >&5 printf "%s\n" "$je_cv_gcc_builtin_ffsl" >&6; } if test "x${je_cv_gcc_builtin_ffsl}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_INTERNAL_FFSLL __builtin_ffsll" >>confdefs.h printf "%s\n" "#define JEMALLOC_INTERNAL_FFSL __builtin_ffsl" >>confdefs.h printf "%s\n" "#define JEMALLOC_INTERNAL_FFS __builtin_ffs" >>confdefs.h else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a program using ffsl is compilable" >&5 printf %s "checking whether a program using ffsl is compilable... " >&6; } if test ${je_cv_function_ffsl+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { { int rv = ffsl(0x08); printf("%d\n", rv); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_function_ffsl=yes else $as_nop je_cv_function_ffsl=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_function_ffsl" >&5 printf "%s\n" "$je_cv_function_ffsl" >&6; } if test "x${je_cv_function_ffsl}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_INTERNAL_FFSLL ffsll" >>confdefs.h printf "%s\n" "#define JEMALLOC_INTERNAL_FFSL ffsl" >>confdefs.h printf "%s\n" "#define JEMALLOC_INTERNAL_FFS ffs" >>confdefs.h else as_fn_error $? "Cannot build without ffsl(3) or __builtin_ffsl()" "$LINENO" 5 fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a program using __builtin_popcountl is compilable" >&5 printf %s "checking whether a program using __builtin_popcountl is compilable... " >&6; } if test ${je_cv_gcc_builtin_popcountl+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { { int rv = __builtin_popcountl(0x08); printf("%d\n", rv); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_builtin_popcountl=yes else $as_nop je_cv_gcc_builtin_popcountl=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_builtin_popcountl" >&5 printf "%s\n" "$je_cv_gcc_builtin_popcountl" >&6; } if test "x${je_cv_gcc_builtin_popcountl}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_INTERNAL_POPCOUNT __builtin_popcount" >>confdefs.h printf "%s\n" "#define JEMALLOC_INTERNAL_POPCOUNTL __builtin_popcountl" >>confdefs.h printf "%s\n" "#define JEMALLOC_INTERNAL_POPCOUNTLL __builtin_popcountll" >>confdefs.h fi # Check whether --with-lg_quantum was given. if test ${with_lg_quantum+y} then : withval=$with_lg_quantum; fi if test "x$with_lg_quantum" != "x" ; then printf "%s\n" "#define LG_QUANTUM $with_lg_quantum" >>confdefs.h fi # Check whether --with-lg_slab_maxregs was given. if test ${with_lg_slab_maxregs+y} then : withval=$with_lg_slab_maxregs; CONFIG_LG_SLAB_MAXREGS="with_lg_slab_maxregs" else $as_nop CONFIG_LG_SLAB_MAXREGS="" fi if test "x$with_lg_slab_maxregs" != "x" ; then printf "%s\n" "#define CONFIG_LG_SLAB_MAXREGS $with_lg_slab_maxregs" >>confdefs.h fi # Check whether --with-lg_page was given. if test ${with_lg_page+y} then : withval=$with_lg_page; LG_PAGE="$with_lg_page" else $as_nop LG_PAGE="detect" fi case "${host}" in aarch64-apple-darwin*) if test "x${host}" != "x${build}" -a "x$LG_PAGE" = "xdetect"; then LG_PAGE=14 fi ;; esac if test "x$LG_PAGE" = "xdetect"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking LG_PAGE" >&5 printf %s "checking LG_PAGE... " >&6; } if test ${je_cv_lg_page+y} then : printf %s "(cached) " >&6 else $as_nop if test "$cross_compiling" = yes then : je_cv_lg_page=12 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifdef _WIN32 #include #else #include #endif #include int main (void) { int result; FILE *f; #ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); result = si.dwPageSize; #else result = sysconf(_SC_PAGESIZE); #endif if (result == -1) { return 1; } result = JEMALLOC_INTERNAL_FFSL(result) - 1; f = fopen("conftest.out", "w"); if (f == NULL) { return 1; } fprintf(f, "%d", result); fclose(f); return 0; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : je_cv_lg_page=`cat conftest.out` else $as_nop je_cv_lg_page=undefined fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_lg_page" >&5 printf "%s\n" "$je_cv_lg_page" >&6; } fi if test "x${je_cv_lg_page}" != "x" ; then LG_PAGE="${je_cv_lg_page}" fi if test "x${LG_PAGE}" != "xundefined" ; then printf "%s\n" "#define LG_PAGE $LG_PAGE" >>confdefs.h else as_fn_error $? "cannot determine value for LG_PAGE" "$LINENO" 5 fi # Check whether --with-lg_hugepage was given. if test ${with_lg_hugepage+y} then : withval=$with_lg_hugepage; je_cv_lg_hugepage="${with_lg_hugepage}" else $as_nop je_cv_lg_hugepage="" fi if test "x${je_cv_lg_hugepage}" = "x" ; then if test -e "/proc/meminfo" ; then hpsk=`cat /proc/meminfo 2>/dev/null | \ grep -e '^Hugepagesize:[[:space:]]\+[0-9]\+[[:space:]]kB$' | \ awk '{print $2}'` if test "x${hpsk}" != "x" ; then je_cv_lg_hugepage=10 while test "${hpsk}" -gt 1 ; do hpsk="$((hpsk / 2))" je_cv_lg_hugepage="$((je_cv_lg_hugepage + 1))" done fi fi if test "x${je_cv_lg_hugepage}" = "x" ; then je_cv_lg_hugepage=21 fi fi if test "x${LG_PAGE}" != "xundefined" -a \ "${je_cv_lg_hugepage}" -lt "${LG_PAGE}" ; then as_fn_error $? "Huge page size (2^${je_cv_lg_hugepage}) must be at least page size (2^${LG_PAGE})" "$LINENO" 5 fi printf "%s\n" "#define LG_HUGEPAGE ${je_cv_lg_hugepage}" >>confdefs.h # Check whether --enable-libdl was given. if test ${enable_libdl+y} then : enableval=$enable_libdl; if test "x$enable_libdl" = "xno" ; then enable_libdl="0" else enable_libdl="1" fi else $as_nop enable_libdl="1" fi if test "x$abi" != "xpecoff" ; then printf "%s\n" "#define JEMALLOC_HAVE_PTHREAD " >>confdefs.h for ac_header in pthread.h do : ac_fn_c_check_header_compile "$LINENO" "pthread.h" "ac_cv_header_pthread_h" "$ac_includes_default" if test "x$ac_cv_header_pthread_h" = xyes then : printf "%s\n" "#define HAVE_PTHREAD_H 1" >>confdefs.h else $as_nop as_fn_error $? "pthread.h is missing" "$LINENO" 5 fi done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 printf %s "checking for pthread_create in -lpthread... " >&6; } if test ${ac_cv_lib_pthread_pthread_create+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lpthread $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char pthread_create (); int main (void) { return pthread_create (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pthread_pthread_create=yes else $as_nop ac_cv_lib_pthread_pthread_create=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 printf "%s\n" "$ac_cv_lib_pthread_pthread_create" >&6; } if test "x$ac_cv_lib_pthread_pthread_create" = xyes then : T_APPEND_V=-pthread if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then LIBS="${LIBS}${T_APPEND_V}" else LIBS="${LIBS} ${T_APPEND_V}" fi else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_create" >&5 printf %s "checking for library containing pthread_create... " >&6; } if test ${ac_cv_search_pthread_create+y} then : printf %s "(cached) " >&6 else $as_nop ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char pthread_create (); int main (void) { return pthread_create (); ; return 0; } _ACEOF for ac_lib in '' do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_pthread_create=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_pthread_create+y} then : break fi done if test ${ac_cv_search_pthread_create+y} then : else $as_nop ac_cv_search_pthread_create=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pthread_create" >&5 printf "%s\n" "$ac_cv_search_pthread_create" >&6; } ac_res=$ac_cv_search_pthread_create if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else $as_nop as_fn_error $? "libpthread is missing" "$LINENO" 5 fi fi wrap_syms="${wrap_syms} pthread_create" have_pthread="1" if test "x$enable_libdl" = "x1" ; then have_dlsym="1" for ac_header in dlfcn.h do : ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default" if test "x$ac_cv_header_dlfcn_h" = xyes then : printf "%s\n" "#define HAVE_DLFCN_H 1" >>confdefs.h ac_fn_c_check_func "$LINENO" "dlsym" "ac_cv_func_dlsym" if test "x$ac_cv_func_dlsym" = xyes then : else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlsym in -ldl" >&5 printf %s "checking for dlsym in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlsym+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char dlsym (); int main (void) { return dlsym (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlsym=yes else $as_nop ac_cv_lib_dl_dlsym=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlsym" >&5 printf "%s\n" "$ac_cv_lib_dl_dlsym" >&6; } if test "x$ac_cv_lib_dl_dlsym" = xyes then : LIBS="$LIBS -ldl" else $as_nop have_dlsym="0" fi fi else $as_nop have_dlsym="0" fi done if test "x$have_dlsym" = "x1" ; then printf "%s\n" "#define JEMALLOC_HAVE_DLSYM " >>confdefs.h fi else have_dlsym="0" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthread_atfork(3) is compilable" >&5 printf %s "checking whether pthread_atfork(3) is compilable... " >&6; } if test ${je_cv_pthread_atfork+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { pthread_atfork((void *)0, (void *)0, (void *)0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pthread_atfork=yes else $as_nop je_cv_pthread_atfork=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_atfork" >&5 printf "%s\n" "$je_cv_pthread_atfork" >&6; } if test "x${je_cv_pthread_atfork}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_PTHREAD_ATFORK " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthread_setname_np(3) is compilable" >&5 printf %s "checking whether pthread_setname_np(3) is compilable... " >&6; } if test ${je_cv_pthread_setname_np+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { pthread_setname_np(pthread_self(), "setname_test"); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pthread_setname_np=yes else $as_nop je_cv_pthread_setname_np=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_setname_np" >&5 printf "%s\n" "$je_cv_pthread_setname_np" >&6; } if test "x${je_cv_pthread_setname_np}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_PTHREAD_SETNAME_NP " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthread_getname_np(3) is compilable" >&5 printf %s "checking whether pthread_getname_np(3) is compilable... " >&6; } if test ${je_cv_pthread_getname_np+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { { char *name = malloc(16); pthread_getname_np(pthread_self(), name, 16); free(name); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pthread_getname_np=yes else $as_nop je_cv_pthread_getname_np=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_getname_np" >&5 printf "%s\n" "$je_cv_pthread_getname_np" >&6; } if test "x${je_cv_pthread_getname_np}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_PTHREAD_GETNAME_NP " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthread_get_name_np(3) is compilable" >&5 printf %s "checking whether pthread_get_name_np(3) is compilable... " >&6; } if test ${je_cv_pthread_get_name_np+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { { char *name = malloc(16); pthread_get_name_np(pthread_self(), name, 16); free(name); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pthread_get_name_np=yes else $as_nop je_cv_pthread_get_name_np=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_get_name_np" >&5 printf "%s\n" "$je_cv_pthread_get_name_np" >&6; } if test "x${je_cv_pthread_get_name_np}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_PTHREAD_GET_NAME_NP " >>confdefs.h fi fi T_APPEND_V=-D_REENTRANT if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CPPFLAGS="${CPPFLAGS}${T_APPEND_V}" else CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5 printf %s "checking for library containing clock_gettime... " >&6; } if test ${ac_cv_search_clock_gettime+y} then : printf %s "(cached) " >&6 else $as_nop ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char clock_gettime (); int main (void) { return clock_gettime (); ; return 0; } _ACEOF for ac_lib in '' rt do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_clock_gettime=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_clock_gettime+y} then : break fi done if test ${ac_cv_search_clock_gettime+y} then : else $as_nop ac_cv_search_clock_gettime=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_clock_gettime" >&5 printf "%s\n" "$ac_cv_search_clock_gettime" >&6; } ac_res=$ac_cv_search_clock_gettime if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then if test "$ac_cv_search_clock_gettime" != "-lrt"; then SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" unset ac_cv_search_clock_gettime { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -dynamic" >&5 printf %s "checking whether compiler supports -dynamic... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-dynamic if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-dynamic { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5 printf %s "checking for library containing clock_gettime... " >&6; } if test ${ac_cv_search_clock_gettime+y} then : printf %s "(cached) " >&6 else $as_nop ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char clock_gettime (); int main (void) { return clock_gettime (); ; return 0; } _ACEOF for ac_lib in '' rt do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi if ac_fn_c_try_link "$LINENO" then : ac_cv_search_clock_gettime=$ac_res fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext if test ${ac_cv_search_clock_gettime+y} then : break fi done if test ${ac_cv_search_clock_gettime+y} then : else $as_nop ac_cv_search_clock_gettime=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_clock_gettime" >&5 printf "%s\n" "$ac_cv_search_clock_gettime" >&6; } ac_res=$ac_cv_search_clock_gettime if test "$ac_res" != no then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is compilable" >&5 printf %s "checking whether clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is compilable... " >&6; } if test ${je_cv_clock_monotonic_coarse+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_clock_monotonic_coarse=yes else $as_nop je_cv_clock_monotonic_coarse=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_clock_monotonic_coarse" >&5 printf "%s\n" "$je_cv_clock_monotonic_coarse" >&6; } if test "x${je_cv_clock_monotonic_coarse}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether clock_gettime(CLOCK_MONOTONIC, ...) is compilable" >&5 printf %s "checking whether clock_gettime(CLOCK_MONOTONIC, ...) is compilable... " >&6; } if test ${je_cv_clock_monotonic+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); #if !defined(_POSIX_MONOTONIC_CLOCK) || _POSIX_MONOTONIC_CLOCK < 0 # error _POSIX_MONOTONIC_CLOCK missing/invalid #endif ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_clock_monotonic=yes else $as_nop je_cv_clock_monotonic=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_clock_monotonic" >&5 printf "%s\n" "$je_cv_clock_monotonic" >&6; } if test "x${je_cv_clock_monotonic}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_CLOCK_MONOTONIC " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether mach_absolute_time() is compilable" >&5 printf %s "checking whether mach_absolute_time() is compilable... " >&6; } if test ${je_cv_mach_absolute_time+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { mach_absolute_time(); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_mach_absolute_time=yes else $as_nop je_cv_mach_absolute_time=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_mach_absolute_time" >&5 printf "%s\n" "$je_cv_mach_absolute_time" >&6; } if test "x${je_cv_mach_absolute_time}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_MACH_ABSOLUTE_TIME " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether clock_gettime(CLOCK_REALTIME, ...) is compilable" >&5 printf %s "checking whether clock_gettime(CLOCK_REALTIME, ...) is compilable... " >&6; } if test ${je_cv_clock_realtime+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_clock_realtime=yes else $as_nop je_cv_clock_realtime=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_clock_realtime" >&5 printf "%s\n" "$je_cv_clock_realtime" >&6; } if test "x${je_cv_clock_realtime}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_CLOCK_REALTIME " >>confdefs.h fi # Check whether --enable-syscall was given. if test ${enable_syscall+y} then : enableval=$enable_syscall; if test "x$enable_syscall" = "xno" ; then enable_syscall="0" else enable_syscall="1" fi else $as_nop enable_syscall="1" fi if test "x$enable_syscall" = "x1" ; then SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether syscall(2) is compilable" >&5 printf %s "checking whether syscall(2) is compilable... " >&6; } if test ${je_cv_syscall+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { syscall(SYS_write, 2, "hello", 5); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_syscall=yes else $as_nop je_cv_syscall=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_syscall" >&5 printf "%s\n" "$je_cv_syscall" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x$je_cv_syscall" = "xyes" ; then printf "%s\n" "#define JEMALLOC_USE_SYSCALL " >>confdefs.h fi fi ac_fn_c_check_func "$LINENO" "secure_getenv" "ac_cv_func_secure_getenv" if test "x$ac_cv_func_secure_getenv" = xyes then : have_secure_getenv="1" else $as_nop have_secure_getenv="0" fi if test "x$have_secure_getenv" = "x1" ; then printf "%s\n" "#define JEMALLOC_HAVE_SECURE_GETENV " >>confdefs.h fi ac_fn_c_check_func "$LINENO" "sched_getcpu" "ac_cv_func_sched_getcpu" if test "x$ac_cv_func_sched_getcpu" = xyes then : have_sched_getcpu="1" else $as_nop have_sched_getcpu="0" fi if test "x$have_sched_getcpu" = "x1" ; then printf "%s\n" "#define JEMALLOC_HAVE_SCHED_GETCPU " >>confdefs.h fi ac_fn_c_check_func "$LINENO" "sched_setaffinity" "ac_cv_func_sched_setaffinity" if test "x$ac_cv_func_sched_setaffinity" = xyes then : have_sched_setaffinity="1" else $as_nop have_sched_setaffinity="0" fi if test "x$have_sched_setaffinity" = "x1" ; then printf "%s\n" "#define JEMALLOC_HAVE_SCHED_SETAFFINITY " >>confdefs.h fi ac_fn_c_check_func "$LINENO" "issetugid" "ac_cv_func_issetugid" if test "x$ac_cv_func_issetugid" = xyes then : have_issetugid="1" else $as_nop have_issetugid="0" fi if test "x$have_issetugid" = "x1" ; then printf "%s\n" "#define JEMALLOC_HAVE_ISSETUGID " >>confdefs.h fi ac_fn_c_check_func "$LINENO" "_malloc_thread_cleanup" "ac_cv_func__malloc_thread_cleanup" if test "x$ac_cv_func__malloc_thread_cleanup" = xyes then : have__malloc_thread_cleanup="1" else $as_nop have__malloc_thread_cleanup="0" fi if test "x$have__malloc_thread_cleanup" = "x1" ; then printf "%s\n" "#define JEMALLOC_MALLOC_THREAD_CLEANUP " >>confdefs.h wrap_syms="${wrap_syms} _malloc_thread_cleanup _malloc_tsd_cleanup_register" force_tls="1" fi ac_fn_c_check_func "$LINENO" "_pthread_mutex_init_calloc_cb" "ac_cv_func__pthread_mutex_init_calloc_cb" if test "x$ac_cv_func__pthread_mutex_init_calloc_cb" = xyes then : have__pthread_mutex_init_calloc_cb="1" else $as_nop have__pthread_mutex_init_calloc_cb="0" fi if test "x$have__pthread_mutex_init_calloc_cb" = "x1" ; then printf "%s\n" "#define JEMALLOC_MUTEX_INIT_CB " >>confdefs.h wrap_syms="${wrap_syms} _malloc_prefork _malloc_postfork" fi ac_fn_c_check_func "$LINENO" "memcntl" "ac_cv_func_memcntl" if test "x$ac_cv_func_memcntl" = xyes then : have_memcntl="1" else $as_nop have_memcntl="0" fi if test "x$have_memcntl" = "x1" ; then printf "%s\n" "#define JEMALLOC_HAVE_MEMCNTL " >>confdefs.h fi # Check whether --enable-lazy_lock was given. if test ${enable_lazy_lock+y} then : enableval=$enable_lazy_lock; if test "x$enable_lazy_lock" = "xno" ; then enable_lazy_lock="0" else enable_lazy_lock="1" fi else $as_nop enable_lazy_lock="" fi if test "x${enable_lazy_lock}" = "x" ; then if test "x${force_lazy_lock}" = "x1" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Forcing lazy-lock to avoid allocator/threading bootstrap issues" >&5 printf "%s\n" "Forcing lazy-lock to avoid allocator/threading bootstrap issues" >&6; } enable_lazy_lock="1" else enable_lazy_lock="0" fi fi if test "x${enable_lazy_lock}" = "x1" -a "x${abi}" = "xpecoff" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Forcing no lazy-lock because thread creation monitoring is unimplemented" >&5 printf "%s\n" "Forcing no lazy-lock because thread creation monitoring is unimplemented" >&6; } enable_lazy_lock="0" fi if test "x$enable_lazy_lock" = "x1" ; then if test "x$have_dlsym" = "x1" ; then printf "%s\n" "#define JEMALLOC_LAZY_LOCK " >>confdefs.h else as_fn_error $? "Missing dlsym support: lazy-lock cannot be enabled." "$LINENO" 5 fi fi if test "x${force_tls}" = "x1" ; then enable_tls="1" elif test "x${force_tls}" = "x0" ; then enable_tls="0" else enable_tls="1" fi if test "x${enable_tls}" = "x1" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for TLS" >&5 printf %s "checking for TLS... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ __thread int x; int main (void) { x = 42; return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } enable_tls="0" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext else enable_tls="0" fi if test "x${enable_tls}" = "x1" ; then printf "%s\n" "#define JEMALLOC_TLS " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C11 atomics is compilable" >&5 printf %s "checking whether C11 atomics is compilable... " >&6; } if test ${je_cv_c11_atomics+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) #include #else #error Atomics not available #endif int main (void) { uint64_t *p = (uint64_t *)0; uint64_t x = 1; volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p; uint64_t r = atomic_fetch_add(a, x) + x; return r == 0; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_c11_atomics=yes else $as_nop je_cv_c11_atomics=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_c11_atomics" >&5 printf "%s\n" "$je_cv_c11_atomics" >&6; } if test "x${je_cv_c11_atomics}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_C11_ATOMICS " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether GCC __atomic atomics is compilable" >&5 printf %s "checking whether GCC __atomic atomics is compilable... " >&6; } if test ${je_cv_gcc_atomic_atomics+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { int x = 0; int val = 1; int y = __atomic_fetch_add(&x, val, __ATOMIC_RELAXED); int after_add = x; return after_add == 1; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_atomic_atomics=yes else $as_nop je_cv_gcc_atomic_atomics=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_atomic_atomics" >&5 printf "%s\n" "$je_cv_gcc_atomic_atomics" >&6; } if test "x${je_cv_gcc_atomic_atomics}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_GCC_ATOMIC_ATOMICS " >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether GCC 8-bit __atomic atomics is compilable" >&5 printf %s "checking whether GCC 8-bit __atomic atomics is compilable... " >&6; } if test ${je_cv_gcc_u8_atomic_atomics+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { unsigned char x = 0; int val = 1; int y = __atomic_fetch_add(&x, val, __ATOMIC_RELAXED); int after_add = (int)x; return after_add == 1; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_u8_atomic_atomics=yes else $as_nop je_cv_gcc_u8_atomic_atomics=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_u8_atomic_atomics" >&5 printf "%s\n" "$je_cv_gcc_u8_atomic_atomics" >&6; } if test "x${je_cv_gcc_u8_atomic_atomics}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_GCC_U8_ATOMIC_ATOMICS " >>confdefs.h fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether GCC __sync atomics is compilable" >&5 printf %s "checking whether GCC __sync atomics is compilable... " >&6; } if test ${je_cv_gcc_sync_atomics+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { int x = 0; int before_add = __sync_fetch_and_add(&x, 1); int after_add = x; return (before_add == 0) && (after_add == 1); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_sync_atomics=yes else $as_nop je_cv_gcc_sync_atomics=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_sync_atomics" >&5 printf "%s\n" "$je_cv_gcc_sync_atomics" >&6; } if test "x${je_cv_gcc_sync_atomics}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_GCC_SYNC_ATOMICS " >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether GCC 8-bit __sync atomics is compilable" >&5 printf %s "checking whether GCC 8-bit __sync atomics is compilable... " >&6; } if test ${je_cv_gcc_u8_sync_atomics+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { unsigned char x = 0; int before_add = __sync_fetch_and_add(&x, 1); int after_add = (int)x; return (before_add == 0) && (after_add == 1); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_gcc_u8_sync_atomics=yes else $as_nop je_cv_gcc_u8_sync_atomics=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_u8_sync_atomics" >&5 printf "%s\n" "$je_cv_gcc_u8_sync_atomics" >&6; } if test "x${je_cv_gcc_u8_sync_atomics}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_GCC_U8_SYNC_ATOMICS " >>confdefs.h fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether Darwin OSAtomic*() is compilable" >&5 printf %s "checking whether Darwin OSAtomic*() is compilable... " >&6; } if test ${je_cv_osatomic+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { { int32_t x32 = 0; volatile int32_t *x32p = &x32; OSAtomicAdd32(1, x32p); } { int64_t x64 = 0; volatile int64_t *x64p = &x64; OSAtomicAdd64(1, x64p); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_osatomic=yes else $as_nop je_cv_osatomic=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_osatomic" >&5 printf "%s\n" "$je_cv_osatomic" >&6; } if test "x${je_cv_osatomic}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_OSATOMIC " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether madvise(2) is compilable" >&5 printf %s "checking whether madvise(2) is compilable... " >&6; } if test ${je_cv_madvise+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { madvise((void *)0, 0, 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_madvise=yes else $as_nop je_cv_madvise=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_madvise" >&5 printf "%s\n" "$je_cv_madvise" >&6; } if test "x${je_cv_madvise}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_MADVISE " >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_FREE) is compilable" >&5 printf %s "checking whether madvise(..., MADV_FREE) is compilable... " >&6; } if test ${je_cv_madv_free+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { madvise((void *)0, 0, MADV_FREE); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_madv_free=yes else $as_nop je_cv_madv_free=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_free" >&5 printf "%s\n" "$je_cv_madv_free" >&6; } if test "x${je_cv_madv_free}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h elif test "x${je_cv_madvise}" = "xyes" ; then case "${host_cpu}" in i686|x86_64) case "${host}" in *-*-linux*) printf "%s\n" "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h printf "%s\n" "#define JEMALLOC_DEFINE_MADVISE_FREE " >>confdefs.h ;; esac ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_DONTNEED) is compilable" >&5 printf %s "checking whether madvise(..., MADV_DONTNEED) is compilable... " >&6; } if test ${je_cv_madv_dontneed+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { madvise((void *)0, 0, MADV_DONTNEED); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_madv_dontneed=yes else $as_nop je_cv_madv_dontneed=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_dontneed" >&5 printf "%s\n" "$je_cv_madv_dontneed" >&6; } if test "x${je_cv_madv_dontneed}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_PURGE_MADVISE_DONTNEED " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_DO[NT]DUMP) is compilable" >&5 printf %s "checking whether madvise(..., MADV_DO[NT]DUMP) is compilable... " >&6; } if test ${je_cv_madv_dontdump+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { madvise((void *)0, 0, MADV_DONTDUMP); madvise((void *)0, 0, MADV_DODUMP); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_madv_dontdump=yes else $as_nop je_cv_madv_dontdump=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_dontdump" >&5 printf "%s\n" "$je_cv_madv_dontdump" >&6; } if test "x${je_cv_madv_dontdump}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_MADVISE_DONTDUMP " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_[NO]HUGEPAGE) is compilable" >&5 printf %s "checking whether madvise(..., MADV_[NO]HUGEPAGE) is compilable... " >&6; } if test ${je_cv_thp+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { madvise((void *)0, 0, MADV_HUGEPAGE); madvise((void *)0, 0, MADV_NOHUGEPAGE); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_thp=yes else $as_nop je_cv_thp=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_thp" >&5 printf "%s\n" "$je_cv_thp" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_[NO]CORE) is compilable" >&5 printf %s "checking whether madvise(..., MADV_[NO]CORE) is compilable... " >&6; } if test ${je_cv_madv_nocore+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { madvise((void *)0, 0, MADV_NOCORE); madvise((void *)0, 0, MADV_CORE); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_madv_nocore=yes else $as_nop je_cv_madv_nocore=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_nocore" >&5 printf "%s\n" "$je_cv_madv_nocore" >&6; } if test "x${je_cv_madv_nocore}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_MADVISE_NOCORE " >>confdefs.h fi case "${host_cpu}" in arm*) ;; *) if test "x${je_cv_thp}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_MADVISE_HUGE " >>confdefs.h fi ;; esac else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether posix_madvise is compilable" >&5 printf %s "checking whether posix_madvise is compilable... " >&6; } if test ${je_cv_posix_madvise+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { posix_madvise((void *)0, 0, 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_posix_madvise=yes else $as_nop je_cv_posix_madvise=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_posix_madvise" >&5 printf "%s\n" "$je_cv_posix_madvise" >&6; } if test "x${je_cv_posix_madvise}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_POSIX_MADVISE " >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether posix_madvise(..., POSIX_MADV_DONTNEED) is compilable" >&5 printf %s "checking whether posix_madvise(..., POSIX_MADV_DONTNEED) is compilable... " >&6; } if test ${je_cv_posix_madv_dontneed+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { posix_madvise((void *)0, 0, POSIX_MADV_DONTNEED); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_posix_madv_dontneed=yes else $as_nop je_cv_posix_madv_dontneed=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_posix_madv_dontneed" >&5 printf "%s\n" "$je_cv_posix_madv_dontneed" >&6; } if test "x${je_cv_posix_madv_dontneed}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED " >>confdefs.h fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether mprotect(2) is compilable" >&5 printf %s "checking whether mprotect(2) is compilable... " >&6; } if test ${je_cv_mprotect+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { mprotect((void *)0, 0, PROT_NONE); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_mprotect=yes else $as_nop je_cv_mprotect=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_mprotect" >&5 printf "%s\n" "$je_cv_mprotect" >&6; } if test "x${je_cv_mprotect}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_MPROTECT " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __builtin_clz" >&5 printf %s "checking for __builtin_clz... " >&6; } if test ${je_cv_builtin_clz+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { { unsigned x = 0; int y = __builtin_clz(x); } { unsigned long x = 0; int y = __builtin_clzl(x); } { unsigned long long x = 0; int y = __builtin_clzll(x); } ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_builtin_clz=yes else $as_nop je_cv_builtin_clz=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_builtin_clz" >&5 printf "%s\n" "$je_cv_builtin_clz" >&6; } if test "x${je_cv_builtin_clz}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_BUILTIN_CLZ " >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether Darwin os_unfair_lock_*() is compilable" >&5 printf %s "checking whether Darwin os_unfair_lock_*() is compilable... " >&6; } if test ${je_cv_os_unfair_lock+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { #if MAC_OS_X_VERSION_MIN_REQUIRED < 101200 #error "os_unfair_lock is not supported" #else os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; os_unfair_lock_lock(&lock); os_unfair_lock_unlock(&lock); #endif ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_os_unfair_lock=yes else $as_nop je_cv_os_unfair_lock=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_os_unfair_lock" >&5 printf "%s\n" "$je_cv_os_unfair_lock" >&6; } if test "x${je_cv_os_unfair_lock}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_OS_UNFAIR_LOCK " >>confdefs.h fi # Check whether --enable-zone-allocator was given. if test ${enable_zone_allocator+y} then : enableval=$enable_zone_allocator; if test "x$enable_zone_allocator" = "xno" ; then enable_zone_allocator="0" else enable_zone_allocator="1" fi else $as_nop if test "x${abi}" = "xmacho"; then enable_zone_allocator="1" fi fi if test "x${enable_zone_allocator}" = "x1" ; then if test "x${abi}" != "xmacho"; then as_fn_error $? "--enable-zone-allocator is only supported on Darwin" "$LINENO" 5 fi printf "%s\n" "#define JEMALLOC_ZONE " >>confdefs.h fi # Check whether --enable-initial-exec-tls was given. if test ${enable_initial_exec_tls+y} then : enableval=$enable_initial_exec_tls; if test "x$enable_initial_exec_tls" = "xno" ; then enable_initial_exec_tls="0" else enable_initial_exec_tls="1" fi else $as_nop enable_initial_exec_tls="1" fi if test "x${je_cv_tls_model}" = "xyes" -a \ "x${enable_initial_exec_tls}" = "x1" ; then printf "%s\n" "#define JEMALLOC_TLS_MODEL __attribute__((tls_model(\"initial-exec\")))" >>confdefs.h else printf "%s\n" "#define JEMALLOC_TLS_MODEL " >>confdefs.h fi if test "x${have_pthread}" = "x1" -a "x${je_cv_os_unfair_lock}" != "xyes" -a \ "x${abi}" != "xmacho" ; then printf "%s\n" "#define JEMALLOC_BACKGROUND_THREAD " >>confdefs.h fi if test "x$glibc" = "x1" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether glibc malloc hook is compilable" >&5 printf %s "checking whether glibc malloc hook is compilable... " >&6; } if test ${je_cv_glibc_malloc_hook+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include extern void (* __free_hook)(void *ptr); extern void *(* __malloc_hook)(size_t size); extern void *(* __realloc_hook)(void *ptr, size_t size); int main (void) { void *ptr = 0L; if (__malloc_hook) ptr = __malloc_hook(1); if (__realloc_hook) ptr = __realloc_hook(ptr, 2); if (__free_hook && ptr) __free_hook(ptr); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_glibc_malloc_hook=yes else $as_nop je_cv_glibc_malloc_hook=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_glibc_malloc_hook" >&5 printf "%s\n" "$je_cv_glibc_malloc_hook" >&6; } if test "x${je_cv_glibc_malloc_hook}" = "xyes" ; then if test "x${JEMALLOC_PREFIX}" = "x" ; then printf "%s\n" "#define JEMALLOC_GLIBC_MALLOC_HOOK " >>confdefs.h wrap_syms="${wrap_syms} __free_hook __malloc_hook __realloc_hook" fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether glibc memalign hook is compilable" >&5 printf %s "checking whether glibc memalign hook is compilable... " >&6; } if test ${je_cv_glibc_memalign_hook+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include extern void *(* __memalign_hook)(size_t alignment, size_t size); int main (void) { void *ptr = 0L; if (__memalign_hook) ptr = __memalign_hook(16, 7); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_glibc_memalign_hook=yes else $as_nop je_cv_glibc_memalign_hook=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_glibc_memalign_hook" >&5 printf "%s\n" "$je_cv_glibc_memalign_hook" >&6; } if test "x${je_cv_glibc_memalign_hook}" = "xyes" ; then if test "x${JEMALLOC_PREFIX}" = "x" ; then printf "%s\n" "#define JEMALLOC_GLIBC_MEMALIGN_HOOK " >>confdefs.h wrap_syms="${wrap_syms} __memalign_hook" fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads adaptive mutexes is compilable" >&5 printf %s "checking whether pthreads adaptive mutexes is compilable... " >&6; } if test ${je_cv_pthread_mutex_adaptive_np+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); pthread_mutexattr_destroy(&attr); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_pthread_mutex_adaptive_np=yes else $as_nop je_cv_pthread_mutex_adaptive_np=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_mutex_adaptive_np" >&5 printf "%s\n" "$je_cv_pthread_mutex_adaptive_np" >&6; } if test "x${je_cv_pthread_mutex_adaptive_np}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP " >>confdefs.h fi SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -D_GNU_SOURCE" >&5 printf %s "checking whether compiler supports -D_GNU_SOURCE... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-D_GNU_SOURCE if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-D_GNU_SOURCE { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5 printf %s "checking whether compiler supports -Werror... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-Werror if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-Werror { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5 printf %s "checking whether compiler supports -herror_on_warning... " >&6; } T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" T_APPEND_V=-herror_on_warning if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}" else CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}" fi if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : je_cv_cflags_added=-herror_on_warning { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else $as_nop je_cv_cflags_added= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether strerror_r returns char with gnu source is compilable" >&5 printf %s "checking whether strerror_r returns char with gnu source is compilable... " >&6; } if test ${je_cv_strerror_r_returns_char_with_gnu_source+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include #include int main (void) { char *buffer = (char *) malloc(100); char *error = strerror_r(EINVAL, buffer, 100); printf("%s\n", error); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : je_cv_strerror_r_returns_char_with_gnu_source=yes else $as_nop je_cv_strerror_r_returns_char_with_gnu_source=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $je_cv_strerror_r_returns_char_with_gnu_source" >&5 printf "%s\n" "$je_cv_strerror_r_returns_char_with_gnu_source" >&6; } CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}" else CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}" fi if test "x${je_cv_strerror_r_returns_char_with_gnu_source}" = "xyes" ; then printf "%s\n" "#define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE " >>confdefs.h fi ac_fn_c_check_type "$LINENO" "_Bool" "ac_cv_type__Bool" "$ac_includes_default" if test "x$ac_cv_type__Bool" = xyes then : printf "%s\n" "#define HAVE__BOOL 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdbool.h that conforms to C99" >&5 printf %s "checking for stdbool.h that conforms to C99... " >&6; } if test ${ac_cv_header_stdbool_h+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifndef __bool_true_false_are_defined #error "__bool_true_false_are_defined is not defined" #endif char a[__bool_true_false_are_defined == 1 ? 1 : -1]; /* Regardless of whether this is C++ or "_Bool" is a valid type name, "true" and "false" should be usable in #if expressions and integer constant expressions, and "bool" should be a valid type name. */ #if !true #error "'true' is not true" #endif #if true != 1 #error "'true' is not equal to 1" #endif char b[true == 1 ? 1 : -1]; char c[true]; #if false #error "'false' is not false" #endif #if false != 0 #error "'false' is not equal to 0" #endif char d[false == 0 ? 1 : -1]; enum { e = false, f = true, g = false * true, h = true * 256 }; char i[(bool) 0.5 == true ? 1 : -1]; char j[(bool) 0.0 == false ? 1 : -1]; char k[sizeof (bool) > 0 ? 1 : -1]; struct sb { bool s: 1; bool t; } s; char l[sizeof s.t > 0 ? 1 : -1]; /* The following fails for HP aC++/ANSI C B3910B A.05.55 [Dec 04 2003]. */ bool m[h]; char n[sizeof m == h * sizeof m[0] ? 1 : -1]; char o[-1 - (bool) 0 < 0 ? 1 : -1]; /* Catch a bug in an HP-UX C compiler. See https://gcc.gnu.org/ml/gcc-patches/2003-12/msg02303.html https://lists.gnu.org/archive/html/bug-coreutils/2005-11/msg00161.html */ bool p = true; bool *pp = &p; /* C 1999 specifies that bool, true, and false are to be macros, but C++ 2011 and later overrule this. */ #if __cplusplus < 201103 #ifndef bool #error "bool is not defined" #endif #ifndef false #error "false is not defined" #endif #ifndef true #error "true is not defined" #endif #endif /* If _Bool is available, repeat with it all the tests above that used bool. */ #ifdef HAVE__BOOL struct sB { _Bool s: 1; _Bool t; } t; char q[(_Bool) 0.5 == true ? 1 : -1]; char r[(_Bool) 0.0 == false ? 1 : -1]; char u[sizeof (_Bool) > 0 ? 1 : -1]; char v[sizeof t.t > 0 ? 1 : -1]; _Bool w[h]; char x[sizeof m == h * sizeof m[0] ? 1 : -1]; char y[-1 - (_Bool) 0 < 0 ? 1 : -1]; _Bool z = true; _Bool *pz = &p; #endif int main (void) { bool ps = &s; *pp |= p; *pp |= ! p; #ifdef HAVE__BOOL _Bool pt = &t; *pz |= z; *pz |= ! z; #endif /* Refer to every declared value, so they cannot be discarded as unused. */ return (!a + !b + !c + !d + !e + !f + !g + !h + !i + !j + !k + !l + !m + !n + !o + !p + !pp + !ps #ifdef HAVE__BOOL + !q + !r + !u + !v + !w + !x + !y + !z + !pt #endif ); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_header_stdbool_h=yes else $as_nop ac_cv_header_stdbool_h=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdbool_h" >&5 printf "%s\n" "$ac_cv_header_stdbool_h" >&6; } if test $ac_cv_header_stdbool_h = yes; then printf "%s\n" "#define HAVE_STDBOOL_H 1" >>confdefs.h fi ac_config_commands="$ac_config_commands include/jemalloc/internal/public_symbols.txt" ac_config_commands="$ac_config_commands include/jemalloc/internal/private_symbols.awk" ac_config_commands="$ac_config_commands include/jemalloc/internal/private_symbols_jet.awk" ac_config_commands="$ac_config_commands include/jemalloc/internal/public_namespace.h" ac_config_commands="$ac_config_commands include/jemalloc/internal/public_unnamespace.h" ac_config_commands="$ac_config_commands include/jemalloc/jemalloc_protos_jet.h" ac_config_commands="$ac_config_commands include/jemalloc/jemalloc_rename.h" ac_config_commands="$ac_config_commands include/jemalloc/jemalloc_mangle.h" ac_config_commands="$ac_config_commands include/jemalloc/jemalloc_mangle_jet.h" ac_config_commands="$ac_config_commands include/jemalloc/jemalloc.h" ac_config_headers="$ac_config_headers $cfghdrs_tup" ac_config_files="$ac_config_files $cfgoutputs_tup config.stamp bin/jemalloc-config bin/jemalloc.sh bin/jeprof" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else $as_nop as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by $as_me, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to the package provider." _ACEOF ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ config.status configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" Copyright (C) 2021 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' AWK='$AWK' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf "%s\n" "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # srcdir="${srcdir}" objroot="${objroot}" mangling_map="${mangling_map}" public_syms="${public_syms}" JEMALLOC_PREFIX="${JEMALLOC_PREFIX}" srcdir="${srcdir}" objroot="${objroot}" public_syms="${public_syms}" wrap_syms="${wrap_syms}" SYM_PREFIX="${SYM_PREFIX}" JEMALLOC_PREFIX="${JEMALLOC_PREFIX}" srcdir="${srcdir}" objroot="${objroot}" public_syms="${public_syms}" wrap_syms="${wrap_syms}" SYM_PREFIX="${SYM_PREFIX}" srcdir="${srcdir}" objroot="${objroot}" srcdir="${srcdir}" objroot="${objroot}" srcdir="${srcdir}" objroot="${objroot}" srcdir="${srcdir}" objroot="${objroot}" srcdir="${srcdir}" objroot="${objroot}" srcdir="${srcdir}" objroot="${objroot}" srcdir="${srcdir}" objroot="${objroot}" install_suffix="${install_suffix}" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "include/jemalloc/internal/public_symbols.txt") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/public_symbols.txt" ;; "include/jemalloc/internal/private_symbols.awk") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/private_symbols.awk" ;; "include/jemalloc/internal/private_symbols_jet.awk") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/private_symbols_jet.awk" ;; "include/jemalloc/internal/public_namespace.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/public_namespace.h" ;; "include/jemalloc/internal/public_unnamespace.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/public_unnamespace.h" ;; "include/jemalloc/jemalloc_protos_jet.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/jemalloc_protos_jet.h" ;; "include/jemalloc/jemalloc_rename.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/jemalloc_rename.h" ;; "include/jemalloc/jemalloc_mangle.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/jemalloc_mangle.h" ;; "include/jemalloc/jemalloc_mangle_jet.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/jemalloc_mangle_jet.h" ;; "include/jemalloc/jemalloc.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/jemalloc.h" ;; "$cfghdrs_tup") CONFIG_HEADERS="$CONFIG_HEADERS $cfghdrs_tup" ;; "$cfgoutputs_tup") CONFIG_FILES="$CONFIG_FILES $cfgoutputs_tup" ;; "config.stamp") CONFIG_FILES="$CONFIG_FILES config.stamp" ;; "bin/jemalloc-config") CONFIG_FILES="$CONFIG_FILES bin/jemalloc-config" ;; "bin/jemalloc.sh") CONFIG_FILES="$CONFIG_FILES bin/jemalloc.sh" ;; "bin/jeprof") CONFIG_FILES="$CONFIG_FILES bin/jeprof" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf "%s\n" "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi ;; :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 printf "%s\n" "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "include/jemalloc/internal/public_symbols.txt":C) f="${objroot}include/jemalloc/internal/public_symbols.txt" mkdir -p "${objroot}include/jemalloc/internal" cp /dev/null "${f}" for nm in `echo ${mangling_map} |tr ',' ' '` ; do n=`echo ${nm} |tr ':' ' ' |awk '{print $1}'` m=`echo ${nm} |tr ':' ' ' |awk '{print $2}'` echo "${n}:${m}" >> "${f}" public_syms=`for sym in ${public_syms}; do echo "${sym}"; done |grep -v "^${n}\$" |tr '\n' ' '` done for sym in ${public_syms} ; do n="${sym}" m="${JEMALLOC_PREFIX}${sym}" echo "${n}:${m}" >> "${f}" done ;; "include/jemalloc/internal/private_symbols.awk":C) f="${objroot}include/jemalloc/internal/private_symbols.awk" mkdir -p "${objroot}include/jemalloc/internal" export_syms=`for sym in ${public_syms}; do echo "${JEMALLOC_PREFIX}${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;` "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols.awk" ;; "include/jemalloc/internal/private_symbols_jet.awk":C) f="${objroot}include/jemalloc/internal/private_symbols_jet.awk" mkdir -p "${objroot}include/jemalloc/internal" export_syms=`for sym in ${public_syms}; do echo "jet_${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;` "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols_jet.awk" ;; "include/jemalloc/internal/public_namespace.h":C) mkdir -p "${objroot}include/jemalloc/internal" "${srcdir}/include/jemalloc/internal/public_namespace.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/internal/public_namespace.h" ;; "include/jemalloc/internal/public_unnamespace.h":C) mkdir -p "${objroot}include/jemalloc/internal" "${srcdir}/include/jemalloc/internal/public_unnamespace.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/internal/public_unnamespace.h" ;; "include/jemalloc/jemalloc_protos_jet.h":C) mkdir -p "${objroot}include/jemalloc" cat "${srcdir}/include/jemalloc/jemalloc_protos.h.in" | sed -e 's/@je_@/jet_/g' > "${objroot}include/jemalloc/jemalloc_protos_jet.h" ;; "include/jemalloc/jemalloc_rename.h":C) mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc_rename.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/jemalloc_rename.h" ;; "include/jemalloc/jemalloc_mangle.h":C) mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc_mangle.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" je_ > "${objroot}include/jemalloc/jemalloc_mangle.h" ;; "include/jemalloc/jemalloc_mangle_jet.h":C) mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc_mangle.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" jet_ > "${objroot}include/jemalloc/jemalloc_mangle_jet.h" ;; "include/jemalloc/jemalloc.h":C) mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc.sh" "${objroot}" > "${objroot}include/jemalloc/jemalloc${install_suffix}.h" ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ===============================================================================" >&5 printf "%s\n" "===============================================================================" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: jemalloc version : ${jemalloc_version}" >&5 printf "%s\n" "jemalloc version : ${jemalloc_version}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: library revision : ${rev}" >&5 printf "%s\n" "library revision : ${rev}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: " >&5 printf "%s\n" "" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: CONFIG : ${CONFIG}" >&5 printf "%s\n" "CONFIG : ${CONFIG}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: CC : ${CC}" >&5 printf "%s\n" "CC : ${CC}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: CONFIGURE_CFLAGS : ${CONFIGURE_CFLAGS}" >&5 printf "%s\n" "CONFIGURE_CFLAGS : ${CONFIGURE_CFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: SPECIFIED_CFLAGS : ${SPECIFIED_CFLAGS}" >&5 printf "%s\n" "SPECIFIED_CFLAGS : ${SPECIFIED_CFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: EXTRA_CFLAGS : ${EXTRA_CFLAGS}" >&5 printf "%s\n" "EXTRA_CFLAGS : ${EXTRA_CFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: CPPFLAGS : ${CPPFLAGS}" >&5 printf "%s\n" "CPPFLAGS : ${CPPFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: CXX : ${CXX}" >&5 printf "%s\n" "CXX : ${CXX}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: CONFIGURE_CXXFLAGS : ${CONFIGURE_CXXFLAGS}" >&5 printf "%s\n" "CONFIGURE_CXXFLAGS : ${CONFIGURE_CXXFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: SPECIFIED_CXXFLAGS : ${SPECIFIED_CXXFLAGS}" >&5 printf "%s\n" "SPECIFIED_CXXFLAGS : ${SPECIFIED_CXXFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: EXTRA_CXXFLAGS : ${EXTRA_CXXFLAGS}" >&5 printf "%s\n" "EXTRA_CXXFLAGS : ${EXTRA_CXXFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: LDFLAGS : ${LDFLAGS}" >&5 printf "%s\n" "LDFLAGS : ${LDFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: EXTRA_LDFLAGS : ${EXTRA_LDFLAGS}" >&5 printf "%s\n" "EXTRA_LDFLAGS : ${EXTRA_LDFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: DSO_LDFLAGS : ${DSO_LDFLAGS}" >&5 printf "%s\n" "DSO_LDFLAGS : ${DSO_LDFLAGS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: LIBS : ${LIBS}" >&5 printf "%s\n" "LIBS : ${LIBS}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: RPATH_EXTRA : ${RPATH_EXTRA}" >&5 printf "%s\n" "RPATH_EXTRA : ${RPATH_EXTRA}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: " >&5 printf "%s\n" "" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: XSLTPROC : ${XSLTPROC}" >&5 printf "%s\n" "XSLTPROC : ${XSLTPROC}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: XSLROOT : ${XSLROOT}" >&5 printf "%s\n" "XSLROOT : ${XSLROOT}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: " >&5 printf "%s\n" "" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: PREFIX : ${PREFIX}" >&5 printf "%s\n" "PREFIX : ${PREFIX}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: BINDIR : ${BINDIR}" >&5 printf "%s\n" "BINDIR : ${BINDIR}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: DATADIR : ${DATADIR}" >&5 printf "%s\n" "DATADIR : ${DATADIR}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: INCLUDEDIR : ${INCLUDEDIR}" >&5 printf "%s\n" "INCLUDEDIR : ${INCLUDEDIR}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: LIBDIR : ${LIBDIR}" >&5 printf "%s\n" "LIBDIR : ${LIBDIR}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: MANDIR : ${MANDIR}" >&5 printf "%s\n" "MANDIR : ${MANDIR}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: " >&5 printf "%s\n" "" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: srcroot : ${srcroot}" >&5 printf "%s\n" "srcroot : ${srcroot}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: abs_srcroot : ${abs_srcroot}" >&5 printf "%s\n" "abs_srcroot : ${abs_srcroot}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: objroot : ${objroot}" >&5 printf "%s\n" "objroot : ${objroot}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: abs_objroot : ${abs_objroot}" >&5 printf "%s\n" "abs_objroot : ${abs_objroot}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: " >&5 printf "%s\n" "" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JEMALLOC_PREFIX : ${JEMALLOC_PREFIX}" >&5 printf "%s\n" "JEMALLOC_PREFIX : ${JEMALLOC_PREFIX}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JEMALLOC_PRIVATE_NAMESPACE" >&5 printf "%s\n" "JEMALLOC_PRIVATE_NAMESPACE" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: : ${JEMALLOC_PRIVATE_NAMESPACE}" >&5 printf "%s\n" " : ${JEMALLOC_PRIVATE_NAMESPACE}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: install_suffix : ${install_suffix}" >&5 printf "%s\n" "install_suffix : ${install_suffix}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: malloc_conf : ${config_malloc_conf}" >&5 printf "%s\n" "malloc_conf : ${config_malloc_conf}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: documentation : ${enable_doc}" >&5 printf "%s\n" "documentation : ${enable_doc}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: shared libs : ${enable_shared}" >&5 printf "%s\n" "shared libs : ${enable_shared}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: static libs : ${enable_static}" >&5 printf "%s\n" "static libs : ${enable_static}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: autogen : ${enable_autogen}" >&5 printf "%s\n" "autogen : ${enable_autogen}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: debug : ${enable_debug}" >&5 printf "%s\n" "debug : ${enable_debug}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: stats : ${enable_stats}" >&5 printf "%s\n" "stats : ${enable_stats}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: experimental_smallocx : ${enable_experimental_smallocx}" >&5 printf "%s\n" "experimental_smallocx : ${enable_experimental_smallocx}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: prof : ${enable_prof}" >&5 printf "%s\n" "prof : ${enable_prof}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: prof-libunwind : ${enable_prof_libunwind}" >&5 printf "%s\n" "prof-libunwind : ${enable_prof_libunwind}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: prof-libgcc : ${enable_prof_libgcc}" >&5 printf "%s\n" "prof-libgcc : ${enable_prof_libgcc}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: prof-gcc : ${enable_prof_gcc}" >&5 printf "%s\n" "prof-gcc : ${enable_prof_gcc}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: fill : ${enable_fill}" >&5 printf "%s\n" "fill : ${enable_fill}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: utrace : ${enable_utrace}" >&5 printf "%s\n" "utrace : ${enable_utrace}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: xmalloc : ${enable_xmalloc}" >&5 printf "%s\n" "xmalloc : ${enable_xmalloc}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: log : ${enable_log}" >&5 printf "%s\n" "log : ${enable_log}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: lazy_lock : ${enable_lazy_lock}" >&5 printf "%s\n" "lazy_lock : ${enable_lazy_lock}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cache-oblivious : ${enable_cache_oblivious}" >&5 printf "%s\n" "cache-oblivious : ${enable_cache_oblivious}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cxx : ${enable_cxx}" >&5 printf "%s\n" "cxx : ${enable_cxx}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ===============================================================================" >&5 printf "%s\n" "===============================================================================" >&6; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/configure.ac��������������������������������������������������������������0000664�0000000�0000000�00000243437�15015331166�0017654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������dnl Process this file with autoconf to produce a configure script. AC_PREREQ(2.68) AC_INIT([Makefile.in]) AC_CONFIG_AUX_DIR([build-aux]) dnl ============================================================================ dnl Custom macro definitions. dnl JE_CONCAT_VVV(r, a, b) dnl dnl Set $r to the concatenation of $a and $b, with a space separating them iff dnl both $a and $b are non-empty. AC_DEFUN([JE_CONCAT_VVV], if test "x[$]{$2}" = "x" -o "x[$]{$3}" = "x" ; then $1="[$]{$2}[$]{$3}" else $1="[$]{$2} [$]{$3}" fi ) dnl JE_APPEND_VS(a, b) dnl dnl Set $a to the concatenation of $a and b, with a space separating them iff dnl both $a and b are non-empty. AC_DEFUN([JE_APPEND_VS], T_APPEND_V=$2 JE_CONCAT_VVV($1, $1, T_APPEND_V) ) CONFIGURE_CFLAGS= SPECIFIED_CFLAGS="${CFLAGS}" dnl JE_CFLAGS_ADD(cflag) dnl dnl CFLAGS is the concatenation of CONFIGURE_CFLAGS and SPECIFIED_CFLAGS dnl (ignoring EXTRA_CFLAGS, which does not impact configure tests. This macro dnl appends to CONFIGURE_CFLAGS and regenerates CFLAGS. AC_DEFUN([JE_CFLAGS_ADD], [ AC_MSG_CHECKING([whether compiler supports $1]) T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" JE_APPEND_VS(CONFIGURE_CFLAGS, $1) JE_CONCAT_VVV(CFLAGS, CONFIGURE_CFLAGS, SPECIFIED_CFLAGS) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[ ]], [[ return 0; ]])], [je_cv_cflags_added=$1] AC_MSG_RESULT([yes]), [je_cv_cflags_added=] AC_MSG_RESULT([no]) [CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"] ) JE_CONCAT_VVV(CFLAGS, CONFIGURE_CFLAGS, SPECIFIED_CFLAGS) ]) dnl JE_CFLAGS_SAVE() dnl JE_CFLAGS_RESTORE() dnl dnl Save/restore CFLAGS. Nesting is not supported. AC_DEFUN([JE_CFLAGS_SAVE], SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}" ) AC_DEFUN([JE_CFLAGS_RESTORE], CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}" JE_CONCAT_VVV(CFLAGS, CONFIGURE_CFLAGS, SPECIFIED_CFLAGS) ) CONFIGURE_CXXFLAGS= SPECIFIED_CXXFLAGS="${CXXFLAGS}" dnl JE_CXXFLAGS_ADD(cxxflag) AC_DEFUN([JE_CXXFLAGS_ADD], [ AC_MSG_CHECKING([whether compiler supports $1]) T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}" JE_APPEND_VS(CONFIGURE_CXXFLAGS, $1) JE_CONCAT_VVV(CXXFLAGS, CONFIGURE_CXXFLAGS, SPECIFIED_CXXFLAGS) AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[ ]], [[ return 0; ]])], [je_cv_cxxflags_added=$1] AC_MSG_RESULT([yes]), [je_cv_cxxflags_added=] AC_MSG_RESULT([no]) [CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"] ) AC_LANG_POP([C++]) JE_CONCAT_VVV(CXXFLAGS, CONFIGURE_CXXFLAGS, SPECIFIED_CXXFLAGS) ]) dnl JE_COMPILABLE(label, hcode, mcode, rvar) dnl dnl Use AC_LINK_IFELSE() rather than AC_COMPILE_IFELSE() so that linker errors dnl cause failure. AC_DEFUN([JE_COMPILABLE], [ AC_CACHE_CHECK([whether $1 is compilable], [$4], [AC_LINK_IFELSE([AC_LANG_PROGRAM([$2], [$3])], [$4=yes], [$4=no])]) ]) dnl ============================================================================ CONFIG=`echo ${ac_configure_args} | sed -e 's#'"'"'\([^ ]*\)'"'"'#\1#g'` AC_SUBST([CONFIG]) dnl Library revision. rev=2 AC_SUBST([rev]) srcroot=$srcdir if test "x${srcroot}" = "x." ; then srcroot="" else srcroot="${srcroot}/" fi AC_SUBST([srcroot]) abs_srcroot="`cd \"${srcdir}\"; pwd`/" AC_SUBST([abs_srcroot]) objroot="" AC_SUBST([objroot]) abs_objroot="`pwd`/" AC_SUBST([abs_objroot]) dnl Munge install path variables. case "$prefix" in *\ * ) AC_MSG_ERROR([Prefix should not contain spaces]) ;; "NONE" ) prefix="/usr/local" ;; esac case "$exec_prefix" in *\ * ) AC_MSG_ERROR([Exec prefix should not contain spaces]) ;; "NONE" ) exec_prefix=$prefix ;; esac PREFIX=$prefix AC_SUBST([PREFIX]) BINDIR=`eval echo $bindir` BINDIR=`eval echo $BINDIR` AC_SUBST([BINDIR]) INCLUDEDIR=`eval echo $includedir` INCLUDEDIR=`eval echo $INCLUDEDIR` AC_SUBST([INCLUDEDIR]) LIBDIR=`eval echo $libdir` LIBDIR=`eval echo $LIBDIR` AC_SUBST([LIBDIR]) DATADIR=`eval echo $datadir` DATADIR=`eval echo $DATADIR` AC_SUBST([DATADIR]) MANDIR=`eval echo $mandir` MANDIR=`eval echo $MANDIR` AC_SUBST([MANDIR]) dnl Support for building documentation. AC_PATH_PROG([XSLTPROC], [xsltproc], [false], [$PATH]) if test -d "/usr/share/xml/docbook/stylesheet/docbook-xsl" ; then DEFAULT_XSLROOT="/usr/share/xml/docbook/stylesheet/docbook-xsl" elif test -d "/usr/share/sgml/docbook/xsl-stylesheets" ; then DEFAULT_XSLROOT="/usr/share/sgml/docbook/xsl-stylesheets" else dnl Documentation building will fail if this default gets used. DEFAULT_XSLROOT="" fi AC_ARG_WITH([xslroot], [AS_HELP_STRING([--with-xslroot=], [XSL stylesheet root path])], [ if test "x$with_xslroot" = "xno" ; then XSLROOT="${DEFAULT_XSLROOT}" else XSLROOT="${with_xslroot}" fi ], XSLROOT="${DEFAULT_XSLROOT}" ) if test "x$XSLTPROC" = "xfalse" ; then XSLROOT="" fi AC_SUBST([XSLROOT]) dnl If CFLAGS isn't defined, set CFLAGS to something reasonable. Otherwise, dnl just prevent autoconf from molesting CFLAGS. CFLAGS=$CFLAGS AC_PROG_CC if test "x$GCC" != "xyes" ; then AC_CACHE_CHECK([whether compiler is MSVC], [je_cv_msvc], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [ #ifndef _MSC_VER int fail[-1]; #endif ])], [je_cv_msvc=yes], [je_cv_msvc=no])]) fi dnl check if a cray prgenv wrapper compiler is being used je_cv_cray_prgenv_wrapper="" if test "x${PE_ENV}" != "x" ; then case "${CC}" in CC|cc) je_cv_cray_prgenv_wrapper="yes" ;; *) ;; esac fi AC_CACHE_CHECK([whether compiler is cray], [je_cv_cray], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [ #ifndef _CRAYC int fail[-1]; #endif ])], [je_cv_cray=yes], [je_cv_cray=no])]) if test "x${je_cv_cray}" = "xyes" ; then AC_CACHE_CHECK([whether cray compiler version is 8.4], [je_cv_cray_84], [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [ #if !(_RELEASE_MAJOR == 8 && _RELEASE_MINOR == 4) int fail[-1]; #endif ])], [je_cv_cray_84=yes], [je_cv_cray_84=no])]) fi if test "x$GCC" = "xyes" ; then JE_CFLAGS_ADD([-std=gnu11]) if test "x$je_cv_cflags_added" = "x-std=gnu11" ; then AC_DEFINE_UNQUOTED([JEMALLOC_HAS_RESTRICT], [ ], [ ]) else JE_CFLAGS_ADD([-std=gnu99]) if test "x$je_cv_cflags_added" = "x-std=gnu99" ; then AC_DEFINE_UNQUOTED([JEMALLOC_HAS_RESTRICT], [ ], [ ]) fi fi JE_CFLAGS_ADD([-Werror=unknown-warning-option]) JE_CFLAGS_ADD([-Wall]) JE_CFLAGS_ADD([-Wextra]) JE_CFLAGS_ADD([-Wshorten-64-to-32]) JE_CFLAGS_ADD([-Wsign-compare]) JE_CFLAGS_ADD([-Wundef]) JE_CFLAGS_ADD([-Wno-format-zero-length]) JE_CFLAGS_ADD([-Wpointer-arith]) dnl This warning triggers on the use of the universal zero initializer, which dnl is a very handy idiom for things like the tcache static initializer (which dnl has lots of nested structs). See the discussion at. dnl https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53119 JE_CFLAGS_ADD([-Wno-missing-braces]) dnl This one too. JE_CFLAGS_ADD([-Wno-missing-field-initializers]) JE_CFLAGS_ADD([-Wno-missing-attributes]) JE_CFLAGS_ADD([-pipe]) JE_CFLAGS_ADD([-g3]) elif test "x$je_cv_msvc" = "xyes" ; then CC="$CC -nologo" JE_CFLAGS_ADD([-Zi]) JE_CFLAGS_ADD([-MT]) JE_CFLAGS_ADD([-W3]) JE_CFLAGS_ADD([-FS]) JE_APPEND_VS(CPPFLAGS, -I${srcdir}/include/msvc_compat) fi if test "x$je_cv_cray" = "xyes" ; then dnl cray compiler 8.4 has an inlining bug if test "x$je_cv_cray_84" = "xyes" ; then JE_CFLAGS_ADD([-hipa2]) JE_CFLAGS_ADD([-hnognu]) fi dnl ignore unreachable code warning JE_CFLAGS_ADD([-hnomessage=128]) dnl ignore redefinition of "malloc", "free", etc warning JE_CFLAGS_ADD([-hnomessage=1357]) fi AC_SUBST([CONFIGURE_CFLAGS]) AC_SUBST([SPECIFIED_CFLAGS]) AC_SUBST([EXTRA_CFLAGS]) AC_PROG_CPP AC_ARG_ENABLE([cxx], [AS_HELP_STRING([--disable-cxx], [Disable C++ integration])], if test "x$enable_cxx" = "xno" ; then enable_cxx="0" else enable_cxx="1" fi , enable_cxx="1" ) if test "x$enable_cxx" = "x1" ; then dnl Require at least c++14, which is the first version to support sized dnl deallocation. C++ support is not compiled otherwise. m4_include([m4/ax_cxx_compile_stdcxx.m4]) AX_CXX_COMPILE_STDCXX([17], [noext], [optional]) if test "x${HAVE_CXX17}" != "x1"; then AX_CXX_COMPILE_STDCXX([14], [noext], [optional]) fi if test "x${HAVE_CXX14}" = "x1" -o "x${HAVE_CXX17}" = "x1"; then JE_CXXFLAGS_ADD([-Wall]) JE_CXXFLAGS_ADD([-Wextra]) JE_CXXFLAGS_ADD([-g3]) SAVED_LIBS="${LIBS}" JE_APPEND_VS(LIBS, -lstdc++) JE_COMPILABLE([libstdc++ linkage], [ #include ], [[ int *arr = (int *)malloc(sizeof(int) * 42); if (arr == NULL) return 1; ]], [je_cv_libstdcxx]) if test "x${je_cv_libstdcxx}" = "xno" ; then LIBS="${SAVED_LIBS}" fi else enable_cxx="0" fi fi if test "x$enable_cxx" = "x1"; then AC_DEFINE([JEMALLOC_ENABLE_CXX], [ ], [ ]) fi AC_SUBST([enable_cxx]) AC_SUBST([CONFIGURE_CXXFLAGS]) AC_SUBST([SPECIFIED_CXXFLAGS]) AC_SUBST([EXTRA_CXXFLAGS]) AC_C_BIGENDIAN([ac_cv_big_endian=1], [ac_cv_big_endian=0]) if test "x${ac_cv_big_endian}" = "x1" ; then AC_DEFINE_UNQUOTED([JEMALLOC_BIG_ENDIAN], [ ], [ ]) fi if test "x${je_cv_msvc}" = "xyes" -a "x${ac_cv_header_inttypes_h}" = "xno"; then JE_APPEND_VS(CPPFLAGS, -I${srcdir}/include/msvc_compat/C99) fi if test "x${je_cv_msvc}" = "xyes" ; then LG_SIZEOF_PTR=LG_SIZEOF_PTR_WIN AC_MSG_RESULT([Using a predefined value for sizeof(void *): 4 for 32-bit, 8 for 64-bit]) else AC_CHECK_SIZEOF([void *]) if test "x${ac_cv_sizeof_void_p}" = "x8" ; then LG_SIZEOF_PTR=3 elif test "x${ac_cv_sizeof_void_p}" = "x4" ; then LG_SIZEOF_PTR=2 else AC_MSG_ERROR([Unsupported pointer size: ${ac_cv_sizeof_void_p}]) fi fi AC_DEFINE_UNQUOTED([LG_SIZEOF_PTR], [$LG_SIZEOF_PTR], [ ]) AC_CHECK_SIZEOF([int]) if test "x${ac_cv_sizeof_int}" = "x8" ; then LG_SIZEOF_INT=3 elif test "x${ac_cv_sizeof_int}" = "x4" ; then LG_SIZEOF_INT=2 else AC_MSG_ERROR([Unsupported int size: ${ac_cv_sizeof_int}]) fi AC_DEFINE_UNQUOTED([LG_SIZEOF_INT], [$LG_SIZEOF_INT], [ ]) AC_CHECK_SIZEOF([long]) if test "x${ac_cv_sizeof_long}" = "x8" ; then LG_SIZEOF_LONG=3 elif test "x${ac_cv_sizeof_long}" = "x4" ; then LG_SIZEOF_LONG=2 else AC_MSG_ERROR([Unsupported long size: ${ac_cv_sizeof_long}]) fi AC_DEFINE_UNQUOTED([LG_SIZEOF_LONG], [$LG_SIZEOF_LONG], [ ]) AC_CHECK_SIZEOF([long long]) if test "x${ac_cv_sizeof_long_long}" = "x8" ; then LG_SIZEOF_LONG_LONG=3 elif test "x${ac_cv_sizeof_long_long}" = "x4" ; then LG_SIZEOF_LONG_LONG=2 else AC_MSG_ERROR([Unsupported long long size: ${ac_cv_sizeof_long_long}]) fi AC_DEFINE_UNQUOTED([LG_SIZEOF_LONG_LONG], [$LG_SIZEOF_LONG_LONG], [ ]) AC_CHECK_SIZEOF([intmax_t]) if test "x${ac_cv_sizeof_intmax_t}" = "x16" ; then LG_SIZEOF_INTMAX_T=4 elif test "x${ac_cv_sizeof_intmax_t}" = "x8" ; then LG_SIZEOF_INTMAX_T=3 elif test "x${ac_cv_sizeof_intmax_t}" = "x4" ; then LG_SIZEOF_INTMAX_T=2 else AC_MSG_ERROR([Unsupported intmax_t size: ${ac_cv_sizeof_intmax_t}]) fi AC_DEFINE_UNQUOTED([LG_SIZEOF_INTMAX_T], [$LG_SIZEOF_INTMAX_T], [ ]) AC_CANONICAL_HOST dnl CPU-specific settings. CPU_SPINWAIT="" case "${host_cpu}" in i686|x86_64) HAVE_CPU_SPINWAIT=1 if test "x${je_cv_msvc}" = "xyes" ; then AC_CACHE_VAL([je_cv_pause_msvc], [JE_COMPILABLE([pause instruction MSVC], [], [[_mm_pause(); return 0;]], [je_cv_pause_msvc])]) if test "x${je_cv_pause_msvc}" = "xyes" ; then CPU_SPINWAIT='_mm_pause()' fi else AC_CACHE_VAL([je_cv_pause], [JE_COMPILABLE([pause instruction], [], [[__asm__ volatile("pause"); return 0;]], [je_cv_pause])]) if test "x${je_cv_pause}" = "xyes" ; then CPU_SPINWAIT='__asm__ volatile("pause")' fi fi ;; aarch64|arm*) HAVE_CPU_SPINWAIT=1 dnl isb is a better equivalent to the pause instruction on x86. AC_CACHE_VAL([je_cv_isb], [JE_COMPILABLE([isb instruction], [], [[__asm__ volatile("isb"); return 0;]], [je_cv_isb])]) if test "x${je_cv_isb}" = "xyes" ; then CPU_SPINWAIT='__asm__ volatile("isb")' fi ;; *) HAVE_CPU_SPINWAIT=0 ;; esac AC_DEFINE_UNQUOTED([HAVE_CPU_SPINWAIT], [$HAVE_CPU_SPINWAIT], [ ]) AC_DEFINE_UNQUOTED([CPU_SPINWAIT], [$CPU_SPINWAIT], [ ]) AC_ARG_WITH([lg_vaddr], [AS_HELP_STRING([--with-lg-vaddr=], [Number of significant virtual address bits])], [LG_VADDR="$with_lg_vaddr"], [LG_VADDR="detect"]) case "${host_cpu}" in aarch64) if test "x$LG_VADDR" = "xdetect"; then AC_MSG_CHECKING([number of significant virtual address bits]) if test "x${LG_SIZEOF_PTR}" = "x2" ; then #aarch64 ILP32 LG_VADDR=32 else #aarch64 LP64 LG_VADDR=48 fi AC_MSG_RESULT([$LG_VADDR]) fi ;; x86_64) if test "x$LG_VADDR" = "xdetect"; then AC_CACHE_CHECK([number of significant virtual address bits], [je_cv_lg_vaddr], AC_RUN_IFELSE([AC_LANG_PROGRAM( [[ #include #ifdef _WIN32 #include #include typedef unsigned __int32 uint32_t; #else #include #endif ]], [[ uint32_t r[[4]]; uint32_t eax_in = 0x80000008U; #ifdef _WIN32 __cpuid((int *)r, (int)eax_in); #else asm volatile ("cpuid" : "=a" (r[[0]]), "=b" (r[[1]]), "=c" (r[[2]]), "=d" (r[[3]]) : "a" (eax_in), "c" (0) ); #endif uint32_t eax_out = r[[0]]; uint32_t vaddr = ((eax_out & 0x0000ff00U) >> 8); FILE *f = fopen("conftest.out", "w"); if (f == NULL) { return 1; } if (vaddr > (sizeof(void *) << 3)) { vaddr = sizeof(void *) << 3; } fprintf(f, "%u", vaddr); fclose(f); return 0; ]])], [je_cv_lg_vaddr=`cat conftest.out`], [je_cv_lg_vaddr=error], [je_cv_lg_vaddr=57])) if test "x${je_cv_lg_vaddr}" != "x" ; then LG_VADDR="${je_cv_lg_vaddr}" fi if test "x${LG_VADDR}" != "xerror" ; then AC_DEFINE_UNQUOTED([LG_VADDR], [$LG_VADDR], [ ]) else AC_MSG_ERROR([cannot determine number of significant virtual address bits]) fi fi ;; *) if test "x$LG_VADDR" = "xdetect"; then AC_MSG_CHECKING([number of significant virtual address bits]) if test "x${LG_SIZEOF_PTR}" = "x3" ; then LG_VADDR=64 elif test "x${LG_SIZEOF_PTR}" = "x2" ; then LG_VADDR=32 elif test "x${LG_SIZEOF_PTR}" = "xLG_SIZEOF_PTR_WIN" ; then LG_VADDR="(1U << (LG_SIZEOF_PTR_WIN+3))" else AC_MSG_ERROR([Unsupported lg(pointer size): ${LG_SIZEOF_PTR}]) fi AC_MSG_RESULT([$LG_VADDR]) fi ;; esac AC_DEFINE_UNQUOTED([LG_VADDR], [$LG_VADDR], [ ]) LD_PRELOAD_VAR="LD_PRELOAD" so="so" importlib="${so}" o="$ac_objext" a="a" exe="$ac_exeext" libprefix="lib" link_whole_archive="0" DSO_LDFLAGS='-shared -Wl,-soname,$(@F)' RPATH='-Wl,-rpath,$(1)' SOREV="${so}.${rev}" PIC_CFLAGS='-fPIC -DPIC' CTARGET='-o $@' LDTARGET='-o $@' TEST_LD_MODE= EXTRA_LDFLAGS= ARFLAGS='crs' AROUT=' $@' CC_MM=1 if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then TEST_LD_MODE='-dynamic' fi if test "x${je_cv_cray}" = "xyes" ; then CC_MM= fi AN_MAKEVAR([AR], [AC_PROG_AR]) AN_PROGRAM([ar], [AC_PROG_AR]) AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL(AR, ar, :)]) AC_PROG_AR AN_MAKEVAR([NM], [AC_PROG_NM]) AN_PROGRAM([nm], [AC_PROG_NM]) AC_DEFUN([AC_PROG_NM], [AC_CHECK_TOOL(NM, nm, :)]) AC_PROG_NM AC_PROG_AWK dnl ============================================================================ dnl jemalloc version. dnl AC_ARG_WITH([version], [AS_HELP_STRING([--with-version=..--g], [Version string])], [ echo "${with_version}" | grep ['^[0-9]\+\.[0-9]\+\.[0-9]\+-[0-9]\+-g[0-9a-f]\+$'] 2>&1 1>/dev/null if test $? -eq 0 ; then echo "$with_version" > "${objroot}VERSION" else echo "${with_version}" | grep ['^VERSION$'] 2>&1 1>/dev/null if test $? -ne 0 ; then AC_MSG_ERROR([${with_version} does not match ..--g or VERSION]) fi fi ], [ dnl Set VERSION if source directory is inside a git repository. if test "x`test ! \"${srcroot}\" && cd \"${srcroot}\"; git rev-parse --is-inside-work-tree 2>/dev/null`" = "xtrue" ; then dnl Pattern globs aren't powerful enough to match both single- and dnl double-digit version numbers, so iterate over patterns to support up dnl to version 99.99.99 without any accidental matches. for pattern in ['[0-9].[0-9].[0-9]' '[0-9].[0-9].[0-9][0-9]' \ '[0-9].[0-9][0-9].[0-9]' '[0-9].[0-9][0-9].[0-9][0-9]' \ '[0-9][0-9].[0-9].[0-9]' '[0-9][0-9].[0-9].[0-9][0-9]' \ '[0-9][0-9].[0-9][0-9].[0-9]' \ '[0-9][0-9].[0-9][0-9].[0-9][0-9]']; do (test ! "${srcroot}" && cd "${srcroot}"; git describe --long --abbrev=40 --match="${pattern}") > "${objroot}VERSION.tmp" 2>/dev/null if test $? -eq 0 ; then mv "${objroot}VERSION.tmp" "${objroot}VERSION" break fi done fi rm -f "${objroot}VERSION.tmp" ]) if test ! -e "${objroot}VERSION" ; then if test ! -e "${srcroot}VERSION" ; then AC_MSG_RESULT( [Missing VERSION file, and unable to generate it; creating bogus VERSION]) echo "0.0.0-0-g000000missing_version_try_git_fetch_tags" > "${objroot}VERSION" else cp ${srcroot}VERSION ${objroot}VERSION fi fi jemalloc_version=`cat "${objroot}VERSION"` jemalloc_version_major=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print [$]1}'` jemalloc_version_minor=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print [$]2}'` jemalloc_version_bugfix=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print [$]3}'` jemalloc_version_nrev=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print [$]4}'` jemalloc_version_gid=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print [$]5}'` AC_SUBST([jemalloc_version]) AC_SUBST([jemalloc_version_major]) AC_SUBST([jemalloc_version_minor]) AC_SUBST([jemalloc_version_bugfix]) AC_SUBST([jemalloc_version_nrev]) AC_SUBST([jemalloc_version_gid]) dnl Platform-specific settings. abi and RPATH can probably be determined dnl programmatically, but doing so is error-prone, which makes it generally dnl not worth the trouble. dnl dnl Define cpp macros in CPPFLAGS, rather than doing AC_DEFINE(macro), since the dnl definitions need to be seen before any headers are included, which is a pain dnl to make happen otherwise. default_retain="0" zero_realloc_default_free="0" maps_coalesce="1" DUMP_SYMS="${NM} -a" SYM_PREFIX="" case "${host}" in *-*-darwin* | *-*-ios*) abi="macho" RPATH="" LD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES" so="dylib" importlib="${so}" force_tls="0" DSO_LDFLAGS='-shared -Wl,-install_name,$(LIBDIR)/$(@F)' SOREV="${rev}.${so}" sbrk_deprecated="1" SYM_PREFIX="_" ;; *-*-freebsd*) JE_APPEND_VS(CPPFLAGS, -D_BSD_SOURCE) abi="elf" AC_DEFINE([JEMALLOC_SYSCTL_VM_OVERCOMMIT], [ ], [ ]) force_lazy_lock="1" ;; *-*-dragonfly*) abi="elf" ;; *-*-openbsd*) abi="elf" force_tls="0" ;; *-*-bitrig*) abi="elf" ;; *-*-linux-android*) dnl syscall(2) and secure_getenv(3) are exposed by _GNU_SOURCE. JE_APPEND_VS(CPPFLAGS, -D_GNU_SOURCE) abi="elf" glibc="0" AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS], [ ], [ ]) AC_DEFINE([JEMALLOC_HAS_ALLOCA_H], [ ], [ ]) AC_DEFINE([JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY], [ ], [ ]) AC_DEFINE([JEMALLOC_THREADED_INIT], [ ], [ ]) AC_DEFINE([JEMALLOC_C11_ATOMICS], [ ], [ ]) force_tls="0" if test "${LG_SIZEOF_PTR}" = "3"; then default_retain="1" fi zero_realloc_default_free="1" ;; *-*-linux*) dnl syscall(2) and secure_getenv(3) are exposed by _GNU_SOURCE. JE_APPEND_VS(CPPFLAGS, -D_GNU_SOURCE) abi="elf" glibc="1" AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS], [ ], [ ]) AC_DEFINE([JEMALLOC_HAS_ALLOCA_H], [ ], [ ]) AC_DEFINE([JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY], [ ], [ ]) AC_DEFINE([JEMALLOC_THREADED_INIT], [ ], [ ]) AC_DEFINE([JEMALLOC_USE_CXX_THROW], [ ], [ ]) if test "${LG_SIZEOF_PTR}" = "3"; then default_retain="1" fi zero_realloc_default_free="1" ;; *-*-kfreebsd*) dnl syscall(2) and secure_getenv(3) are exposed by _GNU_SOURCE. JE_APPEND_VS(CPPFLAGS, -D_GNU_SOURCE) abi="elf" AC_DEFINE([JEMALLOC_HAS_ALLOCA_H], [ ], [ ]) AC_DEFINE([JEMALLOC_SYSCTL_VM_OVERCOMMIT], [ ], [ ]) AC_DEFINE([JEMALLOC_THREADED_INIT], [ ], [ ]) AC_DEFINE([JEMALLOC_USE_CXX_THROW], [ ], [ ]) ;; *-*-netbsd*) AC_MSG_CHECKING([ABI]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[#ifdef __ELF__ /* ELF */ #else #error aout #endif ]])], [abi="elf"], [abi="aout"]) AC_MSG_RESULT([$abi]) ;; *-*-solaris2*) abi="elf" RPATH='-Wl,-R,$(1)' dnl Solaris needs this for sigwait(). JE_APPEND_VS(CPPFLAGS, -D_POSIX_PTHREAD_SEMANTICS) JE_APPEND_VS(LIBS, -lposix4 -lsocket -lnsl) ;; *-ibm-aix*) if test "${LG_SIZEOF_PTR}" = "3"; then dnl 64bit AIX LD_PRELOAD_VAR="LDR_PRELOAD64" else dnl 32bit AIX LD_PRELOAD_VAR="LDR_PRELOAD" fi abi="xcoff" ;; *-*-mingw* | *-*-cygwin*) abi="pecoff" force_tls="0" maps_coalesce="0" RPATH="" so="dll" if test "x$je_cv_msvc" = "xyes" ; then importlib="lib" DSO_LDFLAGS="-LD" EXTRA_LDFLAGS="-link -DEBUG" CTARGET='-Fo$@' LDTARGET='-Fe$@' AR='lib' ARFLAGS='-nologo -out:' AROUT='$@' CC_MM= else importlib="${so}" DSO_LDFLAGS="-shared" link_whole_archive="1" fi case "${host}" in *-*-cygwin*) DUMP_SYMS="dumpbin /SYMBOLS" ;; *) ;; esac a="lib" libprefix="" SOREV="${so}" PIC_CFLAGS="" if test "${LG_SIZEOF_PTR}" = "3"; then default_retain="1" fi zero_realloc_default_free="1" ;; *-*-nto-qnx) abi="elf" force_tls="0" AC_DEFINE([JEMALLOC_HAS_ALLOCA_H], [ ], [ ]) ;; *) AC_MSG_RESULT([Unsupported operating system: ${host}]) abi="elf" ;; esac JEMALLOC_USABLE_SIZE_CONST=const AC_CHECK_HEADERS([malloc.h], [ AC_MSG_CHECKING([whether malloc_usable_size definition can use const argument]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [#include #include size_t malloc_usable_size(const void *ptr); ], [])],[ AC_MSG_RESULT([yes]) ],[ JEMALLOC_USABLE_SIZE_CONST= AC_MSG_RESULT([no]) ]) ]) AC_DEFINE_UNQUOTED([JEMALLOC_USABLE_SIZE_CONST], [$JEMALLOC_USABLE_SIZE_CONST], [ ]) AC_SUBST([abi]) AC_SUBST([RPATH]) AC_SUBST([LD_PRELOAD_VAR]) AC_SUBST([so]) AC_SUBST([importlib]) AC_SUBST([o]) AC_SUBST([a]) AC_SUBST([exe]) AC_SUBST([libprefix]) AC_SUBST([link_whole_archive]) AC_SUBST([DSO_LDFLAGS]) AC_SUBST([EXTRA_LDFLAGS]) AC_SUBST([SOREV]) AC_SUBST([PIC_CFLAGS]) AC_SUBST([CTARGET]) AC_SUBST([LDTARGET]) AC_SUBST([TEST_LD_MODE]) AC_SUBST([MKLIB]) AC_SUBST([ARFLAGS]) AC_SUBST([AROUT]) AC_SUBST([DUMP_SYMS]) AC_SUBST([CC_MM]) dnl Determine whether libm must be linked to use e.g. log(3). AC_SEARCH_LIBS([log], [m], , [AC_MSG_ERROR([Missing math functions])]) if test "x$ac_cv_search_log" != "xnone required" ; then LM="$ac_cv_search_log" else LM= fi AC_SUBST(LM) JE_COMPILABLE([__attribute__ syntax], [static __attribute__((unused)) void foo(void){}], [], [je_cv_attribute]) if test "x${je_cv_attribute}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR], [ ], [ ]) if test "x${GCC}" = "xyes" -a "x${abi}" = "xelf"; then JE_CFLAGS_ADD([-fvisibility=hidden]) JE_CXXFLAGS_ADD([-fvisibility=hidden]) fi fi dnl Check for tls_model attribute support (clang 3.0 still lacks support). JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([tls_model attribute], [], [static __thread int __attribute__((tls_model("initial-exec"), unused)) foo; foo = 0;], [je_cv_tls_model]) JE_CFLAGS_RESTORE() dnl (Setting of JEMALLOC_TLS_MODEL is done later, after we've checked for dnl --disable-initial-exec-tls) dnl Check for alloc_size attribute support. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([alloc_size attribute], [#include ], [void *foo(size_t size) __attribute__((alloc_size(1)));], [je_cv_alloc_size]) JE_CFLAGS_RESTORE() if test "x${je_cv_alloc_size}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR_ALLOC_SIZE], [ ], [ ]) fi dnl Check for format(gnu_printf, ...) attribute support. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([format(gnu_printf, ...) attribute], [#include ], [void *foo(const char *format, ...) __attribute__((format(gnu_printf, 1, 2)));], [je_cv_format_gnu_printf]) JE_CFLAGS_RESTORE() if test "x${je_cv_format_gnu_printf}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF], [ ], [ ]) fi dnl Check for format(printf, ...) attribute support. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([format(printf, ...) attribute], [#include ], [void *foo(const char *format, ...) __attribute__((format(printf, 1, 2)));], [je_cv_format_printf]) JE_CFLAGS_RESTORE() if test "x${je_cv_format_printf}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR_FORMAT_PRINTF], [ ], [ ]) fi dnl Check for format_arg(...) attribute support. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([format(printf, ...) attribute], [#include ], [const char * __attribute__((__format_arg__(1))) foo(const char *format);], [je_cv_format_arg]) JE_CFLAGS_RESTORE() if test "x${je_cv_format_arg}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR_FORMAT_ARG], [ ], [ ]) fi dnl Check for fallthrough attribute support. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Wimplicit-fallthrough]) JE_COMPILABLE([fallthrough attribute], [#if !__has_attribute(fallthrough) #error "foo" #endif], [int x = 0; switch (x) { case 0: __attribute__((__fallthrough__)); case 1: return 1; }], [je_cv_fallthrough]) JE_CFLAGS_RESTORE() if test "x${je_cv_fallthrough}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR_FALLTHROUGH], [ ], [ ]) JE_CFLAGS_ADD([-Wimplicit-fallthrough]) JE_CXXFLAGS_ADD([-Wimplicit-fallthrough]) fi dnl Check for cold attribute support. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([cold attribute], [], [__attribute__((__cold__)) void foo();], [je_cv_cold]) JE_CFLAGS_RESTORE() if test "x${je_cv_cold}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_ATTR_COLD], [ ], [ ]) fi dnl Check for VM_MAKE_TAG for mmap support. JE_COMPILABLE([vm_make_tag], [#include #include ], [void *p; p = mmap(0, 16, PROT_READ, MAP_ANON|MAP_PRIVATE, VM_MAKE_TAG(1), 0); munmap(p, 16);], [je_cv_vm_make_tag]) if test "x${je_cv_vm_make_tag}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_VM_MAKE_TAG], [ ], [ ]) fi dnl Support optional additions to rpath. AC_ARG_WITH([rpath], [AS_HELP_STRING([--with-rpath=], [Colon-separated rpath (ELF systems only)])], if test "x$with_rpath" = "xno" ; then RPATH_EXTRA= else RPATH_EXTRA="`echo $with_rpath | tr \":\" \" \"`" fi, RPATH_EXTRA= ) AC_SUBST([RPATH_EXTRA]) dnl Disable rules that do automatic regeneration of configure output by default. AC_ARG_ENABLE([autogen], [AS_HELP_STRING([--enable-autogen], [Automatically regenerate configure output])], if test "x$enable_autogen" = "xno" ; then enable_autogen="0" else enable_autogen="1" fi , enable_autogen="0" ) AC_SUBST([enable_autogen]) AC_PROG_INSTALL AC_PROG_RANLIB AC_PATH_PROG([LD], [ld], [false], [$PATH]) AC_PATH_PROG([AUTOCONF], [autoconf], [false], [$PATH]) dnl Enable documentation AC_ARG_ENABLE([doc], [AS_HELP_STRING([--enable-doc], [Build documentation])], if test "x$enable_doc" = "xno" ; then enable_doc="0" else enable_doc="1" fi , enable_doc="1" ) AC_SUBST([enable_doc]) dnl Enable shared libs AC_ARG_ENABLE([shared], [AS_HELP_STRING([--enable-shared], [Build shared libaries])], if test "x$enable_shared" = "xno" ; then enable_shared="0" else enable_shared="1" fi , enable_shared="1" ) AC_SUBST([enable_shared]) dnl Enable static libs AC_ARG_ENABLE([static], [AS_HELP_STRING([--enable-static], [Build static libaries])], if test "x$enable_static" = "xno" ; then enable_static="0" else enable_static="1" fi , enable_static="1" ) AC_SUBST([enable_static]) if test "$enable_shared$enable_static" = "00" ; then AC_MSG_ERROR([Please enable one of shared or static builds]) fi dnl Perform no name mangling by default. AC_ARG_WITH([mangling], [AS_HELP_STRING([--with-mangling=], [Mangle symbols in ])], [mangling_map="$with_mangling"], [mangling_map=""]) dnl Do not prefix public APIs by default. AC_ARG_WITH([jemalloc_prefix], [AS_HELP_STRING([--with-jemalloc-prefix=], [Prefix to prepend to all public APIs])], [JEMALLOC_PREFIX="$with_jemalloc_prefix"], [if test "x$abi" != "xmacho" -a "x$abi" != "xpecoff"; then JEMALLOC_PREFIX="" else JEMALLOC_PREFIX="je_" fi] ) if test "x$JEMALLOC_PREFIX" = "x" ; then AC_DEFINE([JEMALLOC_IS_MALLOC], [ ], [ ]) else JEMALLOC_CPREFIX=`echo ${JEMALLOC_PREFIX} | tr "a-z" "A-Z"` AC_DEFINE_UNQUOTED([JEMALLOC_PREFIX], ["$JEMALLOC_PREFIX"], [ ]) AC_DEFINE_UNQUOTED([JEMALLOC_CPREFIX], ["$JEMALLOC_CPREFIX"], [ ]) fi AC_SUBST([JEMALLOC_PREFIX]) AC_SUBST([JEMALLOC_CPREFIX]) AC_ARG_WITH([export], [AS_HELP_STRING([--without-export], [disable exporting jemalloc public APIs])], [if test "x$with_export" = "xno"; then AC_DEFINE([JEMALLOC_EXPORT],[], [ ]) fi] ) public_syms="aligned_alloc calloc dallocx free mallctl mallctlbymib mallctlnametomib malloc malloc_conf malloc_conf_2_conf_harder malloc_message malloc_stats_print malloc_usable_size mallocx smallocx_${jemalloc_version_gid} nallocx posix_memalign rallocx realloc sallocx sdallocx xallocx" dnl Check for additional platform-specific public API functions. AC_CHECK_FUNC([memalign], [AC_DEFINE([JEMALLOC_OVERRIDE_MEMALIGN], [ ], [ ]) public_syms="${public_syms} memalign"]) AC_CHECK_FUNC([valloc], [AC_DEFINE([JEMALLOC_OVERRIDE_VALLOC], [ ], [ ]) public_syms="${public_syms} valloc"]) AC_CHECK_FUNC([malloc_size], [AC_DEFINE([JEMALLOC_HAVE_MALLOC_SIZE], [ ], [ ]) public_syms="${public_syms} malloc_size"]) dnl Check for allocator-related functions that should be wrapped. wrap_syms= if test "x${JEMALLOC_PREFIX}" = "x" ; then AC_CHECK_FUNC([__libc_calloc], [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_CALLOC], [ ], [ ]) wrap_syms="${wrap_syms} __libc_calloc"]) AC_CHECK_FUNC([__libc_free], [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_FREE], [ ], [ ]) wrap_syms="${wrap_syms} __libc_free"]) AC_CHECK_FUNC([__libc_malloc], [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_MALLOC], [ ], [ ]) wrap_syms="${wrap_syms} __libc_malloc"]) AC_CHECK_FUNC([__libc_memalign], [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_MEMALIGN], [ ], [ ]) wrap_syms="${wrap_syms} __libc_memalign"]) AC_CHECK_FUNC([__libc_realloc], [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_REALLOC], [ ], [ ]) wrap_syms="${wrap_syms} __libc_realloc"]) AC_CHECK_FUNC([__libc_valloc], [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_VALLOC], [ ], [ ]) wrap_syms="${wrap_syms} __libc_valloc"]) AC_CHECK_FUNC([__posix_memalign], [AC_DEFINE([JEMALLOC_OVERRIDE___POSIX_MEMALIGN], [ ], [ ]) wrap_syms="${wrap_syms} __posix_memalign"]) fi case "${host}" in *-*-mingw* | *-*-cygwin*) wrap_syms="${wrap_syms} tls_callback" ;; *) ;; esac dnl Mangle library-private APIs. AC_ARG_WITH([private_namespace], [AS_HELP_STRING([--with-private-namespace=], [Prefix to prepend to all library-private APIs])], [JEMALLOC_PRIVATE_NAMESPACE="${with_private_namespace}je_"], [JEMALLOC_PRIVATE_NAMESPACE="je_"] ) AC_DEFINE_UNQUOTED([JEMALLOC_PRIVATE_NAMESPACE], [$JEMALLOC_PRIVATE_NAMESPACE], [ ]) private_namespace="$JEMALLOC_PRIVATE_NAMESPACE" AC_SUBST([private_namespace]) dnl Do not add suffix to installed files by default. AC_ARG_WITH([install_suffix], [AS_HELP_STRING([--with-install-suffix=], [Suffix to append to all installed files])], [case "$with_install_suffix" in *\ * ) AC_MSG_ERROR([Install suffix should not contain spaces]) ;; * ) INSTALL_SUFFIX="$with_install_suffix" ;; esac], [INSTALL_SUFFIX=] ) install_suffix="$INSTALL_SUFFIX" AC_SUBST([install_suffix]) dnl Specify default malloc_conf. AC_ARG_WITH([malloc_conf], [AS_HELP_STRING([--with-malloc-conf=], [config.malloc_conf options string])], [JEMALLOC_CONFIG_MALLOC_CONF="$with_malloc_conf"], [JEMALLOC_CONFIG_MALLOC_CONF=""] ) config_malloc_conf="$JEMALLOC_CONFIG_MALLOC_CONF" AC_DEFINE_UNQUOTED([JEMALLOC_CONFIG_MALLOC_CONF], ["$config_malloc_conf"], [ ]) dnl Substitute @je_@ in jemalloc_protos.h.in, primarily to make generation of dnl jemalloc_protos_jet.h easy. je_="je_" AC_SUBST([je_]) cfgoutputs_in="Makefile.in" cfgoutputs_in="${cfgoutputs_in} jemalloc.pc.in" cfgoutputs_in="${cfgoutputs_in} doc/html.xsl.in" cfgoutputs_in="${cfgoutputs_in} doc/manpages.xsl.in" cfgoutputs_in="${cfgoutputs_in} doc/jemalloc.xml.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_macros.h.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_protos.h.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_typedefs.h.in" cfgoutputs_in="${cfgoutputs_in} include/jemalloc/internal/jemalloc_preamble.h.in" cfgoutputs_in="${cfgoutputs_in} test/test.sh.in" cfgoutputs_in="${cfgoutputs_in} test/include/test/jemalloc_test.h.in" cfgoutputs_out="Makefile" cfgoutputs_out="${cfgoutputs_out} jemalloc.pc" cfgoutputs_out="${cfgoutputs_out} doc/html.xsl" cfgoutputs_out="${cfgoutputs_out} doc/manpages.xsl" cfgoutputs_out="${cfgoutputs_out} doc/jemalloc.xml" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_macros.h" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_protos.h" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_typedefs.h" cfgoutputs_out="${cfgoutputs_out} include/jemalloc/internal/jemalloc_preamble.h" cfgoutputs_out="${cfgoutputs_out} test/test.sh" cfgoutputs_out="${cfgoutputs_out} test/include/test/jemalloc_test.h" cfgoutputs_tup="Makefile" cfgoutputs_tup="${cfgoutputs_tup} jemalloc.pc:jemalloc.pc.in" cfgoutputs_tup="${cfgoutputs_tup} doc/html.xsl:doc/html.xsl.in" cfgoutputs_tup="${cfgoutputs_tup} doc/manpages.xsl:doc/manpages.xsl.in" cfgoutputs_tup="${cfgoutputs_tup} doc/jemalloc.xml:doc/jemalloc.xml.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_macros.h:include/jemalloc/jemalloc_macros.h.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_protos.h:include/jemalloc/jemalloc_protos.h.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_typedefs.h:include/jemalloc/jemalloc_typedefs.h.in" cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/internal/jemalloc_preamble.h" cfgoutputs_tup="${cfgoutputs_tup} test/test.sh:test/test.sh.in" cfgoutputs_tup="${cfgoutputs_tup} test/include/test/jemalloc_test.h:test/include/test/jemalloc_test.h.in" cfghdrs_in="include/jemalloc/jemalloc_defs.h.in" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/jemalloc_internal_defs.h.in" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_symbols.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_namespace.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_namespace.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_unnamespace.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/jemalloc_rename.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/jemalloc_mangle.sh" cfghdrs_in="${cfghdrs_in} include/jemalloc/jemalloc.sh" cfghdrs_in="${cfghdrs_in} test/include/test/jemalloc_test_defs.h.in" cfghdrs_out="include/jemalloc/jemalloc_defs.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc${install_suffix}.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols.awk" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols_jet.awk" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_symbols.txt" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_namespace.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_unnamespace.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_protos_jet.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_rename.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_mangle.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc_mangle_jet.h" cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/jemalloc_internal_defs.h" cfghdrs_out="${cfghdrs_out} test/include/test/jemalloc_test_defs.h" cfghdrs_tup="include/jemalloc/jemalloc_defs.h:include/jemalloc/jemalloc_defs.h.in" cfghdrs_tup="${cfghdrs_tup} include/jemalloc/internal/jemalloc_internal_defs.h:include/jemalloc/internal/jemalloc_internal_defs.h.in" cfghdrs_tup="${cfghdrs_tup} test/include/test/jemalloc_test_defs.h:test/include/test/jemalloc_test_defs.h.in" dnl ============================================================================ dnl jemalloc build options. dnl dnl Do not compile with debugging by default. AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [Build debugging code])], [if test "x$enable_debug" = "xno" ; then enable_debug="0" else enable_debug="1" fi ], [enable_debug="0"] ) if test "x$enable_debug" = "x1" ; then AC_DEFINE([JEMALLOC_DEBUG], [ ], [ ]) fi AC_SUBST([enable_debug]) dnl Only optimize if not debugging. if test "x$enable_debug" = "x0" ; then if test "x$GCC" = "xyes" ; then JE_CFLAGS_ADD([-O3]) JE_CXXFLAGS_ADD([-O3]) JE_CFLAGS_ADD([-funroll-loops]) elif test "x$je_cv_msvc" = "xyes" ; then JE_CFLAGS_ADD([-O2]) JE_CXXFLAGS_ADD([-O2]) else JE_CFLAGS_ADD([-O]) JE_CXXFLAGS_ADD([-O]) fi fi dnl Enable statistics calculation by default. AC_ARG_ENABLE([stats], [AS_HELP_STRING([--disable-stats], [Disable statistics calculation/reporting])], [if test "x$enable_stats" = "xno" ; then enable_stats="0" else enable_stats="1" fi ], [enable_stats="1"] ) if test "x$enable_stats" = "x1" ; then AC_DEFINE([JEMALLOC_STATS], [ ], [ ]) fi AC_SUBST([enable_stats]) dnl Do not enable smallocx by default. AC_ARG_ENABLE([experimental_smallocx], [AS_HELP_STRING([--enable-experimental-smallocx], [Enable experimental smallocx API])], [if test "x$enable_experimental_smallocx" = "xno" ; then enable_experimental_smallocx="0" else enable_experimental_smallocx="1" fi ], [enable_experimental_smallocx="0"] ) if test "x$enable_experimental_smallocx" = "x1" ; then AC_DEFINE([JEMALLOC_EXPERIMENTAL_SMALLOCX_API], [ ], [ ]) fi AC_SUBST([enable_experimental_smallocx]) dnl Do not enable profiling by default. AC_ARG_ENABLE([prof], [AS_HELP_STRING([--enable-prof], [Enable allocation profiling])], [if test "x$enable_prof" = "xno" ; then enable_prof="0" else enable_prof="1" fi ], [enable_prof="0"] ) if test "x$enable_prof" = "x1" ; then backtrace_method="" else backtrace_method="N/A" fi AC_ARG_ENABLE([prof-libunwind], [AS_HELP_STRING([--enable-prof-libunwind], [Use libunwind for backtracing])], [if test "x$enable_prof_libunwind" = "xno" ; then enable_prof_libunwind="0" else enable_prof_libunwind="1" if test "x$enable_prof" = "x0" ; then AC_MSG_ERROR([--enable-prof-libunwind should only be used with --enable-prof]) fi fi ], [enable_prof_libunwind="0"] ) AC_ARG_WITH([static_libunwind], [AS_HELP_STRING([--with-static-libunwind=], [Path to static libunwind library; use rather than dynamically linking])], if test "x$with_static_libunwind" = "xno" ; then LUNWIND="-lunwind" else if test ! -f "$with_static_libunwind" ; then AC_MSG_ERROR([Static libunwind not found: $with_static_libunwind]) fi LUNWIND="$with_static_libunwind" fi, LUNWIND="-lunwind" ) if test "x$backtrace_method" = "x" -a "x$enable_prof_libunwind" = "x1" ; then AC_CHECK_HEADERS([libunwind.h], , [enable_prof_libunwind="0"]) if test "x$LUNWIND" = "x-lunwind" ; then AC_CHECK_LIB([unwind], [unw_backtrace], [JE_APPEND_VS(LIBS, $LUNWIND)], [enable_prof_libunwind="0"]) else JE_APPEND_VS(LIBS, $LUNWIND) fi if test "x${enable_prof_libunwind}" = "x1" ; then backtrace_method="libunwind" AC_DEFINE([JEMALLOC_PROF_LIBUNWIND], [ ], [ ]) fi fi AC_ARG_ENABLE([prof-libgcc], [AS_HELP_STRING([--disable-prof-libgcc], [Do not use libgcc for backtracing])], [if test "x$enable_prof_libgcc" = "xno" ; then enable_prof_libgcc="0" else enable_prof_libgcc="1" fi ], [enable_prof_libgcc="1"] ) if test "x$backtrace_method" = "x" -a "x$enable_prof_libgcc" = "x1" \ -a "x$GCC" = "xyes" ; then AC_CHECK_HEADERS([unwind.h], , [enable_prof_libgcc="0"]) if test "x${enable_prof_libgcc}" = "x1" ; then AC_CHECK_LIB([gcc], [_Unwind_Backtrace], [JE_APPEND_VS(LIBS, -lgcc)], [enable_prof_libgcc="0"]) fi if test "x${enable_prof_libgcc}" = "x1" ; then backtrace_method="libgcc" AC_DEFINE([JEMALLOC_PROF_LIBGCC], [ ], [ ]) fi else enable_prof_libgcc="0" fi AC_ARG_ENABLE([prof-gcc], [AS_HELP_STRING([--disable-prof-gcc], [Do not use gcc intrinsics for backtracing])], [if test "x$enable_prof_gcc" = "xno" ; then enable_prof_gcc="0" else enable_prof_gcc="1" fi ], [enable_prof_gcc="1"] ) if test "x$backtrace_method" = "x" -a "x$enable_prof_gcc" = "x1" \ -a "x$GCC" = "xyes" ; then JE_CFLAGS_ADD([-fno-omit-frame-pointer]) backtrace_method="gcc intrinsics" AC_DEFINE([JEMALLOC_PROF_GCC], [ ], [ ]) else enable_prof_gcc="0" fi if test "x$backtrace_method" = "x" ; then backtrace_method="none (disabling profiling)" enable_prof="0" fi AC_MSG_CHECKING([configured backtracing method]) AC_MSG_RESULT([$backtrace_method]) if test "x$enable_prof" = "x1" ; then dnl Heap profiling uses the log(3) function. JE_APPEND_VS(LIBS, $LM) AC_DEFINE([JEMALLOC_PROF], [ ], [ ]) fi AC_SUBST([enable_prof]) dnl Indicate whether adjacent virtual memory mappings automatically coalesce dnl (and fragment on demand). if test "x${maps_coalesce}" = "x1" ; then AC_DEFINE([JEMALLOC_MAPS_COALESCE], [ ], [ ]) fi dnl Indicate whether to retain memory (rather than using munmap()) by default. if test "x$default_retain" = "x1" ; then AC_DEFINE([JEMALLOC_RETAIN], [ ], [ ]) fi dnl Indicate whether realloc(ptr, 0) defaults to the "alloc" behavior. if test "x$zero_realloc_default_free" = "x1" ; then AC_DEFINE([JEMALLOC_ZERO_REALLOC_DEFAULT_FREE], [ ], [ ]) fi dnl Enable allocation from DSS if supported by the OS. have_dss="1" dnl Check whether the BSD/SUSv1 sbrk() exists. If not, disable DSS support. AC_CHECK_FUNC([sbrk], [have_sbrk="1"], [have_sbrk="0"]) if test "x$have_sbrk" = "x1" ; then if test "x$sbrk_deprecated" = "x1" ; then AC_MSG_RESULT([Disabling dss allocation because sbrk is deprecated]) have_dss="0" fi else have_dss="0" fi if test "x$have_dss" = "x1" ; then AC_DEFINE([JEMALLOC_DSS], [ ], [ ]) fi dnl Support the junk/zero filling option by default. AC_ARG_ENABLE([fill], [AS_HELP_STRING([--disable-fill], [Disable support for junk/zero filling])], [if test "x$enable_fill" = "xno" ; then enable_fill="0" else enable_fill="1" fi ], [enable_fill="1"] ) if test "x$enable_fill" = "x1" ; then AC_DEFINE([JEMALLOC_FILL], [ ], [ ]) fi AC_SUBST([enable_fill]) dnl Disable utrace(2)-based tracing by default. AC_ARG_ENABLE([utrace], [AS_HELP_STRING([--enable-utrace], [Enable utrace(2)-based tracing])], [if test "x$enable_utrace" = "xno" ; then enable_utrace="0" else enable_utrace="1" fi ], [enable_utrace="0"] ) JE_COMPILABLE([utrace(2)], [ #include #include #include #include #include ], [ utrace((void *)0, 0); ], [je_cv_utrace]) if test "x${je_cv_utrace}" = "xno" ; then JE_COMPILABLE([utrace(2) with label], [ #include #include #include #include #include ], [ utrace((void *)0, (void *)0, 0); ], [je_cv_utrace_label]) if test "x${je_cv_utrace_label}" = "xno"; then enable_utrace="0" fi if test "x$enable_utrace" = "x1" ; then AC_DEFINE([JEMALLOC_UTRACE_LABEL], [ ], [ ]) fi else if test "x$enable_utrace" = "x1" ; then AC_DEFINE([JEMALLOC_UTRACE], [ ], [ ]) fi fi AC_SUBST([enable_utrace]) dnl Do not support the xmalloc option by default. AC_ARG_ENABLE([xmalloc], [AS_HELP_STRING([--enable-xmalloc], [Support xmalloc option])], [if test "x$enable_xmalloc" = "xno" ; then enable_xmalloc="0" else enable_xmalloc="1" fi ], [enable_xmalloc="0"] ) if test "x$enable_xmalloc" = "x1" ; then AC_DEFINE([JEMALLOC_XMALLOC], [ ], [ ]) fi AC_SUBST([enable_xmalloc]) dnl Support cache-oblivious allocation alignment by default. AC_ARG_ENABLE([cache-oblivious], [AS_HELP_STRING([--disable-cache-oblivious], [Disable support for cache-oblivious allocation alignment])], [if test "x$enable_cache_oblivious" = "xno" ; then enable_cache_oblivious="0" else enable_cache_oblivious="1" fi ], [enable_cache_oblivious="1"] ) if test "x$enable_cache_oblivious" = "x1" ; then AC_DEFINE([JEMALLOC_CACHE_OBLIVIOUS], [ ], [ ]) fi AC_SUBST([enable_cache_oblivious]) dnl Do not log by default. AC_ARG_ENABLE([log], [AS_HELP_STRING([--enable-log], [Support debug logging])], [if test "x$enable_log" = "xno" ; then enable_log="0" else enable_log="1" fi ], [enable_log="0"] ) if test "x$enable_log" = "x1" ; then AC_DEFINE([JEMALLOC_LOG], [ ], [ ]) fi AC_SUBST([enable_log]) dnl Do not use readlinkat by default AC_ARG_ENABLE([readlinkat], [AS_HELP_STRING([--enable-readlinkat], [Use readlinkat over readlink])], [if test "x$enable_readlinkat" = "xno" ; then enable_readlinkat="0" else enable_readlinkat="1" fi ], [enable_readlinkat="0"] ) if test "x$enable_readlinkat" = "x1" ; then AC_DEFINE([JEMALLOC_READLINKAT], [ ], [ ]) fi AC_SUBST([enable_readlinkat]) dnl Avoid extra safety checks by default AC_ARG_ENABLE([opt-safety-checks], [AS_HELP_STRING([--enable-opt-safety-checks], [Perform certain low-overhead checks, even in opt mode])], [if test "x$enable_opt_safety_checks" = "xno" ; then enable_opt_safety_checks="0" else enable_opt_safety_checks="1" fi ], [enable_opt_safety_checks="0"] ) if test "x$enable_opt_safety_checks" = "x1" ; then AC_DEFINE([JEMALLOC_OPT_SAFETY_CHECKS], [ ], [ ]) fi AC_SUBST([enable_opt_safety_checks]) dnl Look for sized-deallocation bugs while otherwise being in opt mode. AC_ARG_ENABLE([opt-size-checks], [AS_HELP_STRING([--enable-opt-size-checks], [Perform sized-deallocation argument checks, even in opt mode])], [if test "x$enable_opt_size_checks" = "xno" ; then enable_opt_size_checks="0" else enable_opt_size_checks="1" fi ], [enable_opt_size_checks="0"] ) if test "x$enable_opt_size_checks" = "x1" ; then AC_DEFINE([JEMALLOC_OPT_SIZE_CHECKS], [ ], [ ]) fi AC_SUBST([enable_opt_size_checks]) dnl Do not check for use-after-free by default. AC_ARG_ENABLE([uaf-detection], [AS_HELP_STRING([--enable-uaf-detection], [Allow sampled junk-filling on deallocation to detect use-after-free])], [if test "x$enable_uaf_detection" = "xno" ; then enable_uaf_detection="0" else enable_uaf_detection="1" fi ], [enable_uaf_detection="0"] ) if test "x$enable_uaf_detection" = "x1" ; then AC_DEFINE([JEMALLOC_UAF_DETECTION], [ ]) fi AC_SUBST([enable_uaf_detection]) JE_COMPILABLE([a program using __builtin_unreachable], [ void foo (void) { __builtin_unreachable(); } ], [ { foo(); } ], [je_cv_gcc_builtin_unreachable]) if test "x${je_cv_gcc_builtin_unreachable}" = "xyes" ; then AC_DEFINE([JEMALLOC_INTERNAL_UNREACHABLE], [__builtin_unreachable], [ ]) else AC_DEFINE([JEMALLOC_INTERNAL_UNREACHABLE], [abort], [ ]) fi dnl ============================================================================ dnl Check for __builtin_ffsl(), then ffsl(3), and fail if neither are found. dnl One of those two functions should (theoretically) exist on all platforms dnl that jemalloc currently has a chance of functioning on without modification. dnl We additionally assume ffs[ll]() or __builtin_ffs[ll]() are defined if dnl ffsl() or __builtin_ffsl() are defined, respectively. JE_COMPILABLE([a program using __builtin_ffsl], [ #include #include #include ], [ { int rv = __builtin_ffsl(0x08); printf("%d\n", rv); } ], [je_cv_gcc_builtin_ffsl]) if test "x${je_cv_gcc_builtin_ffsl}" = "xyes" ; then AC_DEFINE([JEMALLOC_INTERNAL_FFSLL], [__builtin_ffsll], [ ]) AC_DEFINE([JEMALLOC_INTERNAL_FFSL], [__builtin_ffsl], [ ]) AC_DEFINE([JEMALLOC_INTERNAL_FFS], [__builtin_ffs], [ ]) else JE_COMPILABLE([a program using ffsl], [ #include #include #include ], [ { int rv = ffsl(0x08); printf("%d\n", rv); } ], [je_cv_function_ffsl]) if test "x${je_cv_function_ffsl}" = "xyes" ; then AC_DEFINE([JEMALLOC_INTERNAL_FFSLL], [ffsll], [ ]) AC_DEFINE([JEMALLOC_INTERNAL_FFSL], [ffsl], [ ]) AC_DEFINE([JEMALLOC_INTERNAL_FFS], [ffs], [ ]) else AC_MSG_ERROR([Cannot build without ffsl(3) or __builtin_ffsl()]) fi fi JE_COMPILABLE([a program using __builtin_popcountl], [ #include #include #include ], [ { int rv = __builtin_popcountl(0x08); printf("%d\n", rv); } ], [je_cv_gcc_builtin_popcountl]) if test "x${je_cv_gcc_builtin_popcountl}" = "xyes" ; then AC_DEFINE([JEMALLOC_INTERNAL_POPCOUNT], [__builtin_popcount], [ ]) AC_DEFINE([JEMALLOC_INTERNAL_POPCOUNTL], [__builtin_popcountl], [ ]) AC_DEFINE([JEMALLOC_INTERNAL_POPCOUNTLL], [__builtin_popcountll], [ ]) fi AC_ARG_WITH([lg_quantum], [AS_HELP_STRING([--with-lg-quantum=], [Base 2 log of minimum allocation alignment])]) if test "x$with_lg_quantum" != "x" ; then AC_DEFINE_UNQUOTED([LG_QUANTUM], [$with_lg_quantum], [ ]) fi AC_ARG_WITH([lg_slab_maxregs], [AS_HELP_STRING([--with-lg-slab-maxregs=], [Base 2 log of maximum number of regions in a slab (used with malloc_conf slab_sizes)])], [CONFIG_LG_SLAB_MAXREGS="with_lg_slab_maxregs"], [CONFIG_LG_SLAB_MAXREGS=""]) if test "x$with_lg_slab_maxregs" != "x" ; then AC_DEFINE_UNQUOTED([CONFIG_LG_SLAB_MAXREGS], [$with_lg_slab_maxregs], [ ]) fi AC_ARG_WITH([lg_page], [AS_HELP_STRING([--with-lg-page=], [Base 2 log of system page size])], [LG_PAGE="$with_lg_page"], [LG_PAGE="detect"]) case "${host}" in aarch64-apple-darwin*) dnl When cross-compile for Apple M1 and no page size specified, use the dnl default and skip detecting the page size (which is likely incorrect). if test "x${host}" != "x${build}" -a "x$LG_PAGE" = "xdetect"; then LG_PAGE=14 fi ;; esac if test "x$LG_PAGE" = "xdetect"; then AC_CACHE_CHECK([LG_PAGE], [je_cv_lg_page], AC_RUN_IFELSE([AC_LANG_PROGRAM( [[ #include #ifdef _WIN32 #include #else #include #endif #include ]], [[ int result; FILE *f; #ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); result = si.dwPageSize; #else result = sysconf(_SC_PAGESIZE); #endif if (result == -1) { return 1; } result = JEMALLOC_INTERNAL_FFSL(result) - 1; f = fopen("conftest.out", "w"); if (f == NULL) { return 1; } fprintf(f, "%d", result); fclose(f); return 0; ]])], [je_cv_lg_page=`cat conftest.out`], [je_cv_lg_page=undefined], [je_cv_lg_page=12])) fi if test "x${je_cv_lg_page}" != "x" ; then LG_PAGE="${je_cv_lg_page}" fi if test "x${LG_PAGE}" != "xundefined" ; then AC_DEFINE_UNQUOTED([LG_PAGE], [$LG_PAGE], [ ]) else AC_MSG_ERROR([cannot determine value for LG_PAGE]) fi AC_ARG_WITH([lg_hugepage], [AS_HELP_STRING([--with-lg-hugepage=], [Base 2 log of system huge page size])], [je_cv_lg_hugepage="${with_lg_hugepage}"], [je_cv_lg_hugepage=""]) if test "x${je_cv_lg_hugepage}" = "x" ; then dnl Look in /proc/meminfo (Linux-specific) for information on the default huge dnl page size, if any. The relevant line looks like: dnl dnl Hugepagesize: 2048 kB if test -e "/proc/meminfo" ; then hpsk=[`cat /proc/meminfo 2>/dev/null | \ grep -e '^Hugepagesize:[[:space:]]\+[0-9]\+[[:space:]]kB$' | \ awk '{print $2}'`] if test "x${hpsk}" != "x" ; then je_cv_lg_hugepage=10 while test "${hpsk}" -gt 1 ; do hpsk="$((hpsk / 2))" je_cv_lg_hugepage="$((je_cv_lg_hugepage + 1))" done fi fi dnl Set default if unable to automatically configure. if test "x${je_cv_lg_hugepage}" = "x" ; then je_cv_lg_hugepage=21 fi fi if test "x${LG_PAGE}" != "xundefined" -a \ "${je_cv_lg_hugepage}" -lt "${LG_PAGE}" ; then AC_MSG_ERROR([Huge page size (2^${je_cv_lg_hugepage}) must be at least page size (2^${LG_PAGE})]) fi AC_DEFINE_UNQUOTED([LG_HUGEPAGE], [${je_cv_lg_hugepage}], [ ]) dnl ============================================================================ dnl Enable libdl by default. AC_ARG_ENABLE([libdl], [AS_HELP_STRING([--disable-libdl], [Do not use libdl])], [if test "x$enable_libdl" = "xno" ; then enable_libdl="0" else enable_libdl="1" fi ], [enable_libdl="1"] ) AC_SUBST([libdl]) dnl ============================================================================ dnl Configure pthreads. if test "x$abi" != "xpecoff" ; then AC_DEFINE([JEMALLOC_HAVE_PTHREAD], [ ], [ ]) AC_CHECK_HEADERS([pthread.h], , [AC_MSG_ERROR([pthread.h is missing])]) dnl Some systems may embed pthreads functionality in libc; check for libpthread dnl first, but try libc too before failing. AC_CHECK_LIB([pthread], [pthread_create], [JE_APPEND_VS(LIBS, -pthread)], [AC_SEARCH_LIBS([pthread_create], , , AC_MSG_ERROR([libpthread is missing]))]) wrap_syms="${wrap_syms} pthread_create" have_pthread="1" dnl Check if we have dlsym support. if test "x$enable_libdl" = "x1" ; then have_dlsym="1" AC_CHECK_HEADERS([dlfcn.h], AC_CHECK_FUNC([dlsym], [], [AC_CHECK_LIB([dl], [dlsym], [LIBS="$LIBS -ldl"], [have_dlsym="0"])]), [have_dlsym="0"]) if test "x$have_dlsym" = "x1" ; then AC_DEFINE([JEMALLOC_HAVE_DLSYM], [ ], [ ]) fi else have_dlsym="0" fi JE_COMPILABLE([pthread_atfork(3)], [ #include ], [ pthread_atfork((void *)0, (void *)0, (void *)0); ], [je_cv_pthread_atfork]) if test "x${je_cv_pthread_atfork}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_PTHREAD_ATFORK], [ ], [ ]) fi dnl Check if pthread_setname_np is available with the expected API. JE_COMPILABLE([pthread_setname_np(3)], [ #include ], [ pthread_setname_np(pthread_self(), "setname_test"); ], [je_cv_pthread_setname_np]) if test "x${je_cv_pthread_setname_np}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_PTHREAD_SETNAME_NP], [ ], [ ]) fi dnl Check if pthread_getname_np is not necessarily present despite dnl the pthread_setname_np counterpart JE_COMPILABLE([pthread_getname_np(3)], [ #include #include ], [ { char *name = malloc(16); pthread_getname_np(pthread_self(), name, 16); free(name); } ], [je_cv_pthread_getname_np]) if test "x${je_cv_pthread_getname_np}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_PTHREAD_GETNAME_NP], [ ], [ ]) fi dnl Check if pthread_get_name_np is not necessarily present despite dnl the pthread_set_name_np counterpart JE_COMPILABLE([pthread_get_name_np(3)], [ #include #include #include ], [ { char *name = malloc(16); pthread_get_name_np(pthread_self(), name, 16); free(name); } ], [je_cv_pthread_get_name_np]) if test "x${je_cv_pthread_get_name_np}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_PTHREAD_GET_NAME_NP], [ ], [ ]) fi fi JE_APPEND_VS(CPPFLAGS, -D_REENTRANT) dnl Check whether clock_gettime(2) is in libc or librt. AC_SEARCH_LIBS([clock_gettime], [rt]) dnl Cray wrapper compiler often adds `-lrt` when using `-static`. Check with dnl `-dynamic` as well in case a user tries to dynamically link in jemalloc if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then if test "$ac_cv_search_clock_gettime" != "-lrt"; then JE_CFLAGS_SAVE() unset ac_cv_search_clock_gettime JE_CFLAGS_ADD([-dynamic]) AC_SEARCH_LIBS([clock_gettime], [rt]) JE_CFLAGS_RESTORE() fi fi dnl check for CLOCK_MONOTONIC_COARSE (Linux-specific). JE_COMPILABLE([clock_gettime(CLOCK_MONOTONIC_COARSE, ...)], [ #include ], [ struct timespec ts; clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); ], [je_cv_clock_monotonic_coarse]) if test "x${je_cv_clock_monotonic_coarse}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE], [ ], [ ]) fi dnl check for CLOCK_MONOTONIC. JE_COMPILABLE([clock_gettime(CLOCK_MONOTONIC, ...)], [ #include #include ], [ struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); #if !defined(_POSIX_MONOTONIC_CLOCK) || _POSIX_MONOTONIC_CLOCK < 0 # error _POSIX_MONOTONIC_CLOCK missing/invalid #endif ], [je_cv_clock_monotonic]) if test "x${je_cv_clock_monotonic}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_CLOCK_MONOTONIC], [ ], [ ]) fi dnl Check for mach_absolute_time(). JE_COMPILABLE([mach_absolute_time()], [ #include ], [ mach_absolute_time(); ], [je_cv_mach_absolute_time]) if test "x${je_cv_mach_absolute_time}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_MACH_ABSOLUTE_TIME], [ ], [ ]) fi dnl check for CLOCK_REALTIME (always should be available on Linux) JE_COMPILABLE([clock_gettime(CLOCK_REALTIME, ...)], [ #include ], [ struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ], [je_cv_clock_realtime]) if test "x${je_cv_clock_realtime}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_CLOCK_REALTIME], [ ], [ ]) fi dnl Use syscall(2) (if available) by default. AC_ARG_ENABLE([syscall], [AS_HELP_STRING([--disable-syscall], [Disable use of syscall(2)])], [if test "x$enable_syscall" = "xno" ; then enable_syscall="0" else enable_syscall="1" fi ], [enable_syscall="1"] ) if test "x$enable_syscall" = "x1" ; then dnl Check if syscall(2) is usable. Treat warnings as errors, so that e.g. OS dnl X 10.12's deprecation warning prevents use. JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-Werror]) JE_COMPILABLE([syscall(2)], [ #include #include ], [ syscall(SYS_write, 2, "hello", 5); ], [je_cv_syscall]) JE_CFLAGS_RESTORE() if test "x$je_cv_syscall" = "xyes" ; then AC_DEFINE([JEMALLOC_USE_SYSCALL], [ ], [ ]) fi fi dnl Check if the GNU-specific secure_getenv function exists. AC_CHECK_FUNC([secure_getenv], [have_secure_getenv="1"], [have_secure_getenv="0"] ) if test "x$have_secure_getenv" = "x1" ; then AC_DEFINE([JEMALLOC_HAVE_SECURE_GETENV], [ ], [ ]) fi dnl Check if the GNU-specific sched_getcpu function exists. AC_CHECK_FUNC([sched_getcpu], [have_sched_getcpu="1"], [have_sched_getcpu="0"] ) if test "x$have_sched_getcpu" = "x1" ; then AC_DEFINE([JEMALLOC_HAVE_SCHED_GETCPU], [ ], [ ]) fi dnl Check if the GNU-specific sched_setaffinity function exists. AC_CHECK_FUNC([sched_setaffinity], [have_sched_setaffinity="1"], [have_sched_setaffinity="0"] ) if test "x$have_sched_setaffinity" = "x1" ; then AC_DEFINE([JEMALLOC_HAVE_SCHED_SETAFFINITY], [ ], [ ]) fi dnl Check if the Solaris/BSD issetugid function exists. AC_CHECK_FUNC([issetugid], [have_issetugid="1"], [have_issetugid="0"] ) if test "x$have_issetugid" = "x1" ; then AC_DEFINE([JEMALLOC_HAVE_ISSETUGID], [ ], [ ]) fi dnl Check whether the BSD-specific _malloc_thread_cleanup() exists. If so, use dnl it rather than pthreads TSD cleanup functions to support cleanup during dnl thread exit, in order to avoid pthreads library recursion during dnl bootstrapping. AC_CHECK_FUNC([_malloc_thread_cleanup], [have__malloc_thread_cleanup="1"], [have__malloc_thread_cleanup="0"] ) if test "x$have__malloc_thread_cleanup" = "x1" ; then AC_DEFINE([JEMALLOC_MALLOC_THREAD_CLEANUP], [ ], [ ]) wrap_syms="${wrap_syms} _malloc_thread_cleanup _malloc_tsd_cleanup_register" force_tls="1" fi dnl Check whether the BSD-specific _pthread_mutex_init_calloc_cb() exists. If dnl so, mutex initialization causes allocation, and we need to implement this dnl callback function in order to prevent recursive allocation. AC_CHECK_FUNC([_pthread_mutex_init_calloc_cb], [have__pthread_mutex_init_calloc_cb="1"], [have__pthread_mutex_init_calloc_cb="0"] ) if test "x$have__pthread_mutex_init_calloc_cb" = "x1" ; then AC_DEFINE([JEMALLOC_MUTEX_INIT_CB], [ ], [ ]) wrap_syms="${wrap_syms} _malloc_prefork _malloc_postfork" fi AC_CHECK_FUNC([memcntl], [have_memcntl="1"], [have_memcntl="0"], ) if test "x$have_memcntl" = "x1" ; then AC_DEFINE([JEMALLOC_HAVE_MEMCNTL], [ ], [ ]) fi dnl Disable lazy locking by default. AC_ARG_ENABLE([lazy_lock], [AS_HELP_STRING([--enable-lazy-lock], [Enable lazy locking (only lock when multi-threaded)])], [if test "x$enable_lazy_lock" = "xno" ; then enable_lazy_lock="0" else enable_lazy_lock="1" fi ], [enable_lazy_lock=""] ) if test "x${enable_lazy_lock}" = "x" ; then if test "x${force_lazy_lock}" = "x1" ; then AC_MSG_RESULT([Forcing lazy-lock to avoid allocator/threading bootstrap issues]) enable_lazy_lock="1" else enable_lazy_lock="0" fi fi if test "x${enable_lazy_lock}" = "x1" -a "x${abi}" = "xpecoff" ; then AC_MSG_RESULT([Forcing no lazy-lock because thread creation monitoring is unimplemented]) enable_lazy_lock="0" fi if test "x$enable_lazy_lock" = "x1" ; then if test "x$have_dlsym" = "x1" ; then AC_DEFINE([JEMALLOC_LAZY_LOCK], [ ], [ ]) else AC_MSG_ERROR([Missing dlsym support: lazy-lock cannot be enabled.]) fi fi AC_SUBST([enable_lazy_lock]) dnl Automatically configure TLS. if test "x${force_tls}" = "x1" ; then enable_tls="1" elif test "x${force_tls}" = "x0" ; then enable_tls="0" else enable_tls="1" fi if test "x${enable_tls}" = "x1" ; then AC_MSG_CHECKING([for TLS]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[ __thread int x; ]], [[ x = 42; return 0; ]])], AC_MSG_RESULT([yes]), AC_MSG_RESULT([no]) enable_tls="0") else enable_tls="0" fi AC_SUBST([enable_tls]) if test "x${enable_tls}" = "x1" ; then AC_DEFINE_UNQUOTED([JEMALLOC_TLS], [ ], [ ]) fi dnl ============================================================================ dnl Check for C11 atomics. JE_COMPILABLE([C11 atomics], [ #include #if (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) #include #else #error Atomics not available #endif ], [ uint64_t *p = (uint64_t *)0; uint64_t x = 1; volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p; uint64_t r = atomic_fetch_add(a, x) + x; return r == 0; ], [je_cv_c11_atomics]) if test "x${je_cv_c11_atomics}" = "xyes" ; then AC_DEFINE([JEMALLOC_C11_ATOMICS], [ ], [ ]) fi dnl ============================================================================ dnl Check for GCC-style __atomic atomics. JE_COMPILABLE([GCC __atomic atomics], [ ], [ int x = 0; int val = 1; int y = __atomic_fetch_add(&x, val, __ATOMIC_RELAXED); int after_add = x; return after_add == 1; ], [je_cv_gcc_atomic_atomics]) if test "x${je_cv_gcc_atomic_atomics}" = "xyes" ; then AC_DEFINE([JEMALLOC_GCC_ATOMIC_ATOMICS], [ ], [ ]) dnl check for 8-bit atomic support JE_COMPILABLE([GCC 8-bit __atomic atomics], [ ], [ unsigned char x = 0; int val = 1; int y = __atomic_fetch_add(&x, val, __ATOMIC_RELAXED); int after_add = (int)x; return after_add == 1; ], [je_cv_gcc_u8_atomic_atomics]) if test "x${je_cv_gcc_u8_atomic_atomics}" = "xyes" ; then AC_DEFINE([JEMALLOC_GCC_U8_ATOMIC_ATOMICS], [ ], [ ]) fi fi dnl ============================================================================ dnl Check for GCC-style __sync atomics. JE_COMPILABLE([GCC __sync atomics], [ ], [ int x = 0; int before_add = __sync_fetch_and_add(&x, 1); int after_add = x; return (before_add == 0) && (after_add == 1); ], [je_cv_gcc_sync_atomics]) if test "x${je_cv_gcc_sync_atomics}" = "xyes" ; then AC_DEFINE([JEMALLOC_GCC_SYNC_ATOMICS], [ ], [ ]) dnl check for 8-bit atomic support JE_COMPILABLE([GCC 8-bit __sync atomics], [ ], [ unsigned char x = 0; int before_add = __sync_fetch_and_add(&x, 1); int after_add = (int)x; return (before_add == 0) && (after_add == 1); ], [je_cv_gcc_u8_sync_atomics]) if test "x${je_cv_gcc_u8_sync_atomics}" = "xyes" ; then AC_DEFINE([JEMALLOC_GCC_U8_SYNC_ATOMICS], [ ], [ ]) fi fi dnl ============================================================================ dnl Check for atomic(3) operations as provided on Darwin. dnl We need this not for the atomic operations (which are provided above), but dnl rather for the OS_unfair_lock type it exposes. JE_COMPILABLE([Darwin OSAtomic*()], [ #include #include ], [ { int32_t x32 = 0; volatile int32_t *x32p = &x32; OSAtomicAdd32(1, x32p); } { int64_t x64 = 0; volatile int64_t *x64p = &x64; OSAtomicAdd64(1, x64p); } ], [je_cv_osatomic]) if test "x${je_cv_osatomic}" = "xyes" ; then AC_DEFINE([JEMALLOC_OSATOMIC], [ ], [ ]) fi dnl ============================================================================ dnl Check for madvise(2). JE_COMPILABLE([madvise(2)], [ #include ], [ madvise((void *)0, 0, 0); ], [je_cv_madvise]) if test "x${je_cv_madvise}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_MADVISE], [ ], [ ]) dnl Check for madvise(..., MADV_FREE). JE_COMPILABLE([madvise(..., MADV_FREE)], [ #include ], [ madvise((void *)0, 0, MADV_FREE); ], [je_cv_madv_free]) if test "x${je_cv_madv_free}" = "xyes" ; then AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ], [ ]) elif test "x${je_cv_madvise}" = "xyes" ; then case "${host_cpu}" in i686|x86_64) case "${host}" in *-*-linux*) AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ], [ ]) AC_DEFINE([JEMALLOC_DEFINE_MADVISE_FREE], [ ], [ ]) ;; esac ;; esac fi dnl Check for madvise(..., MADV_DONTNEED). JE_COMPILABLE([madvise(..., MADV_DONTNEED)], [ #include ], [ madvise((void *)0, 0, MADV_DONTNEED); ], [je_cv_madv_dontneed]) if test "x${je_cv_madv_dontneed}" = "xyes" ; then AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED], [ ], [ ]) fi dnl Check for madvise(..., MADV_DO[NT]DUMP). JE_COMPILABLE([madvise(..., MADV_DO[[NT]]DUMP)], [ #include ], [ madvise((void *)0, 0, MADV_DONTDUMP); madvise((void *)0, 0, MADV_DODUMP); ], [je_cv_madv_dontdump]) if test "x${je_cv_madv_dontdump}" = "xyes" ; then AC_DEFINE([JEMALLOC_MADVISE_DONTDUMP], [ ], [ ]) fi dnl Check for madvise(..., MADV_[NO]HUGEPAGE). JE_COMPILABLE([madvise(..., MADV_[[NO]]HUGEPAGE)], [ #include ], [ madvise((void *)0, 0, MADV_HUGEPAGE); madvise((void *)0, 0, MADV_NOHUGEPAGE); ], [je_cv_thp]) dnl Check for madvise(..., MADV_[NO]CORE). JE_COMPILABLE([madvise(..., MADV_[[NO]]CORE)], [ #include ], [ madvise((void *)0, 0, MADV_NOCORE); madvise((void *)0, 0, MADV_CORE); ], [je_cv_madv_nocore]) if test "x${je_cv_madv_nocore}" = "xyes" ; then AC_DEFINE([JEMALLOC_MADVISE_NOCORE], [ ], [ ]) fi case "${host_cpu}" in arm*) ;; *) if test "x${je_cv_thp}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_MADVISE_HUGE], [ ], [ ]) fi ;; esac else dnl Check for posix_madvise. JE_COMPILABLE([posix_madvise], [ #include ], [ posix_madvise((void *)0, 0, 0); ], [je_cv_posix_madvise]) if test "x${je_cv_posix_madvise}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_POSIX_MADVISE], [ ], [ ]) dnl Check for posix_madvise(..., POSIX_MADV_DONTNEED). JE_COMPILABLE([posix_madvise(..., POSIX_MADV_DONTNEED)], [ #include ], [ posix_madvise((void *)0, 0, POSIX_MADV_DONTNEED); ], [je_cv_posix_madv_dontneed]) if test "x${je_cv_posix_madv_dontneed}" = "xyes" ; then AC_DEFINE([JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED], [ ], [ ]) fi fi fi dnl ============================================================================ dnl Check for mprotect(2). JE_COMPILABLE([mprotect(2)], [ #include ], [ mprotect((void *)0, 0, PROT_NONE); ], [je_cv_mprotect]) if test "x${je_cv_mprotect}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_MPROTECT], [ ], [ ]) fi dnl ============================================================================ dnl Check for __builtin_clz(), __builtin_clzl(), and __builtin_clzll(). AC_CACHE_CHECK([for __builtin_clz], [je_cv_builtin_clz], [AC_LINK_IFELSE([AC_LANG_PROGRAM([], [ { unsigned x = 0; int y = __builtin_clz(x); } { unsigned long x = 0; int y = __builtin_clzl(x); } { unsigned long long x = 0; int y = __builtin_clzll(x); } ])], [je_cv_builtin_clz=yes], [je_cv_builtin_clz=no])]) if test "x${je_cv_builtin_clz}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_BUILTIN_CLZ], [ ], [ ]) fi dnl ============================================================================ dnl Check for os_unfair_lock operations as provided on Darwin. JE_COMPILABLE([Darwin os_unfair_lock_*()], [ #include #include ], [ #if MAC_OS_X_VERSION_MIN_REQUIRED < 101200 #error "os_unfair_lock is not supported" #else os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; os_unfair_lock_lock(&lock); os_unfair_lock_unlock(&lock); #endif ], [je_cv_os_unfair_lock]) if test "x${je_cv_os_unfair_lock}" = "xyes" ; then AC_DEFINE([JEMALLOC_OS_UNFAIR_LOCK], [ ], [ ]) fi dnl ============================================================================ dnl Darwin-related configuration. AC_ARG_ENABLE([zone-allocator], [AS_HELP_STRING([--disable-zone-allocator], [Disable zone allocator for Darwin])], [if test "x$enable_zone_allocator" = "xno" ; then enable_zone_allocator="0" else enable_zone_allocator="1" fi ], [if test "x${abi}" = "xmacho"; then enable_zone_allocator="1" fi ] ) AC_SUBST([enable_zone_allocator]) if test "x${enable_zone_allocator}" = "x1" ; then if test "x${abi}" != "xmacho"; then AC_MSG_ERROR([--enable-zone-allocator is only supported on Darwin]) fi AC_DEFINE([JEMALLOC_ZONE], [ ], [ ]) fi dnl ============================================================================ dnl Use initial-exec TLS by default. AC_ARG_ENABLE([initial-exec-tls], [AS_HELP_STRING([--disable-initial-exec-tls], [Disable the initial-exec tls model])], [if test "x$enable_initial_exec_tls" = "xno" ; then enable_initial_exec_tls="0" else enable_initial_exec_tls="1" fi ], [enable_initial_exec_tls="1"] ) AC_SUBST([enable_initial_exec_tls]) if test "x${je_cv_tls_model}" = "xyes" -a \ "x${enable_initial_exec_tls}" = "x1" ; then AC_DEFINE([JEMALLOC_TLS_MODEL], [__attribute__((tls_model("initial-exec")))], [ ]) else AC_DEFINE([JEMALLOC_TLS_MODEL], [ ], [ ]) fi dnl ============================================================================ dnl Enable background threads if possible. if test "x${have_pthread}" = "x1" -a "x${je_cv_os_unfair_lock}" != "xyes" -a \ "x${abi}" != "xmacho" ; then AC_DEFINE([JEMALLOC_BACKGROUND_THREAD], [ ], [ ]) fi dnl ============================================================================ dnl Check for glibc malloc hooks if test "x$glibc" = "x1" ; then JE_COMPILABLE([glibc malloc hook], [ #include extern void (* __free_hook)(void *ptr); extern void *(* __malloc_hook)(size_t size); extern void *(* __realloc_hook)(void *ptr, size_t size); ], [ void *ptr = 0L; if (__malloc_hook) ptr = __malloc_hook(1); if (__realloc_hook) ptr = __realloc_hook(ptr, 2); if (__free_hook && ptr) __free_hook(ptr); ], [je_cv_glibc_malloc_hook]) if test "x${je_cv_glibc_malloc_hook}" = "xyes" ; then if test "x${JEMALLOC_PREFIX}" = "x" ; then AC_DEFINE([JEMALLOC_GLIBC_MALLOC_HOOK], [ ], [ ]) wrap_syms="${wrap_syms} __free_hook __malloc_hook __realloc_hook" fi fi JE_COMPILABLE([glibc memalign hook], [ #include extern void *(* __memalign_hook)(size_t alignment, size_t size); ], [ void *ptr = 0L; if (__memalign_hook) ptr = __memalign_hook(16, 7); ], [je_cv_glibc_memalign_hook]) if test "x${je_cv_glibc_memalign_hook}" = "xyes" ; then if test "x${JEMALLOC_PREFIX}" = "x" ; then AC_DEFINE([JEMALLOC_GLIBC_MEMALIGN_HOOK], [ ], [ ]) wrap_syms="${wrap_syms} __memalign_hook" fi fi fi JE_COMPILABLE([pthreads adaptive mutexes], [ #include ], [ pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); pthread_mutexattr_destroy(&attr); ], [je_cv_pthread_mutex_adaptive_np]) if test "x${je_cv_pthread_mutex_adaptive_np}" = "xyes" ; then AC_DEFINE([JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP], [ ], [ ]) fi JE_CFLAGS_SAVE() JE_CFLAGS_ADD([-D_GNU_SOURCE]) JE_CFLAGS_ADD([-Werror]) JE_CFLAGS_ADD([-herror_on_warning]) JE_COMPILABLE([strerror_r returns char with gnu source], [ #include #include #include #include ], [ char *buffer = (char *) malloc(100); char *error = strerror_r(EINVAL, buffer, 100); printf("%s\n", error); ], [je_cv_strerror_r_returns_char_with_gnu_source]) JE_CFLAGS_RESTORE() if test "x${je_cv_strerror_r_returns_char_with_gnu_source}" = "xyes" ; then AC_DEFINE([JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE], [ ], [ ]) fi dnl ============================================================================ dnl Check for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL dnl ============================================================================ dnl Define commands that generate output files. AC_CONFIG_COMMANDS([include/jemalloc/internal/public_symbols.txt], [ f="${objroot}include/jemalloc/internal/public_symbols.txt" mkdir -p "${objroot}include/jemalloc/internal" cp /dev/null "${f}" for nm in `echo ${mangling_map} |tr ',' ' '` ; do n=`echo ${nm} |tr ':' ' ' |awk '{print $[]1}'` m=`echo ${nm} |tr ':' ' ' |awk '{print $[]2}'` echo "${n}:${m}" >> "${f}" dnl Remove name from public_syms so that it isn't redefined later. public_syms=`for sym in ${public_syms}; do echo "${sym}"; done |grep -v "^${n}\$" |tr '\n' ' '` done for sym in ${public_syms} ; do n="${sym}" m="${JEMALLOC_PREFIX}${sym}" echo "${n}:${m}" >> "${f}" done ], [ srcdir="${srcdir}" objroot="${objroot}" mangling_map="${mangling_map}" public_syms="${public_syms}" JEMALLOC_PREFIX="${JEMALLOC_PREFIX}" ]) AC_CONFIG_COMMANDS([include/jemalloc/internal/private_symbols.awk], [ f="${objroot}include/jemalloc/internal/private_symbols.awk" mkdir -p "${objroot}include/jemalloc/internal" export_syms=`for sym in ${public_syms}; do echo "${JEMALLOC_PREFIX}${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;` "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols.awk" ], [ srcdir="${srcdir}" objroot="${objroot}" public_syms="${public_syms}" wrap_syms="${wrap_syms}" SYM_PREFIX="${SYM_PREFIX}" JEMALLOC_PREFIX="${JEMALLOC_PREFIX}" ]) AC_CONFIG_COMMANDS([include/jemalloc/internal/private_symbols_jet.awk], [ f="${objroot}include/jemalloc/internal/private_symbols_jet.awk" mkdir -p "${objroot}include/jemalloc/internal" export_syms=`for sym in ${public_syms}; do echo "jet_${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;` "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols_jet.awk" ], [ srcdir="${srcdir}" objroot="${objroot}" public_syms="${public_syms}" wrap_syms="${wrap_syms}" SYM_PREFIX="${SYM_PREFIX}" ]) AC_CONFIG_COMMANDS([include/jemalloc/internal/public_namespace.h], [ mkdir -p "${objroot}include/jemalloc/internal" "${srcdir}/include/jemalloc/internal/public_namespace.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/internal/public_namespace.h" ], [ srcdir="${srcdir}" objroot="${objroot}" ]) AC_CONFIG_COMMANDS([include/jemalloc/internal/public_unnamespace.h], [ mkdir -p "${objroot}include/jemalloc/internal" "${srcdir}/include/jemalloc/internal/public_unnamespace.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/internal/public_unnamespace.h" ], [ srcdir="${srcdir}" objroot="${objroot}" ]) AC_CONFIG_COMMANDS([include/jemalloc/jemalloc_protos_jet.h], [ mkdir -p "${objroot}include/jemalloc" cat "${srcdir}/include/jemalloc/jemalloc_protos.h.in" | sed -e 's/@je_@/jet_/g' > "${objroot}include/jemalloc/jemalloc_protos_jet.h" ], [ srcdir="${srcdir}" objroot="${objroot}" ]) AC_CONFIG_COMMANDS([include/jemalloc/jemalloc_rename.h], [ mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc_rename.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/jemalloc_rename.h" ], [ srcdir="${srcdir}" objroot="${objroot}" ]) AC_CONFIG_COMMANDS([include/jemalloc/jemalloc_mangle.h], [ mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc_mangle.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" je_ > "${objroot}include/jemalloc/jemalloc_mangle.h" ], [ srcdir="${srcdir}" objroot="${objroot}" ]) AC_CONFIG_COMMANDS([include/jemalloc/jemalloc_mangle_jet.h], [ mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc_mangle.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" jet_ > "${objroot}include/jemalloc/jemalloc_mangle_jet.h" ], [ srcdir="${srcdir}" objroot="${objroot}" ]) AC_CONFIG_COMMANDS([include/jemalloc/jemalloc.h], [ mkdir -p "${objroot}include/jemalloc" "${srcdir}/include/jemalloc/jemalloc.sh" "${objroot}" > "${objroot}include/jemalloc/jemalloc${install_suffix}.h" ], [ srcdir="${srcdir}" objroot="${objroot}" install_suffix="${install_suffix}" ]) dnl Process .in files. AC_SUBST([cfghdrs_in]) AC_SUBST([cfghdrs_out]) AC_CONFIG_HEADERS([$cfghdrs_tup]) dnl ============================================================================ dnl Generate outputs. AC_CONFIG_FILES([$cfgoutputs_tup config.stamp bin/jemalloc-config bin/jemalloc.sh bin/jeprof]) AC_SUBST([cfgoutputs_in]) AC_SUBST([cfgoutputs_out]) AC_OUTPUT dnl ============================================================================ dnl Print out the results of configuration. AC_MSG_RESULT([===============================================================================]) AC_MSG_RESULT([jemalloc version : ${jemalloc_version}]) AC_MSG_RESULT([library revision : ${rev}]) AC_MSG_RESULT([]) AC_MSG_RESULT([CONFIG : ${CONFIG}]) AC_MSG_RESULT([CC : ${CC}]) AC_MSG_RESULT([CONFIGURE_CFLAGS : ${CONFIGURE_CFLAGS}]) AC_MSG_RESULT([SPECIFIED_CFLAGS : ${SPECIFIED_CFLAGS}]) AC_MSG_RESULT([EXTRA_CFLAGS : ${EXTRA_CFLAGS}]) AC_MSG_RESULT([CPPFLAGS : ${CPPFLAGS}]) AC_MSG_RESULT([CXX : ${CXX}]) AC_MSG_RESULT([CONFIGURE_CXXFLAGS : ${CONFIGURE_CXXFLAGS}]) AC_MSG_RESULT([SPECIFIED_CXXFLAGS : ${SPECIFIED_CXXFLAGS}]) AC_MSG_RESULT([EXTRA_CXXFLAGS : ${EXTRA_CXXFLAGS}]) AC_MSG_RESULT([LDFLAGS : ${LDFLAGS}]) AC_MSG_RESULT([EXTRA_LDFLAGS : ${EXTRA_LDFLAGS}]) AC_MSG_RESULT([DSO_LDFLAGS : ${DSO_LDFLAGS}]) AC_MSG_RESULT([LIBS : ${LIBS}]) AC_MSG_RESULT([RPATH_EXTRA : ${RPATH_EXTRA}]) AC_MSG_RESULT([]) AC_MSG_RESULT([XSLTPROC : ${XSLTPROC}]) AC_MSG_RESULT([XSLROOT : ${XSLROOT}]) AC_MSG_RESULT([]) AC_MSG_RESULT([PREFIX : ${PREFIX}]) AC_MSG_RESULT([BINDIR : ${BINDIR}]) AC_MSG_RESULT([DATADIR : ${DATADIR}]) AC_MSG_RESULT([INCLUDEDIR : ${INCLUDEDIR}]) AC_MSG_RESULT([LIBDIR : ${LIBDIR}]) AC_MSG_RESULT([MANDIR : ${MANDIR}]) AC_MSG_RESULT([]) AC_MSG_RESULT([srcroot : ${srcroot}]) AC_MSG_RESULT([abs_srcroot : ${abs_srcroot}]) AC_MSG_RESULT([objroot : ${objroot}]) AC_MSG_RESULT([abs_objroot : ${abs_objroot}]) AC_MSG_RESULT([]) AC_MSG_RESULT([JEMALLOC_PREFIX : ${JEMALLOC_PREFIX}]) AC_MSG_RESULT([JEMALLOC_PRIVATE_NAMESPACE]) AC_MSG_RESULT([ : ${JEMALLOC_PRIVATE_NAMESPACE}]) AC_MSG_RESULT([install_suffix : ${install_suffix}]) AC_MSG_RESULT([malloc_conf : ${config_malloc_conf}]) AC_MSG_RESULT([documentation : ${enable_doc}]) AC_MSG_RESULT([shared libs : ${enable_shared}]) AC_MSG_RESULT([static libs : ${enable_static}]) AC_MSG_RESULT([autogen : ${enable_autogen}]) AC_MSG_RESULT([debug : ${enable_debug}]) AC_MSG_RESULT([stats : ${enable_stats}]) AC_MSG_RESULT([experimental_smallocx : ${enable_experimental_smallocx}]) AC_MSG_RESULT([prof : ${enable_prof}]) AC_MSG_RESULT([prof-libunwind : ${enable_prof_libunwind}]) AC_MSG_RESULT([prof-libgcc : ${enable_prof_libgcc}]) AC_MSG_RESULT([prof-gcc : ${enable_prof_gcc}]) AC_MSG_RESULT([fill : ${enable_fill}]) AC_MSG_RESULT([utrace : ${enable_utrace}]) AC_MSG_RESULT([xmalloc : ${enable_xmalloc}]) AC_MSG_RESULT([log : ${enable_log}]) AC_MSG_RESULT([lazy_lock : ${enable_lazy_lock}]) AC_MSG_RESULT([cache-oblivious : ${enable_cache_oblivious}]) AC_MSG_RESULT([cxx : ${enable_cxx}]) AC_MSG_RESULT([===============================================================================]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/doc/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�15015331166�0016116�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/doc/html.xsl.in�����������������������������������������������������������0000664�0000000�0000000�00000000371�15015331166�0020220�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������redis-8.0.2/deps/jemalloc/doc/jemalloc.xml.in�������������������������������������������������������0000664�0000000�0000000�00000521350�15015331166�0021041�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ User Manual jemalloc @jemalloc_version@ Jason Evans Author JEMALLOC 3 jemalloc jemalloc general purpose memory allocation functions LIBRARY This manual describes jemalloc @jemalloc_version@. More information can be found at the jemalloc website. SYNOPSIS #include <jemalloc/jemalloc.h> Standard API void *malloc size_t size void *calloc size_t number size_t size int posix_memalign void **ptr size_t alignment size_t size void *aligned_alloc size_t alignment size_t size void *realloc void *ptr size_t size void free void *ptr Non-standard API void *mallocx size_t size int flags void *rallocx void *ptr size_t size int flags size_t xallocx void *ptr size_t size size_t extra int flags size_t sallocx void *ptr int flags void dallocx void *ptr int flags void sdallocx void *ptr size_t size int flags size_t nallocx size_t size int flags int mallctl const char *name void *oldp size_t *oldlenp void *newp size_t newlen int mallctlnametomib const char *name size_t *mibp size_t *miblenp int mallctlbymib const size_t *mib size_t miblen void *oldp size_t *oldlenp void *newp size_t newlen void malloc_stats_print void (*write_cb) void *, const char * void *cbopaque const char *opts size_t malloc_usable_size const void *ptr void (*malloc_message) void *cbopaque const char *s const char *malloc_conf; DESCRIPTION Standard API The malloc() function allocates size bytes of uninitialized memory. The allocated space is suitably aligned (after possible pointer coercion) for storage of any type of object. The calloc() function allocates space for number objects, each size bytes in length. The result is identical to calling malloc() with an argument of number * size, with the exception that the allocated memory is explicitly initialized to zero bytes. The posix_memalign() function allocates size bytes of memory such that the allocation's base address is a multiple of alignment, and returns the allocation in the value pointed to by ptr. The requested alignment must be a power of 2 at least as large as sizeof(void *). The aligned_alloc() function allocates size bytes of memory such that the allocation's base address is a multiple of alignment. The requested alignment must be a power of 2. Behavior is undefined if size is not an integral multiple of alignment. The realloc() function changes the size of the previously allocated memory referenced by ptr to size bytes. The contents of the memory are unchanged up to the lesser of the new and old sizes. If the new size is larger, the contents of the newly allocated portion of the memory are undefined. Upon success, the memory referenced by ptr is freed and a pointer to the newly allocated memory is returned. Note that realloc() may move the memory allocation, resulting in a different return value than ptr. If ptr is NULL, the realloc() function behaves identically to malloc() for the specified size. The free() function causes the allocated memory referenced by ptr to be made available for future allocations. If ptr is NULL, no action occurs. Non-standard API The mallocx(), rallocx(), xallocx(), sallocx(), dallocx(), sdallocx(), and nallocx() functions all have a flags argument that can be used to specify options. The functions only check the options that are contextually relevant. Use bitwise or (|) operations to specify one or more of the following: MALLOCX_LG_ALIGN(la) Align the memory allocation to start at an address that is a multiple of (1 << la). This macro does not validate that la is within the valid range. MALLOCX_ALIGN(a) Align the memory allocation to start at an address that is a multiple of a, where a is a power of two. This macro does not validate that a is a power of 2. MALLOCX_ZERO Initialize newly allocated memory to contain zero bytes. In the growing reallocation case, the real size prior to reallocation defines the boundary between untouched bytes and those that are initialized to contain zero bytes. If this macro is absent, newly allocated memory is uninitialized. MALLOCX_TCACHE(tc) Use the thread-specific cache (tcache) specified by the identifier tc, which must have been acquired via the tcache.create mallctl. This macro does not validate that tc specifies a valid identifier. MALLOCX_TCACHE_NONE Do not use a thread-specific cache (tcache). Unless MALLOCX_TCACHE(tc) or MALLOCX_TCACHE_NONE is specified, an automatically managed tcache will be used under many circumstances. This macro cannot be used in the same flags argument as MALLOCX_TCACHE(tc). MALLOCX_ARENA(a) Use the arena specified by the index a. This macro has no effect for regions that were allocated via an arena other than the one specified. This macro does not validate that a specifies an arena index in the valid range. The mallocx() function allocates at least size bytes of memory, and returns a pointer to the base address of the allocation. Behavior is undefined if size is 0. The rallocx() function resizes the allocation at ptr to be at least size bytes, and returns a pointer to the base address of the resulting allocation, which may or may not have moved from its original location. Behavior is undefined if size is 0. The xallocx() function resizes the allocation at ptr in place to be at least size bytes, and returns the real size of the allocation. If extra is non-zero, an attempt is made to resize the allocation to be at least (size + extra) bytes, though inability to allocate the extra byte(s) will not by itself result in failure to resize. Behavior is undefined if size is 0, or if (size + extra > SIZE_T_MAX). The sallocx() function returns the real size of the allocation at ptr. The dallocx() function causes the memory referenced by ptr to be made available for future allocations. The sdallocx() function is an extension of dallocx() with a size parameter to allow the caller to pass in the allocation size as an optimization. The minimum valid input size is the original requested size of the allocation, and the maximum valid input size is the corresponding value returned by nallocx() or sallocx(). The nallocx() function allocates no memory, but it performs the same size computation as the mallocx() function, and returns the real size of the allocation that would result from the equivalent mallocx() function call, or 0 if the inputs exceed the maximum supported size class and/or alignment. Behavior is undefined if size is 0. The mallctl() function provides a general interface for introspecting the memory allocator, as well as setting modifiable parameters and triggering actions. The period-separated name argument specifies a location in a tree-structured namespace; see the section for documentation on the tree contents. To read a value, pass a pointer via oldp to adequate space to contain the value, and a pointer to its length via oldlenp; otherwise pass NULL and NULL. Similarly, to write a value, pass a pointer to the value via newp, and its length via newlen; otherwise pass NULL and 0. The mallctlnametomib() function provides a way to avoid repeated name lookups for applications that repeatedly query the same portion of the namespace, by translating a name to a Management Information Base (MIB) that can be passed repeatedly to mallctlbymib(). Upon successful return from mallctlnametomib(), mibp contains an array of *miblenp integers, where *miblenp is the lesser of the number of components in name and the input value of *miblenp. Thus it is possible to pass a *miblenp that is smaller than the number of period-separated name components, which results in a partial MIB that can be used as the basis for constructing a complete MIB. For name components that are integers (e.g. the 2 in arenas.bin.2.size), the corresponding MIB component will always be that integer. Therefore, it is legitimate to construct code like the following: The malloc_stats_print() function writes summary statistics via the write_cb callback function pointer and cbopaque data passed to write_cb, or malloc_message() if write_cb is NULL. The statistics are presented in human-readable form unless J is specified as a character within the opts string, in which case the statistics are presented in JSON format. This function can be called repeatedly. General information that never changes during execution can be omitted by specifying g as a character within the opts string. Note that malloc_stats_print() uses the mallctl*() functions internally, so inconsistent statistics can be reported if multiple threads use these functions simultaneously. If is specified during configuration, m, d, and a can be specified to omit merged arena, destroyed merged arena, and per arena statistics, respectively; b and l can be specified to omit per size class statistics for bins and large objects, respectively; x can be specified to omit all mutex statistics; e can be used to omit extent statistics. Unrecognized characters are silently ignored. Note that thread caching may prevent some statistics from being completely up to date, since extra locking would be required to merge counters that track thread cache operations. The malloc_usable_size() function returns the usable size of the allocation pointed to by ptr. The return value may be larger than the size that was requested during allocation. The malloc_usable_size() function is not a mechanism for in-place realloc(); rather it is provided solely as a tool for introspection purposes. Any discrepancy between the requested allocation size and the size reported by malloc_usable_size() should not be depended on, since such behavior is entirely implementation-dependent. TUNING Once, when the first call is made to one of the memory allocation routines, the allocator initializes its internals based in part on various options that can be specified at compile- or run-time. The string specified via , the string pointed to by the global variable malloc_conf, the name of the file referenced by the symbolic link named /etc/malloc.conf, and the value of the environment variable MALLOC_CONF, will be interpreted, in that order, from left to right as options. Note that malloc_conf may be read before main() is entered, so the declaration of malloc_conf should specify an initializer that contains the final value to be read by jemalloc. and malloc_conf are compile-time mechanisms, whereas /etc/malloc.conf and MALLOC_CONF can be safely set any time prior to program invocation. An options string is a comma-separated list of option:value pairs. There is one key corresponding to each opt.* mallctl (see the section for options documentation). For example, abort:true,narenas:1 sets the opt.abort and opt.narenas options. Some options have boolean values (true/false), others have integer values (base 8, 10, or 16, depending on prefix), and yet others have raw string values. IMPLEMENTATION NOTES Traditionally, allocators have used sbrk 2 to obtain memory, which is suboptimal for several reasons, including race conditions, increased fragmentation, and artificial limitations on maximum usable memory. If sbrk 2 is supported by the operating system, this allocator uses both mmap 2 and sbrk 2, in that order of preference; otherwise only mmap 2 is used. This allocator uses multiple arenas in order to reduce lock contention for threaded programs on multi-processor systems. This works well with regard to threading scalability, but incurs some costs. There is a small fixed per-arena overhead, and additionally, arenas manage memory completely independently of each other, which means a small fixed increase in overall memory fragmentation. These overheads are not generally an issue, given the number of arenas normally used. Note that using substantially more arenas than the default is not likely to improve performance, mainly due to reduced cache performance. However, it may make sense to reduce the number of arenas if an application does not make much use of the allocation functions. In addition to multiple arenas, this allocator supports thread-specific caching, in order to make it possible to completely avoid synchronization for most allocation requests. Such caching allows very fast allocation in the common case, but it increases memory usage and fragmentation, since a bounded number of objects can remain allocated in each thread cache. Memory is conceptually broken into extents. Extents are always aligned to multiples of the page size. This alignment makes it possible to find metadata for user objects quickly. User objects are broken into two categories according to size: small and large. Contiguous small objects comprise a slab, which resides within a single extent, whereas large objects each have their own extents backing them. Small objects are managed in groups by slabs. Each slab maintains a bitmap to track which regions are in use. Allocation requests that are no more than half the quantum (8 or 16, depending on architecture) are rounded up to the nearest power of two that is at least sizeof(double). All other object size classes are multiples of the quantum, spaced such that there are four size classes for each doubling in size, which limits internal fragmentation to approximately 20% for all but the smallest size classes. Small size classes are smaller than four times the page size, and large size classes extend from four times the page size up to the largest size class that does not exceed PTRDIFF_MAX. Allocations are packed tightly together, which can be an issue for multi-threaded applications. If you need to assure that allocations do not suffer from cacheline sharing, round your allocation requests up to the nearest multiple of the cacheline size, or specify cacheline alignment when allocating. The realloc(), rallocx(), and xallocx() functions may resize allocations without moving them under limited circumstances. Unlike the *allocx() API, the standard API does not officially round up the usable size of an allocation to the nearest size class, so technically it is necessary to call realloc() to grow e.g. a 9-byte allocation to 16 bytes, or shrink a 16-byte allocation to 9 bytes. Growth and shrinkage trivially succeeds in place as long as the pre-size and post-size both round up to the same size class. No other API guarantees are made regarding in-place resizing, but the current implementation also tries to resize large allocations in place, as long as the pre-size and post-size are both large. For shrinkage to succeed, the extent allocator must support splitting (see arena.<i>.extent_hooks). Growth only succeeds if the trailing memory is currently available, and the extent allocator supports merging. Assuming 4 KiB pages and a 16-byte quantum on a 64-bit system, the size classes in each category are as shown in . Size classes Category Spacing Size Small lg [8] 16 [16, 32, 48, 64, 80, 96, 112, 128] 32 [160, 192, 224, 256] 64 [320, 384, 448, 512] 128 [640, 768, 896, 1024] 256 [1280, 1536, 1792, 2048] 512 [2560, 3072, 3584, 4096] 1 KiB [5 KiB, 6 KiB, 7 KiB, 8 KiB] 2 KiB [10 KiB, 12 KiB, 14 KiB] Large 2 KiB [16 KiB] 4 KiB [20 KiB, 24 KiB, 28 KiB, 32 KiB] 8 KiB [40 KiB, 48 KiB, 56 KiB, 64 KiB] 16 KiB [80 KiB, 96 KiB, 112 KiB, 128 KiB] 32 KiB [160 KiB, 192 KiB, 224 KiB, 256 KiB] 64 KiB [320 KiB, 384 KiB, 448 KiB, 512 KiB] 128 KiB [640 KiB, 768 KiB, 896 KiB, 1 MiB] 256 KiB [1280 KiB, 1536 KiB, 1792 KiB, 2 MiB] 512 KiB [2560 KiB, 3 MiB, 3584 KiB, 4 MiB] 1 MiB [5 MiB, 6 MiB, 7 MiB, 8 MiB] 2 MiB [10 MiB, 12 MiB, 14 MiB, 16 MiB] 4 MiB [20 MiB, 24 MiB, 28 MiB, 32 MiB] 8 MiB [40 MiB, 48 MiB, 56 MiB, 64 MiB] ... ... 512 PiB [2560 PiB, 3 EiB, 3584 PiB, 4 EiB] 1 EiB [5 EiB, 6 EiB, 7 EiB]
MALLCTL NAMESPACE The following names are defined in the namespace accessible via the mallctl*() functions. Value types are specified in parentheses, their readable/writable statuses are encoded as rw, r-, -w, or --, and required build configuration flags follow, if any. A name element encoded as <i> or <j> indicates an integer component, where the integer varies from 0 to some upper value that must be determined via introspection. In the case of stats.arenas.<i>.* and arena.<i>.{initialized,purge,decay,dss}, <i> equal to MALLCTL_ARENAS_ALL can be used to operate on all arenas or access the summation of statistics from all arenas; similarly <i> equal to MALLCTL_ARENAS_DESTROYED can be used to access the summation of statistics from all destroyed arenas. These constants can be utilized either via mallctlnametomib() followed by mallctlbymib(), or via code such as the following: Take special note of the epoch mallctl, which controls refreshing of cached dynamic statistics. version (const char *) r- Return the jemalloc version string. epoch (uint64_t) rw If a value is passed in, refresh the data from which the mallctl*() functions report values, and increment the epoch. Return the current epoch. This is useful for detecting whether another thread caused a refresh. background_thread (bool) rw Enable/disable internal background worker threads. When set to true, background threads are created on demand (the number of background threads will be no more than the number of CPUs or active arenas). Threads run periodically, and handle purging asynchronously. When switching off, background threads are terminated synchronously. Note that after fork2 function, the state in the child process will be disabled regardless the state in parent process. See stats.background_thread for related stats. opt.background_thread can be used to set the default option. This option is only available on selected pthread-based platforms. max_background_threads (size_t) rw Maximum number of background worker threads that will be created. This value is capped at opt.max_background_threads at startup. config.cache_oblivious (bool) r- was specified during build configuration. config.debug (bool) r- was specified during build configuration. config.fill (bool) r- was specified during build configuration. config.lazy_lock (bool) r- was specified during build configuration. config.malloc_conf (const char *) r- Embedded configure-time-specified run-time options string, empty unless was specified during build configuration. config.prof (bool) r- was specified during build configuration. config.prof_libgcc (bool) r- was not specified during build configuration. config.prof_libunwind (bool) r- was specified during build configuration. config.stats (bool) r- was specified during build configuration. config.utrace (bool) r- was specified during build configuration. config.xmalloc (bool) r- was specified during build configuration. opt.abort (bool) r- Abort-on-warning enabled/disabled. If true, most warnings are fatal. Note that runtime option warnings are not included (see opt.abort_conf for that). The process will call abort 3 in these cases. This option is disabled by default unless is specified during configuration, in which case it is enabled by default. opt.confirm_conf (bool) r- Confirm-runtime-options-when-program-starts enabled/disabled. If true, the string specified via , the string pointed to by the global variable malloc_conf, the name of the file referenced by the symbolic link named /etc/malloc.conf, and the value of the environment variable MALLOC_CONF, will be printed in order. Then, each option being set will be individually printed. This option is disabled by default. opt.abort_conf (bool) r- Abort-on-invalid-configuration enabled/disabled. If true, invalid runtime options are fatal. The process will call abort 3 in these cases. This option is disabled by default unless is specified during configuration, in which case it is enabled by default. opt.cache_oblivious (bool) r- Enable / Disable cache-oblivious large allocation alignment, for large requests with no alignment constraints. If this feature is disabled, all large allocations are page-aligned as an implementation artifact, which can severely harm CPU cache utilization. However, the cache-oblivious layout comes at the cost of one extra page per large allocation, which in the most extreme case increases physical memory usage for the 16 KiB size class to 20 KiB. This option is enabled by default. opt.metadata_thp (const char *) r- Controls whether to allow jemalloc to use transparent huge page (THP) for internal metadata (see stats.metadata). always allows such usage. auto uses no THP initially, but may begin to do so when metadata usage reaches certain level. The default is disabled. opt.trust_madvise (bool) r- If true, do not perform runtime check for MADV_DONTNEED, to check that it actually zeros pages. The default is disabled on Linux and enabled elsewhere. opt.retain (bool) r- If true, retain unused virtual memory for later reuse rather than discarding it by calling munmap 2 or equivalent (see stats.retained for related details). It also makes jemalloc use mmap2 or equivalent in a more greedy way, mapping larger chunks in one go. This option is disabled by default unless discarding virtual memory is known to trigger platform-specific performance problems, namely 1) for [64-bit] Linux, which has a quirk in its virtual memory allocation algorithm that causes semi-permanent VM map holes under normal jemalloc operation; and 2) for [64-bit] Windows, which disallows split / merged regions with MEM_RELEASE. Although the same issues may present on 32-bit platforms as well, retaining virtual memory for 32-bit Linux and Windows is disabled by default due to the practical possibility of address space exhaustion. opt.dss (const char *) r- dss (sbrk 2) allocation precedence as related to mmap 2 allocation. The following settings are supported if sbrk 2 is supported by the operating system: disabled, primary, and secondary; otherwise only disabled is supported. The default is secondary if sbrk 2 is supported by the operating system; disabled otherwise. opt.narenas (unsigned) r- Maximum number of arenas to use for automatic multiplexing of threads and arenas. The default is four times the number of CPUs, or one if there is a single CPU. opt.oversize_threshold (size_t) r- The threshold in bytes of which requests are considered oversize. Allocation requests with greater sizes are fulfilled from a dedicated arena (automatically managed, however not within narenas), in order to reduce fragmentation by not mixing huge allocations with small ones. In addition, the decay API guarantees on the extents greater than the specified threshold may be overridden. Note that requests with arena index specified via MALLOCX_ARENA, or threads associated with explicit arenas will not be considered. The default threshold is 8MiB. Values not within large size classes disables this feature. opt.percpu_arena (const char *) r- Per CPU arena mode. Use the percpu setting to enable this feature, which uses number of CPUs to determine number of arenas, and bind threads to arenas dynamically based on the CPU the thread runs on currently. phycpu setting uses one arena per physical CPU, which means the two hyper threads on the same CPU share one arena. Note that no runtime checking regarding the availability of hyper threading is done at the moment. When set to disabled, narenas and thread to arena association will not be impacted by this option. The default is disabled. opt.background_thread (bool) r- Internal background worker threads enabled/disabled. Because of potential circular dependencies, enabling background thread using this option may cause crash or deadlock during initialization. For a reliable way to use this feature, see background_thread for dynamic control options and details. This option is disabled by default. opt.max_background_threads (size_t) r- Maximum number of background threads that will be created if background_thread is set. Defaults to number of cpus. opt.dirty_decay_ms (ssize_t) r- Approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages is purged (i.e. converted to muzzy via e.g. madvise(...MADV_FREE) if supported by the operating system, or converted to clean otherwise) and/or reused. Dirty pages are defined as previously having been potentially written to by the application, and therefore consuming physical memory, yet having no current use. The pages are incrementally purged according to a sigmoidal decay curve that starts and ends with zero purge rate. A decay time of 0 causes all unused dirty pages to be purged immediately upon creation. A decay time of -1 disables purging. The default decay time is 10 seconds. See arenas.dirty_decay_ms and arena.<i>.dirty_decay_ms for related dynamic control options. See opt.muzzy_decay_ms for a description of muzzy pages.for a description of muzzy pages. Note that when the oversize_threshold feature is enabled, the arenas reserved for oversize requests may have its own default decay settings. opt.muzzy_decay_ms (ssize_t) r- Approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages is purged (i.e. converted to clean) and/or reused. Muzzy pages are defined as previously having been unused dirty pages that were subsequently purged in a manner that left them subject to the reclamation whims of the operating system (e.g. madvise(...MADV_FREE)), and therefore in an indeterminate state. The pages are incrementally purged according to a sigmoidal decay curve that starts and ends with zero purge rate. A decay time of 0 causes all unused muzzy pages to be purged immediately upon creation. A decay time of -1 disables purging. The default decay time is 10 seconds. See arenas.muzzy_decay_ms and arena.<i>.muzzy_decay_ms for related dynamic control options. opt.lg_extent_max_active_fit (size_t) r- When reusing dirty extents, this determines the (log base 2 of the) maximum ratio between the size of the active extent selected (to split off from) and the size of the requested allocation. This prevents the splitting of large active extents for smaller allocations, which can reduce fragmentation over the long run (especially for non-active extents). Lower value may reduce fragmentation, at the cost of extra active extents. The default value is 6, which gives a maximum ratio of 64 (2^6). opt.stats_print (bool) r- Enable/disable statistics printing at exit. If enabled, the malloc_stats_print() function is called at program exit via an atexit 3 function. opt.stats_print_opts can be combined to specify output options. If is specified during configuration, this has the potential to cause deadlock for a multi-threaded process that exits while one or more threads are executing in the memory allocation functions. Furthermore, atexit() may allocate memory during application initialization and then deadlock internally when jemalloc in turn calls atexit(), so this option is not universally usable (though the application can register its own atexit() function with equivalent functionality). Therefore, this option should only be used with care; it is primarily intended as a performance tuning aid during application development. This option is disabled by default. opt.stats_print_opts (const char *) r- Options (the opts string) to pass to the malloc_stats_print() at exit (enabled through opt.stats_print). See available options in malloc_stats_print(). Has no effect unless opt.stats_print is enabled. The default is . opt.stats_interval (int64_t) r- Average interval between statistics outputs, as measured in bytes of allocation activity. The actual interval may be sporadic because decentralized event counters are used to avoid synchronization bottlenecks. The output may be triggered on any thread, which then calls malloc_stats_print(). opt.stats_interval_opts can be combined to specify output options. By default, interval-triggered stats output is disabled (encoded as -1). opt.stats_interval_opts (const char *) r- Options (the opts string) to pass to the malloc_stats_print() for interval based statistics printing (enabled through opt.stats_interval). See available options in malloc_stats_print(). Has no effect unless opt.stats_interval is enabled. The default is . opt.junk (const char *) r- [] Junk filling. If set to alloc, each byte of uninitialized allocated memory will be initialized to 0xa5. If set to free, all deallocated memory will be initialized to 0x5a. If set to true, both allocated and deallocated memory will be initialized, and if set to false, junk filling be disabled entirely. This is intended for debugging and will impact performance negatively. This option is false by default unless is specified during configuration, in which case it is true by default. opt.zero (bool) r- [] Zero filling enabled/disabled. If enabled, each byte of uninitialized allocated memory will be initialized to 0. Note that this initialization only happens once for each byte, so realloc() and rallocx() calls do not zero memory that was previously allocated. This is intended for debugging and will impact performance negatively. This option is disabled by default. opt.utrace (bool) r- [] Allocation tracing based on utrace 2 enabled/disabled. This option is disabled by default. opt.xmalloc (bool) r- [] Abort-on-out-of-memory enabled/disabled. If enabled, rather than returning failure for any allocation function, display a diagnostic message on STDERR_FILENO and cause the program to drop core (using abort 3). If an application is designed to depend on this behavior, set the option at compile time by including the following in the source code: This option is disabled by default. opt.tcache (bool) r- Thread-specific caching (tcache) enabled/disabled. When there are multiple threads, each thread uses a tcache for objects up to a certain size. Thread-specific caching allows many allocations to be satisfied without performing any thread synchronization, at the cost of increased memory use. See the opt.tcache_max option for related tuning information. This option is enabled by default. opt.tcache_max (size_t) r- Maximum size class to cache in the thread-specific cache (tcache). At a minimum, the first size class is cached; and at a maximum, size classes up to 8 MiB can be cached. The default maximum is 32 KiB (2^15). As a convenience, this may also be set by specifying lg_tcache_max, which will be taken to be the base-2 logarithm of the setting of tcache_max. opt.thp (const char *) r- Transparent hugepage (THP) mode. Settings "always", "never" and "default" are available if THP is supported by the operating system. The "always" setting enables transparent hugepage for all user memory mappings with MADV_HUGEPAGE; "never" ensures no transparent hugepage with MADV_NOHUGEPAGE; the default setting "default" makes no changes. Note that: this option does not affect THP for jemalloc internal metadata (see opt.metadata_thp); in addition, for arenas with customized extent_hooks, this option is bypassed as it is implemented as part of the default extent hooks. opt.prof (bool) r- [] Memory profiling enabled/disabled. If enabled, profile memory allocation activity. See the opt.prof_active option for on-the-fly activation/deactivation. See the opt.lg_prof_sample option for probabilistic sampling control. See the opt.prof_accum option for control of cumulative sample reporting. See the opt.lg_prof_interval option for information on interval-triggered profile dumping, the opt.prof_gdump option for information on high-water-triggered profile dumping, and the opt.prof_final option for final profile dumping. Profile output is compatible with the jeprof command, which is based on the pprof that is developed as part of the gperftools package. See HEAP PROFILE FORMAT for heap profile format documentation. opt.prof_prefix (const char *) r- [] Filename prefix for profile dumps. If the prefix is set to the empty string, no automatic dumps will occur; this is primarily useful for disabling the automatic final heap dump (which also disables leak reporting, if enabled). The default prefix is jeprof. This prefix value can be overridden by prof.prefix. opt.prof_active (bool) r- [] Profiling activated/deactivated. This is a secondary control mechanism that makes it possible to start the application with profiling enabled (see the opt.prof option) but inactive, then toggle profiling at any time during program execution with the prof.active mallctl. This option is enabled by default. opt.prof_thread_active_init (bool) r- [] Initial setting for thread.prof.active in newly created threads. The initial setting for newly created threads can also be changed during execution via the prof.thread_active_init mallctl. This option is enabled by default. opt.lg_prof_sample (size_t) r- [] Average interval (log base 2) between allocation samples, as measured in bytes of allocation activity. Increasing the sampling interval decreases profile fidelity, but also decreases the computational overhead. The default sample interval is 512 KiB (2^19 B). opt.prof_accum (bool) r- [] Reporting of cumulative object/byte counts in profile dumps enabled/disabled. If this option is enabled, every unique backtrace must be stored for the duration of execution. Depending on the application, this can impose a large memory overhead, and the cumulative counts are not always of interest. This option is disabled by default. opt.lg_prof_interval (ssize_t) r- [] Average interval (log base 2) between memory profile dumps, as measured in bytes of allocation activity. The actual interval between dumps may be sporadic because decentralized allocation counters are used to avoid synchronization bottlenecks. Profiles are dumped to files named according to the pattern <prefix>.<pid>.<seq>.i<iseq>.heap, where <prefix> is controlled by the opt.prof_prefix and prof.prefix options. By default, interval-triggered profile dumping is disabled (encoded as -1). opt.prof_gdump (bool) r- [] Set the initial state of prof.gdump, which when enabled triggers a memory profile dump every time the total virtual memory exceeds the previous maximum. This option is disabled by default. opt.prof_final (bool) r- [] Use an atexit 3 function to dump final memory usage to a file named according to the pattern <prefix>.<pid>.<seq>.f.heap, where <prefix> is controlled by the opt.prof_prefix and prof.prefix options. Note that atexit() may allocate memory during application initialization and then deadlock internally when jemalloc in turn calls atexit(), so this option is not universally usable (though the application can register its own atexit() function with equivalent functionality). This option is disabled by default. opt.prof_leak (bool) r- [] Leak reporting enabled/disabled. If enabled, use an atexit 3 function to report memory leaks detected by allocation sampling. See the opt.prof option for information on analyzing heap profile output. Works only when combined with opt.prof_final , otherwise does nothing. This option is disabled by default. opt.prof_leak_error (bool) r- [] Similar to opt.prof_leak, but makes the process exit with error code 1 if a memory leak is detected. This option supersedes opt.prof_leak, meaning that if both are specified, this option takes precedence. When enabled, also enables opt.prof_leak. Works only when combined with opt.prof_final, otherwise does nothing. This option is disabled by default. opt.zero_realloc (const char *) r- Determines the behavior of realloc() when passed a value of zero for the new size. alloc treats this as an allocation of size zero (and returns a non-null result except in case of resource exhaustion). free treats this as a deallocation of the pointer, and returns NULL without setting errno. abort aborts the process if zero is passed. The default is free on Linux and Windows, and alloc elsewhere. There is considerable divergence of behaviors across implementations in handling this case. Many have the behavior of free. This can introduce security vulnerabilities, since a NULL return value indicates failure, and the continued validity of the passed-in pointer (per POSIX and C11). alloc is safe, but can cause leaks in programs that expect the common behavior. Programs intended to be portable and leak-free cannot assume either behavior, and must therefore never call realloc with a size of 0. The abort option enables these testing this behavior. thread.arena (unsigned) rw Get or set the arena associated with the calling thread. If the specified arena was not initialized beforehand (see the arena.i.initialized mallctl), it will be automatically initialized as a side effect of calling this interface. thread.allocated (uint64_t) r- [] Get the total number of bytes ever allocated by the calling thread. This counter has the potential to wrap around; it is up to the application to appropriately interpret the counter in such cases. thread.allocatedp (uint64_t *) r- [] Get a pointer to the the value that is returned by the thread.allocated mallctl. This is useful for avoiding the overhead of repeated mallctl*() calls. Note that the underlying counter should not be modified by the application. thread.deallocated (uint64_t) r- [] Get the total number of bytes ever deallocated by the calling thread. This counter has the potential to wrap around; it is up to the application to appropriately interpret the counter in such cases. thread.deallocatedp (uint64_t *) r- [] Get a pointer to the the value that is returned by the thread.deallocated mallctl. This is useful for avoiding the overhead of repeated mallctl*() calls. Note that the underlying counter should not be modified by the application. thread.peak.read (uint64_t) r- [] Get an approximation of the maximum value of the difference between the number of bytes allocated and the number of bytes deallocated by the calling thread since the last call to thread.peak.reset, or since the thread's creation if it has not called thread.peak.reset. No guarantees are made about the quality of the approximation, but jemalloc currently endeavors to maintain accuracy to within one hundred kilobytes. thread.peak.reset (void) -- [] Resets the counter for net bytes allocated in the calling thread to zero. This affects subsequent calls to thread.peak.read, but not the values returned by thread.allocated or thread.deallocated. thread.tcache.enabled (bool) rw Enable/disable calling thread's tcache. The tcache is implicitly flushed as a side effect of becoming disabled (see thread.tcache.flush). thread.tcache.flush (void) -- Flush calling thread's thread-specific cache (tcache). This interface releases all cached objects and internal data structures associated with the calling thread's tcache. Ordinarily, this interface need not be called, since automatic periodic incremental garbage collection occurs, and the thread cache is automatically discarded when a thread exits. However, garbage collection is triggered by allocation activity, so it is possible for a thread that stops allocating/deallocating to retain its cache indefinitely, in which case the developer may find manual flushing useful. thread.prof.name (const char *) r- or -w [] Get/set the descriptive name associated with the calling thread in memory profile dumps. An internal copy of the name string is created, so the input string need not be maintained after this interface completes execution. The output string of this interface should be copied for non-ephemeral uses, because multiple implementation details can cause asynchronous string deallocation. Furthermore, each invocation of this interface can only read or write; simultaneous read/write is not supported due to string lifetime limitations. The name string must be nil-terminated and comprised only of characters in the sets recognized by isgraph 3 and isblank 3. thread.prof.active (bool) rw [] Control whether sampling is currently active for the calling thread. This is an activation mechanism in addition to prof.active; both must be active for the calling thread to sample. This flag is enabled by default. thread.idle (void) -- Hints to jemalloc that the calling thread will be idle for some nontrivial period of time (say, on the order of seconds), and that doing some cleanup operations may be beneficial. There are no guarantees as to what specific operations will be performed; currently this flushes the caller's tcache and may (according to some heuristic) purge its associated arena. This is not intended to be a general-purpose background activity mechanism, and threads should not wake up multiple times solely to call it. Rather, a thread waiting for a task should do a timed wait first, call thread.idle if no task appears in the timeout interval, and then do an untimed wait. For such a background activity mechanism, see background_thread. tcache.create (unsigned) r- Create an explicit thread-specific cache (tcache) and return an identifier that can be passed to the MALLOCX_TCACHE(tc) macro to explicitly use the specified cache rather than the automatically managed one that is used by default. Each explicit cache can be used by only one thread at a time; the application must assure that this constraint holds. If the amount of space supplied for storing the thread-specific cache identifier does not equal sizeof(unsigned), no thread-specific cache will be created, no data will be written to the space pointed by oldp, and *oldlenp will be set to 0. tcache.flush (unsigned) -w Flush the specified thread-specific cache (tcache). The same considerations apply to this interface as to thread.tcache.flush, except that the tcache will never be automatically discarded. tcache.destroy (unsigned) -w Flush the specified thread-specific cache (tcache) and make the identifier available for use during a future tcache creation. arena.<i>.initialized (bool) r- Get whether the specified arena's statistics are initialized (i.e. the arena was initialized prior to the current epoch). This interface can also be nominally used to query whether the merged statistics corresponding to MALLCTL_ARENAS_ALL are initialized (always true). arena.<i>.decay (void) -- Trigger decay-based purging of unused dirty/muzzy pages for arena <i>, or for all arenas if <i> equals MALLCTL_ARENAS_ALL. The proportion of unused dirty/muzzy pages to be purged depends on the current time; see opt.dirty_decay_ms and opt.muzy_decay_ms for details. arena.<i>.purge (void) -- Purge all unused dirty pages for arena <i>, or for all arenas if <i> equals MALLCTL_ARENAS_ALL. arena.<i>.reset (void) -- Discard all of the arena's extant allocations. This interface can only be used with arenas explicitly created via arenas.create. None of the arena's discarded/cached allocations may accessed afterward. As part of this requirement, all thread caches which were used to allocate/deallocate in conjunction with the arena must be flushed beforehand. arena.<i>.destroy (void) -- Destroy the arena. Discard all of the arena's extant allocations using the same mechanism as for arena.<i>.reset (with all the same constraints and side effects), merge the arena stats into those accessible at arena index MALLCTL_ARENAS_DESTROYED, and then completely discard all metadata associated with the arena. Future calls to arenas.create may recycle the arena index. Destruction will fail if any threads are currently associated with the arena as a result of calls to thread.arena. arena.<i>.dss (const char *) rw Set the precedence of dss allocation as related to mmap allocation for arena <i>, or for all arenas if <i> equals MALLCTL_ARENAS_ALL. See opt.dss for supported settings. arena.<i>.dirty_decay_ms (ssize_t) rw Current per-arena approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages is purged and/or reused. Each time this interface is set, all currently unused dirty pages are considered to have fully decayed, which causes immediate purging of all unused dirty pages unless the decay time is set to -1 (i.e. purging disabled). See opt.dirty_decay_ms for additional information. arena.<i>.muzzy_decay_ms (ssize_t) rw Current per-arena approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages is purged and/or reused. Each time this interface is set, all currently unused muzzy pages are considered to have fully decayed, which causes immediate purging of all unused muzzy pages unless the decay time is set to -1 (i.e. purging disabled). See opt.muzzy_decay_ms for additional information. arena.<i>.retain_grow_limit (size_t) rw Maximum size to grow retained region (only relevant when opt.retain is enabled). This controls the maximum increment to expand virtual memory, or allocation through arena.<i>extent_hooks. In particular, if customized extent hooks reserve physical memory (e.g. 1G huge pages), this is useful to control the allocation hook's input size. The default is no limit. arena.<i>.extent_hooks (extent_hooks_t *) rw Get or set the extent management hook functions for arena <i>. The functions must be capable of operating on all extant extents associated with arena <i>, usually by passing unknown extents to the replaced functions. In practice, it is feasible to control allocation for arenas explicitly created via arenas.create such that all extents originate from an application-supplied extent allocator (by specifying the custom extent hook functions during arena creation). However, the API guarantees for the automatically created arenas may be relaxed -- hooks set there may be called in a "best effort" fashion; in addition there may be extents created prior to the application having an opportunity to take over extent allocation. The extent_hooks_t structure comprises function pointers which are described individually below. jemalloc uses these functions to manage extent lifetime, which starts off with allocation of mapped committed memory, in the simplest case followed by deallocation. However, there are performance and platform reasons to retain extents for later reuse. Cleanup attempts cascade from deallocation to decommit to forced purging to lazy purging, which gives the extent management functions opportunities to reject the most permanent cleanup operations in favor of less permanent (and often less costly) operations. All operations except allocation can be universally opted out of by setting the hook pointers to NULL, or selectively opted out of by returning failure. Note that once the extent hook is set, the structure is accessed directly by the associated arenas, so it must remain valid for the entire lifetime of the arenas. typedef void *(extent_alloc_t) extent_hooks_t *extent_hooks void *new_addr size_t size size_t alignment bool *zero bool *commit unsigned arena_ind An extent allocation function conforms to the extent_alloc_t type and upon success returns a pointer to size bytes of mapped memory on behalf of arena arena_ind such that the extent's base address is a multiple of alignment, as well as setting *zero to indicate whether the extent is zeroed and *commit to indicate whether the extent is committed. Upon error the function returns NULL and leaves *zero and *commit unmodified. The size parameter is always a multiple of the page size. The alignment parameter is always a power of two at least as large as the page size. Zeroing is mandatory if *zero is true upon function entry. Committing is mandatory if *commit is true upon function entry. If new_addr is not NULL, the returned pointer must be new_addr on success or NULL on error. Committed memory may be committed in absolute terms as on a system that does not overcommit, or in implicit terms as on a system that overcommits and satisfies physical memory needs on demand via soft page faults. Note that replacing the default extent allocation function makes the arena's arena.<i>.dss setting irrelevant. typedef bool (extent_dalloc_t) extent_hooks_t *extent_hooks void *addr size_t size bool committed unsigned arena_ind An extent deallocation function conforms to the extent_dalloc_t type and deallocates an extent at given addr and size with committed/decommited memory as indicated, on behalf of arena arena_ind, returning false upon success. If the function returns true, this indicates opt-out from deallocation; the virtual memory mapping associated with the extent remains mapped, in the same commit state, and available for future use, in which case it will be automatically retained for later reuse. typedef void (extent_destroy_t) extent_hooks_t *extent_hooks void *addr size_t size bool committed unsigned arena_ind An extent destruction function conforms to the extent_destroy_t type and unconditionally destroys an extent at given addr and size with committed/decommited memory as indicated, on behalf of arena arena_ind. This function may be called to destroy retained extents during arena destruction (see arena.<i>.destroy). typedef bool (extent_commit_t) extent_hooks_t *extent_hooks void *addr size_t size size_t offset size_t length unsigned arena_ind An extent commit function conforms to the extent_commit_t type and commits zeroed physical memory to back pages within an extent at given addr and size at offset bytes, extending for length on behalf of arena arena_ind, returning false upon success. Committed memory may be committed in absolute terms as on a system that does not overcommit, or in implicit terms as on a system that overcommits and satisfies physical memory needs on demand via soft page faults. If the function returns true, this indicates insufficient physical memory to satisfy the request. typedef bool (extent_decommit_t) extent_hooks_t *extent_hooks void *addr size_t size size_t offset size_t length unsigned arena_ind An extent decommit function conforms to the extent_decommit_t type and decommits any physical memory that is backing pages within an extent at given addr and size at offset bytes, extending for length on behalf of arena arena_ind, returning false upon success, in which case the pages will be committed via the extent commit function before being reused. If the function returns true, this indicates opt-out from decommit; the memory remains committed and available for future use, in which case it will be automatically retained for later reuse. typedef bool (extent_purge_t) extent_hooks_t *extent_hooks void *addr size_t size size_t offset size_t length unsigned arena_ind An extent purge function conforms to the extent_purge_t type and discards physical pages within the virtual memory mapping associated with an extent at given addr and size at offset bytes, extending for length on behalf of arena arena_ind. A lazy extent purge function (e.g. implemented via madvise(...MADV_FREE)) can delay purging indefinitely and leave the pages within the purged virtual memory range in an indeterminite state, whereas a forced extent purge function immediately purges, and the pages within the virtual memory range will be zero-filled the next time they are accessed. If the function returns true, this indicates failure to purge. typedef bool (extent_split_t) extent_hooks_t *extent_hooks void *addr size_t size size_t size_a size_t size_b bool committed unsigned arena_ind An extent split function conforms to the extent_split_t type and optionally splits an extent at given addr and size into two adjacent extents, the first of size_a bytes, and the second of size_b bytes, operating on committed/decommitted memory as indicated, on behalf of arena arena_ind, returning false upon success. If the function returns true, this indicates that the extent remains unsplit and therefore should continue to be operated on as a whole. typedef bool (extent_merge_t) extent_hooks_t *extent_hooks void *addr_a size_t size_a void *addr_b size_t size_b bool committed unsigned arena_ind An extent merge function conforms to the extent_merge_t type and optionally merges adjacent extents, at given addr_a and size_a with given addr_b and size_b into one contiguous extent, operating on committed/decommitted memory as indicated, on behalf of arena arena_ind, returning false upon success. If the function returns true, this indicates that the extents remain distinct mappings and therefore should continue to be operated on independently. arenas.narenas (unsigned) r- Current limit on number of arenas. arenas.dirty_decay_ms (ssize_t) rw Current default per-arena approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages is purged and/or reused, used to initialize arena.<i>.dirty_decay_ms during arena creation. See opt.dirty_decay_ms for additional information. arenas.muzzy_decay_ms (ssize_t) rw Current default per-arena approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages is purged and/or reused, used to initialize arena.<i>.muzzy_decay_ms during arena creation. See opt.muzzy_decay_ms for additional information. arenas.quantum (size_t) r- Quantum size. arenas.page (size_t) r- Page size. arenas.tcache_max (size_t) r- Maximum thread-cached size class. arenas.nbins (unsigned) r- Number of bin size classes. arenas.nhbins (unsigned) r- Total number of thread cache bin size classes. arenas.bin.<i>.size (size_t) r- Maximum size supported by size class. arenas.bin.<i>.nregs (uint32_t) r- Number of regions per slab. arenas.bin.<i>.slab_size (size_t) r- Number of bytes per slab. arenas.nlextents (unsigned) r- Total number of large size classes. arenas.lextent.<i>.size (size_t) r- Maximum size supported by this large size class. arenas.create (unsigned, extent_hooks_t *) rw Explicitly create a new arena outside the range of automatically managed arenas, with optionally specified extent hooks, and return the new arena index. If the amount of space supplied for storing the arena index does not equal sizeof(unsigned), no arena will be created, no data will be written to the space pointed by oldp, and *oldlenp will be set to 0. arenas.lookup (unsigned, void*) rw Index of the arena to which an allocation belongs to. prof.thread_active_init (bool) rw [] Control the initial setting for thread.prof.active in newly created threads. See the opt.prof_thread_active_init option for additional information. prof.active (bool) rw [] Control whether sampling is currently active. See the opt.prof_active option for additional information, as well as the interrelated thread.prof.active mallctl. prof.dump (const char *) -w [] Dump a memory profile to the specified file, or if NULL is specified, to a file according to the pattern <prefix>.<pid>.<seq>.m<mseq>.heap, where <prefix> is controlled by the opt.prof_prefix and prof.prefix options. prof.prefix (const char *) -w [] Set the filename prefix for profile dumps. See opt.prof_prefix for the default setting. This can be useful to differentiate profile dumps such as from forked processes. prof.gdump (bool) rw [] When enabled, trigger a memory profile dump every time the total virtual memory exceeds the previous maximum. Profiles are dumped to files named according to the pattern <prefix>.<pid>.<seq>.u<useq>.heap, where <prefix> is controlled by the opt.prof_prefix and prof.prefix options. prof.reset (size_t) -w [] Reset all memory profile statistics, and optionally update the sample rate (see opt.lg_prof_sample and prof.lg_sample). prof.lg_sample (size_t) r- [] Get the current sample rate (see opt.lg_prof_sample). prof.interval (uint64_t) r- [] Average number of bytes allocated between interval-based profile dumps. See the opt.lg_prof_interval option for additional information. stats.allocated (size_t) r- [] Total number of bytes allocated by the application. stats.active (size_t) r- [] Total number of bytes in active pages allocated by the application. This is a multiple of the page size, and greater than or equal to stats.allocated. This does not include stats.arenas.<i>.pdirty, stats.arenas.<i>.pmuzzy, nor pages entirely devoted to allocator metadata. stats.metadata (size_t) r- [] Total number of bytes dedicated to metadata, which comprise base allocations used for bootstrap-sensitive allocator metadata structures (see stats.arenas.<i>.base) and internal allocations (see stats.arenas.<i>.internal). Transparent huge page (enabled with opt.metadata_thp) usage is not considered. stats.metadata_thp (size_t) r- [] Number of transparent huge pages (THP) used for metadata. See stats.metadata and opt.metadata_thp) for details. stats.resident (size_t) r- [] Maximum number of bytes in physically resident data pages mapped by the allocator, comprising all pages dedicated to allocator metadata, pages backing active allocations, and unused dirty pages. This is a maximum rather than precise because pages may not actually be physically resident if they correspond to demand-zeroed virtual memory that has not yet been touched. This is a multiple of the page size, and is larger than stats.active. stats.mapped (size_t) r- [] Total number of bytes in active extents mapped by the allocator. This is larger than stats.active. This does not include inactive extents, even those that contain unused dirty pages, which means that there is no strict ordering between this and stats.resident. stats.retained (size_t) r- [] Total number of bytes in virtual memory mappings that were retained rather than being returned to the operating system via e.g. munmap 2 or similar. Retained virtual memory is typically untouched, decommitted, or purged, so it has no strongly associated physical memory (see extent hooks for details). Retained memory is excluded from mapped memory statistics, e.g. stats.mapped. stats.zero_reallocs (size_t) r- [] Number of times that the realloc() was called with a non-NULL pointer argument and a 0 size argument. This is a fundamentally unsafe pattern in portable programs; see opt.zero_realloc for details. stats.background_thread.num_threads (size_t) r- [] Number of background threads running currently. stats.background_thread.num_runs (uint64_t) r- [] Total number of runs from all background threads. stats.background_thread.run_interval (uint64_t) r- [] Average run interval in nanoseconds of background threads. stats.mutexes.ctl.{counter}; (counter specific type) r- [] Statistics on ctl mutex (global scope; mallctl related). {counter} is one of the counters below: num_ops (uint64_t): Total number of lock acquisition operations on this mutex. num_spin_acq (uint64_t): Number of times the mutex was spin-acquired. When the mutex is currently locked and cannot be acquired immediately, a short period of spin-retry within jemalloc will be performed. Acquired through spin generally means the contention was lightweight and not causing context switches. num_wait (uint64_t): Number of times the mutex was wait-acquired, which means the mutex contention was not solved by spin-retry, and blocking operation was likely involved in order to acquire the mutex. This event generally implies higher cost / longer delay, and should be investigated if it happens often. max_wait_time (uint64_t): Maximum length of time in nanoseconds spent on a single wait-acquired lock operation. Note that to avoid profiling overhead on the common path, this does not consider spin-acquired cases. total_wait_time (uint64_t): Cumulative time in nanoseconds spent on wait-acquired lock operations. Similarly, spin-acquired cases are not considered. max_num_thds (uint32_t): Maximum number of threads waiting on this mutex simultaneously. Similarly, spin-acquired cases are not considered. num_owner_switch (uint64_t): Number of times the current mutex owner is different from the previous one. This event does not generally imply an issue; rather it is an indicator of how often the protected data are accessed by different threads. stats.mutexes.background_thread.{counter} (counter specific type) r- [] Statistics on background_thread mutex (global scope; background_thread related). {counter} is one of the counters in mutex profiling counters. stats.mutexes.prof.{counter} (counter specific type) r- [] Statistics on prof mutex (global scope; profiling related). {counter} is one of the counters in mutex profiling counters. stats.mutexes.prof_thds_data.{counter} (counter specific type) r- [] Statistics on prof threads data mutex (global scope; profiling related). {counter} is one of the counters in mutex profiling counters. stats.mutexes.prof_dump.{counter} (counter specific type) r- [] Statistics on prof dumping mutex (global scope; profiling related). {counter} is one of the counters in mutex profiling counters. stats.mutexes.reset (void) -- [] Reset all mutex profile statistics, including global mutexes, arena mutexes and bin mutexes. stats.arenas.<i>.dss (const char *) r- dss (sbrk 2) allocation precedence as related to mmap 2 allocation. See opt.dss for details. stats.arenas.<i>.dirty_decay_ms (ssize_t) r- Approximate time in milliseconds from the creation of a set of unused dirty pages until an equivalent set of unused dirty pages is purged and/or reused. See opt.dirty_decay_ms for details. stats.arenas.<i>.muzzy_decay_ms (ssize_t) r- Approximate time in milliseconds from the creation of a set of unused muzzy pages until an equivalent set of unused muzzy pages is purged and/or reused. See opt.muzzy_decay_ms for details. stats.arenas.<i>.nthreads (unsigned) r- Number of threads currently assigned to arena. stats.arenas.<i>.uptime (uint64_t) r- Time elapsed (in nanoseconds) since the arena was created. If <i> equals 0 or MALLCTL_ARENAS_ALL, this is the uptime since malloc initialization. stats.arenas.<i>.pactive (size_t) r- Number of pages in active extents. stats.arenas.<i>.pdirty (size_t) r- Number of pages within unused extents that are potentially dirty, and for which madvise() or similar has not been called. See opt.dirty_decay_ms for a description of dirty pages. stats.arenas.<i>.pmuzzy (size_t) r- Number of pages within unused extents that are muzzy. See opt.muzzy_decay_ms for a description of muzzy pages. stats.arenas.<i>.mapped (size_t) r- [] Number of mapped bytes. stats.arenas.<i>.retained (size_t) r- [] Number of retained bytes. See stats.retained for details. stats.arenas.<i>.extent_avail (size_t) r- [] Number of allocated (but unused) extent structs in this arena. stats.arenas.<i>.base (size_t) r- [] Number of bytes dedicated to bootstrap-sensitive allocator metadata structures. stats.arenas.<i>.internal (size_t) r- [] Number of bytes dedicated to internal allocations. Internal allocations differ from application-originated allocations in that they are for internal use, and that they are omitted from heap profiles. stats.arenas.<i>.metadata_thp (size_t) r- [] Number of transparent huge pages (THP) used for metadata. See opt.metadata_thp for details. stats.arenas.<i>.resident (size_t) r- [] Maximum number of bytes in physically resident data pages mapped by the arena, comprising all pages dedicated to allocator metadata, pages backing active allocations, and unused dirty pages. This is a maximum rather than precise because pages may not actually be physically resident if they correspond to demand-zeroed virtual memory that has not yet been touched. This is a multiple of the page size. stats.arenas.<i>.dirty_npurge (uint64_t) r- [] Number of dirty page purge sweeps performed. stats.arenas.<i>.dirty_nmadvise (uint64_t) r- [] Number of madvise() or similar calls made to purge dirty pages. stats.arenas.<i>.dirty_purged (uint64_t) r- [] Number of dirty pages purged. stats.arenas.<i>.muzzy_npurge (uint64_t) r- [] Number of muzzy page purge sweeps performed. stats.arenas.<i>.muzzy_nmadvise (uint64_t) r- [] Number of madvise() or similar calls made to purge muzzy pages. stats.arenas.<i>.muzzy_purged (uint64_t) r- [] Number of muzzy pages purged. stats.arenas.<i>.small.allocated (size_t) r- [] Number of bytes currently allocated by small objects. stats.arenas.<i>.small.nmalloc (uint64_t) r- [] Cumulative number of times a small allocation was requested from the arena's bins, whether to fill the relevant tcache if opt.tcache is enabled, or to directly satisfy an allocation request otherwise. stats.arenas.<i>.small.ndalloc (uint64_t) r- [] Cumulative number of times a small allocation was returned to the arena's bins, whether to flush the relevant tcache if opt.tcache is enabled, or to directly deallocate an allocation otherwise. stats.arenas.<i>.small.nrequests (uint64_t) r- [] Cumulative number of allocation requests satisfied by all bin size classes. stats.arenas.<i>.small.nfills (uint64_t) r- [] Cumulative number of tcache fills by all small size classes. stats.arenas.<i>.small.nflushes (uint64_t) r- [] Cumulative number of tcache flushes by all small size classes. stats.arenas.<i>.large.allocated (size_t) r- [] Number of bytes currently allocated by large objects. stats.arenas.<i>.large.nmalloc (uint64_t) r- [] Cumulative number of times a large extent was allocated from the arena, whether to fill the relevant tcache if opt.tcache is enabled and the size class is within the range being cached, or to directly satisfy an allocation request otherwise. stats.arenas.<i>.large.ndalloc (uint64_t) r- [] Cumulative number of times a large extent was returned to the arena, whether to flush the relevant tcache if opt.tcache is enabled and the size class is within the range being cached, or to directly deallocate an allocation otherwise. stats.arenas.<i>.large.nrequests (uint64_t) r- [] Cumulative number of allocation requests satisfied by all large size classes. stats.arenas.<i>.large.nfills (uint64_t) r- [] Cumulative number of tcache fills by all large size classes. stats.arenas.<i>.large.nflushes (uint64_t) r- [] Cumulative number of tcache flushes by all large size classes. stats.arenas.<i>.bins.<j>.nmalloc (uint64_t) r- [] Cumulative number of times a bin region of the corresponding size class was allocated from the arena, whether to fill the relevant tcache if opt.tcache is enabled, or to directly satisfy an allocation request otherwise. stats.arenas.<i>.bins.<j>.ndalloc (uint64_t) r- [] Cumulative number of times a bin region of the corresponding size class was returned to the arena, whether to flush the relevant tcache if opt.tcache is enabled, or to directly deallocate an allocation otherwise. stats.arenas.<i>.bins.<j>.nrequests (uint64_t) r- [] Cumulative number of allocation requests satisfied by bin regions of the corresponding size class. stats.arenas.<i>.bins.<j>.curregs (size_t) r- [] Current number of regions for this size class. stats.arenas.<i>.bins.<j>.nfills (uint64_t) r- Cumulative number of tcache fills. stats.arenas.<i>.bins.<j>.nflushes (uint64_t) r- Cumulative number of tcache flushes. stats.arenas.<i>.bins.<j>.nslabs (uint64_t) r- [] Cumulative number of slabs created. stats.arenas.<i>.bins.<j>.nreslabs (uint64_t) r- [] Cumulative number of times the current slab from which to allocate changed. stats.arenas.<i>.bins.<j>.curslabs (size_t) r- [] Current number of slabs. stats.arenas.<i>.bins.<j>.nonfull_slabs (size_t) r- [] Current number of nonfull slabs. stats.arenas.<i>.bins.<j>.mutex.{counter} (counter specific type) r- [] Statistics on arena.<i>.bins.<j> mutex (arena bin scope; bin operation related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.extents.<j>.n{extent_type} (size_t) r- [] Number of extents of the given type in this arena in the bucket corresponding to page size index <j>. The extent type is one of dirty, muzzy, or retained. stats.arenas.<i>.extents.<j>.{extent_type}_bytes (size_t) r- [] Sum of the bytes managed by extents of the given type in this arena in the bucket corresponding to page size index <j>. The extent type is one of dirty, muzzy, or retained. stats.arenas.<i>.lextents.<j>.nmalloc (uint64_t) r- [] Cumulative number of times a large extent of the corresponding size class was allocated from the arena, whether to fill the relevant tcache if opt.tcache is enabled and the size class is within the range being cached, or to directly satisfy an allocation request otherwise. stats.arenas.<i>.lextents.<j>.ndalloc (uint64_t) r- [] Cumulative number of times a large extent of the corresponding size class was returned to the arena, whether to flush the relevant tcache if opt.tcache is enabled and the size class is within the range being cached, or to directly deallocate an allocation otherwise. stats.arenas.<i>.lextents.<j>.nrequests (uint64_t) r- [] Cumulative number of allocation requests satisfied by large extents of the corresponding size class. stats.arenas.<i>.lextents.<j>.curlextents (size_t) r- [] Current number of large allocations for this size class. stats.arenas.<i>.mutexes.large.{counter} (counter specific type) r- [] Statistics on arena.<i>.large mutex (arena scope; large allocation related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.extent_avail.{counter} (counter specific type) r- [] Statistics on arena.<i>.extent_avail mutex (arena scope; extent avail related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.extents_dirty.{counter} (counter specific type) r- [] Statistics on arena.<i>.extents_dirty mutex (arena scope; dirty extents related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.extents_muzzy.{counter} (counter specific type) r- [] Statistics on arena.<i>.extents_muzzy mutex (arena scope; muzzy extents related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.extents_retained.{counter} (counter specific type) r- [] Statistics on arena.<i>.extents_retained mutex (arena scope; retained extents related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.decay_dirty.{counter} (counter specific type) r- [] Statistics on arena.<i>.decay_dirty mutex (arena scope; decay for dirty pages related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.decay_muzzy.{counter} (counter specific type) r- [] Statistics on arena.<i>.decay_muzzy mutex (arena scope; decay for muzzy pages related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.base.{counter} (counter specific type) r- [] Statistics on arena.<i>.base mutex (arena scope; base allocator related). {counter} is one of the counters in mutex profiling counters. stats.arenas.<i>.mutexes.tcache_list.{counter} (counter specific type) r- [] Statistics on arena.<i>.tcache_list mutex (arena scope; tcache to arena association related). This mutex is expected to be accessed less often. {counter} is one of the counters in mutex profiling counters. HEAP PROFILE FORMAT Although the heap profiling functionality was originally designed to be compatible with the pprof command that is developed as part of the gperftools package, the addition of per thread heap profiling functionality required a different heap profile format. The jeprof command is derived from pprof, with enhancements to support the heap profile format described here. In the following hypothetical heap profile, [...] indicates elision for the sake of compactness. The following matches the above heap profile, but most tokens are replaced with <description> to indicate descriptions of the corresponding fields. / : : [: ] [...] : : [: ] [...] : : [: ] [...] @ [...] [...] : : [: ] : : [: ] : : [: ] [...] MAPPED_LIBRARIES: /maps>]]> DEBUGGING MALLOC PROBLEMS When debugging, it is a good idea to configure/build jemalloc with the and options, and recompile the program with suitable options and symbols for debugger support. When so configured, jemalloc incorporates a wide variety of run-time assertions that catch application errors such as double-free, write-after-free, etc. Programs often accidentally depend on uninitialized memory actually being filled with zero bytes. Junk filling (see the opt.junk option) tends to expose such bugs in the form of obviously incorrect results and/or coredumps. Conversely, zero filling (see the opt.zero option) eliminates the symptoms of such bugs. Between these two options, it is usually possible to quickly detect, diagnose, and eliminate such bugs. This implementation does not provide much detail about the problems it detects, because the performance impact for storing such information would be prohibitive. DIAGNOSTIC MESSAGES If any of the memory allocation/deallocation functions detect an error or warning condition, a message will be printed to file descriptor STDERR_FILENO. Errors will result in the process dumping core. If the opt.abort option is set, most warnings are treated as errors. The malloc_message variable allows the programmer to override the function which emits the text strings forming the errors and warnings if for some reason the STDERR_FILENO file descriptor is not suitable for this. malloc_message() takes the cbopaque pointer argument that is NULL unless overridden by the arguments in a call to malloc_stats_print(), followed by a string pointer. Please note that doing anything which tries to allocate memory in this function is likely to result in a crash or deadlock. All messages are prefixed by <jemalloc>: . RETURN VALUES Standard API The malloc() and calloc() functions return a pointer to the allocated memory if successful; otherwise a NULL pointer is returned and errno is set to ENOMEM. The posix_memalign() function returns the value 0 if successful; otherwise it returns an error value. The posix_memalign() function will fail if: EINVAL The alignment parameter is not a power of 2 at least as large as sizeof(void *). ENOMEM Memory allocation error. The aligned_alloc() function returns a pointer to the allocated memory if successful; otherwise a NULL pointer is returned and errno is set. The aligned_alloc() function will fail if: EINVAL The alignment parameter is not a power of 2. ENOMEM Memory allocation error. The realloc() function returns a pointer, possibly identical to ptr, to the allocated memory if successful; otherwise a NULL pointer is returned, and errno is set to ENOMEM if the error was the result of an allocation failure. The realloc() function always leaves the original buffer intact when an error occurs. The free() function returns no value. Non-standard API The mallocx() and rallocx() functions return a pointer to the allocated memory if successful; otherwise a NULL pointer is returned to indicate insufficient contiguous memory was available to service the allocation request. The xallocx() function returns the real size of the resulting resized allocation pointed to by ptr, which is a value less than size if the allocation could not be adequately grown in place. The sallocx() function returns the real size of the allocation pointed to by ptr. The nallocx() returns the real size that would result from a successful equivalent mallocx() function call, or zero if insufficient memory is available to perform the size computation. The mallctl(), mallctlnametomib(), and mallctlbymib() functions return 0 on success; otherwise they return an error value. The functions will fail if: EINVAL newp is not NULL, and newlen is too large or too small. Alternatively, *oldlenp is too large or too small; when it happens, except for a very few cases explicitly documented otherwise, as much data as possible are read despite the error, with the amount of data read being recorded in *oldlenp. ENOENT name or mib specifies an unknown/invalid value. EPERM Attempt to read or write void value, or attempt to write read-only value. EAGAIN A memory allocation failure occurred. EFAULT An interface with side effects failed in some way not directly related to mallctl*() read/write processing. The malloc_usable_size() function returns the usable size of the allocation pointed to by ptr. ENVIRONMENT The following environment variable affects the execution of the allocation functions: MALLOC_CONF If the environment variable MALLOC_CONF is set, the characters it contains will be interpreted as options. EXAMPLES To dump core whenever a problem occurs: ln -s 'abort:true' /etc/malloc.conf To specify in the source that only one arena should be automatically created: SEE ALSO madvise 2, mmap 2, sbrk 2, utrace 2, alloca 3, atexit 3, getpagesize 3 STANDARDS The malloc(), calloc(), realloc(), and free() functions conform to ISO/IEC 9899:1990 (ISO C90). The posix_memalign() function conforms to IEEE Std 1003.1-2001 (POSIX.1). redis-8.0.2/deps/jemalloc/doc/manpages.xsl.in000066400000000000000000000003171501533116600210470ustar00rootroot00000000000000 redis-8.0.2/deps/jemalloc/doc/stylesheet.xsl000066400000000000000000000006371501533116600210450ustar00rootroot00000000000000 ansi redis-8.0.2/deps/jemalloc/doc_internal/000077500000000000000000000000001501533116600200125ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/doc_internal/PROFILING_INTERNALS.md000066400000000000000000000307041501533116600233100ustar00rootroot00000000000000# jemalloc profiling This describes the mathematical basis behind jemalloc's profiling implementation, as well as the implementation tricks that make it effective. Historically, the jemalloc profiling design simply copied tcmalloc's. The implementation has since diverged, due to both the desire to record additional information, and to correct some biasing bugs. Note: this document is markdown with embedded LaTeX; different markdown renderers may not produce the expected output. Viewing with `pandoc -s PROFILING_INTERNALS.md -o PROFILING_INTERNALS.pdf` is recommended. ## Some tricks in our implementation toolbag ### Sampling Recording our metadata is quite expensive; we need to walk up the stack to get a stack trace. On top of that, we need to allocate storage to record that stack trace, and stick it somewhere where a profile-dumping call can find it. That call might happen on another thread, so we'll probably need to take a lock to do so. These costs are quite large compared to the average cost of an allocation. To manage this, we'll only sample some fraction of allocations. This will miss some of them, so our data will be incomplete, but we'll try to make up for it. We can tune our sampling rate to balance accuracy and performance. ### Fast Bernoulli sampling Compared to our fast paths, even a `coinflip(p)` function can be quite expensive. Having to do a random-number generation and some floating point operations would be a sizeable relative cost. However (as pointed out in [[Vitter, 1987](https://dl.acm.org/doi/10.1145/23002.23003)]), if we can orchestrate our algorithm so that many of our `coinflip` calls share their parameter value, we can do better. We can sample from the geometric distribution, and initialize a counter with the result. When the counter hits 0, the `coinflip` function returns true (and reinitializes its internal counter). This can let us do a random-number generation once per (logical) coinflip that comes up heads, rather than once per (logical) coinflip. Since we expect to sample relatively rarely, this can be a large win. ### Fast-path / slow-path thinking Most programs have a skewed distribution of allocations. Smaller allocations are much more frequent than large ones, but shorter lived and less common as a fraction of program memory. "Small" and "large" are necessarily sort of fuzzy terms, but if we define "small" as "allocations jemalloc puts into slabs" and "large" as the others, then it's not uncommon for small allocations to be hundreds of times more frequent than large ones, but take up around half the amount of heap space as large ones. Moreover, small allocations tend to be much cheaper than large ones (often by a factor of 20-30): they're more likely to hit in thread caches, less likely to have to do an mmap, and cheaper to fill (by the user) once the allocation has been returned. ## An unbiased estimator of space consumption from (almost) arbitrary sampling strategies Suppose we have a sampling strategy that meets the following criteria: - One allocation being sampled is independent of other allocations being sampled. - Each allocation has a non-zero probability of being sampled. We can then estimate the bytes in live allocations through some particular stack trace as: $$ \sum_i S_i I_i \frac{1}{\mathrm{E}[I_i]} $$ where the sum ranges over some index variable of live allocations from that stack, $S_i$ is the size of the $i$'th allocation, and $I_i$ is an indicator random variable for whether or not the $i'th$ allocation is sampled. $S_i$ and $\mathrm{E}[I_i]$ are constants (the program allocations are fixed; the random variables are the sampling decisions), so taking the expectation we get $$ \sum_i S_i \mathrm{E}[I_i] \frac{1}{\mathrm{E}[I_i]}.$$ This is of course $\sum_i S_i$, as we want (and, a similar calculation could be done for allocation counts as well). This is a fairly general strategy; note that while we require that sampling decisions be independent of one another's outcomes, they don't have to be independent of previous allocations, total bytes allocated, etc. You can imagine strategies that: - Sample allocations at program startup at a higher rate than subsequent allocations - Sample even-indexed allocations more frequently than odd-indexed ones (so long as no allocation has zero sampling probability) - Let threads declare themselves as high-sampling-priority, and sample their allocations at an increased rate. These can all be fit into this framework to give an unbiased estimator. ## Evaluating sampling strategies Not all strategies for picking allocations to sample are equally good, of course. Among unbiased estimators, the lower the variance, the lower the mean squared error. Using the estimator above, the variance is: $$ \begin{aligned} & \mathrm{Var}[\sum_i S_i I_i \frac{1}{\mathrm{E}[I_i]}] \\ =& \sum_i \mathrm{Var}[S_i I_i \frac{1}{\mathrm{E}[I_i]}] \\ =& \sum_i \frac{S_i^2}{\mathrm{E}[I_i]^2} \mathrm{Var}[I_i] \\ =& \sum_i \frac{S_i^2}{\mathrm{E}[I_i]^2} \mathrm{Var}[I_i] \\ =& \sum_i \frac{S_i^2}{\mathrm{E}[I_i]^2} \mathrm{E}[I_i](1 - \mathrm{E}[I_i]) \\ =& \sum_i S_i^2 \frac{1 - \mathrm{E}[I_i]}{\mathrm{E}[I_i]}. \end{aligned} $$ We can use this formula to compare various strategy choices. All else being equal, lower-variance strategies are better. ## Possible sampling strategies Because of the desire to avoid the fast-path costs, we'd like to use our Bernoulli trick if possible. There are two obvious counters to use: a coinflip per allocation, and a coinflip per byte allocated. ### Bernoulli sampling per-allocation An obvious strategy is to pick some large $N$, and give each allocation a $1/N$ chance of being sampled. This would let us use our Bernoulli-via-Geometric trick. Using the formula from above, we can compute the variance as: $$ \sum_i S_i^2 \frac{1 - \frac{1}{N}}{\frac{1}{N}} = (N-1) \sum_i S_i^2.$$ That is, an allocation of size $Z$ contributes a term of $(N-1)Z^2$ to the variance. ### Bernoulli sampling per-byte Another option we have is to pick some rate $R$, and give each byte a $1/R$ chance of being picked for sampling (at which point we would sample its contained allocation). The chance of an allocation of size $Z$ being sampled, then, is $$1-(1-\frac{1}{R})^{Z}$$ and an allocation of size $Z$ contributes a term of $$Z^2 \frac{(1-\frac{1}{R})^{Z}}{1-(1-\frac{1}{R})^{Z}}.$$ In practical settings, $R$ is large, and so this is well-approximated by $$Z^2 \frac{e^{-Z/R}}{1 - e^{-Z/R}} .$$ Just to get a sense of the dynamics here, let's look at the behavior for various values of $Z$. When $Z$ is small relative to $R$, we can use $e^z \approx 1 + x$, and conclude that the variance contributed by a small-$Z$ allocation is around $$Z^2 \frac{1-Z/R}{Z/R} \approx RZ.$$ When $Z$ is comparable to $R$, the variance term is near $Z^2$ (we have $\frac{e^{-Z/R}}{1 - e^{-Z/R}} = 1$ when $Z/R = \ln 2 \approx 0.693$). When $Z$ is large relative to $R$, the variance term goes to zero. ## Picking a sampling strategy The fast-path/slow-path dynamics of allocation patterns point us towards the per-byte sampling approach: - The quadratic increase in variance per allocation in the first approach is quite costly when heaps have a non-negligible portion of their bytes in those allocations, which is practically often the case. - The Bernoulli-per-byte approach shifts more of its samples towards large allocations, which are already a slow-path. - We drive several tickers (e.g. tcache gc) by bytes allocated, and report bytes-allocated as a user-visible statistic, so we have to do all the necessary bookkeeping anyways. Indeed, this is the approach we use in jemalloc. Our heap dumps record the size of the allocation and the sampling rate $R$, and jeprof unbiases by dividing by $1 - e^{-Z/R}$. The framework above would suggest dividing by $1-(1-1/R)^Z$; instead, we use the fact that $R$ is large in practical situations, and so $e^{-Z/R}$ is a good approximation (and faster to compute). (Equivalently, we may also see this as the factor that falls out from viewing sampling as a Poisson process directly). ## Consequences for heap dump consumers Using this approach means that there are a few things users need to be aware of. ### Stack counts are not proportional to allocation frequencies If one stack appears twice as often as another, this by itself does not imply that it allocates twice as often. Consider the case in which there are only two types of allocating call stacks in a program. Stack A allocates 8 bytes, and occurs a million times in a program. Stack B allocates 8 MB, and occurs just once in a program. If our sampling rate $R$ is about 1MB, we expect stack A to show up about 8 times, and stack B to show up once. Stack A isn't 8 times more frequent than stack B, though; it's a million times more frequent. ### Aggregation must be done after unbiasing samples Some tools manually parse heap dump output, and aggregate across stacks (or across program runs) to provide wider-scale data analyses. When doing this aggregation, though, it's important to unbias-and-then-sum, rather than sum-and-then-unbias. Reusing our example from the previous section: suppose we collect heap dumps of the program from a million machines. We then have 8 million occurs of stack A (each of 8 bytes), and a million occurrences of stack B (each of 8 MB). If we sum first, we'll attribute 64 MB to stack A, and 8 TB to stack B. Unbiasing changes these numbers by an infinitesimal amount, so that sum-then-unbias dramatically underreports the amount of memory allocated by stack A. ## An avenue for future exploration While the framework we laid out above is pretty general, as an engineering decision we're only interested in fairly simple approaches (i.e. ones for which the chance of an allocation being sampled depends only on its size). Our job is then: for each size class $Z$, pick a probability $p_Z$ that an allocation of that size will be sampled. We made some handwave-y references to statistical distributions to justify our choices, but there's no reason we need to pick them that way. Any set of non-zero probabilities is a valid choice. The real limiting factor in our ability to reduce estimator variance is that fact that sampling is expensive; we want to make sure we only do it on a small fraction of allocations. Our goal, then, is to pick the $p_Z$ to minimize variance given some maximum sampling rate $P$. If we define $a_Z$ to be the fraction of allocations of size $Z$, and $l_Z$ to be the fraction of allocations of size $Z$ still alive at the time of a heap dump, then we can phrase this as an optimization problem over the choices of $p_Z$: Minimize $$ \sum_Z Z^2 l_Z \frac{1-p_Z}{p_Z} $$ subject to $$ \sum_Z a_Z p_Z \leq P $$ Ignoring a term that doesn't depend on $p_Z$, the objective is minimized whenever $$ \sum_Z Z^2 l_Z \frac{1}{p_Z} $$ is. For a particular program, $l_Z$ and $a_Z$ are just numbers that can be obtained (exactly) from existing stats introspection facilities, and we have a fairly tractable convex optimization problem (it can be framed as a second-order cone program). It would be interesting to evaluate, for various common allocation patterns, how well our current strategy adapts. Do our actual choices for $p_Z$ closely correspond to the optimal ones? How close is the variance of our choices to the variance of the optimal strategy? You can imagine an implementation that actually goes all the way, and makes $p_Z$ selections a tuning parameter. I don't think this is a good use of development time for the foreseeable future; but I do wonder about the answers to some of these questions. ## Implementation realities The nice story above is at least partially a lie. Initially, jeprof (copying its logic from pprof) had the sum-then-unbias error described above. The current version of jemalloc does the unbiasing step on a per-allocation basis internally, so that we're always tracking what the unbiased numbers "should" be. The problem is, actually surfacing those unbiased numbers would require a breaking change to jeprof (and the various already-deployed tools that have copied its logic). Instead, we use a little bit more trickery. Since we know at dump time the numbers we want jeprof to report, we simply choose the values we'll output so that the jeprof numbers will match the true numbers. The math is described in `src/prof_data.c` (where the only cleverness is a change of variables that lets the exponentials fall out). This has the effect of making the output of jeprof (and related tools) correct, while making its inputs incorrect. This can be annoying to human readers of raw profiling dump output. redis-8.0.2/deps/jemalloc/doc_internal/jemalloc.svg000066400000000000000000000374321501533116600223320ustar00rootroot00000000000000jemalloc Final Logoredis-8.0.2/deps/jemalloc/include/000077500000000000000000000000001501533116600167745ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/include/jemalloc/000077500000000000000000000000001501533116600205625ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/include/jemalloc/internal/000077500000000000000000000000001501533116600223765ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/include/jemalloc/internal/activity_callback.h000066400000000000000000000014571501533116600262260ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ACTIVITY_CALLBACK_H #define JEMALLOC_INTERNAL_ACTIVITY_CALLBACK_H /* * The callback to be executed "periodically", in response to some amount of * allocator activity. * * This callback need not be computing any sort of peak (although that's the * intended first use case), but we drive it from the peak counter, so it's * keeps things tidy to keep it here. * * The calls to this thunk get driven by the peak_event module. */ #define ACTIVITY_CALLBACK_THUNK_INITIALIZER {NULL, NULL} typedef void (*activity_callback_t)(void *uctx, uint64_t allocated, uint64_t deallocated); typedef struct activity_callback_thunk_s activity_callback_thunk_t; struct activity_callback_thunk_s { activity_callback_t callback; void *uctx; }; #endif /* JEMALLOC_INTERNAL_ACTIVITY_CALLBACK_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/arena_externs.h000066400000000000000000000125631501533116600254140ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ARENA_EXTERNS_H #define JEMALLOC_INTERNAL_ARENA_EXTERNS_H #include "jemalloc/internal/bin.h" #include "jemalloc/internal/div.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/hook.h" #include "jemalloc/internal/pages.h" #include "jemalloc/internal/stats.h" /* * When the amount of pages to be purged exceeds this amount, deferred purge * should happen. */ #define ARENA_DEFERRED_PURGE_NPAGES_THRESHOLD UINT64_C(1024) extern ssize_t opt_dirty_decay_ms; extern ssize_t opt_muzzy_decay_ms; extern percpu_arena_mode_t opt_percpu_arena; extern const char *percpu_arena_mode_names[]; extern div_info_t arena_binind_div_info[SC_NBINS]; extern malloc_mutex_t arenas_lock; extern emap_t arena_emap_global; extern size_t opt_oversize_threshold; extern size_t oversize_threshold; /* * arena_bin_offsets[binind] is the offset of the first bin shard for size class * binind. */ extern uint32_t arena_bin_offsets[SC_NBINS]; void arena_basic_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads, const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms, size_t *nactive, size_t *ndirty, size_t *nmuzzy); void arena_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads, const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms, size_t *nactive, size_t *ndirty, size_t *nmuzzy, arena_stats_t *astats, bin_stats_data_t *bstats, arena_stats_large_t *lstats, pac_estats_t *estats, hpa_shard_stats_t *hpastats, sec_stats_t *secstats); void arena_handle_deferred_work(tsdn_t *tsdn, arena_t *arena); edata_t *arena_extent_alloc_large(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero); void arena_extent_dalloc_large_prep(tsdn_t *tsdn, arena_t *arena, edata_t *edata); void arena_extent_ralloc_large_shrink(tsdn_t *tsdn, arena_t *arena, edata_t *edata, size_t oldsize); void arena_extent_ralloc_large_expand(tsdn_t *tsdn, arena_t *arena, edata_t *edata, size_t oldsize); bool arena_decay_ms_set(tsdn_t *tsdn, arena_t *arena, extent_state_t state, ssize_t decay_ms); ssize_t arena_decay_ms_get(arena_t *arena, extent_state_t state); void arena_decay(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all); uint64_t arena_time_until_deferred(tsdn_t *tsdn, arena_t *arena); void arena_do_deferred_work(tsdn_t *tsdn, arena_t *arena); void arena_reset(tsd_t *tsd, arena_t *arena); void arena_destroy(tsd_t *tsd, arena_t *arena); void arena_cache_bin_fill_small(tsdn_t *tsdn, arena_t *arena, cache_bin_t *cache_bin, cache_bin_info_t *cache_bin_info, szind_t binind, const unsigned nfill); void *arena_malloc_hard(tsdn_t *tsdn, arena_t *arena, size_t size, szind_t ind, bool zero); void *arena_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero, tcache_t *tcache); void arena_prof_promote(tsdn_t *tsdn, void *ptr, size_t usize); void arena_dalloc_promoted(tsdn_t *tsdn, void *ptr, tcache_t *tcache, bool slow_path); void arena_slab_dalloc(tsdn_t *tsdn, arena_t *arena, edata_t *slab); void arena_dalloc_bin_locked_handle_newly_empty(tsdn_t *tsdn, arena_t *arena, edata_t *slab, bin_t *bin); void arena_dalloc_bin_locked_handle_newly_nonempty(tsdn_t *tsdn, arena_t *arena, edata_t *slab, bin_t *bin); void arena_dalloc_small(tsdn_t *tsdn, void *ptr); bool arena_ralloc_no_move(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra, bool zero, size_t *newsize); void *arena_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t oldsize, size_t size, size_t alignment, bool zero, tcache_t *tcache, hook_ralloc_args_t *hook_args); dss_prec_t arena_dss_prec_get(arena_t *arena); ehooks_t *arena_get_ehooks(arena_t *arena); extent_hooks_t *arena_set_extent_hooks(tsd_t *tsd, arena_t *arena, extent_hooks_t *extent_hooks); bool arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec); ssize_t arena_dirty_decay_ms_default_get(void); bool arena_dirty_decay_ms_default_set(ssize_t decay_ms); ssize_t arena_muzzy_decay_ms_default_get(void); bool arena_muzzy_decay_ms_default_set(ssize_t decay_ms); bool arena_retain_grow_limit_get_set(tsd_t *tsd, arena_t *arena, size_t *old_limit, size_t *new_limit); unsigned arena_nthreads_get(arena_t *arena, bool internal); void arena_nthreads_inc(arena_t *arena, bool internal); void arena_nthreads_dec(arena_t *arena, bool internal); arena_t *arena_new(tsdn_t *tsdn, unsigned ind, const arena_config_t *config); bool arena_init_huge(void); bool arena_is_huge(unsigned arena_ind); arena_t *arena_choose_huge(tsd_t *tsd); bin_t *arena_bin_choose(tsdn_t *tsdn, arena_t *arena, szind_t binind, unsigned *binshard); size_t arena_fill_small_fresh(tsdn_t *tsdn, arena_t *arena, szind_t binind, void **ptrs, size_t nfill, bool zero); bool arena_boot(sc_data_t *sc_data, base_t *base, bool hpa); void arena_prefork0(tsdn_t *tsdn, arena_t *arena); void arena_prefork1(tsdn_t *tsdn, arena_t *arena); void arena_prefork2(tsdn_t *tsdn, arena_t *arena); void arena_prefork3(tsdn_t *tsdn, arena_t *arena); void arena_prefork4(tsdn_t *tsdn, arena_t *arena); void arena_prefork5(tsdn_t *tsdn, arena_t *arena); void arena_prefork6(tsdn_t *tsdn, arena_t *arena); void arena_prefork7(tsdn_t *tsdn, arena_t *arena); void arena_prefork8(tsdn_t *tsdn, arena_t *arena); void arena_postfork_parent(tsdn_t *tsdn, arena_t *arena); void arena_postfork_child(tsdn_t *tsdn, arena_t *arena); #endif /* JEMALLOC_INTERNAL_ARENA_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/arena_inlines_a.h000066400000000000000000000011631501533116600256570ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ARENA_INLINES_A_H #define JEMALLOC_INTERNAL_ARENA_INLINES_A_H static inline unsigned arena_ind_get(const arena_t *arena) { return arena->ind; } static inline void arena_internal_add(arena_t *arena, size_t size) { atomic_fetch_add_zu(&arena->stats.internal, size, ATOMIC_RELAXED); } static inline void arena_internal_sub(arena_t *arena, size_t size) { atomic_fetch_sub_zu(&arena->stats.internal, size, ATOMIC_RELAXED); } static inline size_t arena_internal_get(arena_t *arena) { return atomic_load_zu(&arena->stats.internal, ATOMIC_RELAXED); } #endif /* JEMALLOC_INTERNAL_ARENA_INLINES_A_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/arena_inlines_b.h000066400000000000000000000377301501533116600256710ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ARENA_INLINES_B_H #define JEMALLOC_INTERNAL_ARENA_INLINES_B_H #include "jemalloc/internal/div.h" #include "jemalloc/internal/emap.h" #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/rtree.h" #include "jemalloc/internal/safety_check.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/sz.h" #include "jemalloc/internal/ticker.h" static inline arena_t * arena_get_from_edata(edata_t *edata) { return (arena_t *)atomic_load_p(&arenas[edata_arena_ind_get(edata)], ATOMIC_RELAXED); } JEMALLOC_ALWAYS_INLINE arena_t * arena_choose_maybe_huge(tsd_t *tsd, arena_t *arena, size_t size) { if (arena != NULL) { return arena; } /* * For huge allocations, use the dedicated huge arena if both are true: * 1) is using auto arena selection (i.e. arena == NULL), and 2) the * thread is not assigned to a manual arena. */ if (unlikely(size >= oversize_threshold)) { arena_t *tsd_arena = tsd_arena_get(tsd); if (tsd_arena == NULL || arena_is_auto(tsd_arena)) { return arena_choose_huge(tsd); } } return arena_choose(tsd, NULL); } JEMALLOC_ALWAYS_INLINE void arena_prof_info_get(tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx, prof_info_t *prof_info, bool reset_recent) { cassert(config_prof); assert(ptr != NULL); assert(prof_info != NULL); edata_t *edata = NULL; bool is_slab; /* Static check. */ if (alloc_ctx == NULL) { edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); is_slab = edata_slab_get(edata); } else if (unlikely(!(is_slab = alloc_ctx->slab))) { edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); } if (unlikely(!is_slab)) { /* edata must have been initialized at this point. */ assert(edata != NULL); large_prof_info_get(tsd, edata, prof_info, reset_recent); } else { prof_info->alloc_tctx = (prof_tctx_t *)(uintptr_t)1U; /* * No need to set other fields in prof_info; they will never be * accessed if (uintptr_t)alloc_tctx == (uintptr_t)1U. */ } } JEMALLOC_ALWAYS_INLINE void arena_prof_tctx_reset(tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx) { cassert(config_prof); assert(ptr != NULL); /* Static check. */ if (alloc_ctx == NULL) { edata_t *edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); if (unlikely(!edata_slab_get(edata))) { large_prof_tctx_reset(edata); } } else { if (unlikely(!alloc_ctx->slab)) { edata_t *edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); large_prof_tctx_reset(edata); } } } JEMALLOC_ALWAYS_INLINE void arena_prof_tctx_reset_sampled(tsd_t *tsd, const void *ptr) { cassert(config_prof); assert(ptr != NULL); edata_t *edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); assert(!edata_slab_get(edata)); large_prof_tctx_reset(edata); } JEMALLOC_ALWAYS_INLINE void arena_prof_info_set(tsd_t *tsd, edata_t *edata, prof_tctx_t *tctx, size_t size) { cassert(config_prof); assert(!edata_slab_get(edata)); large_prof_info_set(edata, tctx, size); } JEMALLOC_ALWAYS_INLINE void arena_decay_ticks(tsdn_t *tsdn, arena_t *arena, unsigned nticks) { if (unlikely(tsdn_null(tsdn))) { return; } tsd_t *tsd = tsdn_tsd(tsdn); /* * We use the ticker_geom_t to avoid having per-arena state in the tsd. * Instead of having a countdown-until-decay timer running for every * arena in every thread, we flip a coin once per tick, whose * probability of coming up heads is 1/nticks; this is effectively the * operation of the ticker_geom_t. Each arena has the same chance of a * coinflip coming up heads (1/ARENA_DECAY_NTICKS_PER_UPDATE), so we can * use a single ticker for all of them. */ ticker_geom_t *decay_ticker = tsd_arena_decay_tickerp_get(tsd); uint64_t *prng_state = tsd_prng_statep_get(tsd); if (unlikely(ticker_geom_ticks(decay_ticker, prng_state, nticks))) { arena_decay(tsdn, arena, false, false); } } JEMALLOC_ALWAYS_INLINE void arena_decay_tick(tsdn_t *tsdn, arena_t *arena) { arena_decay_ticks(tsdn, arena, 1); } JEMALLOC_ALWAYS_INLINE void * arena_malloc(tsdn_t *tsdn, arena_t *arena, size_t size, szind_t ind, bool zero, tcache_t *tcache, bool slow_path) { assert(!tsdn_null(tsdn) || tcache == NULL); if (likely(tcache != NULL)) { if (likely(size <= SC_SMALL_MAXCLASS)) { return tcache_alloc_small(tsdn_tsd(tsdn), arena, tcache, size, ind, zero, slow_path); } if (likely(size <= tcache_maxclass)) { return tcache_alloc_large(tsdn_tsd(tsdn), arena, tcache, size, ind, zero, slow_path); } /* (size > tcache_maxclass) case falls through. */ assert(size > tcache_maxclass); } return arena_malloc_hard(tsdn, arena, size, ind, zero); } JEMALLOC_ALWAYS_INLINE arena_t * arena_aalloc(tsdn_t *tsdn, const void *ptr) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); unsigned arena_ind = edata_arena_ind_get(edata); return (arena_t *)atomic_load_p(&arenas[arena_ind], ATOMIC_RELAXED); } JEMALLOC_ALWAYS_INLINE size_t arena_salloc(tsdn_t *tsdn, const void *ptr) { assert(ptr != NULL); emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind != SC_NSIZES); return sz_index2size(alloc_ctx.szind); } JEMALLOC_ALWAYS_INLINE size_t arena_vsalloc(tsdn_t *tsdn, const void *ptr) { /* * Return 0 if ptr is not within an extent managed by jemalloc. This * function has two extra costs relative to isalloc(): * - The rtree calls cannot claim to be dependent lookups, which induces * rtree lookup load dependencies. * - The lookup may fail, so there is an extra branch to check for * failure. */ emap_full_alloc_ctx_t full_alloc_ctx; bool missing = emap_full_alloc_ctx_try_lookup(tsdn, &arena_emap_global, ptr, &full_alloc_ctx); if (missing) { return 0; } if (full_alloc_ctx.edata == NULL) { return 0; } assert(edata_state_get(full_alloc_ctx.edata) == extent_state_active); /* Only slab members should be looked up via interior pointers. */ assert(edata_addr_get(full_alloc_ctx.edata) == ptr || edata_slab_get(full_alloc_ctx.edata)); assert(full_alloc_ctx.szind != SC_NSIZES); return sz_index2size(full_alloc_ctx.szind); } JEMALLOC_ALWAYS_INLINE bool large_dalloc_safety_checks(edata_t *edata, void *ptr, szind_t szind) { if (!config_opt_safety_checks) { return false; } /* * Eagerly detect double free and sized dealloc bugs for large sizes. * The cost is low enough (as edata will be accessed anyway) to be * enabled all the time. */ if (unlikely(edata == NULL || edata_state_get(edata) != extent_state_active)) { safety_check_fail("Invalid deallocation detected: " "pages being freed (%p) not currently active, " "possibly caused by double free bugs.", (uintptr_t)edata_addr_get(edata)); return true; } size_t input_size = sz_index2size(szind); if (unlikely(input_size != edata_usize_get(edata))) { safety_check_fail_sized_dealloc(/* current_dealloc */ true, ptr, /* true_size */ edata_usize_get(edata), input_size); return true; } return false; } static inline void arena_dalloc_large_no_tcache(tsdn_t *tsdn, void *ptr, szind_t szind) { if (config_prof && unlikely(szind < SC_NBINS)) { arena_dalloc_promoted(tsdn, ptr, NULL, true); } else { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); if (large_dalloc_safety_checks(edata, ptr, szind)) { /* See the comment in isfree. */ return; } large_dalloc(tsdn, edata); } } static inline void arena_dalloc_no_tcache(tsdn_t *tsdn, void *ptr) { assert(ptr != NULL); emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx); if (config_debug) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); assert(alloc_ctx.szind == edata_szind_get(edata)); assert(alloc_ctx.szind < SC_NSIZES); assert(alloc_ctx.slab == edata_slab_get(edata)); } if (likely(alloc_ctx.slab)) { /* Small allocation. */ arena_dalloc_small(tsdn, ptr); } else { arena_dalloc_large_no_tcache(tsdn, ptr, alloc_ctx.szind); } } JEMALLOC_ALWAYS_INLINE void arena_dalloc_large(tsdn_t *tsdn, void *ptr, tcache_t *tcache, szind_t szind, bool slow_path) { if (szind < nhbins) { if (config_prof && unlikely(szind < SC_NBINS)) { arena_dalloc_promoted(tsdn, ptr, tcache, slow_path); } else { tcache_dalloc_large(tsdn_tsd(tsdn), tcache, ptr, szind, slow_path); } } else { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); if (large_dalloc_safety_checks(edata, ptr, szind)) { /* See the comment in isfree. */ return; } large_dalloc(tsdn, edata); } } JEMALLOC_ALWAYS_INLINE void arena_dalloc(tsdn_t *tsdn, void *ptr, tcache_t *tcache, emap_alloc_ctx_t *caller_alloc_ctx, bool slow_path) { assert(!tsdn_null(tsdn) || tcache == NULL); assert(ptr != NULL); if (unlikely(tcache == NULL)) { arena_dalloc_no_tcache(tsdn, ptr); return; } emap_alloc_ctx_t alloc_ctx; if (caller_alloc_ctx != NULL) { alloc_ctx = *caller_alloc_ctx; } else { util_assume(!tsdn_null(tsdn)); emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx); } if (config_debug) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); assert(alloc_ctx.szind == edata_szind_get(edata)); assert(alloc_ctx.szind < SC_NSIZES); assert(alloc_ctx.slab == edata_slab_get(edata)); } if (likely(alloc_ctx.slab)) { /* Small allocation. */ tcache_dalloc_small(tsdn_tsd(tsdn), tcache, ptr, alloc_ctx.szind, slow_path); } else { arena_dalloc_large(tsdn, ptr, tcache, alloc_ctx.szind, slow_path); } } static inline void arena_sdalloc_no_tcache(tsdn_t *tsdn, void *ptr, size_t size) { assert(ptr != NULL); assert(size <= SC_LARGE_MAXCLASS); emap_alloc_ctx_t alloc_ctx; if (!config_prof || !opt_prof) { /* * There is no risk of being confused by a promoted sampled * object, so base szind and slab on the given size. */ alloc_ctx.szind = sz_size2index(size); alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); } if ((config_prof && opt_prof) || config_debug) { emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind == sz_size2index(size)); assert((config_prof && opt_prof) || alloc_ctx.slab == (alloc_ctx.szind < SC_NBINS)); if (config_debug) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); assert(alloc_ctx.szind == edata_szind_get(edata)); assert(alloc_ctx.slab == edata_slab_get(edata)); } } if (likely(alloc_ctx.slab)) { /* Small allocation. */ arena_dalloc_small(tsdn, ptr); } else { arena_dalloc_large_no_tcache(tsdn, ptr, alloc_ctx.szind); } } JEMALLOC_ALWAYS_INLINE void arena_sdalloc(tsdn_t *tsdn, void *ptr, size_t size, tcache_t *tcache, emap_alloc_ctx_t *caller_alloc_ctx, bool slow_path) { assert(!tsdn_null(tsdn) || tcache == NULL); assert(ptr != NULL); assert(size <= SC_LARGE_MAXCLASS); if (unlikely(tcache == NULL)) { arena_sdalloc_no_tcache(tsdn, ptr, size); return; } emap_alloc_ctx_t alloc_ctx; if (config_prof && opt_prof) { if (caller_alloc_ctx == NULL) { /* Uncommon case and should be a static check. */ emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind == sz_size2index(size)); } else { alloc_ctx = *caller_alloc_ctx; } } else { /* * There is no risk of being confused by a promoted sampled * object, so base szind and slab on the given size. */ alloc_ctx.szind = sz_size2index(size); alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); } if (config_debug) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); assert(alloc_ctx.szind == edata_szind_get(edata)); assert(alloc_ctx.slab == edata_slab_get(edata)); } if (likely(alloc_ctx.slab)) { /* Small allocation. */ tcache_dalloc_small(tsdn_tsd(tsdn), tcache, ptr, alloc_ctx.szind, slow_path); } else { arena_dalloc_large(tsdn, ptr, tcache, alloc_ctx.szind, slow_path); } } static inline void arena_cache_oblivious_randomize(tsdn_t *tsdn, arena_t *arena, edata_t *edata, size_t alignment) { assert(edata_base_get(edata) == edata_addr_get(edata)); if (alignment < PAGE) { unsigned lg_range = LG_PAGE - lg_floor(CACHELINE_CEILING(alignment)); size_t r; if (!tsdn_null(tsdn)) { tsd_t *tsd = tsdn_tsd(tsdn); r = (size_t)prng_lg_range_u64( tsd_prng_statep_get(tsd), lg_range); } else { uint64_t stack_value = (uint64_t)(uintptr_t)&r; r = (size_t)prng_lg_range_u64(&stack_value, lg_range); } uintptr_t random_offset = ((uintptr_t)r) << (LG_PAGE - lg_range); edata->e_addr = (void *)((uintptr_t)edata->e_addr + random_offset); assert(ALIGNMENT_ADDR2BASE(edata->e_addr, alignment) == edata->e_addr); } } /* * The dalloc bin info contains just the information that the common paths need * during tcache flushes. By force-inlining these paths, and using local copies * of data (so that the compiler knows it's constant), we avoid a whole bunch of * redundant loads and stores by leaving this information in registers. */ typedef struct arena_dalloc_bin_locked_info_s arena_dalloc_bin_locked_info_t; struct arena_dalloc_bin_locked_info_s { div_info_t div_info; uint32_t nregs; uint64_t ndalloc; }; JEMALLOC_ALWAYS_INLINE size_t arena_slab_regind(arena_dalloc_bin_locked_info_t *info, szind_t binind, edata_t *slab, const void *ptr) { size_t diff, regind; /* Freeing a pointer outside the slab can cause assertion failure. */ assert((uintptr_t)ptr >= (uintptr_t)edata_addr_get(slab)); assert((uintptr_t)ptr < (uintptr_t)edata_past_get(slab)); /* Freeing an interior pointer can cause assertion failure. */ assert(((uintptr_t)ptr - (uintptr_t)edata_addr_get(slab)) % (uintptr_t)bin_infos[binind].reg_size == 0); diff = (size_t)((uintptr_t)ptr - (uintptr_t)edata_addr_get(slab)); /* Avoid doing division with a variable divisor. */ regind = div_compute(&info->div_info, diff); assert(regind < bin_infos[binind].nregs); return regind; } JEMALLOC_ALWAYS_INLINE void arena_dalloc_bin_locked_begin(arena_dalloc_bin_locked_info_t *info, szind_t binind) { info->div_info = arena_binind_div_info[binind]; info->nregs = bin_infos[binind].nregs; info->ndalloc = 0; } /* * Does the deallocation work associated with freeing a single pointer (a * "step") in between a arena_dalloc_bin_locked begin and end call. * * Returns true if arena_slab_dalloc must be called on slab. Doesn't do * stats updates, which happen during finish (this lets running counts get left * in a register). */ JEMALLOC_ALWAYS_INLINE bool arena_dalloc_bin_locked_step(tsdn_t *tsdn, arena_t *arena, bin_t *bin, arena_dalloc_bin_locked_info_t *info, szind_t binind, edata_t *slab, void *ptr) { const bin_info_t *bin_info = &bin_infos[binind]; size_t regind = arena_slab_regind(info, binind, slab, ptr); slab_data_t *slab_data = edata_slab_data_get(slab); assert(edata_nfree_get(slab) < bin_info->nregs); /* Freeing an unallocated pointer can cause assertion failure. */ assert(bitmap_get(slab_data->bitmap, &bin_info->bitmap_info, regind)); bitmap_unset(slab_data->bitmap, &bin_info->bitmap_info, regind); edata_nfree_inc(slab); if (config_stats) { info->ndalloc++; } unsigned nfree = edata_nfree_get(slab); if (nfree == bin_info->nregs) { arena_dalloc_bin_locked_handle_newly_empty(tsdn, arena, slab, bin); return true; } else if (nfree == 1 && slab != bin->slabcur) { arena_dalloc_bin_locked_handle_newly_nonempty(tsdn, arena, slab, bin); } return false; } JEMALLOC_ALWAYS_INLINE void arena_dalloc_bin_locked_finish(tsdn_t *tsdn, arena_t *arena, bin_t *bin, arena_dalloc_bin_locked_info_t *info) { if (config_stats) { bin->stats.ndalloc += info->ndalloc; assert(bin->stats.curregs >= (size_t)info->ndalloc); bin->stats.curregs -= (size_t)info->ndalloc; } } static inline bin_t * arena_get_bin(arena_t *arena, szind_t binind, unsigned binshard) { bin_t *shard0 = (bin_t *)((uintptr_t)arena + arena_bin_offsets[binind]); return shard0 + binshard; } #endif /* JEMALLOC_INTERNAL_ARENA_INLINES_B_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/arena_stats.h000066400000000000000000000067621501533116600250660ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ARENA_STATS_H #define JEMALLOC_INTERNAL_ARENA_STATS_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/lockedint.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/mutex_prof.h" #include "jemalloc/internal/pa.h" #include "jemalloc/internal/sc.h" JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS typedef struct arena_stats_large_s arena_stats_large_t; struct arena_stats_large_s { /* * Total number of allocation/deallocation requests served directly by * the arena. */ locked_u64_t nmalloc; locked_u64_t ndalloc; /* * Number of allocation requests that correspond to this size class. * This includes requests served by tcache, though tcache only * periodically merges into this counter. */ locked_u64_t nrequests; /* Partially derived. */ /* * Number of tcache fills / flushes for large (similarly, periodically * merged). Note that there is no large tcache batch-fill currently * (i.e. only fill 1 at a time); however flush may be batched. */ locked_u64_t nfills; /* Partially derived. */ locked_u64_t nflushes; /* Partially derived. */ /* Current number of allocations of this size class. */ size_t curlextents; /* Derived. */ }; /* * Arena stats. Note that fields marked "derived" are not directly maintained * within the arena code; rather their values are derived during stats merge * requests. */ typedef struct arena_stats_s arena_stats_t; struct arena_stats_s { LOCKEDINT_MTX_DECLARE(mtx) /* * resident includes the base stats -- that's why it lives here and not * in pa_shard_stats_t. */ size_t base; /* Derived. */ size_t resident; /* Derived. */ size_t metadata_thp; /* Derived. */ size_t mapped; /* Derived. */ atomic_zu_t internal; size_t allocated_large; /* Derived. */ uint64_t nmalloc_large; /* Derived. */ uint64_t ndalloc_large; /* Derived. */ uint64_t nfills_large; /* Derived. */ uint64_t nflushes_large; /* Derived. */ uint64_t nrequests_large; /* Derived. */ /* * The stats logically owned by the pa_shard in the same arena. This * lives here only because it's convenient for the purposes of the ctl * module -- it only knows about the single arena_stats. */ pa_shard_stats_t pa_shard_stats; /* Number of bytes cached in tcache associated with this arena. */ size_t tcache_bytes; /* Derived. */ size_t tcache_stashed_bytes; /* Derived. */ mutex_prof_data_t mutex_prof_data[mutex_prof_num_arena_mutexes]; /* One element for each large size class. */ arena_stats_large_t lstats[SC_NSIZES - SC_NBINS]; /* Arena uptime. */ nstime_t uptime; }; static inline bool arena_stats_init(tsdn_t *tsdn, arena_stats_t *arena_stats) { if (config_debug) { for (size_t i = 0; i < sizeof(arena_stats_t); i++) { assert(((char *)arena_stats)[i] == 0); } } if (LOCKEDINT_MTX_INIT(arena_stats->mtx, "arena_stats", WITNESS_RANK_ARENA_STATS, malloc_mutex_rank_exclusive)) { return true; } /* Memory is zeroed, so there is no need to clear stats. */ return false; } static inline void arena_stats_large_flush_nrequests_add(tsdn_t *tsdn, arena_stats_t *arena_stats, szind_t szind, uint64_t nrequests) { LOCKEDINT_MTX_LOCK(tsdn, arena_stats->mtx); arena_stats_large_t *lstats = &arena_stats->lstats[szind - SC_NBINS]; locked_inc_u64(tsdn, LOCKEDINT_MTX(arena_stats->mtx), &lstats->nrequests, nrequests); locked_inc_u64(tsdn, LOCKEDINT_MTX(arena_stats->mtx), &lstats->nflushes, 1); LOCKEDINT_MTX_UNLOCK(tsdn, arena_stats->mtx); } #endif /* JEMALLOC_INTERNAL_ARENA_STATS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/arena_structs.h000066400000000000000000000056531501533116600254350ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ARENA_STRUCTS_H #define JEMALLOC_INTERNAL_ARENA_STRUCTS_H #include "jemalloc/internal/arena_stats.h" #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/bin.h" #include "jemalloc/internal/bitmap.h" #include "jemalloc/internal/counter.h" #include "jemalloc/internal/ecache.h" #include "jemalloc/internal/edata_cache.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/nstime.h" #include "jemalloc/internal/pa.h" #include "jemalloc/internal/ql.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/ticker.h" struct arena_s { /* * Number of threads currently assigned to this arena. Each thread has * two distinct assignments, one for application-serving allocation, and * the other for internal metadata allocation. Internal metadata must * not be allocated from arenas explicitly created via the arenas.create * mallctl, because the arena..reset mallctl indiscriminately * discards all allocations for the affected arena. * * 0: Application allocation. * 1: Internal metadata allocation. * * Synchronization: atomic. */ atomic_u_t nthreads[2]; /* Next bin shard for binding new threads. Synchronization: atomic. */ atomic_u_t binshard_next; /* * When percpu_arena is enabled, to amortize the cost of reading / * updating the current CPU id, track the most recent thread accessing * this arena, and only read CPU if there is a mismatch. */ tsdn_t *last_thd; /* Synchronization: internal. */ arena_stats_t stats; /* * Lists of tcaches and cache_bin_array_descriptors for extant threads * associated with this arena. Stats from these are merged * incrementally, and at exit if opt_stats_print is enabled. * * Synchronization: tcache_ql_mtx. */ ql_head(tcache_slow_t) tcache_ql; ql_head(cache_bin_array_descriptor_t) cache_bin_array_descriptor_ql; malloc_mutex_t tcache_ql_mtx; /* * Represents a dss_prec_t, but atomically. * * Synchronization: atomic. */ atomic_u_t dss_prec; /* * Extant large allocations. * * Synchronization: large_mtx. */ edata_list_active_t large; /* Synchronizes all large allocation/update/deallocation. */ malloc_mutex_t large_mtx; /* The page-level allocator shard this arena uses. */ pa_shard_t pa_shard; /* * A cached copy of base->ind. This can get accessed on hot paths; * looking it up in base requires an extra pointer hop / cache miss. */ unsigned ind; /* * Base allocator, from which arena metadata are allocated. * * Synchronization: internal. */ base_t *base; /* Used to determine uptime. Read-only after initialization. */ nstime_t create_time; /* * The arena is allocated alongside its bins; really this is a * dynamically sized array determined by the binshard settings. */ bin_t bins[0]; }; #endif /* JEMALLOC_INTERNAL_ARENA_STRUCTS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/arena_types.h000066400000000000000000000033121501533116600250600ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ARENA_TYPES_H #define JEMALLOC_INTERNAL_ARENA_TYPES_H #include "jemalloc/internal/sc.h" /* Default decay times in milliseconds. */ #define DIRTY_DECAY_MS_DEFAULT ZD(10 * 1000) #define MUZZY_DECAY_MS_DEFAULT (0) /* Number of event ticks between time checks. */ #define ARENA_DECAY_NTICKS_PER_UPDATE 1000 typedef struct arena_decay_s arena_decay_t; typedef struct arena_s arena_t; typedef enum { percpu_arena_mode_names_base = 0, /* Used for options processing. */ /* * *_uninit are used only during bootstrapping, and must correspond * to initialized variant plus percpu_arena_mode_enabled_base. */ percpu_arena_uninit = 0, per_phycpu_arena_uninit = 1, /* All non-disabled modes must come after percpu_arena_disabled. */ percpu_arena_disabled = 2, percpu_arena_mode_names_limit = 3, /* Used for options processing. */ percpu_arena_mode_enabled_base = 3, percpu_arena = 3, per_phycpu_arena = 4 /* Hyper threads share arena. */ } percpu_arena_mode_t; #define PERCPU_ARENA_ENABLED(m) ((m) >= percpu_arena_mode_enabled_base) #define PERCPU_ARENA_DEFAULT percpu_arena_disabled /* * When allocation_size >= oversize_threshold, use the dedicated huge arena * (unless have explicitly spicified arena index). 0 disables the feature. */ #define OVERSIZE_THRESHOLD_DEFAULT (8 << 20) struct arena_config_s { /* extent hooks to be used for the arena */ extent_hooks_t *extent_hooks; /* * Use extent hooks for metadata (base) allocations when true. */ bool metadata_use_hooks; }; typedef struct arena_config_s arena_config_t; extern const arena_config_t arena_config_default; #endif /* JEMALLOC_INTERNAL_ARENA_TYPES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/assert.h000066400000000000000000000024621501533116600240540ustar00rootroot00000000000000#include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/util.h" /* * Define a custom assert() in order to reduce the chances of deadlock during * assertion failure. */ #ifndef assert #define assert(e) do { \ if (unlikely(config_debug && !(e))) { \ malloc_printf( \ ": %s:%d: Failed assertion: \"%s\"\n", \ __FILE__, __LINE__, #e); \ abort(); \ } \ } while (0) #endif #ifndef not_reached #define not_reached() do { \ if (config_debug) { \ malloc_printf( \ ": %s:%d: Unreachable code reached\n", \ __FILE__, __LINE__); \ abort(); \ } \ unreachable(); \ } while (0) #endif #ifndef not_implemented #define not_implemented() do { \ if (config_debug) { \ malloc_printf(": %s:%d: Not implemented\n", \ __FILE__, __LINE__); \ abort(); \ } \ } while (0) #endif #ifndef assert_not_implemented #define assert_not_implemented(e) do { \ if (unlikely(config_debug && !(e))) { \ not_implemented(); \ } \ } while (0) #endif /* Use to assert a particular configuration, e.g., cassert(config_debug). */ #ifndef cassert #define cassert(c) do { \ if (unlikely(!(c))) { \ not_reached(); \ } \ } while (0) #endif redis-8.0.2/deps/jemalloc/include/jemalloc/internal/atomic.h000066400000000000000000000070001501533116600240200ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ATOMIC_H #define JEMALLOC_INTERNAL_ATOMIC_H #define ATOMIC_INLINE JEMALLOC_ALWAYS_INLINE #define JEMALLOC_U8_ATOMICS #if defined(JEMALLOC_GCC_ATOMIC_ATOMICS) # include "jemalloc/internal/atomic_gcc_atomic.h" # if !defined(JEMALLOC_GCC_U8_ATOMIC_ATOMICS) # undef JEMALLOC_U8_ATOMICS # endif #elif defined(JEMALLOC_GCC_SYNC_ATOMICS) # include "jemalloc/internal/atomic_gcc_sync.h" # if !defined(JEMALLOC_GCC_U8_SYNC_ATOMICS) # undef JEMALLOC_U8_ATOMICS # endif #elif defined(_MSC_VER) # include "jemalloc/internal/atomic_msvc.h" #elif defined(JEMALLOC_C11_ATOMICS) # include "jemalloc/internal/atomic_c11.h" #else # error "Don't have atomics implemented on this platform." #endif /* * This header gives more or less a backport of C11 atomics. The user can write * JEMALLOC_GENERATE_ATOMICS(type, short_type, lg_sizeof_type); to generate * counterparts of the C11 atomic functions for type, as so: * JEMALLOC_GENERATE_ATOMICS(int *, pi, 3); * and then write things like: * int *some_ptr; * atomic_pi_t atomic_ptr_to_int; * atomic_store_pi(&atomic_ptr_to_int, some_ptr, ATOMIC_RELAXED); * int *prev_value = atomic_exchange_pi(&ptr_to_int, NULL, ATOMIC_ACQ_REL); * assert(some_ptr == prev_value); * and expect things to work in the obvious way. * * Also included (with naming differences to avoid conflicts with the standard * library): * atomic_fence(atomic_memory_order_t) (mimics C11's atomic_thread_fence). * ATOMIC_INIT (mimics C11's ATOMIC_VAR_INIT). */ /* * Pure convenience, so that we don't have to type "atomic_memory_order_" * quite so often. */ #define ATOMIC_RELAXED atomic_memory_order_relaxed #define ATOMIC_ACQUIRE atomic_memory_order_acquire #define ATOMIC_RELEASE atomic_memory_order_release #define ATOMIC_ACQ_REL atomic_memory_order_acq_rel #define ATOMIC_SEQ_CST atomic_memory_order_seq_cst /* * Another convenience -- simple atomic helper functions. */ #define JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(type, short_type, \ lg_size) \ JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, lg_size) \ ATOMIC_INLINE void \ atomic_load_add_store_##short_type(atomic_##short_type##_t *a, \ type inc) { \ type oldval = atomic_load_##short_type(a, ATOMIC_RELAXED); \ type newval = oldval + inc; \ atomic_store_##short_type(a, newval, ATOMIC_RELAXED); \ } \ ATOMIC_INLINE void \ atomic_load_sub_store_##short_type(atomic_##short_type##_t *a, \ type inc) { \ type oldval = atomic_load_##short_type(a, ATOMIC_RELAXED); \ type newval = oldval - inc; \ atomic_store_##short_type(a, newval, ATOMIC_RELAXED); \ } /* * Not all platforms have 64-bit atomics. If we do, this #define exposes that * fact. */ #if (LG_SIZEOF_PTR == 3 || LG_SIZEOF_INT == 3) # define JEMALLOC_ATOMIC_U64 #endif JEMALLOC_GENERATE_ATOMICS(void *, p, LG_SIZEOF_PTR) /* * There's no actual guarantee that sizeof(bool) == 1, but it's true on the only * platform that actually needs to know the size, MSVC. */ JEMALLOC_GENERATE_ATOMICS(bool, b, 0) JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(unsigned, u, LG_SIZEOF_INT) JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(size_t, zu, LG_SIZEOF_PTR) JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(ssize_t, zd, LG_SIZEOF_PTR) JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(uint8_t, u8, 0) JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(uint32_t, u32, 2) #ifdef JEMALLOC_ATOMIC_U64 JEMALLOC_GENERATE_EXPANDED_INT_ATOMICS(uint64_t, u64, 3) #endif #undef ATOMIC_INLINE #endif /* JEMALLOC_INTERNAL_ATOMIC_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/atomic_c11.h000066400000000000000000000067321501533116600244770ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ATOMIC_C11_H #define JEMALLOC_INTERNAL_ATOMIC_C11_H #include #define ATOMIC_INIT(...) ATOMIC_VAR_INIT(__VA_ARGS__) #define atomic_memory_order_t memory_order #define atomic_memory_order_relaxed memory_order_relaxed #define atomic_memory_order_acquire memory_order_acquire #define atomic_memory_order_release memory_order_release #define atomic_memory_order_acq_rel memory_order_acq_rel #define atomic_memory_order_seq_cst memory_order_seq_cst #define atomic_fence atomic_thread_fence #define JEMALLOC_GENERATE_ATOMICS(type, short_type, \ /* unused */ lg_size) \ typedef _Atomic(type) atomic_##short_type##_t; \ \ ATOMIC_INLINE type \ atomic_load_##short_type(const atomic_##short_type##_t *a, \ atomic_memory_order_t mo) { \ /* \ * A strict interpretation of the C standard prevents \ * atomic_load from taking a const argument, but it's \ * convenient for our purposes. This cast is a workaround. \ */ \ atomic_##short_type##_t* a_nonconst = \ (atomic_##short_type##_t*)a; \ return atomic_load_explicit(a_nonconst, mo); \ } \ \ ATOMIC_INLINE void \ atomic_store_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ atomic_store_explicit(a, val, mo); \ } \ \ ATOMIC_INLINE type \ atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return atomic_exchange_explicit(a, val, mo); \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \ type *expected, type desired, atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ return atomic_compare_exchange_weak_explicit(a, expected, \ desired, success_mo, failure_mo); \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \ type *expected, type desired, atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ return atomic_compare_exchange_strong_explicit(a, expected, \ desired, success_mo, failure_mo); \ } /* * Integral types have some special operations available that non-integral ones * lack. */ #define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, \ /* unused */ lg_size) \ JEMALLOC_GENERATE_ATOMICS(type, short_type, /* unused */ lg_size) \ \ ATOMIC_INLINE type \ atomic_fetch_add_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return atomic_fetch_add_explicit(a, val, mo); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return atomic_fetch_sub_explicit(a, val, mo); \ } \ ATOMIC_INLINE type \ atomic_fetch_and_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return atomic_fetch_and_explicit(a, val, mo); \ } \ ATOMIC_INLINE type \ atomic_fetch_or_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return atomic_fetch_or_explicit(a, val, mo); \ } \ ATOMIC_INLINE type \ atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return atomic_fetch_xor_explicit(a, val, mo); \ } #endif /* JEMALLOC_INTERNAL_ATOMIC_C11_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/atomic_gcc_atomic.h000066400000000000000000000100711501533116600261720ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ATOMIC_GCC_ATOMIC_H #define JEMALLOC_INTERNAL_ATOMIC_GCC_ATOMIC_H #include "jemalloc/internal/assert.h" #define ATOMIC_INIT(...) {__VA_ARGS__} typedef enum { atomic_memory_order_relaxed, atomic_memory_order_acquire, atomic_memory_order_release, atomic_memory_order_acq_rel, atomic_memory_order_seq_cst } atomic_memory_order_t; ATOMIC_INLINE int atomic_enum_to_builtin(atomic_memory_order_t mo) { switch (mo) { case atomic_memory_order_relaxed: return __ATOMIC_RELAXED; case atomic_memory_order_acquire: return __ATOMIC_ACQUIRE; case atomic_memory_order_release: return __ATOMIC_RELEASE; case atomic_memory_order_acq_rel: return __ATOMIC_ACQ_REL; case atomic_memory_order_seq_cst: return __ATOMIC_SEQ_CST; } /* Can't happen; the switch is exhaustive. */ not_reached(); } ATOMIC_INLINE void atomic_fence(atomic_memory_order_t mo) { __atomic_thread_fence(atomic_enum_to_builtin(mo)); } #define JEMALLOC_GENERATE_ATOMICS(type, short_type, \ /* unused */ lg_size) \ typedef struct { \ type repr; \ } atomic_##short_type##_t; \ \ ATOMIC_INLINE type \ atomic_load_##short_type(const atomic_##short_type##_t *a, \ atomic_memory_order_t mo) { \ type result; \ __atomic_load(&a->repr, &result, atomic_enum_to_builtin(mo)); \ return result; \ } \ \ ATOMIC_INLINE void \ atomic_store_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ __atomic_store(&a->repr, &val, atomic_enum_to_builtin(mo)); \ } \ \ ATOMIC_INLINE type \ atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ type result; \ __atomic_exchange(&a->repr, &val, &result, \ atomic_enum_to_builtin(mo)); \ return result; \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \ UNUSED type *expected, type desired, \ atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ return __atomic_compare_exchange(&a->repr, expected, &desired, \ true, atomic_enum_to_builtin(success_mo), \ atomic_enum_to_builtin(failure_mo)); \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \ UNUSED type *expected, type desired, \ atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ return __atomic_compare_exchange(&a->repr, expected, &desired, \ false, \ atomic_enum_to_builtin(success_mo), \ atomic_enum_to_builtin(failure_mo)); \ } #define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, \ /* unused */ lg_size) \ JEMALLOC_GENERATE_ATOMICS(type, short_type, /* unused */ lg_size) \ \ ATOMIC_INLINE type \ atomic_fetch_add_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __atomic_fetch_add(&a->repr, val, \ atomic_enum_to_builtin(mo)); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __atomic_fetch_sub(&a->repr, val, \ atomic_enum_to_builtin(mo)); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_and_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __atomic_fetch_and(&a->repr, val, \ atomic_enum_to_builtin(mo)); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_or_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __atomic_fetch_or(&a->repr, val, \ atomic_enum_to_builtin(mo)); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __atomic_fetch_xor(&a->repr, val, \ atomic_enum_to_builtin(mo)); \ } #endif /* JEMALLOC_INTERNAL_ATOMIC_GCC_ATOMIC_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/atomic_gcc_sync.h000066400000000000000000000143331501533116600256770ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ATOMIC_GCC_SYNC_H #define JEMALLOC_INTERNAL_ATOMIC_GCC_SYNC_H #define ATOMIC_INIT(...) {__VA_ARGS__} typedef enum { atomic_memory_order_relaxed, atomic_memory_order_acquire, atomic_memory_order_release, atomic_memory_order_acq_rel, atomic_memory_order_seq_cst } atomic_memory_order_t; ATOMIC_INLINE void atomic_fence(atomic_memory_order_t mo) { /* Easy cases first: no barrier, and full barrier. */ if (mo == atomic_memory_order_relaxed) { asm volatile("" ::: "memory"); return; } if (mo == atomic_memory_order_seq_cst) { asm volatile("" ::: "memory"); __sync_synchronize(); asm volatile("" ::: "memory"); return; } asm volatile("" ::: "memory"); # if defined(__i386__) || defined(__x86_64__) /* This is implicit on x86. */ # elif defined(__ppc64__) asm volatile("lwsync"); # elif defined(__ppc__) asm volatile("sync"); # elif defined(__sparc__) && defined(__arch64__) if (mo == atomic_memory_order_acquire) { asm volatile("membar #LoadLoad | #LoadStore"); } else if (mo == atomic_memory_order_release) { asm volatile("membar #LoadStore | #StoreStore"); } else { asm volatile("membar #LoadLoad | #LoadStore | #StoreStore"); } # else __sync_synchronize(); # endif asm volatile("" ::: "memory"); } /* * A correct implementation of seq_cst loads and stores on weakly ordered * architectures could do either of the following: * 1. store() is weak-fence -> store -> strong fence, load() is load -> * strong-fence. * 2. store() is strong-fence -> store, load() is strong-fence -> load -> * weak-fence. * The tricky thing is, load() and store() above can be the load or store * portions of a gcc __sync builtin, so we have to follow GCC's lead, which * means going with strategy 2. * On strongly ordered architectures, the natural strategy is to stick a strong * fence after seq_cst stores, and have naked loads. So we want the strong * fences in different places on different architectures. * atomic_pre_sc_load_fence and atomic_post_sc_store_fence allow us to * accomplish this. */ ATOMIC_INLINE void atomic_pre_sc_load_fence() { # if defined(__i386__) || defined(__x86_64__) || \ (defined(__sparc__) && defined(__arch64__)) atomic_fence(atomic_memory_order_relaxed); # else atomic_fence(atomic_memory_order_seq_cst); # endif } ATOMIC_INLINE void atomic_post_sc_store_fence() { # if defined(__i386__) || defined(__x86_64__) || \ (defined(__sparc__) && defined(__arch64__)) atomic_fence(atomic_memory_order_seq_cst); # else atomic_fence(atomic_memory_order_relaxed); # endif } #define JEMALLOC_GENERATE_ATOMICS(type, short_type, \ /* unused */ lg_size) \ typedef struct { \ type volatile repr; \ } atomic_##short_type##_t; \ \ ATOMIC_INLINE type \ atomic_load_##short_type(const atomic_##short_type##_t *a, \ atomic_memory_order_t mo) { \ if (mo == atomic_memory_order_seq_cst) { \ atomic_pre_sc_load_fence(); \ } \ type result = a->repr; \ if (mo != atomic_memory_order_relaxed) { \ atomic_fence(atomic_memory_order_acquire); \ } \ return result; \ } \ \ ATOMIC_INLINE void \ atomic_store_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ if (mo != atomic_memory_order_relaxed) { \ atomic_fence(atomic_memory_order_release); \ } \ a->repr = val; \ if (mo == atomic_memory_order_seq_cst) { \ atomic_post_sc_store_fence(); \ } \ } \ \ ATOMIC_INLINE type \ atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ /* \ * Because of FreeBSD, we care about gcc 4.2, which doesn't have\ * an atomic exchange builtin. We fake it with a CAS loop. \ */ \ while (true) { \ type old = a->repr; \ if (__sync_bool_compare_and_swap(&a->repr, old, val)) { \ return old; \ } \ } \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \ type *expected, type desired, \ atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ type prev = __sync_val_compare_and_swap(&a->repr, *expected, \ desired); \ if (prev == *expected) { \ return true; \ } else { \ *expected = prev; \ return false; \ } \ } \ ATOMIC_INLINE bool \ atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \ type *expected, type desired, \ atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ type prev = __sync_val_compare_and_swap(&a->repr, *expected, \ desired); \ if (prev == *expected) { \ return true; \ } else { \ *expected = prev; \ return false; \ } \ } #define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, \ /* unused */ lg_size) \ JEMALLOC_GENERATE_ATOMICS(type, short_type, /* unused */ lg_size) \ \ ATOMIC_INLINE type \ atomic_fetch_add_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __sync_fetch_and_add(&a->repr, val); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __sync_fetch_and_sub(&a->repr, val); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_and_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __sync_fetch_and_and(&a->repr, val); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_or_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __sync_fetch_and_or(&a->repr, val); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return __sync_fetch_and_xor(&a->repr, val); \ } #endif /* JEMALLOC_INTERNAL_ATOMIC_GCC_SYNC_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/atomic_msvc.h000066400000000000000000000126441501533116600250620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ATOMIC_MSVC_H #define JEMALLOC_INTERNAL_ATOMIC_MSVC_H #define ATOMIC_INIT(...) {__VA_ARGS__} typedef enum { atomic_memory_order_relaxed, atomic_memory_order_acquire, atomic_memory_order_release, atomic_memory_order_acq_rel, atomic_memory_order_seq_cst } atomic_memory_order_t; typedef char atomic_repr_0_t; typedef short atomic_repr_1_t; typedef long atomic_repr_2_t; typedef __int64 atomic_repr_3_t; ATOMIC_INLINE void atomic_fence(atomic_memory_order_t mo) { _ReadWriteBarrier(); # if defined(_M_ARM) || defined(_M_ARM64) /* ARM needs a barrier for everything but relaxed. */ if (mo != atomic_memory_order_relaxed) { MemoryBarrier(); } # elif defined(_M_IX86) || defined (_M_X64) /* x86 needs a barrier only for seq_cst. */ if (mo == atomic_memory_order_seq_cst) { MemoryBarrier(); } # else # error "Don't know how to create atomics for this platform for MSVC." # endif _ReadWriteBarrier(); } #define ATOMIC_INTERLOCKED_REPR(lg_size) atomic_repr_ ## lg_size ## _t #define ATOMIC_CONCAT(a, b) ATOMIC_RAW_CONCAT(a, b) #define ATOMIC_RAW_CONCAT(a, b) a ## b #define ATOMIC_INTERLOCKED_NAME(base_name, lg_size) ATOMIC_CONCAT( \ base_name, ATOMIC_INTERLOCKED_SUFFIX(lg_size)) #define ATOMIC_INTERLOCKED_SUFFIX(lg_size) \ ATOMIC_CONCAT(ATOMIC_INTERLOCKED_SUFFIX_, lg_size) #define ATOMIC_INTERLOCKED_SUFFIX_0 8 #define ATOMIC_INTERLOCKED_SUFFIX_1 16 #define ATOMIC_INTERLOCKED_SUFFIX_2 #define ATOMIC_INTERLOCKED_SUFFIX_3 64 #define JEMALLOC_GENERATE_ATOMICS(type, short_type, lg_size) \ typedef struct { \ ATOMIC_INTERLOCKED_REPR(lg_size) repr; \ } atomic_##short_type##_t; \ \ ATOMIC_INLINE type \ atomic_load_##short_type(const atomic_##short_type##_t *a, \ atomic_memory_order_t mo) { \ ATOMIC_INTERLOCKED_REPR(lg_size) ret = a->repr; \ if (mo != atomic_memory_order_relaxed) { \ atomic_fence(atomic_memory_order_acquire); \ } \ return (type) ret; \ } \ \ ATOMIC_INLINE void \ atomic_store_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ if (mo != atomic_memory_order_relaxed) { \ atomic_fence(atomic_memory_order_release); \ } \ a->repr = (ATOMIC_INTERLOCKED_REPR(lg_size)) val; \ if (mo == atomic_memory_order_seq_cst) { \ atomic_fence(atomic_memory_order_seq_cst); \ } \ } \ \ ATOMIC_INLINE type \ atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \ atomic_memory_order_t mo) { \ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedExchange, \ lg_size)(&a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \ type *expected, type desired, atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ ATOMIC_INTERLOCKED_REPR(lg_size) e = \ (ATOMIC_INTERLOCKED_REPR(lg_size))*expected; \ ATOMIC_INTERLOCKED_REPR(lg_size) d = \ (ATOMIC_INTERLOCKED_REPR(lg_size))desired; \ ATOMIC_INTERLOCKED_REPR(lg_size) old = \ ATOMIC_INTERLOCKED_NAME(_InterlockedCompareExchange, \ lg_size)(&a->repr, d, e); \ if (old == e) { \ return true; \ } else { \ *expected = (type)old; \ return false; \ } \ } \ \ ATOMIC_INLINE bool \ atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \ type *expected, type desired, atomic_memory_order_t success_mo, \ atomic_memory_order_t failure_mo) { \ /* We implement the weak version with strong semantics. */ \ return atomic_compare_exchange_weak_##short_type(a, expected, \ desired, success_mo, failure_mo); \ } #define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, lg_size) \ JEMALLOC_GENERATE_ATOMICS(type, short_type, lg_size) \ \ ATOMIC_INLINE type \ atomic_fetch_add_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedExchangeAdd, \ lg_size)(&a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \ } \ \ ATOMIC_INLINE type \ atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ /* \ * MSVC warns on negation of unsigned operands, but for us it \ * gives exactly the right semantics (MAX_TYPE + 1 - operand). \ */ \ __pragma(warning(push)) \ __pragma(warning(disable: 4146)) \ return atomic_fetch_add_##short_type(a, -val, mo); \ __pragma(warning(pop)) \ } \ ATOMIC_INLINE type \ atomic_fetch_and_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedAnd, lg_size)( \ &a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \ } \ ATOMIC_INLINE type \ atomic_fetch_or_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedOr, lg_size)( \ &a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \ } \ ATOMIC_INLINE type \ atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, \ type val, atomic_memory_order_t mo) { \ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedXor, lg_size)( \ &a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \ } #endif /* JEMALLOC_INTERNAL_ATOMIC_MSVC_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/background_thread_externs.h000066400000000000000000000025611501533116600277710ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BACKGROUND_THREAD_EXTERNS_H #define JEMALLOC_INTERNAL_BACKGROUND_THREAD_EXTERNS_H extern bool opt_background_thread; extern size_t opt_max_background_threads; extern malloc_mutex_t background_thread_lock; extern atomic_b_t background_thread_enabled_state; extern size_t n_background_threads; extern size_t max_background_threads; extern background_thread_info_t *background_thread_info; bool background_thread_create(tsd_t *tsd, unsigned arena_ind); bool background_threads_enable(tsd_t *tsd); bool background_threads_disable(tsd_t *tsd); bool background_thread_is_started(background_thread_info_t* info); void background_thread_wakeup_early(background_thread_info_t *info, nstime_t *remaining_sleep); void background_thread_prefork0(tsdn_t *tsdn); void background_thread_prefork1(tsdn_t *tsdn); void background_thread_postfork_parent(tsdn_t *tsdn); void background_thread_postfork_child(tsdn_t *tsdn); bool background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats); void background_thread_ctl_init(tsdn_t *tsdn); #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER extern int pthread_create_wrapper(pthread_t *__restrict, const pthread_attr_t *, void *(*)(void *), void *__restrict); #endif bool background_thread_boot0(void); bool background_thread_boot1(tsdn_t *tsdn, base_t *base); #endif /* JEMALLOC_INTERNAL_BACKGROUND_THREAD_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/background_thread_inlines.h000066400000000000000000000032641501533116600277430ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BACKGROUND_THREAD_INLINES_H #define JEMALLOC_INTERNAL_BACKGROUND_THREAD_INLINES_H JEMALLOC_ALWAYS_INLINE bool background_thread_enabled(void) { return atomic_load_b(&background_thread_enabled_state, ATOMIC_RELAXED); } JEMALLOC_ALWAYS_INLINE void background_thread_enabled_set(tsdn_t *tsdn, bool state) { malloc_mutex_assert_owner(tsdn, &background_thread_lock); atomic_store_b(&background_thread_enabled_state, state, ATOMIC_RELAXED); } JEMALLOC_ALWAYS_INLINE background_thread_info_t * arena_background_thread_info_get(arena_t *arena) { unsigned arena_ind = arena_ind_get(arena); return &background_thread_info[arena_ind % max_background_threads]; } JEMALLOC_ALWAYS_INLINE background_thread_info_t * background_thread_info_get(size_t ind) { return &background_thread_info[ind % max_background_threads]; } JEMALLOC_ALWAYS_INLINE uint64_t background_thread_wakeup_time_get(background_thread_info_t *info) { uint64_t next_wakeup = nstime_ns(&info->next_wakeup); assert(atomic_load_b(&info->indefinite_sleep, ATOMIC_ACQUIRE) == (next_wakeup == BACKGROUND_THREAD_INDEFINITE_SLEEP)); return next_wakeup; } JEMALLOC_ALWAYS_INLINE void background_thread_wakeup_time_set(tsdn_t *tsdn, background_thread_info_t *info, uint64_t wakeup_time) { malloc_mutex_assert_owner(tsdn, &info->mtx); atomic_store_b(&info->indefinite_sleep, wakeup_time == BACKGROUND_THREAD_INDEFINITE_SLEEP, ATOMIC_RELEASE); nstime_init(&info->next_wakeup, wakeup_time); } JEMALLOC_ALWAYS_INLINE bool background_thread_indefinite_sleep(background_thread_info_t *info) { return atomic_load_b(&info->indefinite_sleep, ATOMIC_ACQUIRE); } #endif /* JEMALLOC_INTERNAL_BACKGROUND_THREAD_INLINES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/background_thread_structs.h000066400000000000000000000042771501533116600300160ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BACKGROUND_THREAD_STRUCTS_H #define JEMALLOC_INTERNAL_BACKGROUND_THREAD_STRUCTS_H /* This file really combines "structs" and "types", but only transitionally. */ #if defined(JEMALLOC_BACKGROUND_THREAD) || defined(JEMALLOC_LAZY_LOCK) # define JEMALLOC_PTHREAD_CREATE_WRAPPER #endif #define BACKGROUND_THREAD_INDEFINITE_SLEEP UINT64_MAX #define MAX_BACKGROUND_THREAD_LIMIT MALLOCX_ARENA_LIMIT #define DEFAULT_NUM_BACKGROUND_THREAD 4 /* * These exist only as a transitional state. Eventually, deferral should be * part of the PAI, and each implementation can indicate wait times with more * specificity. */ #define BACKGROUND_THREAD_HPA_INTERVAL_MAX_UNINITIALIZED (-2) #define BACKGROUND_THREAD_HPA_INTERVAL_MAX_DEFAULT_WHEN_ENABLED 5000 #define BACKGROUND_THREAD_DEFERRED_MIN UINT64_C(0) #define BACKGROUND_THREAD_DEFERRED_MAX UINT64_MAX typedef enum { background_thread_stopped, background_thread_started, /* Thread waits on the global lock when paused (for arena_reset). */ background_thread_paused, } background_thread_state_t; struct background_thread_info_s { #ifdef JEMALLOC_BACKGROUND_THREAD /* Background thread is pthread specific. */ pthread_t thread; pthread_cond_t cond; #endif malloc_mutex_t mtx; background_thread_state_t state; /* When true, it means no wakeup scheduled. */ atomic_b_t indefinite_sleep; /* Next scheduled wakeup time (absolute time in ns). */ nstime_t next_wakeup; /* * Since the last background thread run, newly added number of pages * that need to be purged by the next wakeup. This is adjusted on * epoch advance, and is used to determine whether we should signal the * background thread to wake up earlier. */ size_t npages_to_purge_new; /* Stats: total number of runs since started. */ uint64_t tot_n_runs; /* Stats: total sleep time since started. */ nstime_t tot_sleep_time; }; typedef struct background_thread_info_s background_thread_info_t; struct background_thread_stats_s { size_t num_threads; uint64_t num_runs; nstime_t run_interval; mutex_prof_data_t max_counter_per_bg_thd; }; typedef struct background_thread_stats_s background_thread_stats_t; #endif /* JEMALLOC_INTERNAL_BACKGROUND_THREAD_STRUCTS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/base.h000066400000000000000000000063511501533116600234660ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BASE_H #define JEMALLOC_INTERNAL_BASE_H #include "jemalloc/internal/edata.h" #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/mutex.h" enum metadata_thp_mode_e { metadata_thp_disabled = 0, /* * Lazily enable hugepage for metadata. To avoid high RSS caused by THP * + low usage arena (i.e. THP becomes a significant percentage), the * "auto" option only starts using THP after a base allocator used up * the first THP region. Starting from the second hugepage (in a single * arena), "auto" behaves the same as "always", i.e. madvise hugepage * right away. */ metadata_thp_auto = 1, metadata_thp_always = 2, metadata_thp_mode_limit = 3 }; typedef enum metadata_thp_mode_e metadata_thp_mode_t; #define METADATA_THP_DEFAULT metadata_thp_disabled extern metadata_thp_mode_t opt_metadata_thp; extern const char *metadata_thp_mode_names[]; /* Embedded at the beginning of every block of base-managed virtual memory. */ typedef struct base_block_s base_block_t; struct base_block_s { /* Total size of block's virtual memory mapping. */ size_t size; /* Next block in list of base's blocks. */ base_block_t *next; /* Tracks unused trailing space. */ edata_t edata; }; typedef struct base_s base_t; struct base_s { /* * User-configurable extent hook functions. */ ehooks_t ehooks; /* * User-configurable extent hook functions for metadata allocations. */ ehooks_t ehooks_base; /* Protects base_alloc() and base_stats_get() operations. */ malloc_mutex_t mtx; /* Using THP when true (metadata_thp auto mode). */ bool auto_thp_switched; /* * Most recent size class in the series of increasingly large base * extents. Logarithmic spacing between subsequent allocations ensures * that the total number of distinct mappings remains small. */ pszind_t pind_last; /* Serial number generation state. */ size_t extent_sn_next; /* Chain of all blocks associated with base. */ base_block_t *blocks; /* Heap of extents that track unused trailing space within blocks. */ edata_heap_t avail[SC_NSIZES]; /* Stats, only maintained if config_stats. */ size_t allocated; size_t resident; size_t mapped; /* Number of THP regions touched. */ size_t n_thp; }; static inline unsigned base_ind_get(const base_t *base) { return ehooks_ind_get(&base->ehooks); } static inline bool metadata_thp_enabled(void) { return (opt_metadata_thp != metadata_thp_disabled); } base_t *b0get(void); base_t *base_new(tsdn_t *tsdn, unsigned ind, const extent_hooks_t *extent_hooks, bool metadata_use_hooks); void base_delete(tsdn_t *tsdn, base_t *base); ehooks_t *base_ehooks_get(base_t *base); ehooks_t *base_ehooks_get_for_metadata(base_t *base); extent_hooks_t *base_extent_hooks_set(base_t *base, extent_hooks_t *extent_hooks); void *base_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment); edata_t *base_alloc_edata(tsdn_t *tsdn, base_t *base); void base_stats_get(tsdn_t *tsdn, base_t *base, size_t *allocated, size_t *resident, size_t *mapped, size_t *n_thp); void base_prefork(tsdn_t *tsdn, base_t *base); void base_postfork_parent(tsdn_t *tsdn, base_t *base); void base_postfork_child(tsdn_t *tsdn, base_t *base); bool base_boot(tsdn_t *tsdn); #endif /* JEMALLOC_INTERNAL_BASE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/bin.h000066400000000000000000000047471501533116600233330ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BIN_H #define JEMALLOC_INTERNAL_BIN_H #include "jemalloc/internal/bin_stats.h" #include "jemalloc/internal/bin_types.h" #include "jemalloc/internal/edata.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/sc.h" /* * A bin contains a set of extents that are currently being used for slab * allocations. */ typedef struct bin_s bin_t; struct bin_s { /* All operations on bin_t fields require lock ownership. */ malloc_mutex_t lock; /* * Bin statistics. These get touched every time the lock is acquired, * so put them close by in the hopes of getting some cache locality. */ bin_stats_t stats; /* * Current slab being used to service allocations of this bin's size * class. slabcur is independent of slabs_{nonfull,full}; whenever * slabcur is reassigned, the previous slab must be deallocated or * inserted into slabs_{nonfull,full}. */ edata_t *slabcur; /* * Heap of non-full slabs. This heap is used to assure that new * allocations come from the non-full slab that is oldest/lowest in * memory. */ edata_heap_t slabs_nonfull; /* List used to track full slabs. */ edata_list_active_t slabs_full; }; /* A set of sharded bins of the same size class. */ typedef struct bins_s bins_t; struct bins_s { /* Sharded bins. Dynamically sized. */ bin_t *bin_shards; }; void bin_shard_sizes_boot(unsigned bin_shards[SC_NBINS]); bool bin_update_shard_size(unsigned bin_shards[SC_NBINS], size_t start_size, size_t end_size, size_t nshards); /* Initializes a bin to empty. Returns true on error. */ bool bin_init(bin_t *bin); /* Forking. */ void bin_prefork(tsdn_t *tsdn, bin_t *bin); void bin_postfork_parent(tsdn_t *tsdn, bin_t *bin); void bin_postfork_child(tsdn_t *tsdn, bin_t *bin); /* Stats. */ static inline void bin_stats_merge(tsdn_t *tsdn, bin_stats_data_t *dst_bin_stats, bin_t *bin) { malloc_mutex_lock(tsdn, &bin->lock); malloc_mutex_prof_accum(tsdn, &dst_bin_stats->mutex_data, &bin->lock); bin_stats_t *stats = &dst_bin_stats->stats_data; stats->nmalloc += bin->stats.nmalloc; stats->ndalloc += bin->stats.ndalloc; stats->nrequests += bin->stats.nrequests; stats->curregs += bin->stats.curregs; stats->nfills += bin->stats.nfills; stats->nflushes += bin->stats.nflushes; stats->nslabs += bin->stats.nslabs; stats->reslabs += bin->stats.reslabs; stats->curslabs += bin->stats.curslabs; stats->nonfull_slabs += bin->stats.nonfull_slabs; malloc_mutex_unlock(tsdn, &bin->lock); } #endif /* JEMALLOC_INTERNAL_BIN_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/bin_info.h000066400000000000000000000025321501533116600243340ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BIN_INFO_H #define JEMALLOC_INTERNAL_BIN_INFO_H #include "jemalloc/internal/bitmap.h" /* * Read-only information associated with each element of arena_t's bins array * is stored separately, partly to reduce memory usage (only one copy, rather * than one per arena), but mainly to avoid false cacheline sharing. * * Each slab has the following layout: * * /--------------------\ * | region 0 | * |--------------------| * | region 1 | * |--------------------| * | ... | * | ... | * | ... | * |--------------------| * | region nregs-1 | * \--------------------/ */ typedef struct bin_info_s bin_info_t; struct bin_info_s { /* Size of regions in a slab for this bin's size class. */ size_t reg_size; /* Total size of a slab for this bin's size class. */ size_t slab_size; /* Total number of regions in a slab for this bin's size class. */ uint32_t nregs; /* Number of sharded bins in each arena for this size class. */ uint32_t n_shards; /* * Metadata used to manipulate bitmaps for slabs associated with this * bin. */ bitmap_info_t bitmap_info; }; extern bin_info_t bin_infos[SC_NBINS]; void bin_info_boot(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS]); #endif /* JEMALLOC_INTERNAL_BIN_INFO_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/bin_stats.h000066400000000000000000000027161501533116600245430ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BIN_STATS_H #define JEMALLOC_INTERNAL_BIN_STATS_H #include "jemalloc/internal/mutex_prof.h" typedef struct bin_stats_s bin_stats_t; struct bin_stats_s { /* * Total number of allocation/deallocation requests served directly by * the bin. Note that tcache may allocate an object, then recycle it * many times, resulting many increments to nrequests, but only one * each to nmalloc and ndalloc. */ uint64_t nmalloc; uint64_t ndalloc; /* * Number of allocation requests that correspond to the size of this * bin. This includes requests served by tcache, though tcache only * periodically merges into this counter. */ uint64_t nrequests; /* * Current number of regions of this size class, including regions * currently cached by tcache. */ size_t curregs; /* Number of tcache fills from this bin. */ uint64_t nfills; /* Number of tcache flushes to this bin. */ uint64_t nflushes; /* Total number of slabs created for this bin's size class. */ uint64_t nslabs; /* * Total number of slabs reused by extracting them from the slabs heap * for this bin's size class. */ uint64_t reslabs; /* Current number of slabs in this bin. */ size_t curslabs; /* Current size of nonfull slabs heap in this bin. */ size_t nonfull_slabs; }; typedef struct bin_stats_data_s bin_stats_data_t; struct bin_stats_data_s { bin_stats_t stats_data; mutex_prof_data_t mutex_data; }; #endif /* JEMALLOC_INTERNAL_BIN_STATS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/bin_types.h000066400000000000000000000007311501533116600245440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BIN_TYPES_H #define JEMALLOC_INTERNAL_BIN_TYPES_H #include "jemalloc/internal/sc.h" #define BIN_SHARDS_MAX (1 << EDATA_BITS_BINSHARD_WIDTH) #define N_BIN_SHARDS_DEFAULT 1 /* Used in TSD static initializer only. Real init in arena_bind(). */ #define TSD_BINSHARDS_ZERO_INITIALIZER {{UINT8_MAX}} typedef struct tsd_binshards_s tsd_binshards_t; struct tsd_binshards_s { uint8_t binshard[SC_NBINS]; }; #endif /* JEMALLOC_INTERNAL_BIN_TYPES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/bit_util.h000066400000000000000000000241121501533116600243620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BIT_UTIL_H #define JEMALLOC_INTERNAL_BIT_UTIL_H #include "jemalloc/internal/assert.h" /* Sanity check. */ #if !defined(JEMALLOC_INTERNAL_FFSLL) || !defined(JEMALLOC_INTERNAL_FFSL) \ || !defined(JEMALLOC_INTERNAL_FFS) # error JEMALLOC_INTERNAL_FFS{,L,LL} should have been defined by configure #endif /* * Unlike the builtins and posix ffs functions, our ffs requires a non-zero * input, and returns the position of the lowest bit set (as opposed to the * posix versions, which return 1 larger than that position and use a return * value of zero as a sentinel. This tends to simplify logic in callers, and * allows for consistency with the builtins we build fls on top of. */ static inline unsigned ffs_llu(unsigned long long x) { util_assume(x != 0); return JEMALLOC_INTERNAL_FFSLL(x) - 1; } static inline unsigned ffs_lu(unsigned long x) { util_assume(x != 0); return JEMALLOC_INTERNAL_FFSL(x) - 1; } static inline unsigned ffs_u(unsigned x) { util_assume(x != 0); return JEMALLOC_INTERNAL_FFS(x) - 1; } #define DO_FLS_SLOW(x, suffix) do { \ util_assume(x != 0); \ x |= (x >> 1); \ x |= (x >> 2); \ x |= (x >> 4); \ x |= (x >> 8); \ x |= (x >> 16); \ if (sizeof(x) > 4) { \ /* \ * If sizeof(x) is 4, then the expression "x >> 32" \ * will generate compiler warnings even if the code \ * never executes. This circumvents the warning, and \ * gets compiled out in optimized builds. \ */ \ int constant_32 = sizeof(x) * 4; \ x |= (x >> constant_32); \ } \ x++; \ if (x == 0) { \ return 8 * sizeof(x) - 1; \ } \ return ffs_##suffix(x) - 1; \ } while(0) static inline unsigned fls_llu_slow(unsigned long long x) { DO_FLS_SLOW(x, llu); } static inline unsigned fls_lu_slow(unsigned long x) { DO_FLS_SLOW(x, lu); } static inline unsigned fls_u_slow(unsigned x) { DO_FLS_SLOW(x, u); } #undef DO_FLS_SLOW #ifdef JEMALLOC_HAVE_BUILTIN_CLZ static inline unsigned fls_llu(unsigned long long x) { util_assume(x != 0); /* * Note that the xor here is more naturally written as subtraction; the * last bit set is the number of bits in the type minus the number of * leading zero bits. But GCC implements that as: * bsr edi, edi * mov eax, 31 * xor edi, 31 * sub eax, edi * If we write it as xor instead, then we get * bsr eax, edi * as desired. */ return (8 * sizeof(x) - 1) ^ __builtin_clzll(x); } static inline unsigned fls_lu(unsigned long x) { util_assume(x != 0); return (8 * sizeof(x) - 1) ^ __builtin_clzl(x); } static inline unsigned fls_u(unsigned x) { util_assume(x != 0); return (8 * sizeof(x) - 1) ^ __builtin_clz(x); } #elif defined(_MSC_VER) #if LG_SIZEOF_PTR == 3 #define DO_BSR64(bit, x) _BitScanReverse64(&bit, x) #else /* * This never actually runs; we're just dodging a compiler error for the * never-taken branch where sizeof(void *) == 8. */ #define DO_BSR64(bit, x) bit = 0; unreachable() #endif #define DO_FLS(x) do { \ if (x == 0) { \ return 8 * sizeof(x); \ } \ unsigned long bit; \ if (sizeof(x) == 4) { \ _BitScanReverse(&bit, (unsigned)x); \ return (unsigned)bit; \ } \ if (sizeof(x) == 8 && sizeof(void *) == 8) { \ DO_BSR64(bit, x); \ return (unsigned)bit; \ } \ if (sizeof(x) == 8 && sizeof(void *) == 4) { \ /* Dodge a compiler warning, as above. */ \ int constant_32 = sizeof(x) * 4; \ if (_BitScanReverse(&bit, \ (unsigned)(x >> constant_32))) { \ return 32 + (unsigned)bit; \ } else { \ _BitScanReverse(&bit, (unsigned)x); \ return (unsigned)bit; \ } \ } \ unreachable(); \ } while (0) static inline unsigned fls_llu(unsigned long long x) { DO_FLS(x); } static inline unsigned fls_lu(unsigned long x) { DO_FLS(x); } static inline unsigned fls_u(unsigned x) { DO_FLS(x); } #undef DO_FLS #undef DO_BSR64 #else static inline unsigned fls_llu(unsigned long long x) { return fls_llu_slow(x); } static inline unsigned fls_lu(unsigned long x) { return fls_lu_slow(x); } static inline unsigned fls_u(unsigned x) { return fls_u_slow(x); } #endif #if LG_SIZEOF_LONG_LONG > 3 # error "Haven't implemented popcount for 16-byte ints." #endif #define DO_POPCOUNT(x, type) do { \ /* \ * Algorithm from an old AMD optimization reference manual. \ * We're putting a little bit more work than you might expect \ * into the no-instrinsic case, since we only support the \ * GCC intrinsics spelling of popcount (for now). Detecting \ * whether or not the popcount builtin is actually useable in \ * MSVC is nontrivial. \ */ \ \ type bmul = (type)0x0101010101010101ULL; \ \ /* \ * Replace each 2 bits with the sideways sum of the original \ * values. 0x5 = 0b0101. \ * \ * You might expect this to be: \ * x = (x & 0x55...) + ((x >> 1) & 0x55...). \ * That costs an extra mask relative to this, though. \ */ \ x = x - ((x >> 1) & (0x55U * bmul)); \ /* Replace each 4 bits with their sideays sum. 0x3 = 0b0011. */\ x = (x & (bmul * 0x33U)) + ((x >> 2) & (bmul * 0x33U)); \ /* \ * Replace each 8 bits with their sideways sum. Note that we \ * can't overflow within each 4-bit sum here, so we can skip \ * the initial mask. \ */ \ x = (x + (x >> 4)) & (bmul * 0x0FU); \ /* \ * None of the partial sums in this multiplication (viewed in \ * base-256) can overflow into the next digit. So the least \ * significant byte of the product will be the least \ * significant byte of the original value, the second least \ * significant byte will be the sum of the two least \ * significant bytes of the original value, and so on. \ * Importantly, the high byte will be the byte-wise sum of all \ * the bytes of the original value. \ */ \ x = x * bmul; \ x >>= ((sizeof(x) - 1) * 8); \ return (unsigned)x; \ } while(0) static inline unsigned popcount_u_slow(unsigned bitmap) { DO_POPCOUNT(bitmap, unsigned); } static inline unsigned popcount_lu_slow(unsigned long bitmap) { DO_POPCOUNT(bitmap, unsigned long); } static inline unsigned popcount_llu_slow(unsigned long long bitmap) { DO_POPCOUNT(bitmap, unsigned long long); } #undef DO_POPCOUNT static inline unsigned popcount_u(unsigned bitmap) { #ifdef JEMALLOC_INTERNAL_POPCOUNT return JEMALLOC_INTERNAL_POPCOUNT(bitmap); #else return popcount_u_slow(bitmap); #endif } static inline unsigned popcount_lu(unsigned long bitmap) { #ifdef JEMALLOC_INTERNAL_POPCOUNTL return JEMALLOC_INTERNAL_POPCOUNTL(bitmap); #else return popcount_lu_slow(bitmap); #endif } static inline unsigned popcount_llu(unsigned long long bitmap) { #ifdef JEMALLOC_INTERNAL_POPCOUNTLL return JEMALLOC_INTERNAL_POPCOUNTLL(bitmap); #else return popcount_llu_slow(bitmap); #endif } /* * Clears first unset bit in bitmap, and returns * place of bit. bitmap *must not* be 0. */ static inline size_t cfs_lu(unsigned long* bitmap) { util_assume(*bitmap != 0); size_t bit = ffs_lu(*bitmap); *bitmap ^= ZU(1) << bit; return bit; } static inline unsigned ffs_zu(size_t x) { #if LG_SIZEOF_PTR == LG_SIZEOF_INT return ffs_u(x); #elif LG_SIZEOF_PTR == LG_SIZEOF_LONG return ffs_lu(x); #elif LG_SIZEOF_PTR == LG_SIZEOF_LONG_LONG return ffs_llu(x); #else #error No implementation for size_t ffs() #endif } static inline unsigned fls_zu(size_t x) { #if LG_SIZEOF_PTR == LG_SIZEOF_INT return fls_u(x); #elif LG_SIZEOF_PTR == LG_SIZEOF_LONG return fls_lu(x); #elif LG_SIZEOF_PTR == LG_SIZEOF_LONG_LONG return fls_llu(x); #else #error No implementation for size_t fls() #endif } static inline unsigned ffs_u64(uint64_t x) { #if LG_SIZEOF_LONG == 3 return ffs_lu(x); #elif LG_SIZEOF_LONG_LONG == 3 return ffs_llu(x); #else #error No implementation for 64-bit ffs() #endif } static inline unsigned fls_u64(uint64_t x) { #if LG_SIZEOF_LONG == 3 return fls_lu(x); #elif LG_SIZEOF_LONG_LONG == 3 return fls_llu(x); #else #error No implementation for 64-bit fls() #endif } static inline unsigned ffs_u32(uint32_t x) { #if LG_SIZEOF_INT == 2 return ffs_u(x); #else #error No implementation for 32-bit ffs() #endif return ffs_u(x); } static inline unsigned fls_u32(uint32_t x) { #if LG_SIZEOF_INT == 2 return fls_u(x); #else #error No implementation for 32-bit fls() #endif return fls_u(x); } static inline uint64_t pow2_ceil_u64(uint64_t x) { if (unlikely(x <= 1)) { return x; } size_t msb_on_index = fls_u64(x - 1); /* * Range-check; it's on the callers to ensure that the result of this * call won't overflow. */ assert(msb_on_index < 63); return 1ULL << (msb_on_index + 1); } static inline uint32_t pow2_ceil_u32(uint32_t x) { if (unlikely(x <= 1)) { return x; } size_t msb_on_index = fls_u32(x - 1); /* As above. */ assert(msb_on_index < 31); return 1U << (msb_on_index + 1); } /* Compute the smallest power of 2 that is >= x. */ static inline size_t pow2_ceil_zu(size_t x) { #if (LG_SIZEOF_PTR == 3) return pow2_ceil_u64(x); #else return pow2_ceil_u32(x); #endif } static inline unsigned lg_floor(size_t x) { util_assume(x != 0); #if (LG_SIZEOF_PTR == 3) return fls_u64(x); #else return fls_u32(x); #endif } static inline unsigned lg_ceil(size_t x) { return lg_floor(x) + ((x & (x - 1)) == 0 ? 0 : 1); } /* A compile-time version of lg_floor and lg_ceil. */ #define LG_FLOOR_1(x) 0 #define LG_FLOOR_2(x) (x < (1ULL << 1) ? LG_FLOOR_1(x) : 1 + LG_FLOOR_1(x >> 1)) #define LG_FLOOR_4(x) (x < (1ULL << 2) ? LG_FLOOR_2(x) : 2 + LG_FLOOR_2(x >> 2)) #define LG_FLOOR_8(x) (x < (1ULL << 4) ? LG_FLOOR_4(x) : 4 + LG_FLOOR_4(x >> 4)) #define LG_FLOOR_16(x) (x < (1ULL << 8) ? LG_FLOOR_8(x) : 8 + LG_FLOOR_8(x >> 8)) #define LG_FLOOR_32(x) (x < (1ULL << 16) ? LG_FLOOR_16(x) : 16 + LG_FLOOR_16(x >> 16)) #define LG_FLOOR_64(x) (x < (1ULL << 32) ? LG_FLOOR_32(x) : 32 + LG_FLOOR_32(x >> 32)) #if LG_SIZEOF_PTR == 2 # define LG_FLOOR(x) LG_FLOOR_32((x)) #else # define LG_FLOOR(x) LG_FLOOR_64((x)) #endif #define LG_CEIL(x) (LG_FLOOR(x) + (((x) & ((x) - 1)) == 0 ? 0 : 1)) #endif /* JEMALLOC_INTERNAL_BIT_UTIL_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/bitmap.h000066400000000000000000000256221501533116600240320ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BITMAP_H #define JEMALLOC_INTERNAL_BITMAP_H #include "jemalloc/internal/bit_util.h" #include "jemalloc/internal/sc.h" typedef unsigned long bitmap_t; #define LG_SIZEOF_BITMAP LG_SIZEOF_LONG /* Maximum bitmap bit count is 2^LG_BITMAP_MAXBITS. */ #if SC_LG_SLAB_MAXREGS > LG_CEIL(SC_NSIZES) /* Maximum bitmap bit count is determined by maximum regions per slab. */ # define LG_BITMAP_MAXBITS SC_LG_SLAB_MAXREGS #else /* Maximum bitmap bit count is determined by number of extent size classes. */ # define LG_BITMAP_MAXBITS LG_CEIL(SC_NSIZES) #endif #define BITMAP_MAXBITS (ZU(1) << LG_BITMAP_MAXBITS) /* Number of bits per group. */ #define LG_BITMAP_GROUP_NBITS (LG_SIZEOF_BITMAP + 3) #define BITMAP_GROUP_NBITS (1U << LG_BITMAP_GROUP_NBITS) #define BITMAP_GROUP_NBITS_MASK (BITMAP_GROUP_NBITS-1) /* * Do some analysis on how big the bitmap is before we use a tree. For a brute * force linear search, if we would have to call ffs_lu() more than 2^3 times, * use a tree instead. */ #if LG_BITMAP_MAXBITS - LG_BITMAP_GROUP_NBITS > 3 # define BITMAP_USE_TREE #endif /* Number of groups required to store a given number of bits. */ #define BITMAP_BITS2GROUPS(nbits) \ (((nbits) + BITMAP_GROUP_NBITS_MASK) >> LG_BITMAP_GROUP_NBITS) /* * Number of groups required at a particular level for a given number of bits. */ #define BITMAP_GROUPS_L0(nbits) \ BITMAP_BITS2GROUPS(nbits) #define BITMAP_GROUPS_L1(nbits) \ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(nbits)) #define BITMAP_GROUPS_L2(nbits) \ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS((nbits)))) #define BITMAP_GROUPS_L3(nbits) \ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS( \ BITMAP_BITS2GROUPS((nbits))))) #define BITMAP_GROUPS_L4(nbits) \ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS( \ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS((nbits)))))) /* * Assuming the number of levels, number of groups required for a given number * of bits. */ #define BITMAP_GROUPS_1_LEVEL(nbits) \ BITMAP_GROUPS_L0(nbits) #define BITMAP_GROUPS_2_LEVEL(nbits) \ (BITMAP_GROUPS_1_LEVEL(nbits) + BITMAP_GROUPS_L1(nbits)) #define BITMAP_GROUPS_3_LEVEL(nbits) \ (BITMAP_GROUPS_2_LEVEL(nbits) + BITMAP_GROUPS_L2(nbits)) #define BITMAP_GROUPS_4_LEVEL(nbits) \ (BITMAP_GROUPS_3_LEVEL(nbits) + BITMAP_GROUPS_L3(nbits)) #define BITMAP_GROUPS_5_LEVEL(nbits) \ (BITMAP_GROUPS_4_LEVEL(nbits) + BITMAP_GROUPS_L4(nbits)) /* * Maximum number of groups required to support LG_BITMAP_MAXBITS. */ #ifdef BITMAP_USE_TREE #if LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS # define BITMAP_GROUPS(nbits) BITMAP_GROUPS_1_LEVEL(nbits) # define BITMAP_GROUPS_MAX BITMAP_GROUPS_1_LEVEL(BITMAP_MAXBITS) #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 2 # define BITMAP_GROUPS(nbits) BITMAP_GROUPS_2_LEVEL(nbits) # define BITMAP_GROUPS_MAX BITMAP_GROUPS_2_LEVEL(BITMAP_MAXBITS) #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 3 # define BITMAP_GROUPS(nbits) BITMAP_GROUPS_3_LEVEL(nbits) # define BITMAP_GROUPS_MAX BITMAP_GROUPS_3_LEVEL(BITMAP_MAXBITS) #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 4 # define BITMAP_GROUPS(nbits) BITMAP_GROUPS_4_LEVEL(nbits) # define BITMAP_GROUPS_MAX BITMAP_GROUPS_4_LEVEL(BITMAP_MAXBITS) #elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 5 # define BITMAP_GROUPS(nbits) BITMAP_GROUPS_5_LEVEL(nbits) # define BITMAP_GROUPS_MAX BITMAP_GROUPS_5_LEVEL(BITMAP_MAXBITS) #else # error "Unsupported bitmap size" #endif /* * Maximum number of levels possible. This could be statically computed based * on LG_BITMAP_MAXBITS: * * #define BITMAP_MAX_LEVELS \ * (LG_BITMAP_MAXBITS / LG_SIZEOF_BITMAP) \ * + !!(LG_BITMAP_MAXBITS % LG_SIZEOF_BITMAP) * * However, that would not allow the generic BITMAP_INFO_INITIALIZER() macro, so * instead hardcode BITMAP_MAX_LEVELS to the largest number supported by the * various cascading macros. The only additional cost this incurs is some * unused trailing entries in bitmap_info_t structures; the bitmaps themselves * are not impacted. */ #define BITMAP_MAX_LEVELS 5 #define BITMAP_INFO_INITIALIZER(nbits) { \ /* nbits. */ \ nbits, \ /* nlevels. */ \ (BITMAP_GROUPS_L0(nbits) > BITMAP_GROUPS_L1(nbits)) + \ (BITMAP_GROUPS_L1(nbits) > BITMAP_GROUPS_L2(nbits)) + \ (BITMAP_GROUPS_L2(nbits) > BITMAP_GROUPS_L3(nbits)) + \ (BITMAP_GROUPS_L3(nbits) > BITMAP_GROUPS_L4(nbits)) + 1, \ /* levels. */ \ { \ {0}, \ {BITMAP_GROUPS_L0(nbits)}, \ {BITMAP_GROUPS_L1(nbits) + BITMAP_GROUPS_L0(nbits)}, \ {BITMAP_GROUPS_L2(nbits) + BITMAP_GROUPS_L1(nbits) + \ BITMAP_GROUPS_L0(nbits)}, \ {BITMAP_GROUPS_L3(nbits) + BITMAP_GROUPS_L2(nbits) + \ BITMAP_GROUPS_L1(nbits) + BITMAP_GROUPS_L0(nbits)}, \ {BITMAP_GROUPS_L4(nbits) + BITMAP_GROUPS_L3(nbits) + \ BITMAP_GROUPS_L2(nbits) + BITMAP_GROUPS_L1(nbits) \ + BITMAP_GROUPS_L0(nbits)} \ } \ } #else /* BITMAP_USE_TREE */ #define BITMAP_GROUPS(nbits) BITMAP_BITS2GROUPS(nbits) #define BITMAP_GROUPS_MAX BITMAP_BITS2GROUPS(BITMAP_MAXBITS) #define BITMAP_INFO_INITIALIZER(nbits) { \ /* nbits. */ \ nbits, \ /* ngroups. */ \ BITMAP_BITS2GROUPS(nbits) \ } #endif /* BITMAP_USE_TREE */ typedef struct bitmap_level_s { /* Offset of this level's groups within the array of groups. */ size_t group_offset; } bitmap_level_t; typedef struct bitmap_info_s { /* Logical number of bits in bitmap (stored at bottom level). */ size_t nbits; #ifdef BITMAP_USE_TREE /* Number of levels necessary for nbits. */ unsigned nlevels; /* * Only the first (nlevels+1) elements are used, and levels are ordered * bottom to top (e.g. the bottom level is stored in levels[0]). */ bitmap_level_t levels[BITMAP_MAX_LEVELS+1]; #else /* BITMAP_USE_TREE */ /* Number of groups necessary for nbits. */ size_t ngroups; #endif /* BITMAP_USE_TREE */ } bitmap_info_t; void bitmap_info_init(bitmap_info_t *binfo, size_t nbits); void bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill); size_t bitmap_size(const bitmap_info_t *binfo); static inline bool bitmap_full(bitmap_t *bitmap, const bitmap_info_t *binfo) { #ifdef BITMAP_USE_TREE size_t rgoff = binfo->levels[binfo->nlevels].group_offset - 1; bitmap_t rg = bitmap[rgoff]; /* The bitmap is full iff the root group is 0. */ return (rg == 0); #else size_t i; for (i = 0; i < binfo->ngroups; i++) { if (bitmap[i] != 0) { return false; } } return true; #endif } static inline bool bitmap_get(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit) { size_t goff; bitmap_t g; assert(bit < binfo->nbits); goff = bit >> LG_BITMAP_GROUP_NBITS; g = bitmap[goff]; return !(g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK))); } static inline void bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit) { size_t goff; bitmap_t *gp; bitmap_t g; assert(bit < binfo->nbits); assert(!bitmap_get(bitmap, binfo, bit)); goff = bit >> LG_BITMAP_GROUP_NBITS; gp = &bitmap[goff]; g = *gp; assert(g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK))); g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK); *gp = g; assert(bitmap_get(bitmap, binfo, bit)); #ifdef BITMAP_USE_TREE /* Propagate group state transitions up the tree. */ if (g == 0) { unsigned i; for (i = 1; i < binfo->nlevels; i++) { bit = goff; goff = bit >> LG_BITMAP_GROUP_NBITS; gp = &bitmap[binfo->levels[i].group_offset + goff]; g = *gp; assert(g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK))); g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK); *gp = g; if (g != 0) { break; } } } #endif } /* ffu: find first unset >= bit. */ static inline size_t bitmap_ffu(const bitmap_t *bitmap, const bitmap_info_t *binfo, size_t min_bit) { assert(min_bit < binfo->nbits); #ifdef BITMAP_USE_TREE size_t bit = 0; for (unsigned level = binfo->nlevels; level--;) { size_t lg_bits_per_group = (LG_BITMAP_GROUP_NBITS * (level + 1)); bitmap_t group = bitmap[binfo->levels[level].group_offset + (bit >> lg_bits_per_group)]; unsigned group_nmask = (unsigned)(((min_bit > bit) ? (min_bit - bit) : 0) >> (lg_bits_per_group - LG_BITMAP_GROUP_NBITS)); assert(group_nmask <= BITMAP_GROUP_NBITS); bitmap_t group_mask = ~((1LU << group_nmask) - 1); bitmap_t group_masked = group & group_mask; if (group_masked == 0LU) { if (group == 0LU) { return binfo->nbits; } /* * min_bit was preceded by one or more unset bits in * this group, but there are no other unset bits in this * group. Try again starting at the first bit of the * next sibling. This will recurse at most once per * non-root level. */ size_t sib_base = bit + (ZU(1) << lg_bits_per_group); assert(sib_base > min_bit); assert(sib_base > bit); if (sib_base >= binfo->nbits) { return binfo->nbits; } return bitmap_ffu(bitmap, binfo, sib_base); } bit += ((size_t)ffs_lu(group_masked)) << (lg_bits_per_group - LG_BITMAP_GROUP_NBITS); } assert(bit >= min_bit); assert(bit < binfo->nbits); return bit; #else size_t i = min_bit >> LG_BITMAP_GROUP_NBITS; bitmap_t g = bitmap[i] & ~((1LU << (min_bit & BITMAP_GROUP_NBITS_MASK)) - 1); size_t bit; do { if (g != 0) { bit = ffs_lu(g); return (i << LG_BITMAP_GROUP_NBITS) + bit; } i++; g = bitmap[i]; } while (i < binfo->ngroups); return binfo->nbits; #endif } /* sfu: set first unset. */ static inline size_t bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo) { size_t bit; bitmap_t g; unsigned i; assert(!bitmap_full(bitmap, binfo)); #ifdef BITMAP_USE_TREE i = binfo->nlevels - 1; g = bitmap[binfo->levels[i].group_offset]; bit = ffs_lu(g); while (i > 0) { i--; g = bitmap[binfo->levels[i].group_offset + bit]; bit = (bit << LG_BITMAP_GROUP_NBITS) + ffs_lu(g); } #else i = 0; g = bitmap[0]; while (g == 0) { i++; g = bitmap[i]; } bit = (i << LG_BITMAP_GROUP_NBITS) + ffs_lu(g); #endif bitmap_set(bitmap, binfo, bit); return bit; } static inline void bitmap_unset(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit) { size_t goff; bitmap_t *gp; bitmap_t g; UNUSED bool propagate; assert(bit < binfo->nbits); assert(bitmap_get(bitmap, binfo, bit)); goff = bit >> LG_BITMAP_GROUP_NBITS; gp = &bitmap[goff]; g = *gp; propagate = (g == 0); assert((g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK))) == 0); g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK); *gp = g; assert(!bitmap_get(bitmap, binfo, bit)); #ifdef BITMAP_USE_TREE /* Propagate group state transitions up the tree. */ if (propagate) { unsigned i; for (i = 1; i < binfo->nlevels; i++) { bit = goff; goff = bit >> LG_BITMAP_GROUP_NBITS; gp = &bitmap[binfo->levels[i].group_offset + goff]; g = *gp; propagate = (g == 0); assert((g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK))) == 0); g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK); *gp = g; if (!propagate) { break; } } } #endif /* BITMAP_USE_TREE */ } #endif /* JEMALLOC_INTERNAL_BITMAP_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/buf_writer.h000066400000000000000000000022211501533116600247140ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_BUF_WRITER_H #define JEMALLOC_INTERNAL_BUF_WRITER_H /* * Note: when using the buffered writer, cbopaque is passed to write_cb only * when the buffer is flushed. It would make a difference if cbopaque points * to something that's changing for each write_cb call, or something that * affects write_cb in a way dependent on the content of the output string. * However, the most typical usage case in practice is that cbopaque points to * some "option like" content for the write_cb, so it doesn't matter. */ typedef struct { write_cb_t *write_cb; void *cbopaque; char *buf; size_t buf_size; size_t buf_end; bool internal_buf; } buf_writer_t; bool buf_writer_init(tsdn_t *tsdn, buf_writer_t *buf_writer, write_cb_t *write_cb, void *cbopaque, char *buf, size_t buf_len); void buf_writer_flush(buf_writer_t *buf_writer); write_cb_t buf_writer_cb; void buf_writer_terminate(tsdn_t *tsdn, buf_writer_t *buf_writer); typedef ssize_t (read_cb_t)(void *read_cbopaque, void *buf, size_t limit); void buf_writer_pipe(buf_writer_t *buf_writer, read_cb_t *read_cb, void *read_cbopaque); #endif /* JEMALLOC_INTERNAL_BUF_WRITER_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/cache_bin.h000066400000000000000000000536331501533116600244540ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_CACHE_BIN_H #define JEMALLOC_INTERNAL_CACHE_BIN_H #include "jemalloc/internal/ql.h" #include "jemalloc/internal/sz.h" /* * The cache_bins are the mechanism that the tcache and the arena use to * communicate. The tcache fills from and flushes to the arena by passing a * cache_bin_t to fill/flush. When the arena needs to pull stats from the * tcaches associated with it, it does so by iterating over its * cache_bin_array_descriptor_t objects and reading out per-bin stats it * contains. This makes it so that the arena need not know about the existence * of the tcache at all. */ /* * The size in bytes of each cache bin stack. We also use this to indicate * *counts* of individual objects. */ typedef uint16_t cache_bin_sz_t; /* * Leave a noticeable mark pattern on the cache bin stack boundaries, in case a * bug starts leaking those. Make it look like the junk pattern but be distinct * from it. */ static const uintptr_t cache_bin_preceding_junk = (uintptr_t)0x7a7a7a7a7a7a7a7aULL; /* Note: a7 vs. 7a above -- this tells you which pointer leaked. */ static const uintptr_t cache_bin_trailing_junk = (uintptr_t)0xa7a7a7a7a7a7a7a7ULL; /* * That implies the following value, for the maximum number of items in any * individual bin. The cache bins track their bounds looking just at the low * bits of a pointer, compared against a cache_bin_sz_t. So that's * 1 << (sizeof(cache_bin_sz_t) * 8) * bytes spread across pointer sized objects to get the maximum. */ #define CACHE_BIN_NCACHED_MAX (((size_t)1 << sizeof(cache_bin_sz_t) * 8) \ / sizeof(void *) - 1) /* * This lives inside the cache_bin (for locality reasons), and is initialized * alongside it, but is otherwise not modified by any cache bin operations. * It's logically public and maintained by its callers. */ typedef struct cache_bin_stats_s cache_bin_stats_t; struct cache_bin_stats_s { /* * Number of allocation requests that corresponded to the size of this * bin. */ uint64_t nrequests; }; /* * Read-only information associated with each element of tcache_t's tbins array * is stored separately, mainly to reduce memory usage. */ typedef struct cache_bin_info_s cache_bin_info_t; struct cache_bin_info_s { cache_bin_sz_t ncached_max; }; /* * Responsible for caching allocations associated with a single size. * * Several pointers are used to track the stack. To save on metadata bytes, * only the stack_head is a full sized pointer (which is dereferenced on the * fastpath), while the others store only the low 16 bits -- this is correct * because a single stack never takes more space than 2^16 bytes, and at the * same time only equality checks are performed on the low bits. * * (low addr) (high addr) * |------stashed------|------available------|------cached-----| * ^ ^ ^ ^ * low_bound(derived) low_bits_full stack_head low_bits_empty */ typedef struct cache_bin_s cache_bin_t; struct cache_bin_s { /* * The stack grows down. Whenever the bin is nonempty, the head points * to an array entry containing a valid allocation. When it is empty, * the head points to one element past the owned array. */ void **stack_head; /* * cur_ptr and stats are both modified frequently. Let's keep them * close so that they have a higher chance of being on the same * cacheline, thus less write-backs. */ cache_bin_stats_t tstats; /* * The low bits of the address of the first item in the stack that * hasn't been used since the last GC, to track the low water mark (min * # of cached items). * * Since the stack grows down, this is a higher address than * low_bits_full. */ uint16_t low_bits_low_water; /* * The low bits of the value that stack_head will take on when the array * is full (of cached & stashed items). But remember that stack_head * always points to a valid item when the array is nonempty -- this is * in the array. * * Recall that since the stack grows down, this is the lowest available * address in the array for caching. Only adjusted when stashing items. */ uint16_t low_bits_full; /* * The low bits of the value that stack_head will take on when the array * is empty. * * The stack grows down -- this is one past the highest address in the * array. Immutable after initialization. */ uint16_t low_bits_empty; }; /* * The cache_bins live inside the tcache, but the arena (by design) isn't * supposed to know much about tcache internals. To let the arena iterate over * associated bins, we keep (with the tcache) a linked list of * cache_bin_array_descriptor_ts that tell the arena how to find the bins. */ typedef struct cache_bin_array_descriptor_s cache_bin_array_descriptor_t; struct cache_bin_array_descriptor_s { /* * The arena keeps a list of the cache bins associated with it, for * stats collection. */ ql_elm(cache_bin_array_descriptor_t) link; /* Pointers to the tcache bins. */ cache_bin_t *bins; }; static inline void cache_bin_array_descriptor_init(cache_bin_array_descriptor_t *descriptor, cache_bin_t *bins) { ql_elm_new(descriptor, link); descriptor->bins = bins; } JEMALLOC_ALWAYS_INLINE bool cache_bin_nonfast_aligned(const void *ptr) { if (!config_uaf_detection) { return false; } /* * Currently we use alignment to decide which pointer to junk & stash on * dealloc (for catching use-after-free). In some common cases a * page-aligned check is needed already (sdalloc w/ config_prof), so we * are getting it more or less for free -- no added instructions on * free_fastpath. * * Another way of deciding which pointer to sample, is adding another * thread_event to pick one every N bytes. That also adds no cost on * the fastpath, however it will tend to pick large allocations which is * not the desired behavior. */ return ((uintptr_t)ptr & san_cache_bin_nonfast_mask) == 0; } /* Returns ncached_max: Upper limit on ncached. */ static inline cache_bin_sz_t cache_bin_info_ncached_max(cache_bin_info_t *info) { return info->ncached_max; } /* * Internal. * * Asserts that the pointer associated with earlier is <= the one associated * with later. */ static inline void cache_bin_assert_earlier(cache_bin_t *bin, uint16_t earlier, uint16_t later) { if (earlier > later) { assert(bin->low_bits_full > bin->low_bits_empty); } } /* * Internal. * * Does difference calculations that handle wraparound correctly. Earlier must * be associated with the position earlier in memory. */ static inline uint16_t cache_bin_diff(cache_bin_t *bin, uint16_t earlier, uint16_t later, bool racy) { /* * When it's racy, bin->low_bits_full can be modified concurrently. It * can cross the uint16_t max value and become less than * bin->low_bits_empty at the time of the check. */ if (!racy) { cache_bin_assert_earlier(bin, earlier, later); } return later - earlier; } /* * Number of items currently cached in the bin, without checking ncached_max. * We require specifying whether or not the request is racy or not (i.e. whether * or not concurrent modifications are possible). */ static inline cache_bin_sz_t cache_bin_ncached_get_internal(cache_bin_t *bin, bool racy) { cache_bin_sz_t diff = cache_bin_diff(bin, (uint16_t)(uintptr_t)bin->stack_head, bin->low_bits_empty, racy); cache_bin_sz_t n = diff / sizeof(void *); /* * We have undefined behavior here; if this function is called from the * arena stats updating code, then stack_head could change from the * first line to the next one. Morally, these loads should be atomic, * but compilers won't currently generate comparisons with in-memory * operands against atomics, and these variables get accessed on the * fast paths. This should still be "safe" in the sense of generating * the correct assembly for the foreseeable future, though. */ assert(n == 0 || *(bin->stack_head) != NULL || racy); return n; } /* * Number of items currently cached in the bin, with checking ncached_max. The * caller must know that no concurrent modification of the cache_bin is * possible. */ static inline cache_bin_sz_t cache_bin_ncached_get_local(cache_bin_t *bin, cache_bin_info_t *info) { cache_bin_sz_t n = cache_bin_ncached_get_internal(bin, /* racy */ false); assert(n <= cache_bin_info_ncached_max(info)); return n; } /* * Internal. * * A pointer to the position one past the end of the backing array. * * Do not call if racy, because both 'bin->stack_head' and 'bin->low_bits_full' * are subject to concurrent modifications. */ static inline void ** cache_bin_empty_position_get(cache_bin_t *bin) { cache_bin_sz_t diff = cache_bin_diff(bin, (uint16_t)(uintptr_t)bin->stack_head, bin->low_bits_empty, /* racy */ false); uintptr_t empty_bits = (uintptr_t)bin->stack_head + diff; void **ret = (void **)empty_bits; assert(ret >= bin->stack_head); return ret; } /* * Internal. * * Calculates low bits of the lower bound of the usable cache bin's range (see * cache_bin_t visual representation above). * * No values are concurrently modified, so should be safe to read in a * multithreaded environment. Currently concurrent access happens only during * arena statistics collection. */ static inline uint16_t cache_bin_low_bits_low_bound_get(cache_bin_t *bin, cache_bin_info_t *info) { return (uint16_t)bin->low_bits_empty - info->ncached_max * sizeof(void *); } /* * Internal. * * A pointer to the position with the lowest address of the backing array. */ static inline void ** cache_bin_low_bound_get(cache_bin_t *bin, cache_bin_info_t *info) { cache_bin_sz_t ncached_max = cache_bin_info_ncached_max(info); void **ret = cache_bin_empty_position_get(bin) - ncached_max; assert(ret <= bin->stack_head); return ret; } /* * As the name implies. This is important since it's not correct to try to * batch fill a nonempty cache bin. */ static inline void cache_bin_assert_empty(cache_bin_t *bin, cache_bin_info_t *info) { assert(cache_bin_ncached_get_local(bin, info) == 0); assert(cache_bin_empty_position_get(bin) == bin->stack_head); } /* * Get low water, but without any of the correctness checking we do for the * caller-usable version, if we are temporarily breaking invariants (like * ncached >= low_water during flush). */ static inline cache_bin_sz_t cache_bin_low_water_get_internal(cache_bin_t *bin) { return cache_bin_diff(bin, bin->low_bits_low_water, bin->low_bits_empty, /* racy */ false) / sizeof(void *); } /* Returns the numeric value of low water in [0, ncached]. */ static inline cache_bin_sz_t cache_bin_low_water_get(cache_bin_t *bin, cache_bin_info_t *info) { cache_bin_sz_t low_water = cache_bin_low_water_get_internal(bin); assert(low_water <= cache_bin_info_ncached_max(info)); assert(low_water <= cache_bin_ncached_get_local(bin, info)); cache_bin_assert_earlier(bin, (uint16_t)(uintptr_t)bin->stack_head, bin->low_bits_low_water); return low_water; } /* * Indicates that the current cache bin position should be the low water mark * going forward. */ static inline void cache_bin_low_water_set(cache_bin_t *bin) { bin->low_bits_low_water = (uint16_t)(uintptr_t)bin->stack_head; } static inline void cache_bin_low_water_adjust(cache_bin_t *bin) { if (cache_bin_ncached_get_internal(bin, /* racy */ false) < cache_bin_low_water_get_internal(bin)) { cache_bin_low_water_set(bin); } } JEMALLOC_ALWAYS_INLINE void * cache_bin_alloc_impl(cache_bin_t *bin, bool *success, bool adjust_low_water) { /* * success (instead of ret) should be checked upon the return of this * function. We avoid checking (ret == NULL) because there is never a * null stored on the avail stack (which is unknown to the compiler), * and eagerly checking ret would cause pipeline stall (waiting for the * cacheline). */ /* * This may read from the empty position; however the loaded value won't * be used. It's safe because the stack has one more slot reserved. */ void *ret = *bin->stack_head; uint16_t low_bits = (uint16_t)(uintptr_t)bin->stack_head; void **new_head = bin->stack_head + 1; /* * Note that the low water mark is at most empty; if we pass this check, * we know we're non-empty. */ if (likely(low_bits != bin->low_bits_low_water)) { bin->stack_head = new_head; *success = true; return ret; } if (!adjust_low_water) { *success = false; return NULL; } /* * In the fast-path case where we call alloc_easy and then alloc, the * previous checking and computation is optimized away -- we didn't * actually commit any of our operations. */ if (likely(low_bits != bin->low_bits_empty)) { bin->stack_head = new_head; bin->low_bits_low_water = (uint16_t)(uintptr_t)new_head; *success = true; return ret; } *success = false; return NULL; } /* * Allocate an item out of the bin, failing if we're at the low-water mark. */ JEMALLOC_ALWAYS_INLINE void * cache_bin_alloc_easy(cache_bin_t *bin, bool *success) { /* We don't look at info if we're not adjusting low-water. */ return cache_bin_alloc_impl(bin, success, false); } /* * Allocate an item out of the bin, even if we're currently at the low-water * mark (and failing only if the bin is empty). */ JEMALLOC_ALWAYS_INLINE void * cache_bin_alloc(cache_bin_t *bin, bool *success) { return cache_bin_alloc_impl(bin, success, true); } JEMALLOC_ALWAYS_INLINE cache_bin_sz_t cache_bin_alloc_batch(cache_bin_t *bin, size_t num, void **out) { cache_bin_sz_t n = cache_bin_ncached_get_internal(bin, /* racy */ false); if (n > num) { n = (cache_bin_sz_t)num; } memcpy(out, bin->stack_head, n * sizeof(void *)); bin->stack_head += n; cache_bin_low_water_adjust(bin); return n; } JEMALLOC_ALWAYS_INLINE bool cache_bin_full(cache_bin_t *bin) { return ((uint16_t)(uintptr_t)bin->stack_head == bin->low_bits_full); } /* * Free an object into the given bin. Fails only if the bin is full. */ JEMALLOC_ALWAYS_INLINE bool cache_bin_dalloc_easy(cache_bin_t *bin, void *ptr) { if (unlikely(cache_bin_full(bin))) { return false; } bin->stack_head--; *bin->stack_head = ptr; cache_bin_assert_earlier(bin, bin->low_bits_full, (uint16_t)(uintptr_t)bin->stack_head); return true; } /* Returns false if failed to stash (i.e. bin is full). */ JEMALLOC_ALWAYS_INLINE bool cache_bin_stash(cache_bin_t *bin, void *ptr) { if (cache_bin_full(bin)) { return false; } /* Stash at the full position, in the [full, head) range. */ uint16_t low_bits_head = (uint16_t)(uintptr_t)bin->stack_head; /* Wraparound handled as well. */ uint16_t diff = cache_bin_diff(bin, bin->low_bits_full, low_bits_head, /* racy */ false); *(void **)((uintptr_t)bin->stack_head - diff) = ptr; assert(!cache_bin_full(bin)); bin->low_bits_full += sizeof(void *); cache_bin_assert_earlier(bin, bin->low_bits_full, low_bits_head); return true; } /* * Get the number of stashed pointers. * * When called from a thread not owning the TLS (i.e. racy = true), it's * important to keep in mind that 'bin->stack_head' and 'bin->low_bits_full' can * be modified concurrently and almost none assertions about their values can be * made. */ JEMALLOC_ALWAYS_INLINE cache_bin_sz_t cache_bin_nstashed_get_internal(cache_bin_t *bin, cache_bin_info_t *info, bool racy) { cache_bin_sz_t ncached_max = cache_bin_info_ncached_max(info); uint16_t low_bits_low_bound = cache_bin_low_bits_low_bound_get(bin, info); cache_bin_sz_t n = cache_bin_diff(bin, low_bits_low_bound, bin->low_bits_full, racy) / sizeof(void *); assert(n <= ncached_max); if (!racy) { /* Below are for assertions only. */ void **low_bound = cache_bin_low_bound_get(bin, info); assert((uint16_t)(uintptr_t)low_bound == low_bits_low_bound); void *stashed = *(low_bound + n - 1); bool aligned = cache_bin_nonfast_aligned(stashed); #ifdef JEMALLOC_JET /* Allow arbitrary pointers to be stashed in tests. */ aligned = true; #endif assert(n == 0 || (stashed != NULL && aligned)); } return n; } JEMALLOC_ALWAYS_INLINE cache_bin_sz_t cache_bin_nstashed_get_local(cache_bin_t *bin, cache_bin_info_t *info) { cache_bin_sz_t n = cache_bin_nstashed_get_internal(bin, info, /* racy */ false); assert(n <= cache_bin_info_ncached_max(info)); return n; } /* * Obtain a racy view of the number of items currently in the cache bin, in the * presence of possible concurrent modifications. */ static inline void cache_bin_nitems_get_remote(cache_bin_t *bin, cache_bin_info_t *info, cache_bin_sz_t *ncached, cache_bin_sz_t *nstashed) { cache_bin_sz_t n = cache_bin_ncached_get_internal(bin, /* racy */ true); assert(n <= cache_bin_info_ncached_max(info)); *ncached = n; n = cache_bin_nstashed_get_internal(bin, info, /* racy */ true); assert(n <= cache_bin_info_ncached_max(info)); *nstashed = n; /* Note that cannot assert ncached + nstashed <= ncached_max (racy). */ } /* * Filling and flushing are done in batch, on arrays of void *s. For filling, * the arrays go forward, and can be accessed with ordinary array arithmetic. * For flushing, we work from the end backwards, and so need to use special * accessors that invert the usual ordering. * * This is important for maintaining first-fit; the arena code fills with * earliest objects first, and so those are the ones we should return first for * cache_bin_alloc calls. When flushing, we should flush the objects that we * wish to return later; those at the end of the array. This is better for the * first-fit heuristic as well as for cache locality; the most recently freed * objects are the ones most likely to still be in cache. * * This all sounds very hand-wavey and theoretical, but reverting the ordering * on one or the other pathway leads to measurable slowdowns. */ typedef struct cache_bin_ptr_array_s cache_bin_ptr_array_t; struct cache_bin_ptr_array_s { cache_bin_sz_t n; void **ptr; }; /* * Declare a cache_bin_ptr_array_t sufficient for nval items. * * In the current implementation, this could be just part of a * cache_bin_ptr_array_init_... call, since we reuse the cache bin stack memory. * Indirecting behind a macro, though, means experimenting with linked-list * representations is easy (since they'll require an alloca in the calling * frame). */ #define CACHE_BIN_PTR_ARRAY_DECLARE(name, nval) \ cache_bin_ptr_array_t name; \ name.n = (nval) /* * Start a fill. The bin must be empty, and This must be followed by a * finish_fill call before doing any alloc/dalloc operations on the bin. */ static inline void cache_bin_init_ptr_array_for_fill(cache_bin_t *bin, cache_bin_info_t *info, cache_bin_ptr_array_t *arr, cache_bin_sz_t nfill) { cache_bin_assert_empty(bin, info); arr->ptr = cache_bin_empty_position_get(bin) - nfill; } /* * While nfill in cache_bin_init_ptr_array_for_fill is the number we *intend* to * fill, nfilled here is the number we actually filled (which may be less, in * case of OOM. */ static inline void cache_bin_finish_fill(cache_bin_t *bin, cache_bin_info_t *info, cache_bin_ptr_array_t *arr, cache_bin_sz_t nfilled) { cache_bin_assert_empty(bin, info); void **empty_position = cache_bin_empty_position_get(bin); if (nfilled < arr->n) { memmove(empty_position - nfilled, empty_position - arr->n, nfilled * sizeof(void *)); } bin->stack_head = empty_position - nfilled; } /* * Same deal, but with flush. Unlike fill (which can fail), the user must flush * everything we give them. */ static inline void cache_bin_init_ptr_array_for_flush(cache_bin_t *bin, cache_bin_info_t *info, cache_bin_ptr_array_t *arr, cache_bin_sz_t nflush) { arr->ptr = cache_bin_empty_position_get(bin) - nflush; assert(cache_bin_ncached_get_local(bin, info) == 0 || *arr->ptr != NULL); } static inline void cache_bin_finish_flush(cache_bin_t *bin, cache_bin_info_t *info, cache_bin_ptr_array_t *arr, cache_bin_sz_t nflushed) { unsigned rem = cache_bin_ncached_get_local(bin, info) - nflushed; memmove(bin->stack_head + nflushed, bin->stack_head, rem * sizeof(void *)); bin->stack_head = bin->stack_head + nflushed; cache_bin_low_water_adjust(bin); } static inline void cache_bin_init_ptr_array_for_stashed(cache_bin_t *bin, szind_t binind, cache_bin_info_t *info, cache_bin_ptr_array_t *arr, cache_bin_sz_t nstashed) { assert(nstashed > 0); assert(cache_bin_nstashed_get_local(bin, info) == nstashed); void **low_bound = cache_bin_low_bound_get(bin, info); arr->ptr = low_bound; assert(*arr->ptr != NULL); } static inline void cache_bin_finish_flush_stashed(cache_bin_t *bin, cache_bin_info_t *info) { void **low_bound = cache_bin_low_bound_get(bin, info); /* Reset the bin local full position. */ bin->low_bits_full = (uint16_t)(uintptr_t)low_bound; assert(cache_bin_nstashed_get_local(bin, info) == 0); } /* * Initialize a cache_bin_info to represent up to the given number of items in * the cache_bins it is associated with. */ void cache_bin_info_init(cache_bin_info_t *bin_info, cache_bin_sz_t ncached_max); /* * Given an array of initialized cache_bin_info_ts, determine how big an * allocation is required to initialize a full set of cache_bin_ts. */ void cache_bin_info_compute_alloc(cache_bin_info_t *infos, szind_t ninfos, size_t *size, size_t *alignment); /* * Actually initialize some cache bins. Callers should allocate the backing * memory indicated by a call to cache_bin_compute_alloc. They should then * preincrement, call init once for each bin and info, and then call * cache_bin_postincrement. *alloc_cur will then point immediately past the end * of the allocation. */ void cache_bin_preincrement(cache_bin_info_t *infos, szind_t ninfos, void *alloc, size_t *cur_offset); void cache_bin_postincrement(cache_bin_info_t *infos, szind_t ninfos, void *alloc, size_t *cur_offset); void cache_bin_init(cache_bin_t *bin, cache_bin_info_t *info, void *alloc, size_t *cur_offset); /* * If a cache bin was zero initialized (either because it lives in static or * thread-local storage, or was memset to 0), this function indicates whether or * not cache_bin_init was called on it. */ bool cache_bin_still_zero_initialized(cache_bin_t *bin); #endif /* JEMALLOC_INTERNAL_CACHE_BIN_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ckh.h000066400000000000000000000063011501533116600233140ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_CKH_H #define JEMALLOC_INTERNAL_CKH_H #include "jemalloc/internal/tsd.h" /* Cuckoo hashing implementation. Skip to the end for the interface. */ /******************************************************************************/ /* INTERNAL DEFINITIONS -- IGNORE */ /******************************************************************************/ /* Maintain counters used to get an idea of performance. */ /* #define CKH_COUNT */ /* Print counter values in ckh_delete() (requires CKH_COUNT). */ /* #define CKH_VERBOSE */ /* * There are 2^LG_CKH_BUCKET_CELLS cells in each hash table bucket. Try to fit * one bucket per L1 cache line. */ #define LG_CKH_BUCKET_CELLS (LG_CACHELINE - LG_SIZEOF_PTR - 1) /* Typedefs to allow easy function pointer passing. */ typedef void ckh_hash_t (const void *, size_t[2]); typedef bool ckh_keycomp_t (const void *, const void *); /* Hash table cell. */ typedef struct { const void *key; const void *data; } ckhc_t; /* The hash table itself. */ typedef struct { #ifdef CKH_COUNT /* Counters used to get an idea of performance. */ uint64_t ngrows; uint64_t nshrinks; uint64_t nshrinkfails; uint64_t ninserts; uint64_t nrelocs; #endif /* Used for pseudo-random number generation. */ uint64_t prng_state; /* Total number of items. */ size_t count; /* * Minimum and current number of hash table buckets. There are * 2^LG_CKH_BUCKET_CELLS cells per bucket. */ unsigned lg_minbuckets; unsigned lg_curbuckets; /* Hash and comparison functions. */ ckh_hash_t *hash; ckh_keycomp_t *keycomp; /* Hash table with 2^lg_curbuckets buckets. */ ckhc_t *tab; } ckh_t; /******************************************************************************/ /* BEGIN PUBLIC API */ /******************************************************************************/ /* Lifetime management. Minitems is the initial capacity. */ bool ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *hash, ckh_keycomp_t *keycomp); void ckh_delete(tsd_t *tsd, ckh_t *ckh); /* Get the number of elements in the set. */ size_t ckh_count(ckh_t *ckh); /* * To iterate over the elements in the table, initialize *tabind to 0 and call * this function until it returns true. Each call that returns false will * update *key and *data to the next element in the table, assuming the pointers * are non-NULL. */ bool ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data); /* * Basic hash table operations -- insert, removal, lookup. For ckh_remove and * ckh_search, key or data can be NULL. The hash-table only stores pointers to * the key and value, and doesn't do any lifetime management. */ bool ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data); bool ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key, void **data); bool ckh_search(ckh_t *ckh, const void *searchkey, void **key, void **data); /* Some useful hash and comparison functions for strings and pointers. */ void ckh_string_hash(const void *key, size_t r_hash[2]); bool ckh_string_keycomp(const void *k1, const void *k2); void ckh_pointer_hash(const void *key, size_t r_hash[2]); bool ckh_pointer_keycomp(const void *k1, const void *k2); #endif /* JEMALLOC_INTERNAL_CKH_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/counter.h000066400000000000000000000022141501533116600242250ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_COUNTER_H #define JEMALLOC_INTERNAL_COUNTER_H #include "jemalloc/internal/mutex.h" typedef struct counter_accum_s { LOCKEDINT_MTX_DECLARE(mtx) locked_u64_t accumbytes; uint64_t interval; } counter_accum_t; JEMALLOC_ALWAYS_INLINE bool counter_accum(tsdn_t *tsdn, counter_accum_t *counter, uint64_t bytes) { uint64_t interval = counter->interval; assert(interval > 0); LOCKEDINT_MTX_LOCK(tsdn, counter->mtx); /* * If the event moves fast enough (and/or if the event handling is slow * enough), extreme overflow can cause counter trigger coalescing. * This is an intentional mechanism that avoids rate-limiting * allocation. */ bool overflow = locked_inc_mod_u64(tsdn, LOCKEDINT_MTX(counter->mtx), &counter->accumbytes, bytes, interval); LOCKEDINT_MTX_UNLOCK(tsdn, counter->mtx); return overflow; } bool counter_accum_init(counter_accum_t *counter, uint64_t interval); void counter_prefork(tsdn_t *tsdn, counter_accum_t *counter); void counter_postfork_parent(tsdn_t *tsdn, counter_accum_t *counter); void counter_postfork_child(tsdn_t *tsdn, counter_accum_t *counter); #endif /* JEMALLOC_INTERNAL_COUNTER_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ctl.h000066400000000000000000000110511501533116600233270ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_CTL_H #define JEMALLOC_INTERNAL_CTL_H #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/mutex_prof.h" #include "jemalloc/internal/ql.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/stats.h" /* Maximum ctl tree depth. */ #define CTL_MAX_DEPTH 7 typedef struct ctl_node_s { bool named; } ctl_node_t; typedef struct ctl_named_node_s { ctl_node_t node; const char *name; /* If (nchildren == 0), this is a terminal node. */ size_t nchildren; const ctl_node_t *children; int (*ctl)(tsd_t *, const size_t *, size_t, void *, size_t *, void *, size_t); } ctl_named_node_t; typedef struct ctl_indexed_node_s { struct ctl_node_s node; const ctl_named_node_t *(*index)(tsdn_t *, const size_t *, size_t, size_t); } ctl_indexed_node_t; typedef struct ctl_arena_stats_s { arena_stats_t astats; /* Aggregate stats for small size classes, based on bin stats. */ size_t allocated_small; uint64_t nmalloc_small; uint64_t ndalloc_small; uint64_t nrequests_small; uint64_t nfills_small; uint64_t nflushes_small; bin_stats_data_t bstats[SC_NBINS]; arena_stats_large_t lstats[SC_NSIZES - SC_NBINS]; pac_estats_t estats[SC_NPSIZES]; hpa_shard_stats_t hpastats; sec_stats_t secstats; } ctl_arena_stats_t; typedef struct ctl_stats_s { size_t allocated; size_t active; size_t metadata; size_t metadata_thp; size_t resident; size_t mapped; size_t retained; background_thread_stats_t background_thread; mutex_prof_data_t mutex_prof_data[mutex_prof_num_global_mutexes]; } ctl_stats_t; typedef struct ctl_arena_s ctl_arena_t; struct ctl_arena_s { unsigned arena_ind; bool initialized; ql_elm(ctl_arena_t) destroyed_link; /* Basic stats, supported even if !config_stats. */ unsigned nthreads; const char *dss; ssize_t dirty_decay_ms; ssize_t muzzy_decay_ms; size_t pactive; size_t pdirty; size_t pmuzzy; /* NULL if !config_stats. */ ctl_arena_stats_t *astats; }; typedef struct ctl_arenas_s { uint64_t epoch; unsigned narenas; ql_head(ctl_arena_t) destroyed; /* * Element 0 corresponds to merged stats for extant arenas (accessed via * MALLCTL_ARENAS_ALL), element 1 corresponds to merged stats for * destroyed arenas (accessed via MALLCTL_ARENAS_DESTROYED), and the * remaining MALLOCX_ARENA_LIMIT elements correspond to arenas. */ ctl_arena_t *arenas[2 + MALLOCX_ARENA_LIMIT]; } ctl_arenas_t; int ctl_byname(tsd_t *tsd, const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen); int ctl_nametomib(tsd_t *tsd, const char *name, size_t *mibp, size_t *miblenp); int ctl_bymib(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen); int ctl_mibnametomib(tsd_t *tsd, size_t *mib, size_t miblen, const char *name, size_t *miblenp); int ctl_bymibname(tsd_t *tsd, size_t *mib, size_t miblen, const char *name, size_t *miblenp, void *oldp, size_t *oldlenp, void *newp, size_t newlen); bool ctl_boot(void); void ctl_prefork(tsdn_t *tsdn); void ctl_postfork_parent(tsdn_t *tsdn); void ctl_postfork_child(tsdn_t *tsdn); void ctl_mtx_assert_held(tsdn_t *tsdn); #define xmallctl(name, oldp, oldlenp, newp, newlen) do { \ if (je_mallctl(name, oldp, oldlenp, newp, newlen) \ != 0) { \ malloc_printf( \ ": Failure in xmallctl(\"%s\", ...)\n", \ name); \ abort(); \ } \ } while (0) #define xmallctlnametomib(name, mibp, miblenp) do { \ if (je_mallctlnametomib(name, mibp, miblenp) != 0) { \ malloc_printf(": Failure in " \ "xmallctlnametomib(\"%s\", ...)\n", name); \ abort(); \ } \ } while (0) #define xmallctlbymib(mib, miblen, oldp, oldlenp, newp, newlen) do { \ if (je_mallctlbymib(mib, miblen, oldp, oldlenp, newp, \ newlen) != 0) { \ malloc_write( \ ": Failure in xmallctlbymib()\n"); \ abort(); \ } \ } while (0) #define xmallctlmibnametomib(mib, miblen, name, miblenp) do { \ if (ctl_mibnametomib(tsd_fetch(), mib, miblen, name, miblenp) \ != 0) { \ malloc_write( \ ": Failure in ctl_mibnametomib()\n"); \ abort(); \ } \ } while (0) #define xmallctlbymibname(mib, miblen, name, miblenp, oldp, oldlenp, \ newp, newlen) do { \ if (ctl_bymibname(tsd_fetch(), mib, miblen, name, miblenp, \ oldp, oldlenp, newp, newlen) != 0) { \ malloc_write( \ ": Failure in ctl_bymibname()\n"); \ abort(); \ } \ } while (0) #endif /* JEMALLOC_INTERNAL_CTL_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/decay.h000066400000000000000000000141571501533116600236440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_DECAY_H #define JEMALLOC_INTERNAL_DECAY_H #include "jemalloc/internal/smoothstep.h" #define DECAY_UNBOUNDED_TIME_TO_PURGE ((uint64_t)-1) /* * The decay_t computes the number of pages we should purge at any given time. * Page allocators inform a decay object when pages enter a decay-able state * (i.e. dirty or muzzy), and query it to determine how many pages should be * purged at any given time. * * This is mostly a single-threaded data structure and doesn't care about * synchronization at all; it's the caller's responsibility to manage their * synchronization on their own. There are two exceptions: * 1) It's OK to racily call decay_ms_read (i.e. just the simplest state query). * 2) The mtx and purging fields live (and are initialized) here, but are * logically owned by the page allocator. This is just a convenience (since * those fields would be duplicated for both the dirty and muzzy states * otherwise). */ typedef struct decay_s decay_t; struct decay_s { /* Synchronizes all non-atomic fields. */ malloc_mutex_t mtx; /* * True if a thread is currently purging the extents associated with * this decay structure. */ bool purging; /* * Approximate time in milliseconds from the creation of a set of unused * dirty pages until an equivalent set of unused dirty pages is purged * and/or reused. */ atomic_zd_t time_ms; /* time / SMOOTHSTEP_NSTEPS. */ nstime_t interval; /* * Time at which the current decay interval logically started. We do * not actually advance to a new epoch until sometime after it starts * because of scheduling and computation delays, and it is even possible * to completely skip epochs. In all cases, during epoch advancement we * merge all relevant activity into the most recently recorded epoch. */ nstime_t epoch; /* Deadline randomness generator. */ uint64_t jitter_state; /* * Deadline for current epoch. This is the sum of interval and per * epoch jitter which is a uniform random variable in [0..interval). * Epochs always advance by precise multiples of interval, but we * randomize the deadline to reduce the likelihood of arenas purging in * lockstep. */ nstime_t deadline; /* * The number of pages we cap ourselves at in the current epoch, per * decay policies. Updated on an epoch change. After an epoch change, * the caller should take steps to try to purge down to this amount. */ size_t npages_limit; /* * Number of unpurged pages at beginning of current epoch. During epoch * advancement we use the delta between arena->decay_*.nunpurged and * ecache_npages_get(&arena->ecache_*) to determine how many dirty pages, * if any, were generated. */ size_t nunpurged; /* * Trailing log of how many unused dirty pages were generated during * each of the past SMOOTHSTEP_NSTEPS decay epochs, where the last * element is the most recent epoch. Corresponding epoch times are * relative to epoch. * * Updated only on epoch advance, triggered by * decay_maybe_advance_epoch, below. */ size_t backlog[SMOOTHSTEP_NSTEPS]; /* Peak number of pages in associated extents. Used for debug only. */ uint64_t ceil_npages; }; /* * The current decay time setting. This is the only public access to a decay_t * that's allowed without holding mtx. */ static inline ssize_t decay_ms_read(const decay_t *decay) { return atomic_load_zd(&decay->time_ms, ATOMIC_RELAXED); } /* * See the comment on the struct field -- the limit on pages we should allow in * this decay state this epoch. */ static inline size_t decay_npages_limit_get(const decay_t *decay) { return decay->npages_limit; } /* How many unused dirty pages were generated during the last epoch. */ static inline size_t decay_epoch_npages_delta(const decay_t *decay) { return decay->backlog[SMOOTHSTEP_NSTEPS - 1]; } /* * Current epoch duration, in nanoseconds. Given that new epochs are started * somewhat haphazardly, this is not necessarily exactly the time between any * two calls to decay_maybe_advance_epoch; see the comments on fields in the * decay_t. */ static inline uint64_t decay_epoch_duration_ns(const decay_t *decay) { return nstime_ns(&decay->interval); } static inline bool decay_immediately(const decay_t *decay) { ssize_t decay_ms = decay_ms_read(decay); return decay_ms == 0; } static inline bool decay_disabled(const decay_t *decay) { ssize_t decay_ms = decay_ms_read(decay); return decay_ms < 0; } /* Returns true if decay is enabled and done gradually. */ static inline bool decay_gradually(const decay_t *decay) { ssize_t decay_ms = decay_ms_read(decay); return decay_ms > 0; } /* * Returns true if the passed in decay time setting is valid. * < -1 : invalid * -1 : never decay * 0 : decay immediately * > 0 : some positive decay time, up to a maximum allowed value of * NSTIME_SEC_MAX * 1000, which corresponds to decaying somewhere in the early * 27th century. By that time, we expect to have implemented alternate purging * strategies. */ bool decay_ms_valid(ssize_t decay_ms); /* * As a precondition, the decay_t must be zeroed out (as if with memset). * * Returns true on error. */ bool decay_init(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms); /* * Given an already-initialized decay_t, reinitialize it with the given decay * time. The decay_t must have previously been initialized (and should not then * be zeroed). */ void decay_reinit(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms); /* * Compute how many of 'npages_new' pages we would need to purge in 'time'. */ uint64_t decay_npages_purge_in(decay_t *decay, nstime_t *time, size_t npages_new); /* Returns true if the epoch advanced and there are pages to purge. */ bool decay_maybe_advance_epoch(decay_t *decay, nstime_t *new_time, size_t current_npages); /* * Calculates wait time until a number of pages in the interval * [0.5 * npages_threshold .. 1.5 * npages_threshold] should be purged. * * Returns number of nanoseconds or DECAY_UNBOUNDED_TIME_TO_PURGE in case of * indefinite wait. */ uint64_t decay_ns_until_purge(decay_t *decay, size_t npages_current, uint64_t npages_threshold); #endif /* JEMALLOC_INTERNAL_DECAY_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/div.h000066400000000000000000000022241501533116600233310ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_DIV_H #define JEMALLOC_INTERNAL_DIV_H #include "jemalloc/internal/assert.h" /* * This module does the division that computes the index of a region in a slab, * given its offset relative to the base. * That is, given a divisor d, an n = i * d (all integers), we'll return i. * We do some pre-computation to do this more quickly than a CPU division * instruction. * We bound n < 2^32, and don't support dividing by one. */ typedef struct div_info_s div_info_t; struct div_info_s { uint32_t magic; #ifdef JEMALLOC_DEBUG size_t d; #endif }; void div_init(div_info_t *div_info, size_t divisor); static inline size_t div_compute(div_info_t *div_info, size_t n) { assert(n <= (uint32_t)-1); /* * This generates, e.g. mov; imul; shr on x86-64. On a 32-bit machine, * the compilers I tried were all smart enough to turn this into the * appropriate "get the high 32 bits of the result of a multiply" (e.g. * mul; mov edx eax; on x86, umull on arm, etc.). */ size_t i = ((uint64_t)n * (uint64_t)div_info->magic) >> 32; #ifdef JEMALLOC_DEBUG assert(i * div_info->d == n); #endif return i; } #endif /* JEMALLOC_INTERNAL_DIV_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ecache.h000066400000000000000000000031121501533116600237540ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ECACHE_H #define JEMALLOC_INTERNAL_ECACHE_H #include "jemalloc/internal/eset.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/mutex.h" typedef struct ecache_s ecache_t; struct ecache_s { malloc_mutex_t mtx; eset_t eset; eset_t guarded_eset; /* All stored extents must be in the same state. */ extent_state_t state; /* The index of the ehooks the ecache is associated with. */ unsigned ind; /* * If true, delay coalescing until eviction; otherwise coalesce during * deallocation. */ bool delay_coalesce; }; static inline size_t ecache_npages_get(ecache_t *ecache) { return eset_npages_get(&ecache->eset) + eset_npages_get(&ecache->guarded_eset); } /* Get the number of extents in the given page size index. */ static inline size_t ecache_nextents_get(ecache_t *ecache, pszind_t ind) { return eset_nextents_get(&ecache->eset, ind) + eset_nextents_get(&ecache->guarded_eset, ind); } /* Get the sum total bytes of the extents in the given page size index. */ static inline size_t ecache_nbytes_get(ecache_t *ecache, pszind_t ind) { return eset_nbytes_get(&ecache->eset, ind) + eset_nbytes_get(&ecache->guarded_eset, ind); } static inline unsigned ecache_ind_get(ecache_t *ecache) { return ecache->ind; } bool ecache_init(tsdn_t *tsdn, ecache_t *ecache, extent_state_t state, unsigned ind, bool delay_coalesce); void ecache_prefork(tsdn_t *tsdn, ecache_t *ecache); void ecache_postfork_parent(tsdn_t *tsdn, ecache_t *ecache); void ecache_postfork_child(tsdn_t *tsdn, ecache_t *ecache); #endif /* JEMALLOC_INTERNAL_ECACHE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/edata.h000066400000000000000000000506771501533116600236440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EDATA_H #define JEMALLOC_INTERNAL_EDATA_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/bin_info.h" #include "jemalloc/internal/bit_util.h" #include "jemalloc/internal/hpdata.h" #include "jemalloc/internal/nstime.h" #include "jemalloc/internal/ph.h" #include "jemalloc/internal/ql.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/slab_data.h" #include "jemalloc/internal/sz.h" #include "jemalloc/internal/typed_list.h" /* * sizeof(edata_t) is 128 bytes on 64-bit architectures. Ensure the alignment * to free up the low bits in the rtree leaf. */ #define EDATA_ALIGNMENT 128 enum extent_state_e { extent_state_active = 0, extent_state_dirty = 1, extent_state_muzzy = 2, extent_state_retained = 3, extent_state_transition = 4, /* States below are intermediate. */ extent_state_merging = 5, extent_state_max = 5 /* Sanity checking only. */ }; typedef enum extent_state_e extent_state_t; enum extent_head_state_e { EXTENT_NOT_HEAD, EXTENT_IS_HEAD /* See comments in ehooks_default_merge_impl(). */ }; typedef enum extent_head_state_e extent_head_state_t; /* * Which implementation of the page allocator interface, (PAI, defined in * pai.h) owns the given extent? */ enum extent_pai_e { EXTENT_PAI_PAC = 0, EXTENT_PAI_HPA = 1 }; typedef enum extent_pai_e extent_pai_t; struct e_prof_info_s { /* Time when this was allocated. */ nstime_t e_prof_alloc_time; /* Allocation request size. */ size_t e_prof_alloc_size; /* Points to a prof_tctx_t. */ atomic_p_t e_prof_tctx; /* * Points to a prof_recent_t for the allocation; NULL * means the recent allocation record no longer exists. * Protected by prof_recent_alloc_mtx. */ atomic_p_t e_prof_recent_alloc; }; typedef struct e_prof_info_s e_prof_info_t; /* * The information about a particular edata that lives in an emap. Space is * more precious there (the information, plus the edata pointer, has to live in * a 64-bit word if we want to enable a packed representation. * * There are two things that are special about the information here: * - It's quicker to access. You have one fewer pointer hop, since finding the * edata_t associated with an item always requires accessing the rtree leaf in * which this data is stored. * - It can be read unsynchronized, and without worrying about lifetime issues. */ typedef struct edata_map_info_s edata_map_info_t; struct edata_map_info_s { bool slab; szind_t szind; }; typedef struct edata_cmp_summary_s edata_cmp_summary_t; struct edata_cmp_summary_s { uint64_t sn; uintptr_t addr; }; /* Extent (span of pages). Use accessor functions for e_* fields. */ typedef struct edata_s edata_t; ph_structs(edata_avail, edata_t); ph_structs(edata_heap, edata_t); struct edata_s { /* * Bitfield containing several fields: * * a: arena_ind * b: slab * c: committed * p: pai * z: zeroed * g: guarded * t: state * i: szind * f: nfree * s: bin_shard * * 00000000 ... 0000ssss ssffffff ffffiiii iiiitttg zpcbaaaa aaaaaaaa * * arena_ind: Arena from which this extent came, or all 1 bits if * unassociated. * * slab: The slab flag indicates whether the extent is used for a slab * of small regions. This helps differentiate small size classes, * and it indicates whether interior pointers can be looked up via * iealloc(). * * committed: The committed flag indicates whether physical memory is * committed to the extent, whether explicitly or implicitly * as on a system that overcommits and satisfies physical * memory needs on demand via soft page faults. * * pai: The pai flag is an extent_pai_t. * * zeroed: The zeroed flag is used by extent recycling code to track * whether memory is zero-filled. * * guarded: The guarded flag is use by the sanitizer to track whether * the extent has page guards around it. * * state: The state flag is an extent_state_t. * * szind: The szind flag indicates usable size class index for * allocations residing in this extent, regardless of whether the * extent is a slab. Extent size and usable size often differ * even for non-slabs, either due to sz_large_pad or promotion of * sampled small regions. * * nfree: Number of free regions in slab. * * bin_shard: the shard of the bin from which this extent came. */ uint64_t e_bits; #define MASK(CURRENT_FIELD_WIDTH, CURRENT_FIELD_SHIFT) ((((((uint64_t)0x1U) << (CURRENT_FIELD_WIDTH)) - 1)) << (CURRENT_FIELD_SHIFT)) #define EDATA_BITS_ARENA_WIDTH MALLOCX_ARENA_BITS #define EDATA_BITS_ARENA_SHIFT 0 #define EDATA_BITS_ARENA_MASK MASK(EDATA_BITS_ARENA_WIDTH, EDATA_BITS_ARENA_SHIFT) #define EDATA_BITS_SLAB_WIDTH 1 #define EDATA_BITS_SLAB_SHIFT (EDATA_BITS_ARENA_WIDTH + EDATA_BITS_ARENA_SHIFT) #define EDATA_BITS_SLAB_MASK MASK(EDATA_BITS_SLAB_WIDTH, EDATA_BITS_SLAB_SHIFT) #define EDATA_BITS_COMMITTED_WIDTH 1 #define EDATA_BITS_COMMITTED_SHIFT (EDATA_BITS_SLAB_WIDTH + EDATA_BITS_SLAB_SHIFT) #define EDATA_BITS_COMMITTED_MASK MASK(EDATA_BITS_COMMITTED_WIDTH, EDATA_BITS_COMMITTED_SHIFT) #define EDATA_BITS_PAI_WIDTH 1 #define EDATA_BITS_PAI_SHIFT (EDATA_BITS_COMMITTED_WIDTH + EDATA_BITS_COMMITTED_SHIFT) #define EDATA_BITS_PAI_MASK MASK(EDATA_BITS_PAI_WIDTH, EDATA_BITS_PAI_SHIFT) #define EDATA_BITS_ZEROED_WIDTH 1 #define EDATA_BITS_ZEROED_SHIFT (EDATA_BITS_PAI_WIDTH + EDATA_BITS_PAI_SHIFT) #define EDATA_BITS_ZEROED_MASK MASK(EDATA_BITS_ZEROED_WIDTH, EDATA_BITS_ZEROED_SHIFT) #define EDATA_BITS_GUARDED_WIDTH 1 #define EDATA_BITS_GUARDED_SHIFT (EDATA_BITS_ZEROED_WIDTH + EDATA_BITS_ZEROED_SHIFT) #define EDATA_BITS_GUARDED_MASK MASK(EDATA_BITS_GUARDED_WIDTH, EDATA_BITS_GUARDED_SHIFT) #define EDATA_BITS_STATE_WIDTH 3 #define EDATA_BITS_STATE_SHIFT (EDATA_BITS_GUARDED_WIDTH + EDATA_BITS_GUARDED_SHIFT) #define EDATA_BITS_STATE_MASK MASK(EDATA_BITS_STATE_WIDTH, EDATA_BITS_STATE_SHIFT) #define EDATA_BITS_SZIND_WIDTH LG_CEIL(SC_NSIZES) #define EDATA_BITS_SZIND_SHIFT (EDATA_BITS_STATE_WIDTH + EDATA_BITS_STATE_SHIFT) #define EDATA_BITS_SZIND_MASK MASK(EDATA_BITS_SZIND_WIDTH, EDATA_BITS_SZIND_SHIFT) #define EDATA_BITS_NFREE_WIDTH (SC_LG_SLAB_MAXREGS + 1) #define EDATA_BITS_NFREE_SHIFT (EDATA_BITS_SZIND_WIDTH + EDATA_BITS_SZIND_SHIFT) #define EDATA_BITS_NFREE_MASK MASK(EDATA_BITS_NFREE_WIDTH, EDATA_BITS_NFREE_SHIFT) #define EDATA_BITS_BINSHARD_WIDTH 6 #define EDATA_BITS_BINSHARD_SHIFT (EDATA_BITS_NFREE_WIDTH + EDATA_BITS_NFREE_SHIFT) #define EDATA_BITS_BINSHARD_MASK MASK(EDATA_BITS_BINSHARD_WIDTH, EDATA_BITS_BINSHARD_SHIFT) #define EDATA_BITS_IS_HEAD_WIDTH 1 #define EDATA_BITS_IS_HEAD_SHIFT (EDATA_BITS_BINSHARD_WIDTH + EDATA_BITS_BINSHARD_SHIFT) #define EDATA_BITS_IS_HEAD_MASK MASK(EDATA_BITS_IS_HEAD_WIDTH, EDATA_BITS_IS_HEAD_SHIFT) /* Pointer to the extent that this structure is responsible for. */ void *e_addr; union { /* * Extent size and serial number associated with the extent * structure (different than the serial number for the extent at * e_addr). * * ssssssss [...] ssssssss ssssnnnn nnnnnnnn */ size_t e_size_esn; #define EDATA_SIZE_MASK ((size_t)~(PAGE-1)) #define EDATA_ESN_MASK ((size_t)PAGE-1) /* Base extent size, which may not be a multiple of PAGE. */ size_t e_bsize; }; /* * If this edata is a user allocation from an HPA, it comes out of some * pageslab (we don't yet support huegpage allocations that don't fit * into pageslabs). This tracks it. */ hpdata_t *e_ps; /* * Serial number. These are not necessarily unique; splitting an extent * results in two extents with the same serial number. */ uint64_t e_sn; union { /* * List linkage used when the edata_t is active; either in * arena's large allocations or bin_t's slabs_full. */ ql_elm(edata_t) ql_link_active; /* * Pairing heap linkage. Used whenever the extent is inactive * (in the page allocators), or when it is active and in * slabs_nonfull, or when the edata_t is unassociated with an * extent and sitting in an edata_cache. */ union { edata_heap_link_t heap_link; edata_avail_link_t avail_link; }; }; union { /* * List linkage used when the extent is inactive: * - Stashed dirty extents * - Ecache LRU functionality. */ ql_elm(edata_t) ql_link_inactive; /* Small region slab metadata. */ slab_data_t e_slab_data; /* Profiling data, used for large objects. */ e_prof_info_t e_prof_info; }; }; TYPED_LIST(edata_list_active, edata_t, ql_link_active) TYPED_LIST(edata_list_inactive, edata_t, ql_link_inactive) static inline unsigned edata_arena_ind_get(const edata_t *edata) { unsigned arena_ind = (unsigned)((edata->e_bits & EDATA_BITS_ARENA_MASK) >> EDATA_BITS_ARENA_SHIFT); assert(arena_ind < MALLOCX_ARENA_LIMIT); return arena_ind; } static inline szind_t edata_szind_get_maybe_invalid(const edata_t *edata) { szind_t szind = (szind_t)((edata->e_bits & EDATA_BITS_SZIND_MASK) >> EDATA_BITS_SZIND_SHIFT); assert(szind <= SC_NSIZES); return szind; } static inline szind_t edata_szind_get(const edata_t *edata) { szind_t szind = edata_szind_get_maybe_invalid(edata); assert(szind < SC_NSIZES); /* Never call when "invalid". */ return szind; } static inline size_t edata_usize_get(const edata_t *edata) { return sz_index2size(edata_szind_get(edata)); } static inline unsigned edata_binshard_get(const edata_t *edata) { unsigned binshard = (unsigned)((edata->e_bits & EDATA_BITS_BINSHARD_MASK) >> EDATA_BITS_BINSHARD_SHIFT); assert(binshard < bin_infos[edata_szind_get(edata)].n_shards); return binshard; } static inline uint64_t edata_sn_get(const edata_t *edata) { return edata->e_sn; } static inline extent_state_t edata_state_get(const edata_t *edata) { return (extent_state_t)((edata->e_bits & EDATA_BITS_STATE_MASK) >> EDATA_BITS_STATE_SHIFT); } static inline bool edata_guarded_get(const edata_t *edata) { return (bool)((edata->e_bits & EDATA_BITS_GUARDED_MASK) >> EDATA_BITS_GUARDED_SHIFT); } static inline bool edata_zeroed_get(const edata_t *edata) { return (bool)((edata->e_bits & EDATA_BITS_ZEROED_MASK) >> EDATA_BITS_ZEROED_SHIFT); } static inline bool edata_committed_get(const edata_t *edata) { return (bool)((edata->e_bits & EDATA_BITS_COMMITTED_MASK) >> EDATA_BITS_COMMITTED_SHIFT); } static inline extent_pai_t edata_pai_get(const edata_t *edata) { return (extent_pai_t)((edata->e_bits & EDATA_BITS_PAI_MASK) >> EDATA_BITS_PAI_SHIFT); } static inline bool edata_slab_get(const edata_t *edata) { return (bool)((edata->e_bits & EDATA_BITS_SLAB_MASK) >> EDATA_BITS_SLAB_SHIFT); } static inline unsigned edata_nfree_get(const edata_t *edata) { assert(edata_slab_get(edata)); return (unsigned)((edata->e_bits & EDATA_BITS_NFREE_MASK) >> EDATA_BITS_NFREE_SHIFT); } static inline void * edata_base_get(const edata_t *edata) { assert(edata->e_addr == PAGE_ADDR2BASE(edata->e_addr) || !edata_slab_get(edata)); return PAGE_ADDR2BASE(edata->e_addr); } static inline void * edata_addr_get(const edata_t *edata) { assert(edata->e_addr == PAGE_ADDR2BASE(edata->e_addr) || !edata_slab_get(edata)); return edata->e_addr; } static inline size_t edata_size_get(const edata_t *edata) { return (edata->e_size_esn & EDATA_SIZE_MASK); } static inline size_t edata_esn_get(const edata_t *edata) { return (edata->e_size_esn & EDATA_ESN_MASK); } static inline size_t edata_bsize_get(const edata_t *edata) { return edata->e_bsize; } static inline hpdata_t * edata_ps_get(const edata_t *edata) { assert(edata_pai_get(edata) == EXTENT_PAI_HPA); return edata->e_ps; } static inline void * edata_before_get(const edata_t *edata) { return (void *)((uintptr_t)edata_base_get(edata) - PAGE); } static inline void * edata_last_get(const edata_t *edata) { return (void *)((uintptr_t)edata_base_get(edata) + edata_size_get(edata) - PAGE); } static inline void * edata_past_get(const edata_t *edata) { return (void *)((uintptr_t)edata_base_get(edata) + edata_size_get(edata)); } static inline slab_data_t * edata_slab_data_get(edata_t *edata) { assert(edata_slab_get(edata)); return &edata->e_slab_data; } static inline const slab_data_t * edata_slab_data_get_const(const edata_t *edata) { assert(edata_slab_get(edata)); return &edata->e_slab_data; } static inline prof_tctx_t * edata_prof_tctx_get(const edata_t *edata) { return (prof_tctx_t *)atomic_load_p(&edata->e_prof_info.e_prof_tctx, ATOMIC_ACQUIRE); } static inline const nstime_t * edata_prof_alloc_time_get(const edata_t *edata) { return &edata->e_prof_info.e_prof_alloc_time; } static inline size_t edata_prof_alloc_size_get(const edata_t *edata) { return edata->e_prof_info.e_prof_alloc_size; } static inline prof_recent_t * edata_prof_recent_alloc_get_dont_call_directly(const edata_t *edata) { return (prof_recent_t *)atomic_load_p( &edata->e_prof_info.e_prof_recent_alloc, ATOMIC_RELAXED); } static inline void edata_arena_ind_set(edata_t *edata, unsigned arena_ind) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_ARENA_MASK) | ((uint64_t)arena_ind << EDATA_BITS_ARENA_SHIFT); } static inline void edata_binshard_set(edata_t *edata, unsigned binshard) { /* The assertion assumes szind is set already. */ assert(binshard < bin_infos[edata_szind_get(edata)].n_shards); edata->e_bits = (edata->e_bits & ~EDATA_BITS_BINSHARD_MASK) | ((uint64_t)binshard << EDATA_BITS_BINSHARD_SHIFT); } static inline void edata_addr_set(edata_t *edata, void *addr) { edata->e_addr = addr; } static inline void edata_size_set(edata_t *edata, size_t size) { assert((size & ~EDATA_SIZE_MASK) == 0); edata->e_size_esn = size | (edata->e_size_esn & ~EDATA_SIZE_MASK); } static inline void edata_esn_set(edata_t *edata, size_t esn) { edata->e_size_esn = (edata->e_size_esn & ~EDATA_ESN_MASK) | (esn & EDATA_ESN_MASK); } static inline void edata_bsize_set(edata_t *edata, size_t bsize) { edata->e_bsize = bsize; } static inline void edata_ps_set(edata_t *edata, hpdata_t *ps) { assert(edata_pai_get(edata) == EXTENT_PAI_HPA); edata->e_ps = ps; } static inline void edata_szind_set(edata_t *edata, szind_t szind) { assert(szind <= SC_NSIZES); /* SC_NSIZES means "invalid". */ edata->e_bits = (edata->e_bits & ~EDATA_BITS_SZIND_MASK) | ((uint64_t)szind << EDATA_BITS_SZIND_SHIFT); } static inline void edata_nfree_set(edata_t *edata, unsigned nfree) { assert(edata_slab_get(edata)); edata->e_bits = (edata->e_bits & ~EDATA_BITS_NFREE_MASK) | ((uint64_t)nfree << EDATA_BITS_NFREE_SHIFT); } static inline void edata_nfree_binshard_set(edata_t *edata, unsigned nfree, unsigned binshard) { /* The assertion assumes szind is set already. */ assert(binshard < bin_infos[edata_szind_get(edata)].n_shards); edata->e_bits = (edata->e_bits & (~EDATA_BITS_NFREE_MASK & ~EDATA_BITS_BINSHARD_MASK)) | ((uint64_t)binshard << EDATA_BITS_BINSHARD_SHIFT) | ((uint64_t)nfree << EDATA_BITS_NFREE_SHIFT); } static inline void edata_nfree_inc(edata_t *edata) { assert(edata_slab_get(edata)); edata->e_bits += ((uint64_t)1U << EDATA_BITS_NFREE_SHIFT); } static inline void edata_nfree_dec(edata_t *edata) { assert(edata_slab_get(edata)); edata->e_bits -= ((uint64_t)1U << EDATA_BITS_NFREE_SHIFT); } static inline void edata_nfree_sub(edata_t *edata, uint64_t n) { assert(edata_slab_get(edata)); edata->e_bits -= (n << EDATA_BITS_NFREE_SHIFT); } static inline void edata_sn_set(edata_t *edata, uint64_t sn) { edata->e_sn = sn; } static inline void edata_state_set(edata_t *edata, extent_state_t state) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_STATE_MASK) | ((uint64_t)state << EDATA_BITS_STATE_SHIFT); } static inline void edata_guarded_set(edata_t *edata, bool guarded) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_GUARDED_MASK) | ((uint64_t)guarded << EDATA_BITS_GUARDED_SHIFT); } static inline void edata_zeroed_set(edata_t *edata, bool zeroed) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_ZEROED_MASK) | ((uint64_t)zeroed << EDATA_BITS_ZEROED_SHIFT); } static inline void edata_committed_set(edata_t *edata, bool committed) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_COMMITTED_MASK) | ((uint64_t)committed << EDATA_BITS_COMMITTED_SHIFT); } static inline void edata_pai_set(edata_t *edata, extent_pai_t pai) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_PAI_MASK) | ((uint64_t)pai << EDATA_BITS_PAI_SHIFT); } static inline void edata_slab_set(edata_t *edata, bool slab) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_SLAB_MASK) | ((uint64_t)slab << EDATA_BITS_SLAB_SHIFT); } static inline void edata_prof_tctx_set(edata_t *edata, prof_tctx_t *tctx) { atomic_store_p(&edata->e_prof_info.e_prof_tctx, tctx, ATOMIC_RELEASE); } static inline void edata_prof_alloc_time_set(edata_t *edata, nstime_t *t) { nstime_copy(&edata->e_prof_info.e_prof_alloc_time, t); } static inline void edata_prof_alloc_size_set(edata_t *edata, size_t size) { edata->e_prof_info.e_prof_alloc_size = size; } static inline void edata_prof_recent_alloc_set_dont_call_directly(edata_t *edata, prof_recent_t *recent_alloc) { atomic_store_p(&edata->e_prof_info.e_prof_recent_alloc, recent_alloc, ATOMIC_RELAXED); } static inline bool edata_is_head_get(edata_t *edata) { return (bool)((edata->e_bits & EDATA_BITS_IS_HEAD_MASK) >> EDATA_BITS_IS_HEAD_SHIFT); } static inline void edata_is_head_set(edata_t *edata, bool is_head) { edata->e_bits = (edata->e_bits & ~EDATA_BITS_IS_HEAD_MASK) | ((uint64_t)is_head << EDATA_BITS_IS_HEAD_SHIFT); } static inline bool edata_state_in_transition(extent_state_t state) { return state >= extent_state_transition; } /* * Because this function is implemented as a sequence of bitfield modifications, * even though each individual bit is properly initialized, we technically read * uninitialized data within it. This is mostly fine, since most callers get * their edatas from zeroing sources, but callers who make stack edata_ts need * to manually zero them. */ static inline void edata_init(edata_t *edata, unsigned arena_ind, void *addr, size_t size, bool slab, szind_t szind, uint64_t sn, extent_state_t state, bool zeroed, bool committed, extent_pai_t pai, extent_head_state_t is_head) { assert(addr == PAGE_ADDR2BASE(addr) || !slab); edata_arena_ind_set(edata, arena_ind); edata_addr_set(edata, addr); edata_size_set(edata, size); edata_slab_set(edata, slab); edata_szind_set(edata, szind); edata_sn_set(edata, sn); edata_state_set(edata, state); edata_guarded_set(edata, false); edata_zeroed_set(edata, zeroed); edata_committed_set(edata, committed); edata_pai_set(edata, pai); edata_is_head_set(edata, is_head == EXTENT_IS_HEAD); if (config_prof) { edata_prof_tctx_set(edata, NULL); } } static inline void edata_binit(edata_t *edata, void *addr, size_t bsize, uint64_t sn) { edata_arena_ind_set(edata, (1U << MALLOCX_ARENA_BITS) - 1); edata_addr_set(edata, addr); edata_bsize_set(edata, bsize); edata_slab_set(edata, false); edata_szind_set(edata, SC_NSIZES); edata_sn_set(edata, sn); edata_state_set(edata, extent_state_active); edata_guarded_set(edata, false); edata_zeroed_set(edata, true); edata_committed_set(edata, true); /* * This isn't strictly true, but base allocated extents never get * deallocated and can't be looked up in the emap, but no sense in * wasting a state bit to encode this fact. */ edata_pai_set(edata, EXTENT_PAI_PAC); } static inline int edata_esn_comp(const edata_t *a, const edata_t *b) { size_t a_esn = edata_esn_get(a); size_t b_esn = edata_esn_get(b); return (a_esn > b_esn) - (a_esn < b_esn); } static inline int edata_ead_comp(const edata_t *a, const edata_t *b) { uintptr_t a_eaddr = (uintptr_t)a; uintptr_t b_eaddr = (uintptr_t)b; return (a_eaddr > b_eaddr) - (a_eaddr < b_eaddr); } static inline edata_cmp_summary_t edata_cmp_summary_get(const edata_t *edata) { return (edata_cmp_summary_t){edata_sn_get(edata), (uintptr_t)edata_addr_get(edata)}; } static inline int edata_cmp_summary_comp(edata_cmp_summary_t a, edata_cmp_summary_t b) { int ret; ret = (a.sn > b.sn) - (a.sn < b.sn); if (ret != 0) { return ret; } ret = (a.addr > b.addr) - (a.addr < b.addr); return ret; } static inline int edata_snad_comp(const edata_t *a, const edata_t *b) { edata_cmp_summary_t a_cmp = edata_cmp_summary_get(a); edata_cmp_summary_t b_cmp = edata_cmp_summary_get(b); return edata_cmp_summary_comp(a_cmp, b_cmp); } static inline int edata_esnead_comp(const edata_t *a, const edata_t *b) { int ret; ret = edata_esn_comp(a, b); if (ret != 0) { return ret; } ret = edata_ead_comp(a, b); return ret; } ph_proto(, edata_avail, edata_t) ph_proto(, edata_heap, edata_t) #endif /* JEMALLOC_INTERNAL_EDATA_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/edata_cache.h000066400000000000000000000031121501533116600247450ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EDATA_CACHE_H #define JEMALLOC_INTERNAL_EDATA_CACHE_H #include "jemalloc/internal/base.h" /* For tests only. */ #define EDATA_CACHE_FAST_FILL 4 /* * A cache of edata_t structures allocated via base_alloc_edata (as opposed to * the underlying extents they describe). The contents of returned edata_t * objects are garbage and cannot be relied upon. */ typedef struct edata_cache_s edata_cache_t; struct edata_cache_s { edata_avail_t avail; atomic_zu_t count; malloc_mutex_t mtx; base_t *base; }; bool edata_cache_init(edata_cache_t *edata_cache, base_t *base); edata_t *edata_cache_get(tsdn_t *tsdn, edata_cache_t *edata_cache); void edata_cache_put(tsdn_t *tsdn, edata_cache_t *edata_cache, edata_t *edata); void edata_cache_prefork(tsdn_t *tsdn, edata_cache_t *edata_cache); void edata_cache_postfork_parent(tsdn_t *tsdn, edata_cache_t *edata_cache); void edata_cache_postfork_child(tsdn_t *tsdn, edata_cache_t *edata_cache); /* * An edata_cache_small is like an edata_cache, but it relies on external * synchronization and avoids first-fit strategies. */ typedef struct edata_cache_fast_s edata_cache_fast_t; struct edata_cache_fast_s { edata_list_inactive_t list; edata_cache_t *fallback; bool disabled; }; void edata_cache_fast_init(edata_cache_fast_t *ecs, edata_cache_t *fallback); edata_t *edata_cache_fast_get(tsdn_t *tsdn, edata_cache_fast_t *ecs); void edata_cache_fast_put(tsdn_t *tsdn, edata_cache_fast_t *ecs, edata_t *edata); void edata_cache_fast_disable(tsdn_t *tsdn, edata_cache_fast_t *ecs); #endif /* JEMALLOC_INTERNAL_EDATA_CACHE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ehooks.h000066400000000000000000000330621501533116600240430ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EHOOKS_H #define JEMALLOC_INTERNAL_EHOOKS_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/extent_mmap.h" /* * This module is the internal interface to the extent hooks (both * user-specified and external). Eventually, this will give us the flexibility * to use multiple different versions of user-visible extent-hook APIs under a * single user interface. * * Current API expansions (not available to anyone but the default hooks yet): * - Head state tracking. Hooks can decide whether or not to merge two * extents based on whether or not one of them is the head (i.e. was * allocated on its own). The later extent loses its "head" status. */ extern const extent_hooks_t ehooks_default_extent_hooks; typedef struct ehooks_s ehooks_t; struct ehooks_s { /* * The user-visible id that goes with the ehooks (i.e. that of the base * they're a part of, the associated arena's index within the arenas * array). */ unsigned ind; /* Logically an extent_hooks_t *. */ atomic_p_t ptr; }; extern const extent_hooks_t ehooks_default_extent_hooks; /* * These are not really part of the public API. Each hook has a fast-path for * the default-hooks case that can avoid various small inefficiencies: * - Forgetting tsd and then calling tsd_get within the hook. * - Getting more state than necessary out of the extent_t. * - Doing arena_ind -> arena -> arena_ind lookups. * By making the calls to these functions visible to the compiler, it can move * those extra bits of computation down below the fast-paths where they get ignored. */ void *ehooks_default_alloc_impl(tsdn_t *tsdn, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_ind); bool ehooks_default_dalloc_impl(void *addr, size_t size); void ehooks_default_destroy_impl(void *addr, size_t size); bool ehooks_default_commit_impl(void *addr, size_t offset, size_t length); bool ehooks_default_decommit_impl(void *addr, size_t offset, size_t length); #ifdef PAGES_CAN_PURGE_LAZY bool ehooks_default_purge_lazy_impl(void *addr, size_t offset, size_t length); #endif #ifdef PAGES_CAN_PURGE_FORCED bool ehooks_default_purge_forced_impl(void *addr, size_t offset, size_t length); #endif bool ehooks_default_split_impl(); /* * Merge is the only default extent hook we declare -- see the comment in * ehooks_merge. */ bool ehooks_default_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, void *addr_b, size_t size_b, bool committed, unsigned arena_ind); bool ehooks_default_merge_impl(tsdn_t *tsdn, void *addr_a, void *addr_b); void ehooks_default_zero_impl(void *addr, size_t size); void ehooks_default_guard_impl(void *guard1, void *guard2); void ehooks_default_unguard_impl(void *guard1, void *guard2); /* * We don't officially support reentrancy from wtihin the extent hooks. But * various people who sit within throwing distance of the jemalloc team want * that functionality in certain limited cases. The default reentrancy guards * assert that we're not reentrant from a0 (since it's the bootstrap arena, * where reentrant allocations would be redirected), which we would incorrectly * trigger in cases where a0 has extent hooks (those hooks themselves can't be * reentrant, then, but there are reasonable uses for such functionality, like * putting internal metadata on hugepages). Therefore, we use the raw * reentrancy guards. * * Eventually, we need to think more carefully about whether and where we * support allocating from within extent hooks (and what that means for things * like profiling, stats collection, etc.), and document what the guarantee is. */ static inline void ehooks_pre_reentrancy(tsdn_t *tsdn) { tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn); tsd_pre_reentrancy_raw(tsd); } static inline void ehooks_post_reentrancy(tsdn_t *tsdn) { tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn); tsd_post_reentrancy_raw(tsd); } /* Beginning of the public API. */ void ehooks_init(ehooks_t *ehooks, extent_hooks_t *extent_hooks, unsigned ind); static inline unsigned ehooks_ind_get(const ehooks_t *ehooks) { return ehooks->ind; } static inline void ehooks_set_extent_hooks_ptr(ehooks_t *ehooks, extent_hooks_t *extent_hooks) { atomic_store_p(&ehooks->ptr, extent_hooks, ATOMIC_RELEASE); } static inline extent_hooks_t * ehooks_get_extent_hooks_ptr(ehooks_t *ehooks) { return (extent_hooks_t *)atomic_load_p(&ehooks->ptr, ATOMIC_ACQUIRE); } static inline bool ehooks_are_default(ehooks_t *ehooks) { return ehooks_get_extent_hooks_ptr(ehooks) == &ehooks_default_extent_hooks; } /* * In some cases, a caller needs to allocate resources before attempting to call * a hook. If that hook is doomed to fail, this is wasteful. We therefore * include some checks for such cases. */ static inline bool ehooks_dalloc_will_fail(ehooks_t *ehooks) { if (ehooks_are_default(ehooks)) { return opt_retain; } else { return ehooks_get_extent_hooks_ptr(ehooks)->dalloc == NULL; } } static inline bool ehooks_split_will_fail(ehooks_t *ehooks) { return ehooks_get_extent_hooks_ptr(ehooks)->split == NULL; } static inline bool ehooks_merge_will_fail(ehooks_t *ehooks) { return ehooks_get_extent_hooks_ptr(ehooks)->merge == NULL; } static inline bool ehooks_guard_will_fail(ehooks_t *ehooks) { /* * Before the guard hooks are officially introduced, limit the use to * the default hooks only. */ return !ehooks_are_default(ehooks); } /* * Some hooks are required to return zeroed memory in certain situations. In * debug mode, we do some heuristic checks that they did what they were supposed * to. * * This isn't really ehooks-specific (i.e. anyone can check for zeroed memory). * But incorrect zero information indicates an ehook bug. */ static inline void ehooks_debug_zero_check(void *addr, size_t size) { assert(((uintptr_t)addr & PAGE_MASK) == 0); assert((size & PAGE_MASK) == 0); assert(size > 0); if (config_debug) { /* Check the whole first page. */ size_t *p = (size_t *)addr; for (size_t i = 0; i < PAGE / sizeof(size_t); i++) { assert(p[i] == 0); } /* * And 4 spots within. There's a tradeoff here; the larger * this number, the more likely it is that we'll catch a bug * where ehooks return a sparsely non-zero range. But * increasing the number of checks also increases the number of * page faults in debug mode. FreeBSD does much of their * day-to-day development work in debug mode, so we don't want * even the debug builds to be too slow. */ const size_t nchecks = 4; assert(PAGE >= sizeof(size_t) * nchecks); for (size_t i = 0; i < nchecks; ++i) { assert(p[i * (size / sizeof(size_t) / nchecks)] == 0); } } } static inline void * ehooks_alloc(tsdn_t *tsdn, ehooks_t *ehooks, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit) { bool orig_zero = *zero; void *ret; extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { ret = ehooks_default_alloc_impl(tsdn, new_addr, size, alignment, zero, commit, ehooks_ind_get(ehooks)); } else { ehooks_pre_reentrancy(tsdn); ret = extent_hooks->alloc(extent_hooks, new_addr, size, alignment, zero, commit, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); } assert(new_addr == NULL || ret == NULL || new_addr == ret); assert(!orig_zero || *zero); if (*zero && ret != NULL) { ehooks_debug_zero_check(ret, size); } return ret; } static inline bool ehooks_dalloc(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, bool committed) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { return ehooks_default_dalloc_impl(addr, size); } else if (extent_hooks->dalloc == NULL) { return true; } else { ehooks_pre_reentrancy(tsdn); bool err = extent_hooks->dalloc(extent_hooks, addr, size, committed, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); return err; } } static inline void ehooks_destroy(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, bool committed) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { ehooks_default_destroy_impl(addr, size); } else if (extent_hooks->destroy == NULL) { /* Do nothing. */ } else { ehooks_pre_reentrancy(tsdn); extent_hooks->destroy(extent_hooks, addr, size, committed, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); } } static inline bool ehooks_commit(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, size_t offset, size_t length) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); bool err; if (extent_hooks == &ehooks_default_extent_hooks) { err = ehooks_default_commit_impl(addr, offset, length); } else if (extent_hooks->commit == NULL) { err = true; } else { ehooks_pre_reentrancy(tsdn); err = extent_hooks->commit(extent_hooks, addr, size, offset, length, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); } if (!err) { ehooks_debug_zero_check(addr, size); } return err; } static inline bool ehooks_decommit(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, size_t offset, size_t length) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { return ehooks_default_decommit_impl(addr, offset, length); } else if (extent_hooks->decommit == NULL) { return true; } else { ehooks_pre_reentrancy(tsdn); bool err = extent_hooks->decommit(extent_hooks, addr, size, offset, length, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); return err; } } static inline bool ehooks_purge_lazy(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, size_t offset, size_t length) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); #ifdef PAGES_CAN_PURGE_LAZY if (extent_hooks == &ehooks_default_extent_hooks) { return ehooks_default_purge_lazy_impl(addr, offset, length); } #endif if (extent_hooks->purge_lazy == NULL) { return true; } else { ehooks_pre_reentrancy(tsdn); bool err = extent_hooks->purge_lazy(extent_hooks, addr, size, offset, length, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); return err; } } static inline bool ehooks_purge_forced(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, size_t offset, size_t length) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); /* * It would be correct to have a ehooks_debug_zero_check call at the end * of this function; purge_forced is required to zero. But checking * would touch the page in question, which may have performance * consequences (imagine the hooks are using hugepages, with a global * zero page off). Even in debug mode, it's usually a good idea to * avoid cases that can dramatically increase memory consumption. */ #ifdef PAGES_CAN_PURGE_FORCED if (extent_hooks == &ehooks_default_extent_hooks) { return ehooks_default_purge_forced_impl(addr, offset, length); } #endif if (extent_hooks->purge_forced == NULL) { return true; } else { ehooks_pre_reentrancy(tsdn); bool err = extent_hooks->purge_forced(extent_hooks, addr, size, offset, length, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); return err; } } static inline bool ehooks_split(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size, size_t size_a, size_t size_b, bool committed) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (ehooks_are_default(ehooks)) { return ehooks_default_split_impl(); } else if (extent_hooks->split == NULL) { return true; } else { ehooks_pre_reentrancy(tsdn); bool err = extent_hooks->split(extent_hooks, addr, size, size_a, size_b, committed, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); return err; } } static inline bool ehooks_merge(tsdn_t *tsdn, ehooks_t *ehooks, void *addr_a, size_t size_a, void *addr_b, size_t size_b, bool committed) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { return ehooks_default_merge_impl(tsdn, addr_a, addr_b); } else if (extent_hooks->merge == NULL) { return true; } else { ehooks_pre_reentrancy(tsdn); bool err = extent_hooks->merge(extent_hooks, addr_a, size_a, addr_b, size_b, committed, ehooks_ind_get(ehooks)); ehooks_post_reentrancy(tsdn); return err; } } static inline void ehooks_zero(tsdn_t *tsdn, ehooks_t *ehooks, void *addr, size_t size) { extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { ehooks_default_zero_impl(addr, size); } else { /* * It would be correct to try using the user-provided purge * hooks (since they are required to have zeroed the extent if * they indicate success), but we don't necessarily know their * cost. We'll be conservative and use memset. */ memset(addr, 0, size); } } static inline bool ehooks_guard(tsdn_t *tsdn, ehooks_t *ehooks, void *guard1, void *guard2) { bool err; extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { ehooks_default_guard_impl(guard1, guard2); err = false; } else { err = true; } return err; } static inline bool ehooks_unguard(tsdn_t *tsdn, ehooks_t *ehooks, void *guard1, void *guard2) { bool err; extent_hooks_t *extent_hooks = ehooks_get_extent_hooks_ptr(ehooks); if (extent_hooks == &ehooks_default_extent_hooks) { ehooks_default_unguard_impl(guard1, guard2); err = false; } else { err = true; } return err; } #endif /* JEMALLOC_INTERNAL_EHOOKS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/emap.h000066400000000000000000000320011501533116600234650ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EMAP_H #define JEMALLOC_INTERNAL_EMAP_H #include "jemalloc/internal/base.h" #include "jemalloc/internal/rtree.h" /* * Note: Ends without at semicolon, so that * EMAP_DECLARE_RTREE_CTX; * in uses will avoid empty-statement warnings. */ #define EMAP_DECLARE_RTREE_CTX \ rtree_ctx_t rtree_ctx_fallback; \ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback) typedef struct emap_s emap_t; struct emap_s { rtree_t rtree; }; /* Used to pass rtree lookup context down the path. */ typedef struct emap_alloc_ctx_t emap_alloc_ctx_t; struct emap_alloc_ctx_t { szind_t szind; bool slab; }; typedef struct emap_full_alloc_ctx_s emap_full_alloc_ctx_t; struct emap_full_alloc_ctx_s { szind_t szind; bool slab; edata_t *edata; }; bool emap_init(emap_t *emap, base_t *base, bool zeroed); void emap_remap(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind, bool slab); void emap_update_edata_state(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_state_t state); /* * The two acquire functions below allow accessing neighbor edatas, if it's safe * and valid to do so (i.e. from the same arena, of the same state, etc.). This * is necessary because the ecache locks are state based, and only protect * edatas with the same state. Therefore the neighbor edata's state needs to be * verified first, before chasing the edata pointer. The returned edata will be * in an acquired state, meaning other threads will be prevented from accessing * it, even if technically the edata can still be discovered from the rtree. * * This means, at any moment when holding pointers to edata, either one of the * state based locks is held (and the edatas are all of the protected state), or * the edatas are in an acquired state (e.g. in active or merging state). The * acquire operation itself (changing the edata to an acquired state) is done * under the state locks. */ edata_t *emap_try_acquire_edata_neighbor(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_pai_t pai, extent_state_t expected_state, bool forward); edata_t *emap_try_acquire_edata_neighbor_expand(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_pai_t pai, extent_state_t expected_state); void emap_release_edata(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_state_t new_state); /* * Associate the given edata with its beginning and end address, setting the * szind and slab info appropriately. * Returns true on error (i.e. resource exhaustion). */ bool emap_register_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind, bool slab); /* * Does the same thing, but with the interior of the range, for slab * allocations. * * You might wonder why we don't just have a single emap_register function that * does both depending on the value of 'slab'. The answer is twofold: * - As a practical matter, in places like the extract->split->commit pathway, * we defer the interior operation until we're sure that the commit won't fail * (but we have to register the split boundaries there). * - In general, we're trying to move to a world where the page-specific * allocator doesn't know as much about how the pages it allocates will be * used, and passing a 'slab' parameter everywhere makes that more * complicated. * * Unlike the boundary version, this function can't fail; this is because slabs * can't get big enough to touch a new page that neither of the boundaries * touched, so no allocation is necessary to fill the interior once the boundary * has been touched. */ void emap_register_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind); void emap_deregister_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata); void emap_deregister_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata); typedef struct emap_prepare_s emap_prepare_t; struct emap_prepare_s { rtree_leaf_elm_t *lead_elm_a; rtree_leaf_elm_t *lead_elm_b; rtree_leaf_elm_t *trail_elm_a; rtree_leaf_elm_t *trail_elm_b; }; /** * These functions the emap metadata management for merging, splitting, and * reusing extents. In particular, they set the boundary mappings from * addresses to edatas. If the result is going to be used as a slab, you * still need to call emap_register_interior on it, though. * * Remap simply changes the szind and slab status of an extent's boundary * mappings. If the extent is not a slab, it doesn't bother with updating the * end mapping (since lookups only occur in the interior of an extent for * slabs). Since the szind and slab status only make sense for active extents, * this should only be called while activating or deactivating an extent. * * Split and merge have a "prepare" and a "commit" portion. The prepare portion * does the operations that can be done without exclusive access to the extent * in question, while the commit variant requires exclusive access to maintain * the emap invariants. The only function that can fail is emap_split_prepare, * and it returns true on failure (at which point the caller shouldn't commit). * * In all cases, "lead" refers to the lower-addressed extent, and trail to the * higher-addressed one. It's the caller's responsibility to set the edata * state appropriately. */ bool emap_split_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *edata, size_t size_a, edata_t *trail, size_t size_b); void emap_split_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *lead, size_t size_a, edata_t *trail, size_t size_b); void emap_merge_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *lead, edata_t *trail); void emap_merge_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *lead, edata_t *trail); /* Assert that the emap's view of the given edata matches the edata's view. */ void emap_do_assert_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata); static inline void emap_assert_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { if (config_debug) { emap_do_assert_mapped(tsdn, emap, edata); } } /* Assert that the given edata isn't in the map. */ void emap_do_assert_not_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata); static inline void emap_assert_not_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { if (config_debug) { emap_do_assert_not_mapped(tsdn, emap, edata); } } JEMALLOC_ALWAYS_INLINE bool emap_edata_in_transition(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { assert(config_debug); emap_assert_mapped(tsdn, emap, edata); EMAP_DECLARE_RTREE_CTX; rtree_contents_t contents = rtree_read(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata)); return edata_state_in_transition(contents.metadata.state); } JEMALLOC_ALWAYS_INLINE bool emap_edata_is_acquired(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { if (!config_debug) { /* For assertions only. */ return false; } /* * The edata is considered acquired if no other threads will attempt to * read / write any fields from it. This includes a few cases: * * 1) edata not hooked into emap yet -- This implies the edata just got * allocated or initialized. * * 2) in an active or transition state -- In both cases, the edata can * be discovered from the emap, however the state tracked in the rtree * will prevent other threads from accessing the actual edata. */ EMAP_DECLARE_RTREE_CTX; rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata), /* dependent */ true, /* init_missing */ false); if (elm == NULL) { return true; } rtree_contents_t contents = rtree_leaf_elm_read(tsdn, &emap->rtree, elm, /* dependent */ true); if (contents.edata == NULL || contents.metadata.state == extent_state_active || edata_state_in_transition(contents.metadata.state)) { return true; } return false; } JEMALLOC_ALWAYS_INLINE void extent_assert_can_coalesce(const edata_t *inner, const edata_t *outer) { assert(edata_arena_ind_get(inner) == edata_arena_ind_get(outer)); assert(edata_pai_get(inner) == edata_pai_get(outer)); assert(edata_committed_get(inner) == edata_committed_get(outer)); assert(edata_state_get(inner) == extent_state_active); assert(edata_state_get(outer) == extent_state_merging); assert(!edata_guarded_get(inner) && !edata_guarded_get(outer)); assert(edata_base_get(inner) == edata_past_get(outer) || edata_base_get(outer) == edata_past_get(inner)); } JEMALLOC_ALWAYS_INLINE void extent_assert_can_expand(const edata_t *original, const edata_t *expand) { assert(edata_arena_ind_get(original) == edata_arena_ind_get(expand)); assert(edata_pai_get(original) == edata_pai_get(expand)); assert(edata_state_get(original) == extent_state_active); assert(edata_state_get(expand) == extent_state_merging); assert(edata_past_get(original) == edata_base_get(expand)); } JEMALLOC_ALWAYS_INLINE edata_t * emap_edata_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr) { EMAP_DECLARE_RTREE_CTX; return rtree_read(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)ptr).edata; } /* Fills in alloc_ctx with the info in the map. */ JEMALLOC_ALWAYS_INLINE void emap_alloc_ctx_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr, emap_alloc_ctx_t *alloc_ctx) { EMAP_DECLARE_RTREE_CTX; rtree_metadata_t metadata = rtree_metadata_read(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)ptr); alloc_ctx->szind = metadata.szind; alloc_ctx->slab = metadata.slab; } /* The pointer must be mapped. */ JEMALLOC_ALWAYS_INLINE void emap_full_alloc_ctx_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr, emap_full_alloc_ctx_t *full_alloc_ctx) { EMAP_DECLARE_RTREE_CTX; rtree_contents_t contents = rtree_read(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)ptr); full_alloc_ctx->edata = contents.edata; full_alloc_ctx->szind = contents.metadata.szind; full_alloc_ctx->slab = contents.metadata.slab; } /* * The pointer is allowed to not be mapped. * * Returns true when the pointer is not present. */ JEMALLOC_ALWAYS_INLINE bool emap_full_alloc_ctx_try_lookup(tsdn_t *tsdn, emap_t *emap, const void *ptr, emap_full_alloc_ctx_t *full_alloc_ctx) { EMAP_DECLARE_RTREE_CTX; rtree_contents_t contents; bool err = rtree_read_independent(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)ptr, &contents); if (err) { return true; } full_alloc_ctx->edata = contents.edata; full_alloc_ctx->szind = contents.metadata.szind; full_alloc_ctx->slab = contents.metadata.slab; return false; } /* * Only used on the fastpath of free. Returns true when cannot be fulfilled by * fast path, e.g. when the metadata key is not cached. */ JEMALLOC_ALWAYS_INLINE bool emap_alloc_ctx_try_lookup_fast(tsd_t *tsd, emap_t *emap, const void *ptr, emap_alloc_ctx_t *alloc_ctx) { /* Use the unsafe getter since this may gets called during exit. */ rtree_ctx_t *rtree_ctx = tsd_rtree_ctxp_get_unsafe(tsd); rtree_metadata_t metadata; bool err = rtree_metadata_try_read_fast(tsd_tsdn(tsd), &emap->rtree, rtree_ctx, (uintptr_t)ptr, &metadata); if (err) { return true; } alloc_ctx->szind = metadata.szind; alloc_ctx->slab = metadata.slab; return false; } /* * We want to do batch lookups out of the cache bins, which use * cache_bin_ptr_array_get to access the i'th element of the bin (since they * invert usual ordering in deciding what to flush). This lets the emap avoid * caring about its caller's ordering. */ typedef const void *(*emap_ptr_getter)(void *ctx, size_t ind); /* * This allows size-checking assertions, which we can only do while we're in the * process of edata lookups. */ typedef void (*emap_metadata_visitor)(void *ctx, emap_full_alloc_ctx_t *alloc_ctx); typedef union emap_batch_lookup_result_u emap_batch_lookup_result_t; union emap_batch_lookup_result_u { edata_t *edata; rtree_leaf_elm_t *rtree_leaf; }; JEMALLOC_ALWAYS_INLINE void emap_edata_lookup_batch(tsd_t *tsd, emap_t *emap, size_t nptrs, emap_ptr_getter ptr_getter, void *ptr_getter_ctx, emap_metadata_visitor metadata_visitor, void *metadata_visitor_ctx, emap_batch_lookup_result_t *result) { /* Avoids null-checking tsdn in the loop below. */ util_assume(tsd != NULL); rtree_ctx_t *rtree_ctx = tsd_rtree_ctxp_get(tsd); for (size_t i = 0; i < nptrs; i++) { const void *ptr = ptr_getter(ptr_getter_ctx, i); /* * Reuse the edatas array as a temp buffer, lying a little about * the types. */ result[i].rtree_leaf = rtree_leaf_elm_lookup(tsd_tsdn(tsd), &emap->rtree, rtree_ctx, (uintptr_t)ptr, /* dependent */ true, /* init_missing */ false); } for (size_t i = 0; i < nptrs; i++) { rtree_leaf_elm_t *elm = result[i].rtree_leaf; rtree_contents_t contents = rtree_leaf_elm_read(tsd_tsdn(tsd), &emap->rtree, elm, /* dependent */ true); result[i].edata = contents.edata; emap_full_alloc_ctx_t alloc_ctx; /* * Not all these fields are read in practice by the metadata * visitor. But the compiler can easily optimize away the ones * that aren't, so no sense in being incomplete. */ alloc_ctx.szind = contents.metadata.szind; alloc_ctx.slab = contents.metadata.slab; alloc_ctx.edata = contents.edata; metadata_visitor(metadata_visitor_ctx, &alloc_ctx); } } #endif /* JEMALLOC_INTERNAL_EMAP_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/emitter.h000066400000000000000000000332171501533116600242260ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EMITTER_H #define JEMALLOC_INTERNAL_EMITTER_H #include "jemalloc/internal/ql.h" typedef enum emitter_output_e emitter_output_t; enum emitter_output_e { emitter_output_json, emitter_output_json_compact, emitter_output_table }; typedef enum emitter_justify_e emitter_justify_t; enum emitter_justify_e { emitter_justify_left, emitter_justify_right, /* Not for users; just to pass to internal functions. */ emitter_justify_none }; typedef enum emitter_type_e emitter_type_t; enum emitter_type_e { emitter_type_bool, emitter_type_int, emitter_type_int64, emitter_type_unsigned, emitter_type_uint32, emitter_type_uint64, emitter_type_size, emitter_type_ssize, emitter_type_string, /* * A title is a column title in a table; it's just a string, but it's * not quoted. */ emitter_type_title, }; typedef struct emitter_col_s emitter_col_t; struct emitter_col_s { /* Filled in by the user. */ emitter_justify_t justify; int width; emitter_type_t type; union { bool bool_val; int int_val; unsigned unsigned_val; uint32_t uint32_val; uint32_t uint32_t_val; uint64_t uint64_val; uint64_t uint64_t_val; size_t size_val; ssize_t ssize_val; const char *str_val; }; /* Filled in by initialization. */ ql_elm(emitter_col_t) link; }; typedef struct emitter_row_s emitter_row_t; struct emitter_row_s { ql_head(emitter_col_t) cols; }; typedef struct emitter_s emitter_t; struct emitter_s { emitter_output_t output; /* The output information. */ write_cb_t *write_cb; void *cbopaque; int nesting_depth; /* True if we've already emitted a value at the given depth. */ bool item_at_depth; /* True if we emitted a key and will emit corresponding value next. */ bool emitted_key; }; static inline bool emitter_outputs_json(emitter_t *emitter) { return emitter->output == emitter_output_json || emitter->output == emitter_output_json_compact; } /* Internal convenience function. Write to the emitter the given string. */ JEMALLOC_FORMAT_PRINTF(2, 3) static inline void emitter_printf(emitter_t *emitter, const char *format, ...) { va_list ap; va_start(ap, format); malloc_vcprintf(emitter->write_cb, emitter->cbopaque, format, ap); va_end(ap); } static inline const char * JEMALLOC_FORMAT_ARG(3) emitter_gen_fmt(char *out_fmt, size_t out_size, const char *fmt_specifier, emitter_justify_t justify, int width) { size_t written; fmt_specifier++; if (justify == emitter_justify_none) { written = malloc_snprintf(out_fmt, out_size, "%%%s", fmt_specifier); } else if (justify == emitter_justify_left) { written = malloc_snprintf(out_fmt, out_size, "%%-%d%s", width, fmt_specifier); } else { written = malloc_snprintf(out_fmt, out_size, "%%%d%s", width, fmt_specifier); } /* Only happens in case of bad format string, which *we* choose. */ assert(written < out_size); return out_fmt; } /* * Internal. Emit the given value type in the relevant encoding (so that the * bool true gets mapped to json "true", but the string "true" gets mapped to * json "\"true\"", for instance. * * Width is ignored if justify is emitter_justify_none. */ static inline void emitter_print_value(emitter_t *emitter, emitter_justify_t justify, int width, emitter_type_t value_type, const void *value) { size_t str_written; #define BUF_SIZE 256 #define FMT_SIZE 10 /* * We dynamically generate a format string to emit, to let us use the * snprintf machinery. This is kinda hacky, but gets the job done * quickly without having to think about the various snprintf edge * cases. */ char fmt[FMT_SIZE]; char buf[BUF_SIZE]; #define EMIT_SIMPLE(type, format) \ emitter_printf(emitter, \ emitter_gen_fmt(fmt, FMT_SIZE, format, justify, width), \ *(const type *)value); switch (value_type) { case emitter_type_bool: emitter_printf(emitter, emitter_gen_fmt(fmt, FMT_SIZE, "%s", justify, width), *(const bool *)value ? "true" : "false"); break; case emitter_type_int: EMIT_SIMPLE(int, "%d") break; case emitter_type_int64: EMIT_SIMPLE(int64_t, "%" FMTd64) break; case emitter_type_unsigned: EMIT_SIMPLE(unsigned, "%u") break; case emitter_type_ssize: EMIT_SIMPLE(ssize_t, "%zd") break; case emitter_type_size: EMIT_SIMPLE(size_t, "%zu") break; case emitter_type_string: str_written = malloc_snprintf(buf, BUF_SIZE, "\"%s\"", *(const char *const *)value); /* * We control the strings we output; we shouldn't get anything * anywhere near the fmt size. */ assert(str_written < BUF_SIZE); emitter_printf(emitter, emitter_gen_fmt(fmt, FMT_SIZE, "%s", justify, width), buf); break; case emitter_type_uint32: EMIT_SIMPLE(uint32_t, "%" FMTu32) break; case emitter_type_uint64: EMIT_SIMPLE(uint64_t, "%" FMTu64) break; case emitter_type_title: EMIT_SIMPLE(char *const, "%s"); break; default: unreachable(); } #undef BUF_SIZE #undef FMT_SIZE } /* Internal functions. In json mode, tracks nesting state. */ static inline void emitter_nest_inc(emitter_t *emitter) { emitter->nesting_depth++; emitter->item_at_depth = false; } static inline void emitter_nest_dec(emitter_t *emitter) { emitter->nesting_depth--; emitter->item_at_depth = true; } static inline void emitter_indent(emitter_t *emitter) { int amount = emitter->nesting_depth; const char *indent_str; assert(emitter->output != emitter_output_json_compact); if (emitter->output == emitter_output_json) { indent_str = "\t"; } else { amount *= 2; indent_str = " "; } for (int i = 0; i < amount; i++) { emitter_printf(emitter, "%s", indent_str); } } static inline void emitter_json_key_prefix(emitter_t *emitter) { assert(emitter_outputs_json(emitter)); if (emitter->emitted_key) { emitter->emitted_key = false; return; } if (emitter->item_at_depth) { emitter_printf(emitter, ","); } if (emitter->output != emitter_output_json_compact) { emitter_printf(emitter, "\n"); emitter_indent(emitter); } } /******************************************************************************/ /* Public functions for emitter_t. */ static inline void emitter_init(emitter_t *emitter, emitter_output_t emitter_output, write_cb_t *write_cb, void *cbopaque) { emitter->output = emitter_output; emitter->write_cb = write_cb; emitter->cbopaque = cbopaque; emitter->item_at_depth = false; emitter->emitted_key = false; emitter->nesting_depth = 0; } /******************************************************************************/ /* JSON public API. */ /* * Emits a key (e.g. as appears in an object). The next json entity emitted will * be the corresponding value. */ static inline void emitter_json_key(emitter_t *emitter, const char *json_key) { if (emitter_outputs_json(emitter)) { emitter_json_key_prefix(emitter); emitter_printf(emitter, "\"%s\":%s", json_key, emitter->output == emitter_output_json_compact ? "" : " "); emitter->emitted_key = true; } } static inline void emitter_json_value(emitter_t *emitter, emitter_type_t value_type, const void *value) { if (emitter_outputs_json(emitter)) { emitter_json_key_prefix(emitter); emitter_print_value(emitter, emitter_justify_none, -1, value_type, value); emitter->item_at_depth = true; } } /* Shorthand for calling emitter_json_key and then emitter_json_value. */ static inline void emitter_json_kv(emitter_t *emitter, const char *json_key, emitter_type_t value_type, const void *value) { emitter_json_key(emitter, json_key); emitter_json_value(emitter, value_type, value); } static inline void emitter_json_array_begin(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { emitter_json_key_prefix(emitter); emitter_printf(emitter, "["); emitter_nest_inc(emitter); } } /* Shorthand for calling emitter_json_key and then emitter_json_array_begin. */ static inline void emitter_json_array_kv_begin(emitter_t *emitter, const char *json_key) { emitter_json_key(emitter, json_key); emitter_json_array_begin(emitter); } static inline void emitter_json_array_end(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { assert(emitter->nesting_depth > 0); emitter_nest_dec(emitter); if (emitter->output != emitter_output_json_compact) { emitter_printf(emitter, "\n"); emitter_indent(emitter); } emitter_printf(emitter, "]"); } } static inline void emitter_json_object_begin(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { emitter_json_key_prefix(emitter); emitter_printf(emitter, "{"); emitter_nest_inc(emitter); } } /* Shorthand for calling emitter_json_key and then emitter_json_object_begin. */ static inline void emitter_json_object_kv_begin(emitter_t *emitter, const char *json_key) { emitter_json_key(emitter, json_key); emitter_json_object_begin(emitter); } static inline void emitter_json_object_end(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { assert(emitter->nesting_depth > 0); emitter_nest_dec(emitter); if (emitter->output != emitter_output_json_compact) { emitter_printf(emitter, "\n"); emitter_indent(emitter); } emitter_printf(emitter, "}"); } } /******************************************************************************/ /* Table public API. */ static inline void emitter_table_dict_begin(emitter_t *emitter, const char *table_key) { if (emitter->output == emitter_output_table) { emitter_indent(emitter); emitter_printf(emitter, "%s\n", table_key); emitter_nest_inc(emitter); } } static inline void emitter_table_dict_end(emitter_t *emitter) { if (emitter->output == emitter_output_table) { emitter_nest_dec(emitter); } } static inline void emitter_table_kv_note(emitter_t *emitter, const char *table_key, emitter_type_t value_type, const void *value, const char *table_note_key, emitter_type_t table_note_value_type, const void *table_note_value) { if (emitter->output == emitter_output_table) { emitter_indent(emitter); emitter_printf(emitter, "%s: ", table_key); emitter_print_value(emitter, emitter_justify_none, -1, value_type, value); if (table_note_key != NULL) { emitter_printf(emitter, " (%s: ", table_note_key); emitter_print_value(emitter, emitter_justify_none, -1, table_note_value_type, table_note_value); emitter_printf(emitter, ")"); } emitter_printf(emitter, "\n"); } emitter->item_at_depth = true; } static inline void emitter_table_kv(emitter_t *emitter, const char *table_key, emitter_type_t value_type, const void *value) { emitter_table_kv_note(emitter, table_key, value_type, value, NULL, emitter_type_bool, NULL); } /* Write to the emitter the given string, but only in table mode. */ JEMALLOC_FORMAT_PRINTF(2, 3) static inline void emitter_table_printf(emitter_t *emitter, const char *format, ...) { if (emitter->output == emitter_output_table) { va_list ap; va_start(ap, format); malloc_vcprintf(emitter->write_cb, emitter->cbopaque, format, ap); va_end(ap); } } static inline void emitter_table_row(emitter_t *emitter, emitter_row_t *row) { if (emitter->output != emitter_output_table) { return; } emitter_col_t *col; ql_foreach(col, &row->cols, link) { emitter_print_value(emitter, col->justify, col->width, col->type, (const void *)&col->bool_val); } emitter_table_printf(emitter, "\n"); } static inline void emitter_row_init(emitter_row_t *row) { ql_new(&row->cols); } static inline void emitter_col_init(emitter_col_t *col, emitter_row_t *row) { ql_elm_new(col, link); ql_tail_insert(&row->cols, col, link); } /******************************************************************************/ /* * Generalized public API. Emits using either JSON or table, according to * settings in the emitter_t. */ /* * Note emits a different kv pair as well, but only in table mode. Omits the * note if table_note_key is NULL. */ static inline void emitter_kv_note(emitter_t *emitter, const char *json_key, const char *table_key, emitter_type_t value_type, const void *value, const char *table_note_key, emitter_type_t table_note_value_type, const void *table_note_value) { if (emitter_outputs_json(emitter)) { emitter_json_key(emitter, json_key); emitter_json_value(emitter, value_type, value); } else { emitter_table_kv_note(emitter, table_key, value_type, value, table_note_key, table_note_value_type, table_note_value); } emitter->item_at_depth = true; } static inline void emitter_kv(emitter_t *emitter, const char *json_key, const char *table_key, emitter_type_t value_type, const void *value) { emitter_kv_note(emitter, json_key, table_key, value_type, value, NULL, emitter_type_bool, NULL); } static inline void emitter_dict_begin(emitter_t *emitter, const char *json_key, const char *table_header) { if (emitter_outputs_json(emitter)) { emitter_json_key(emitter, json_key); emitter_json_object_begin(emitter); } else { emitter_table_dict_begin(emitter, table_header); } } static inline void emitter_dict_end(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { emitter_json_object_end(emitter); } else { emitter_table_dict_end(emitter); } } static inline void emitter_begin(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { assert(emitter->nesting_depth == 0); emitter_printf(emitter, "{"); emitter_nest_inc(emitter); } else { /* * This guarantees that we always call write_cb at least once. * This is useful if some invariant is established by each call * to write_cb, but doesn't hold initially: e.g., some buffer * holds a null-terminated string. */ emitter_printf(emitter, "%s", ""); } } static inline void emitter_end(emitter_t *emitter) { if (emitter_outputs_json(emitter)) { assert(emitter->nesting_depth == 1); emitter_nest_dec(emitter); emitter_printf(emitter, "%s", emitter->output == emitter_output_json_compact ? "}" : "\n}\n"); } } #endif /* JEMALLOC_INTERNAL_EMITTER_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/eset.h000066400000000000000000000044611501533116600235140ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_ESET_H #define JEMALLOC_INTERNAL_ESET_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/fb.h" #include "jemalloc/internal/edata.h" #include "jemalloc/internal/mutex.h" /* * An eset ("extent set") is a quantized collection of extents, with built-in * LRU queue. * * This class is not thread-safe; synchronization must be done externally if * there are mutating operations. One exception is the stats counters, which * may be read without any locking. */ typedef struct eset_bin_s eset_bin_t; struct eset_bin_s { edata_heap_t heap; /* * We do first-fit across multiple size classes. If we compared against * the min element in each heap directly, we'd take a cache miss per * extent we looked at. If we co-locate the edata summaries, we only * take a miss on the edata we're actually going to return (which is * inevitable anyways). */ edata_cmp_summary_t heap_min; }; typedef struct eset_bin_stats_s eset_bin_stats_t; struct eset_bin_stats_s { atomic_zu_t nextents; atomic_zu_t nbytes; }; typedef struct eset_s eset_t; struct eset_s { /* Bitmap for which set bits correspond to non-empty heaps. */ fb_group_t bitmap[FB_NGROUPS(SC_NPSIZES + 1)]; /* Quantized per size class heaps of extents. */ eset_bin_t bins[SC_NPSIZES + 1]; eset_bin_stats_t bin_stats[SC_NPSIZES + 1]; /* LRU of all extents in heaps. */ edata_list_inactive_t lru; /* Page sum for all extents in heaps. */ atomic_zu_t npages; /* * A duplication of the data in the containing ecache. We use this only * for assertions on the states of the passed-in extents. */ extent_state_t state; }; void eset_init(eset_t *eset, extent_state_t state); size_t eset_npages_get(eset_t *eset); /* Get the number of extents in the given page size index. */ size_t eset_nextents_get(eset_t *eset, pszind_t ind); /* Get the sum total bytes of the extents in the given page size index. */ size_t eset_nbytes_get(eset_t *eset, pszind_t ind); void eset_insert(eset_t *eset, edata_t *edata); void eset_remove(eset_t *eset, edata_t *edata); /* * Select an extent from this eset of the given size and alignment. Returns * null if no such item could be found. */ edata_t *eset_fit(eset_t *eset, size_t esize, size_t alignment, bool exact_only, unsigned lg_max_fit); #endif /* JEMALLOC_INTERNAL_ESET_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/exp_grow.h000066400000000000000000000026151501533116600244050ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EXP_GROW_H #define JEMALLOC_INTERNAL_EXP_GROW_H typedef struct exp_grow_s exp_grow_t; struct exp_grow_s { /* * Next extent size class in a growing series to use when satisfying a * request via the extent hooks (only if opt_retain). This limits the * number of disjoint virtual memory ranges so that extent merging can * be effective even if multiple arenas' extent allocation requests are * highly interleaved. * * retain_grow_limit is the max allowed size ind to expand (unless the * required size is greater). Default is no limit, and controlled * through mallctl only. */ pszind_t next; pszind_t limit; }; static inline bool exp_grow_size_prepare(exp_grow_t *exp_grow, size_t alloc_size_min, size_t *r_alloc_size, pszind_t *r_skip) { *r_skip = 0; *r_alloc_size = sz_pind2sz(exp_grow->next + *r_skip); while (*r_alloc_size < alloc_size_min) { (*r_skip)++; if (exp_grow->next + *r_skip >= sz_psz2ind(SC_LARGE_MAXCLASS)) { /* Outside legal range. */ return true; } *r_alloc_size = sz_pind2sz(exp_grow->next + *r_skip); } return false; } static inline void exp_grow_size_commit(exp_grow_t *exp_grow, pszind_t skip) { if (exp_grow->next + skip + 1 <= exp_grow->limit) { exp_grow->next += skip + 1; } else { exp_grow->next = exp_grow->limit; } } void exp_grow_init(exp_grow_t *exp_grow); #endif /* JEMALLOC_INTERNAL_EXP_GROW_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/extent.h000066400000000000000000000112421501533116600240560ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EXTENT_H #define JEMALLOC_INTERNAL_EXTENT_H #include "jemalloc/internal/ecache.h" #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/ph.h" #include "jemalloc/internal/rtree.h" /* * This module contains the page-level allocator. It chooses the addresses that * allocations requested by other modules will inhabit, and updates the global * metadata to reflect allocation/deallocation/purging decisions. */ /* * When reuse (and split) an active extent, (1U << opt_lg_extent_max_active_fit) * is the max ratio between the size of the active extent and the new extent. */ #define LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT 6 extern size_t opt_lg_extent_max_active_fit; edata_t *ecache_alloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool guarded); edata_t *ecache_alloc_grow(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool guarded); void ecache_dalloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata); edata_t *ecache_evict(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, size_t npages_min); void extent_gdump_add(tsdn_t *tsdn, const edata_t *edata); void extent_record(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata); void extent_dalloc_gap(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata); edata_t *extent_alloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, void *new_addr, size_t size, size_t alignment, bool zero, bool *commit, bool growing_retained); void extent_dalloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata); void extent_destroy_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata); bool extent_commit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length); bool extent_decommit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length); bool extent_purge_lazy_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length); bool extent_purge_forced_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length); edata_t *extent_split_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks); bool extent_merge_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *a, edata_t *b); bool extent_commit_zero(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, bool commit, bool zero, bool growing_retained); size_t extent_sn_next(pac_t *pac); bool extent_boot(void); JEMALLOC_ALWAYS_INLINE bool extent_neighbor_head_state_mergeable(bool edata_is_head, bool neighbor_is_head, bool forward) { /* * Head states checking: disallow merging if the higher addr extent is a * head extent. This helps preserve first-fit, and more importantly * makes sure no merge across arenas. */ if (forward) { if (neighbor_is_head) { return false; } } else { if (edata_is_head) { return false; } } return true; } JEMALLOC_ALWAYS_INLINE bool extent_can_acquire_neighbor(edata_t *edata, rtree_contents_t contents, extent_pai_t pai, extent_state_t expected_state, bool forward, bool expanding) { edata_t *neighbor = contents.edata; if (neighbor == NULL) { return false; } /* It's not safe to access *neighbor yet; must verify states first. */ bool neighbor_is_head = contents.metadata.is_head; if (!extent_neighbor_head_state_mergeable(edata_is_head_get(edata), neighbor_is_head, forward)) { return false; } extent_state_t neighbor_state = contents.metadata.state; if (pai == EXTENT_PAI_PAC) { if (neighbor_state != expected_state) { return false; } /* From this point, it's safe to access *neighbor. */ if (!expanding && (edata_committed_get(edata) != edata_committed_get(neighbor))) { /* * Some platforms (e.g. Windows) require an explicit * commit step (and writing to uncommitted memory is not * allowed). */ return false; } } else { if (neighbor_state == extent_state_active) { return false; } /* From this point, it's safe to access *neighbor. */ } assert(edata_pai_get(edata) == pai); if (edata_pai_get(neighbor) != pai) { return false; } if (opt_retain) { assert(edata_arena_ind_get(edata) == edata_arena_ind_get(neighbor)); } else { if (edata_arena_ind_get(edata) != edata_arena_ind_get(neighbor)) { return false; } } assert(!edata_guarded_get(edata) && !edata_guarded_get(neighbor)); return true; } #endif /* JEMALLOC_INTERNAL_EXTENT_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/extent_dss.h000066400000000000000000000013301501533116600247240ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EXTENT_DSS_H #define JEMALLOC_INTERNAL_EXTENT_DSS_H typedef enum { dss_prec_disabled = 0, dss_prec_primary = 1, dss_prec_secondary = 2, dss_prec_limit = 3 } dss_prec_t; #define DSS_PREC_DEFAULT dss_prec_secondary #define DSS_DEFAULT "secondary" extern const char *dss_prec_names[]; extern const char *opt_dss; dss_prec_t extent_dss_prec_get(void); bool extent_dss_prec_set(dss_prec_t dss_prec); void *extent_alloc_dss(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit); bool extent_in_dss(void *addr); bool extent_dss_mergeable(void *addr_a, void *addr_b); void extent_dss_boot(void); #endif /* JEMALLOC_INTERNAL_EXTENT_DSS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/extent_mmap.h000066400000000000000000000005101501533116600250640ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EXTENT_MMAP_EXTERNS_H #define JEMALLOC_INTERNAL_EXTENT_MMAP_EXTERNS_H extern bool opt_retain; void *extent_alloc_mmap(void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit); bool extent_dalloc_mmap(void *addr, size_t size); #endif /* JEMALLOC_INTERNAL_EXTENT_MMAP_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/fb.h000066400000000000000000000260261501533116600231440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_FB_H #define JEMALLOC_INTERNAL_FB_H /* * The flat bitmap module. This has a larger API relative to the bitmap module * (supporting things like backwards searches, and searching for both set and * unset bits), at the cost of slower operations for very large bitmaps. * * Initialized flat bitmaps start at all-zeros (all bits unset). */ typedef unsigned long fb_group_t; #define FB_GROUP_BITS (ZU(1) << (LG_SIZEOF_LONG + 3)) #define FB_NGROUPS(nbits) ((nbits) / FB_GROUP_BITS \ + ((nbits) % FB_GROUP_BITS == 0 ? 0 : 1)) static inline void fb_init(fb_group_t *fb, size_t nbits) { size_t ngroups = FB_NGROUPS(nbits); memset(fb, 0, ngroups * sizeof(fb_group_t)); } static inline bool fb_empty(fb_group_t *fb, size_t nbits) { size_t ngroups = FB_NGROUPS(nbits); for (size_t i = 0; i < ngroups; i++) { if (fb[i] != 0) { return false; } } return true; } static inline bool fb_full(fb_group_t *fb, size_t nbits) { size_t ngroups = FB_NGROUPS(nbits); size_t trailing_bits = nbits % FB_GROUP_BITS; size_t limit = (trailing_bits == 0 ? ngroups : ngroups - 1); for (size_t i = 0; i < limit; i++) { if (fb[i] != ~(fb_group_t)0) { return false; } } if (trailing_bits == 0) { return true; } return fb[ngroups - 1] == ((fb_group_t)1 << trailing_bits) - 1; } static inline bool fb_get(fb_group_t *fb, size_t nbits, size_t bit) { assert(bit < nbits); size_t group_ind = bit / FB_GROUP_BITS; size_t bit_ind = bit % FB_GROUP_BITS; return (bool)(fb[group_ind] & ((fb_group_t)1 << bit_ind)); } static inline void fb_set(fb_group_t *fb, size_t nbits, size_t bit) { assert(bit < nbits); size_t group_ind = bit / FB_GROUP_BITS; size_t bit_ind = bit % FB_GROUP_BITS; fb[group_ind] |= ((fb_group_t)1 << bit_ind); } static inline void fb_unset(fb_group_t *fb, size_t nbits, size_t bit) { assert(bit < nbits); size_t group_ind = bit / FB_GROUP_BITS; size_t bit_ind = bit % FB_GROUP_BITS; fb[group_ind] &= ~((fb_group_t)1 << bit_ind); } /* * Some implementation details. This visitation function lets us apply a group * visitor to each group in the bitmap (potentially modifying it). The mask * indicates which bits are logically part of the visitation. */ typedef void (*fb_group_visitor_t)(void *ctx, fb_group_t *fb, fb_group_t mask); JEMALLOC_ALWAYS_INLINE void fb_visit_impl(fb_group_t *fb, size_t nbits, fb_group_visitor_t visit, void *ctx, size_t start, size_t cnt) { assert(cnt > 0); assert(start + cnt <= nbits); size_t group_ind = start / FB_GROUP_BITS; size_t start_bit_ind = start % FB_GROUP_BITS; /* * The first group is special; it's the only one we don't start writing * to from bit 0. */ size_t first_group_cnt = (start_bit_ind + cnt > FB_GROUP_BITS ? FB_GROUP_BITS - start_bit_ind : cnt); /* * We can basically split affected words into: * - The first group, where we touch only the high bits * - The last group, where we touch only the low bits * - The middle, where we set all the bits to the same thing. * We treat each case individually. The last two could be merged, but * this can lead to bad codegen for those middle words. */ /* First group */ fb_group_t mask = ((~(fb_group_t)0) >> (FB_GROUP_BITS - first_group_cnt)) << start_bit_ind; visit(ctx, &fb[group_ind], mask); cnt -= first_group_cnt; group_ind++; /* Middle groups */ while (cnt > FB_GROUP_BITS) { visit(ctx, &fb[group_ind], ~(fb_group_t)0); cnt -= FB_GROUP_BITS; group_ind++; } /* Last group */ if (cnt != 0) { mask = (~(fb_group_t)0) >> (FB_GROUP_BITS - cnt); visit(ctx, &fb[group_ind], mask); } } JEMALLOC_ALWAYS_INLINE void fb_assign_visitor(void *ctx, fb_group_t *fb, fb_group_t mask) { bool val = *(bool *)ctx; if (val) { *fb |= mask; } else { *fb &= ~mask; } } /* Sets the cnt bits starting at position start. Must not have a 0 count. */ static inline void fb_set_range(fb_group_t *fb, size_t nbits, size_t start, size_t cnt) { bool val = true; fb_visit_impl(fb, nbits, &fb_assign_visitor, &val, start, cnt); } /* Unsets the cnt bits starting at position start. Must not have a 0 count. */ static inline void fb_unset_range(fb_group_t *fb, size_t nbits, size_t start, size_t cnt) { bool val = false; fb_visit_impl(fb, nbits, &fb_assign_visitor, &val, start, cnt); } JEMALLOC_ALWAYS_INLINE void fb_scount_visitor(void *ctx, fb_group_t *fb, fb_group_t mask) { size_t *scount = (size_t *)ctx; *scount += popcount_lu(*fb & mask); } /* Finds the number of set bit in the of length cnt starting at start. */ JEMALLOC_ALWAYS_INLINE size_t fb_scount(fb_group_t *fb, size_t nbits, size_t start, size_t cnt) { size_t scount = 0; fb_visit_impl(fb, nbits, &fb_scount_visitor, &scount, start, cnt); return scount; } /* Finds the number of unset bit in the of length cnt starting at start. */ JEMALLOC_ALWAYS_INLINE size_t fb_ucount(fb_group_t *fb, size_t nbits, size_t start, size_t cnt) { size_t scount = fb_scount(fb, nbits, start, cnt); return cnt - scount; } /* * An implementation detail; find the first bit at position >= min_bit with the * value val. * * Returns the number of bits in the bitmap if no such bit exists. */ JEMALLOC_ALWAYS_INLINE ssize_t fb_find_impl(fb_group_t *fb, size_t nbits, size_t start, bool val, bool forward) { assert(start < nbits); size_t ngroups = FB_NGROUPS(nbits); ssize_t group_ind = start / FB_GROUP_BITS; size_t bit_ind = start % FB_GROUP_BITS; fb_group_t maybe_invert = (val ? 0 : (fb_group_t)-1); fb_group_t group = fb[group_ind]; group ^= maybe_invert; if (forward) { /* Only keep ones in bits bit_ind and above. */ group &= ~((1LU << bit_ind) - 1); } else { /* * Only keep ones in bits bit_ind and below. You might more * naturally express this as (1 << (bit_ind + 1)) - 1, but * that shifts by an invalid amount if bit_ind is one less than * FB_GROUP_BITS. */ group &= ((2LU << bit_ind) - 1); } ssize_t group_ind_bound = forward ? (ssize_t)ngroups : -1; while (group == 0) { group_ind += forward ? 1 : -1; if (group_ind == group_ind_bound) { return forward ? (ssize_t)nbits : (ssize_t)-1; } group = fb[group_ind]; group ^= maybe_invert; } assert(group != 0); size_t bit = forward ? ffs_lu(group) : fls_lu(group); size_t pos = group_ind * FB_GROUP_BITS + bit; /* * The high bits of a partially filled last group are zeros, so if we're * looking for zeros we don't want to report an invalid result. */ if (forward && !val && pos > nbits) { return nbits; } return pos; } /* * Find the first set bit in the bitmap with an index >= min_bit. Returns the * number of bits in the bitmap if no such bit exists. */ static inline size_t fb_ffu(fb_group_t *fb, size_t nbits, size_t min_bit) { return (size_t)fb_find_impl(fb, nbits, min_bit, /* val */ false, /* forward */ true); } /* The same, but looks for an unset bit. */ static inline size_t fb_ffs(fb_group_t *fb, size_t nbits, size_t min_bit) { return (size_t)fb_find_impl(fb, nbits, min_bit, /* val */ true, /* forward */ true); } /* * Find the last set bit in the bitmap with an index <= max_bit. Returns -1 if * no such bit exists. */ static inline ssize_t fb_flu(fb_group_t *fb, size_t nbits, size_t max_bit) { return fb_find_impl(fb, nbits, max_bit, /* val */ false, /* forward */ false); } static inline ssize_t fb_fls(fb_group_t *fb, size_t nbits, size_t max_bit) { return fb_find_impl(fb, nbits, max_bit, /* val */ true, /* forward */ false); } /* Returns whether or not we found a range. */ JEMALLOC_ALWAYS_INLINE bool fb_iter_range_impl(fb_group_t *fb, size_t nbits, size_t start, size_t *r_begin, size_t *r_len, bool val, bool forward) { assert(start < nbits); ssize_t next_range_begin = fb_find_impl(fb, nbits, start, val, forward); if ((forward && next_range_begin == (ssize_t)nbits) || (!forward && next_range_begin == (ssize_t)-1)) { return false; } /* Half open range; the set bits are [begin, end). */ ssize_t next_range_end = fb_find_impl(fb, nbits, next_range_begin, !val, forward); if (forward) { *r_begin = next_range_begin; *r_len = next_range_end - next_range_begin; } else { *r_begin = next_range_end + 1; *r_len = next_range_begin - next_range_end; } return true; } /* * Used to iterate through ranges of set bits. * * Tries to find the next contiguous sequence of set bits with a first index >= * start. If one exists, puts the earliest bit of the range in *r_begin, its * length in *r_len, and returns true. Otherwise, returns false (without * touching *r_begin or *r_end). */ static inline bool fb_srange_iter(fb_group_t *fb, size_t nbits, size_t start, size_t *r_begin, size_t *r_len) { return fb_iter_range_impl(fb, nbits, start, r_begin, r_len, /* val */ true, /* forward */ true); } /* * The same as fb_srange_iter, but searches backwards from start rather than * forwards. (The position returned is still the earliest bit in the range). */ static inline bool fb_srange_riter(fb_group_t *fb, size_t nbits, size_t start, size_t *r_begin, size_t *r_len) { return fb_iter_range_impl(fb, nbits, start, r_begin, r_len, /* val */ true, /* forward */ false); } /* Similar to fb_srange_iter, but searches for unset bits. */ static inline bool fb_urange_iter(fb_group_t *fb, size_t nbits, size_t start, size_t *r_begin, size_t *r_len) { return fb_iter_range_impl(fb, nbits, start, r_begin, r_len, /* val */ false, /* forward */ true); } /* Similar to fb_srange_riter, but searches for unset bits. */ static inline bool fb_urange_riter(fb_group_t *fb, size_t nbits, size_t start, size_t *r_begin, size_t *r_len) { return fb_iter_range_impl(fb, nbits, start, r_begin, r_len, /* val */ false, /* forward */ false); } JEMALLOC_ALWAYS_INLINE size_t fb_range_longest_impl(fb_group_t *fb, size_t nbits, bool val) { size_t begin = 0; size_t longest_len = 0; size_t len = 0; while (begin < nbits && fb_iter_range_impl(fb, nbits, begin, &begin, &len, val, /* forward */ true)) { if (len > longest_len) { longest_len = len; } begin += len; } return longest_len; } static inline size_t fb_srange_longest(fb_group_t *fb, size_t nbits) { return fb_range_longest_impl(fb, nbits, /* val */ true); } static inline size_t fb_urange_longest(fb_group_t *fb, size_t nbits) { return fb_range_longest_impl(fb, nbits, /* val */ false); } /* * Initializes each bit of dst with the bitwise-AND of the corresponding bits of * src1 and src2. All bitmaps must be the same size. */ static inline void fb_bit_and(fb_group_t *dst, fb_group_t *src1, fb_group_t *src2, size_t nbits) { size_t ngroups = FB_NGROUPS(nbits); for (size_t i = 0; i < ngroups; i++) { dst[i] = src1[i] & src2[i]; } } /* Like fb_bit_and, but with bitwise-OR. */ static inline void fb_bit_or(fb_group_t *dst, fb_group_t *src1, fb_group_t *src2, size_t nbits) { size_t ngroups = FB_NGROUPS(nbits); for (size_t i = 0; i < ngroups; i++) { dst[i] = src1[i] | src2[i]; } } /* Initializes dst bit i to the negation of source bit i. */ static inline void fb_bit_not(fb_group_t *dst, fb_group_t *src, size_t nbits) { size_t ngroups = FB_NGROUPS(nbits); for (size_t i = 0; i < ngroups; i++) { dst[i] = ~src[i]; } } #endif /* JEMALLOC_INTERNAL_FB_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/fxp.h000066400000000000000000000076551501533116600233610ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_FXP_H #define JEMALLOC_INTERNAL_FXP_H /* * A simple fixed-point math implementation, supporting only unsigned values * (with overflow being an error). * * It's not in general safe to use floating point in core code, because various * libc implementations we get linked against can assume that malloc won't touch * floating point state and call it with an unusual calling convention. */ /* * High 16 bits are the integer part, low 16 are the fractional part. Or * equivalently, repr == 2**16 * val, where we use "val" to refer to the * (imaginary) fractional representation of the true value. * * We pick a uint32_t here since it's convenient in some places to * double the representation size (i.e. multiplication and division use * 64-bit integer types), and a uint64_t is the largest type we're * certain is available. */ typedef uint32_t fxp_t; #define FXP_INIT_INT(x) ((x) << 16) #define FXP_INIT_PERCENT(pct) (((pct) << 16) / 100) /* * Amount of precision used in parsing and printing numbers. The integer bound * is simply because the integer part of the number gets 16 bits, and so is * bounded by 65536. * * We use a lot of precision for the fractional part, even though most of it * gets rounded off; this lets us get exact values for the important special * case where the denominator is a small power of 2 (for instance, * 1/512 == 0.001953125 is exactly representable even with only 16 bits of * fractional precision). We need to left-shift by 16 before dividing by * 10**precision, so we pick precision to be floor(log(2**48)) = 14. */ #define FXP_INTEGER_PART_DIGITS 5 #define FXP_FRACTIONAL_PART_DIGITS 14 /* * In addition to the integer and fractional parts of the number, we need to * include a null character and (possibly) a decimal point. */ #define FXP_BUF_SIZE (FXP_INTEGER_PART_DIGITS + FXP_FRACTIONAL_PART_DIGITS + 2) static inline fxp_t fxp_add(fxp_t a, fxp_t b) { return a + b; } static inline fxp_t fxp_sub(fxp_t a, fxp_t b) { assert(a >= b); return a - b; } static inline fxp_t fxp_mul(fxp_t a, fxp_t b) { uint64_t unshifted = (uint64_t)a * (uint64_t)b; /* * Unshifted is (a.val * 2**16) * (b.val * 2**16) * == (a.val * b.val) * 2**32, but we want * (a.val * b.val) * 2 ** 16. */ return (uint32_t)(unshifted >> 16); } static inline fxp_t fxp_div(fxp_t a, fxp_t b) { assert(b != 0); uint64_t unshifted = ((uint64_t)a << 32) / (uint64_t)b; /* * Unshifted is (a.val * 2**16) * (2**32) / (b.val * 2**16) * == (a.val / b.val) * (2 ** 32), which again corresponds to a right * shift of 16. */ return (uint32_t)(unshifted >> 16); } static inline uint32_t fxp_round_down(fxp_t a) { return a >> 16; } static inline uint32_t fxp_round_nearest(fxp_t a) { uint32_t fractional_part = (a & ((1U << 16) - 1)); uint32_t increment = (uint32_t)(fractional_part >= (1U << 15)); return (a >> 16) + increment; } /* * Approximately computes x * frac, without the size limitations that would be * imposed by converting u to an fxp_t. */ static inline size_t fxp_mul_frac(size_t x_orig, fxp_t frac) { assert(frac <= (1U << 16)); /* * Work around an over-enthusiastic warning about type limits below (on * 32-bit platforms, a size_t is always less than 1ULL << 48). */ uint64_t x = (uint64_t)x_orig; /* * If we can guarantee no overflow, multiply first before shifting, to * preserve some precision. Otherwise, shift first and then multiply. * In the latter case, we only lose the low 16 bits of a 48-bit number, * so we're still accurate to within 1/2**32. */ if (x < (1ULL << 48)) { return (size_t)((x * frac) >> 16); } else { return (size_t)((x >> 16) * (uint64_t)frac); } } /* * Returns true on error. Otherwise, returns false and updates *ptr to point to * the first character not parsed (because it wasn't a digit). */ bool fxp_parse(fxp_t *a, const char *ptr, char **end); void fxp_print(fxp_t a, char buf[FXP_BUF_SIZE]); #endif /* JEMALLOC_INTERNAL_FXP_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/hash.h000066400000000000000000000176731501533116600235100ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_HASH_H #define JEMALLOC_INTERNAL_HASH_H #include "jemalloc/internal/assert.h" /* * The following hash function is based on MurmurHash3, placed into the public * domain by Austin Appleby. See https://github.com/aappleby/smhasher for * details. */ /******************************************************************************/ /* Internal implementation. */ static inline uint32_t hash_rotl_32(uint32_t x, int8_t r) { return ((x << r) | (x >> (32 - r))); } static inline uint64_t hash_rotl_64(uint64_t x, int8_t r) { return ((x << r) | (x >> (64 - r))); } static inline uint32_t hash_get_block_32(const uint32_t *p, int i) { /* Handle unaligned read. */ if (unlikely((uintptr_t)p & (sizeof(uint32_t)-1)) != 0) { uint32_t ret; memcpy(&ret, (uint8_t *)(p + i), sizeof(uint32_t)); return ret; } return p[i]; } static inline uint64_t hash_get_block_64(const uint64_t *p, int i) { /* Handle unaligned read. */ if (unlikely((uintptr_t)p & (sizeof(uint64_t)-1)) != 0) { uint64_t ret; memcpy(&ret, (uint8_t *)(p + i), sizeof(uint64_t)); return ret; } return p[i]; } static inline uint32_t hash_fmix_32(uint32_t h) { h ^= h >> 16; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; return h; } static inline uint64_t hash_fmix_64(uint64_t k) { k ^= k >> 33; k *= KQU(0xff51afd7ed558ccd); k ^= k >> 33; k *= KQU(0xc4ceb9fe1a85ec53); k ^= k >> 33; return k; } static inline uint32_t hash_x86_32(const void *key, int len, uint32_t seed) { const uint8_t *data = (const uint8_t *) key; const int nblocks = len / 4; uint32_t h1 = seed; const uint32_t c1 = 0xcc9e2d51; const uint32_t c2 = 0x1b873593; /* body */ { const uint32_t *blocks = (const uint32_t *) (data + nblocks*4); int i; for (i = -nblocks; i; i++) { uint32_t k1 = hash_get_block_32(blocks, i); k1 *= c1; k1 = hash_rotl_32(k1, 15); k1 *= c2; h1 ^= k1; h1 = hash_rotl_32(h1, 13); h1 = h1*5 + 0xe6546b64; } } /* tail */ { const uint8_t *tail = (const uint8_t *) (data + nblocks*4); uint32_t k1 = 0; switch (len & 3) { case 3: k1 ^= tail[2] << 16; JEMALLOC_FALLTHROUGH; case 2: k1 ^= tail[1] << 8; JEMALLOC_FALLTHROUGH; case 1: k1 ^= tail[0]; k1 *= c1; k1 = hash_rotl_32(k1, 15); k1 *= c2; h1 ^= k1; } } /* finalization */ h1 ^= len; h1 = hash_fmix_32(h1); return h1; } static inline void hash_x86_128(const void *key, const int len, uint32_t seed, uint64_t r_out[2]) { const uint8_t * data = (const uint8_t *) key; const int nblocks = len / 16; uint32_t h1 = seed; uint32_t h2 = seed; uint32_t h3 = seed; uint32_t h4 = seed; const uint32_t c1 = 0x239b961b; const uint32_t c2 = 0xab0e9789; const uint32_t c3 = 0x38b34ae5; const uint32_t c4 = 0xa1e38b93; /* body */ { const uint32_t *blocks = (const uint32_t *) (data + nblocks*16); int i; for (i = -nblocks; i; i++) { uint32_t k1 = hash_get_block_32(blocks, i*4 + 0); uint32_t k2 = hash_get_block_32(blocks, i*4 + 1); uint32_t k3 = hash_get_block_32(blocks, i*4 + 2); uint32_t k4 = hash_get_block_32(blocks, i*4 + 3); k1 *= c1; k1 = hash_rotl_32(k1, 15); k1 *= c2; h1 ^= k1; h1 = hash_rotl_32(h1, 19); h1 += h2; h1 = h1*5 + 0x561ccd1b; k2 *= c2; k2 = hash_rotl_32(k2, 16); k2 *= c3; h2 ^= k2; h2 = hash_rotl_32(h2, 17); h2 += h3; h2 = h2*5 + 0x0bcaa747; k3 *= c3; k3 = hash_rotl_32(k3, 17); k3 *= c4; h3 ^= k3; h3 = hash_rotl_32(h3, 15); h3 += h4; h3 = h3*5 + 0x96cd1c35; k4 *= c4; k4 = hash_rotl_32(k4, 18); k4 *= c1; h4 ^= k4; h4 = hash_rotl_32(h4, 13); h4 += h1; h4 = h4*5 + 0x32ac3b17; } } /* tail */ { const uint8_t *tail = (const uint8_t *) (data + nblocks*16); uint32_t k1 = 0; uint32_t k2 = 0; uint32_t k3 = 0; uint32_t k4 = 0; switch (len & 15) { case 15: k4 ^= tail[14] << 16; JEMALLOC_FALLTHROUGH; case 14: k4 ^= tail[13] << 8; JEMALLOC_FALLTHROUGH; case 13: k4 ^= tail[12] << 0; k4 *= c4; k4 = hash_rotl_32(k4, 18); k4 *= c1; h4 ^= k4; JEMALLOC_FALLTHROUGH; case 12: k3 ^= (uint32_t) tail[11] << 24; JEMALLOC_FALLTHROUGH; case 11: k3 ^= tail[10] << 16; JEMALLOC_FALLTHROUGH; case 10: k3 ^= tail[ 9] << 8; JEMALLOC_FALLTHROUGH; case 9: k3 ^= tail[ 8] << 0; k3 *= c3; k3 = hash_rotl_32(k3, 17); k3 *= c4; h3 ^= k3; JEMALLOC_FALLTHROUGH; case 8: k2 ^= (uint32_t) tail[ 7] << 24; JEMALLOC_FALLTHROUGH; case 7: k2 ^= tail[ 6] << 16; JEMALLOC_FALLTHROUGH; case 6: k2 ^= tail[ 5] << 8; JEMALLOC_FALLTHROUGH; case 5: k2 ^= tail[ 4] << 0; k2 *= c2; k2 = hash_rotl_32(k2, 16); k2 *= c3; h2 ^= k2; JEMALLOC_FALLTHROUGH; case 4: k1 ^= (uint32_t) tail[ 3] << 24; JEMALLOC_FALLTHROUGH; case 3: k1 ^= tail[ 2] << 16; JEMALLOC_FALLTHROUGH; case 2: k1 ^= tail[ 1] << 8; JEMALLOC_FALLTHROUGH; case 1: k1 ^= tail[ 0] << 0; k1 *= c1; k1 = hash_rotl_32(k1, 15); k1 *= c2; h1 ^= k1; break; } } /* finalization */ h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; h1 = hash_fmix_32(h1); h2 = hash_fmix_32(h2); h3 = hash_fmix_32(h3); h4 = hash_fmix_32(h4); h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; r_out[0] = (((uint64_t) h2) << 32) | h1; r_out[1] = (((uint64_t) h4) << 32) | h3; } static inline void hash_x64_128(const void *key, const int len, const uint32_t seed, uint64_t r_out[2]) { const uint8_t *data = (const uint8_t *) key; const int nblocks = len / 16; uint64_t h1 = seed; uint64_t h2 = seed; const uint64_t c1 = KQU(0x87c37b91114253d5); const uint64_t c2 = KQU(0x4cf5ad432745937f); /* body */ { const uint64_t *blocks = (const uint64_t *) (data); int i; for (i = 0; i < nblocks; i++) { uint64_t k1 = hash_get_block_64(blocks, i*2 + 0); uint64_t k2 = hash_get_block_64(blocks, i*2 + 1); k1 *= c1; k1 = hash_rotl_64(k1, 31); k1 *= c2; h1 ^= k1; h1 = hash_rotl_64(h1, 27); h1 += h2; h1 = h1*5 + 0x52dce729; k2 *= c2; k2 = hash_rotl_64(k2, 33); k2 *= c1; h2 ^= k2; h2 = hash_rotl_64(h2, 31); h2 += h1; h2 = h2*5 + 0x38495ab5; } } /* tail */ { const uint8_t *tail = (const uint8_t*)(data + nblocks*16); uint64_t k1 = 0; uint64_t k2 = 0; switch (len & 15) { case 15: k2 ^= ((uint64_t)(tail[14])) << 48; JEMALLOC_FALLTHROUGH; case 14: k2 ^= ((uint64_t)(tail[13])) << 40; JEMALLOC_FALLTHROUGH; case 13: k2 ^= ((uint64_t)(tail[12])) << 32; JEMALLOC_FALLTHROUGH; case 12: k2 ^= ((uint64_t)(tail[11])) << 24; JEMALLOC_FALLTHROUGH; case 11: k2 ^= ((uint64_t)(tail[10])) << 16; JEMALLOC_FALLTHROUGH; case 10: k2 ^= ((uint64_t)(tail[ 9])) << 8; JEMALLOC_FALLTHROUGH; case 9: k2 ^= ((uint64_t)(tail[ 8])) << 0; k2 *= c2; k2 = hash_rotl_64(k2, 33); k2 *= c1; h2 ^= k2; JEMALLOC_FALLTHROUGH; case 8: k1 ^= ((uint64_t)(tail[ 7])) << 56; JEMALLOC_FALLTHROUGH; case 7: k1 ^= ((uint64_t)(tail[ 6])) << 48; JEMALLOC_FALLTHROUGH; case 6: k1 ^= ((uint64_t)(tail[ 5])) << 40; JEMALLOC_FALLTHROUGH; case 5: k1 ^= ((uint64_t)(tail[ 4])) << 32; JEMALLOC_FALLTHROUGH; case 4: k1 ^= ((uint64_t)(tail[ 3])) << 24; JEMALLOC_FALLTHROUGH; case 3: k1 ^= ((uint64_t)(tail[ 2])) << 16; JEMALLOC_FALLTHROUGH; case 2: k1 ^= ((uint64_t)(tail[ 1])) << 8; JEMALLOC_FALLTHROUGH; case 1: k1 ^= ((uint64_t)(tail[ 0])) << 0; k1 *= c1; k1 = hash_rotl_64(k1, 31); k1 *= c2; h1 ^= k1; break; } } /* finalization */ h1 ^= len; h2 ^= len; h1 += h2; h2 += h1; h1 = hash_fmix_64(h1); h2 = hash_fmix_64(h2); h1 += h2; h2 += h1; r_out[0] = h1; r_out[1] = h2; } /******************************************************************************/ /* API. */ static inline void hash(const void *key, size_t len, const uint32_t seed, size_t r_hash[2]) { assert(len <= INT_MAX); /* Unfortunate implementation limitation. */ #if (LG_SIZEOF_PTR == 3 && !defined(JEMALLOC_BIG_ENDIAN)) hash_x64_128(key, (int)len, seed, (uint64_t *)r_hash); #else { uint64_t hashes[2]; hash_x86_128(key, (int)len, seed, hashes); r_hash[0] = (size_t)hashes[0]; r_hash[1] = (size_t)hashes[1]; } #endif } #endif /* JEMALLOC_INTERNAL_HASH_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/hook.h000066400000000000000000000126471501533116600235210ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_HOOK_H #define JEMALLOC_INTERNAL_HOOK_H #include "jemalloc/internal/tsd.h" /* * This API is *extremely* experimental, and may get ripped out, changed in API- * and ABI-incompatible ways, be insufficiently or incorrectly documented, etc. * * It allows hooking the stateful parts of the API to see changes as they * happen. * * Allocation hooks are called after the allocation is done, free hooks are * called before the free is done, and expand hooks are called after the * allocation is expanded. * * For realloc and rallocx, if the expansion happens in place, the expansion * hook is called. If it is moved, then the alloc hook is called on the new * location, and then the free hook is called on the old location (i.e. both * hooks are invoked in between the alloc and the dalloc). * * If we return NULL from OOM, then usize might not be trustworthy. Calling * realloc(NULL, size) only calls the alloc hook, and calling realloc(ptr, 0) * only calls the free hook. (Calling realloc(NULL, 0) is treated as malloc(0), * and only calls the alloc hook). * * Reentrancy: * Reentrancy is guarded against from within the hook implementation. If you * call allocator functions from within a hook, the hooks will not be invoked * again. * Threading: * The installation of a hook synchronizes with all its uses. If you can * prove the installation of a hook happens-before a jemalloc entry point, * then the hook will get invoked (unless there's a racing removal). * * Hook insertion appears to be atomic at a per-thread level (i.e. if a thread * allocates and has the alloc hook invoked, then a subsequent free on the * same thread will also have the free hook invoked). * * The *removal* of a hook does *not* block until all threads are done with * the hook. Hook authors have to be resilient to this, and need some * out-of-band mechanism for cleaning up any dynamically allocated memory * associated with their hook. * Ordering: * Order of hook execution is unspecified, and may be different than insertion * order. */ #define HOOK_MAX 4 enum hook_alloc_e { hook_alloc_malloc, hook_alloc_posix_memalign, hook_alloc_aligned_alloc, hook_alloc_calloc, hook_alloc_memalign, hook_alloc_valloc, hook_alloc_mallocx, /* The reallocating functions have both alloc and dalloc variants */ hook_alloc_realloc, hook_alloc_rallocx, }; /* * We put the enum typedef after the enum, since this file may get included by * jemalloc_cpp.cpp, and C++ disallows enum forward declarations. */ typedef enum hook_alloc_e hook_alloc_t; enum hook_dalloc_e { hook_dalloc_free, hook_dalloc_dallocx, hook_dalloc_sdallocx, /* * The dalloc halves of reallocation (not called if in-place expansion * happens). */ hook_dalloc_realloc, hook_dalloc_rallocx, }; typedef enum hook_dalloc_e hook_dalloc_t; enum hook_expand_e { hook_expand_realloc, hook_expand_rallocx, hook_expand_xallocx, }; typedef enum hook_expand_e hook_expand_t; typedef void (*hook_alloc)( void *extra, hook_alloc_t type, void *result, uintptr_t result_raw, uintptr_t args_raw[3]); typedef void (*hook_dalloc)( void *extra, hook_dalloc_t type, void *address, uintptr_t args_raw[3]); typedef void (*hook_expand)( void *extra, hook_expand_t type, void *address, size_t old_usize, size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]); typedef struct hooks_s hooks_t; struct hooks_s { hook_alloc alloc_hook; hook_dalloc dalloc_hook; hook_expand expand_hook; void *extra; }; /* * Begin implementation details; everything above this point might one day live * in a public API. Everything below this point never will. */ /* * The realloc pathways haven't gotten any refactoring love in a while, and it's * fairly difficult to pass information from the entry point to the hooks. We * put the informaiton the hooks will need into a struct to encapsulate * everything. * * Much of these pathways are force-inlined, so that the compiler can avoid * materializing this struct until we hit an extern arena function. For fairly * goofy reasons, *many* of the realloc paths hit an extern arena function. * These paths are cold enough that it doesn't matter; eventually, we should * rewrite the realloc code to make the expand-in-place and the * free-then-realloc paths more orthogonal, at which point we don't need to * spread the hook logic all over the place. */ typedef struct hook_ralloc_args_s hook_ralloc_args_t; struct hook_ralloc_args_s { /* I.e. as opposed to rallocx. */ bool is_realloc; /* * The expand hook takes 4 arguments, even if only 3 are actually used; * we add an extra one in case the user decides to memcpy without * looking too closely at the hooked function. */ uintptr_t args[4]; }; /* * Returns an opaque handle to be used when removing the hook. NULL means that * we couldn't install the hook. */ bool hook_boot(); void *hook_install(tsdn_t *tsdn, hooks_t *hooks); /* Uninstalls the hook with the handle previously returned from hook_install. */ void hook_remove(tsdn_t *tsdn, void *opaque); /* Hooks */ void hook_invoke_alloc(hook_alloc_t type, void *result, uintptr_t result_raw, uintptr_t args_raw[3]); void hook_invoke_dalloc(hook_dalloc_t type, void *address, uintptr_t args_raw[3]); void hook_invoke_expand(hook_expand_t type, void *address, size_t old_usize, size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]); #endif /* JEMALLOC_INTERNAL_HOOK_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/hpa.h000066400000000000000000000116321501533116600233220ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_HPA_H #define JEMALLOC_INTERNAL_HPA_H #include "jemalloc/internal/exp_grow.h" #include "jemalloc/internal/hpa_hooks.h" #include "jemalloc/internal/hpa_opts.h" #include "jemalloc/internal/pai.h" #include "jemalloc/internal/psset.h" typedef struct hpa_central_s hpa_central_t; struct hpa_central_s { /* * The mutex guarding most of the operations on the central data * structure. */ malloc_mutex_t mtx; /* * Guards expansion of eden. We separate this from the regular mutex so * that cheaper operations can still continue while we're doing the OS * call. */ malloc_mutex_t grow_mtx; /* * Either NULL (if empty), or some integer multiple of a * hugepage-aligned number of hugepages. We carve them off one at a * time to satisfy new pageslab requests. * * Guarded by grow_mtx. */ void *eden; size_t eden_len; /* Source for metadata. */ base_t *base; /* Number of grow operations done on this hpa_central_t. */ uint64_t age_counter; /* The HPA hooks. */ hpa_hooks_t hooks; }; typedef struct hpa_shard_nonderived_stats_s hpa_shard_nonderived_stats_t; struct hpa_shard_nonderived_stats_s { /* * The number of times we've purged within a hugepage. * * Guarded by mtx. */ uint64_t npurge_passes; /* * The number of individual purge calls we perform (which should always * be bigger than npurge_passes, since each pass purges at least one * extent within a hugepage. * * Guarded by mtx. */ uint64_t npurges; /* * The number of times we've hugified a pageslab. * * Guarded by mtx. */ uint64_t nhugifies; /* * The number of times we've dehugified a pageslab. * * Guarded by mtx. */ uint64_t ndehugifies; }; /* Completely derived; only used by CTL. */ typedef struct hpa_shard_stats_s hpa_shard_stats_t; struct hpa_shard_stats_s { psset_stats_t psset_stats; hpa_shard_nonderived_stats_t nonderived_stats; }; typedef struct hpa_shard_s hpa_shard_t; struct hpa_shard_s { /* * pai must be the first member; we cast from a pointer to it to a * pointer to the hpa_shard_t. */ pai_t pai; /* The central allocator we get our hugepages from. */ hpa_central_t *central; /* Protects most of this shard's state. */ malloc_mutex_t mtx; /* * Guards the shard's access to the central allocator (preventing * multiple threads operating on this shard from accessing the central * allocator). */ malloc_mutex_t grow_mtx; /* The base metadata allocator. */ base_t *base; /* * This edata cache is the one we use when allocating a small extent * from a pageslab. The pageslab itself comes from the centralized * allocator, and so will use its edata_cache. */ edata_cache_fast_t ecf; psset_t psset; /* * How many grow operations have occurred. * * Guarded by grow_mtx. */ uint64_t age_counter; /* The arena ind we're associated with. */ unsigned ind; /* * Our emap. This is just a cache of the emap pointer in the associated * hpa_central. */ emap_t *emap; /* The configuration choices for this hpa shard. */ hpa_shard_opts_t opts; /* * How many pages have we started but not yet finished purging in this * hpa shard. */ size_t npending_purge; /* * Those stats which are copied directly into the CTL-centric hpa shard * stats. */ hpa_shard_nonderived_stats_t stats; /* * Last time we performed purge on this shard. */ nstime_t last_purge; }; /* * Whether or not the HPA can be used given the current configuration. This is * is not necessarily a guarantee that it backs its allocations by hugepages, * just that it can function properly given the system it's running on. */ bool hpa_supported(); bool hpa_central_init(hpa_central_t *central, base_t *base, const hpa_hooks_t *hooks); bool hpa_shard_init(hpa_shard_t *shard, hpa_central_t *central, emap_t *emap, base_t *base, edata_cache_t *edata_cache, unsigned ind, const hpa_shard_opts_t *opts); void hpa_shard_stats_accum(hpa_shard_stats_t *dst, hpa_shard_stats_t *src); void hpa_shard_stats_merge(tsdn_t *tsdn, hpa_shard_t *shard, hpa_shard_stats_t *dst); /* * Notify the shard that we won't use it for allocations much longer. Due to * the possibility of races, we don't actually prevent allocations; just flush * and disable the embedded edata_cache_small. */ void hpa_shard_disable(tsdn_t *tsdn, hpa_shard_t *shard); void hpa_shard_destroy(tsdn_t *tsdn, hpa_shard_t *shard); void hpa_shard_set_deferral_allowed(tsdn_t *tsdn, hpa_shard_t *shard, bool deferral_allowed); void hpa_shard_do_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard); /* * We share the fork ordering with the PA and arena prefork handling; that's why * these are 3 and 4 rather than 0 and 1. */ void hpa_shard_prefork3(tsdn_t *tsdn, hpa_shard_t *shard); void hpa_shard_prefork4(tsdn_t *tsdn, hpa_shard_t *shard); void hpa_shard_postfork_parent(tsdn_t *tsdn, hpa_shard_t *shard); void hpa_shard_postfork_child(tsdn_t *tsdn, hpa_shard_t *shard); #endif /* JEMALLOC_INTERNAL_HPA_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/hpa_hooks.h000066400000000000000000000010011501533116600245120ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_HPA_HOOKS_H #define JEMALLOC_INTERNAL_HPA_HOOKS_H typedef struct hpa_hooks_s hpa_hooks_t; struct hpa_hooks_s { void *(*map)(size_t size); void (*unmap)(void *ptr, size_t size); void (*purge)(void *ptr, size_t size); void (*hugify)(void *ptr, size_t size); void (*dehugify)(void *ptr, size_t size); void (*curtime)(nstime_t *r_time, bool first_reading); uint64_t (*ms_since)(nstime_t *r_time); }; extern hpa_hooks_t hpa_hooks_default; #endif /* JEMALLOC_INTERNAL_HPA_HOOKS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/hpa_opts.h000066400000000000000000000036721501533116600243740ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_HPA_OPTS_H #define JEMALLOC_INTERNAL_HPA_OPTS_H #include "jemalloc/internal/fxp.h" /* * This file is morally part of hpa.h, but is split out for header-ordering * reasons. */ typedef struct hpa_shard_opts_s hpa_shard_opts_t; struct hpa_shard_opts_s { /* * The largest size we'll allocate out of the shard. For those * allocations refused, the caller (in practice, the PA module) will * fall back to the more general (for now) PAC, which can always handle * any allocation request. */ size_t slab_max_alloc; /* * When the number of active bytes in a hugepage is >= * hugification_threshold, we force hugify it. */ size_t hugification_threshold; /* * The HPA purges whenever the number of pages exceeds dirty_mult * * active_pages. This may be set to (fxp_t)-1 to disable purging. */ fxp_t dirty_mult; /* * Whether or not the PAI methods are allowed to defer work to a * subsequent hpa_shard_do_deferred_work() call. Practically, this * corresponds to background threads being enabled. We track this * ourselves for encapsulation purposes. */ bool deferral_allowed; /* * How long a hugepage has to be a hugification candidate before it will * actually get hugified. */ uint64_t hugify_delay_ms; /* * Minimum amount of time between purges. */ uint64_t min_purge_interval_ms; }; #define HPA_SHARD_OPTS_DEFAULT { \ /* slab_max_alloc */ \ 64 * 1024, \ /* hugification_threshold */ \ HUGEPAGE * 95 / 100, \ /* dirty_mult */ \ FXP_INIT_PERCENT(25), \ /* \ * deferral_allowed \ * \ * Really, this is always set by the arena during creation \ * or by an hpa_shard_set_deferral_allowed call, so the value \ * we put here doesn't matter. \ */ \ false, \ /* hugify_delay_ms */ \ 10 * 1000, \ /* min_purge_interval_ms */ \ 5 * 1000 \ } #endif /* JEMALLOC_INTERNAL_HPA_OPTS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/hpdata.h000066400000000000000000000267531501533116600240250ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_HPDATA_H #define JEMALLOC_INTERNAL_HPDATA_H #include "jemalloc/internal/fb.h" #include "jemalloc/internal/ph.h" #include "jemalloc/internal/ql.h" #include "jemalloc/internal/typed_list.h" /* * The metadata representation we use for extents in hugepages. While the PAC * uses the edata_t to represent both active and inactive extents, the HP only * uses the edata_t for active ones; instead, inactive extent state is tracked * within hpdata associated with the enclosing hugepage-sized, hugepage-aligned * region of virtual address space. * * An hpdata need not be "truly" backed by a hugepage (which is not necessarily * an observable property of any given region of address space). It's just * hugepage-sized and hugepage-aligned; it's *potentially* huge. */ typedef struct hpdata_s hpdata_t; ph_structs(hpdata_age_heap, hpdata_t); struct hpdata_s { /* * We likewise follow the edata convention of mangling names and forcing * the use of accessors -- this lets us add some consistency checks on * access. */ /* * The address of the hugepage in question. This can't be named h_addr, * since that conflicts with a macro defined in Windows headers. */ void *h_address; /* Its age (measured in psset operations). */ uint64_t h_age; /* Whether or not we think the hugepage is mapped that way by the OS. */ bool h_huge; /* * For some properties, we keep parallel sets of bools; h_foo_allowed * and h_in_psset_foo_container. This is a decoupling mechanism to * avoid bothering the hpa (which manages policies) from the psset * (which is the mechanism used to enforce those policies). This allows * all the container management logic to live in one place, without the * HPA needing to know or care how that happens. */ /* * Whether or not the hpdata is allowed to be used to serve allocations, * and whether or not the psset is currently tracking it as such. */ bool h_alloc_allowed; bool h_in_psset_alloc_container; /* * The same, but with purging. There's no corresponding * h_in_psset_purge_container, because the psset (currently) always * removes hpdatas from their containers during updates (to implement * LRU for purging). */ bool h_purge_allowed; /* And with hugifying. */ bool h_hugify_allowed; /* When we became a hugification candidate. */ nstime_t h_time_hugify_allowed; bool h_in_psset_hugify_container; /* Whether or not a purge or hugify is currently happening. */ bool h_mid_purge; bool h_mid_hugify; /* * Whether or not the hpdata is being updated in the psset (i.e. if * there has been a psset_update_begin call issued without a matching * psset_update_end call). Eventually this will expand to other types * of updates. */ bool h_updating; /* Whether or not the hpdata is in a psset. */ bool h_in_psset; union { /* When nonempty (and also nonfull), used by the psset bins. */ hpdata_age_heap_link_t age_link; /* * When empty (or not corresponding to any hugepage), list * linkage. */ ql_elm(hpdata_t) ql_link_empty; }; /* * Linkage for the psset to track candidates for purging and hugifying. */ ql_elm(hpdata_t) ql_link_purge; ql_elm(hpdata_t) ql_link_hugify; /* The length of the largest contiguous sequence of inactive pages. */ size_t h_longest_free_range; /* Number of active pages. */ size_t h_nactive; /* A bitmap with bits set in the active pages. */ fb_group_t active_pages[FB_NGROUPS(HUGEPAGE_PAGES)]; /* * Number of dirty or active pages, and a bitmap tracking them. One * way to think of this is as which pages are dirty from the OS's * perspective. */ size_t h_ntouched; /* The touched pages (using the same definition as above). */ fb_group_t touched_pages[FB_NGROUPS(HUGEPAGE_PAGES)]; }; TYPED_LIST(hpdata_empty_list, hpdata_t, ql_link_empty) TYPED_LIST(hpdata_purge_list, hpdata_t, ql_link_purge) TYPED_LIST(hpdata_hugify_list, hpdata_t, ql_link_hugify) ph_proto(, hpdata_age_heap, hpdata_t); static inline void * hpdata_addr_get(const hpdata_t *hpdata) { return hpdata->h_address; } static inline void hpdata_addr_set(hpdata_t *hpdata, void *addr) { assert(HUGEPAGE_ADDR2BASE(addr) == addr); hpdata->h_address = addr; } static inline uint64_t hpdata_age_get(const hpdata_t *hpdata) { return hpdata->h_age; } static inline void hpdata_age_set(hpdata_t *hpdata, uint64_t age) { hpdata->h_age = age; } static inline bool hpdata_huge_get(const hpdata_t *hpdata) { return hpdata->h_huge; } static inline bool hpdata_alloc_allowed_get(const hpdata_t *hpdata) { return hpdata->h_alloc_allowed; } static inline void hpdata_alloc_allowed_set(hpdata_t *hpdata, bool alloc_allowed) { hpdata->h_alloc_allowed = alloc_allowed; } static inline bool hpdata_in_psset_alloc_container_get(const hpdata_t *hpdata) { return hpdata->h_in_psset_alloc_container; } static inline void hpdata_in_psset_alloc_container_set(hpdata_t *hpdata, bool in_container) { assert(in_container != hpdata->h_in_psset_alloc_container); hpdata->h_in_psset_alloc_container = in_container; } static inline bool hpdata_purge_allowed_get(const hpdata_t *hpdata) { return hpdata->h_purge_allowed; } static inline void hpdata_purge_allowed_set(hpdata_t *hpdata, bool purge_allowed) { assert(purge_allowed == false || !hpdata->h_mid_purge); hpdata->h_purge_allowed = purge_allowed; } static inline bool hpdata_hugify_allowed_get(const hpdata_t *hpdata) { return hpdata->h_hugify_allowed; } static inline void hpdata_allow_hugify(hpdata_t *hpdata, nstime_t now) { assert(!hpdata->h_mid_hugify); hpdata->h_hugify_allowed = true; hpdata->h_time_hugify_allowed = now; } static inline nstime_t hpdata_time_hugify_allowed(hpdata_t *hpdata) { return hpdata->h_time_hugify_allowed; } static inline void hpdata_disallow_hugify(hpdata_t *hpdata) { hpdata->h_hugify_allowed = false; } static inline bool hpdata_in_psset_hugify_container_get(const hpdata_t *hpdata) { return hpdata->h_in_psset_hugify_container; } static inline void hpdata_in_psset_hugify_container_set(hpdata_t *hpdata, bool in_container) { assert(in_container != hpdata->h_in_psset_hugify_container); hpdata->h_in_psset_hugify_container = in_container; } static inline bool hpdata_mid_purge_get(const hpdata_t *hpdata) { return hpdata->h_mid_purge; } static inline void hpdata_mid_purge_set(hpdata_t *hpdata, bool mid_purge) { assert(mid_purge != hpdata->h_mid_purge); hpdata->h_mid_purge = mid_purge; } static inline bool hpdata_mid_hugify_get(const hpdata_t *hpdata) { return hpdata->h_mid_hugify; } static inline void hpdata_mid_hugify_set(hpdata_t *hpdata, bool mid_hugify) { assert(mid_hugify != hpdata->h_mid_hugify); hpdata->h_mid_hugify = mid_hugify; } static inline bool hpdata_changing_state_get(const hpdata_t *hpdata) { return hpdata->h_mid_purge || hpdata->h_mid_hugify; } static inline bool hpdata_updating_get(const hpdata_t *hpdata) { return hpdata->h_updating; } static inline void hpdata_updating_set(hpdata_t *hpdata, bool updating) { assert(updating != hpdata->h_updating); hpdata->h_updating = updating; } static inline bool hpdata_in_psset_get(const hpdata_t *hpdata) { return hpdata->h_in_psset; } static inline void hpdata_in_psset_set(hpdata_t *hpdata, bool in_psset) { assert(in_psset != hpdata->h_in_psset); hpdata->h_in_psset = in_psset; } static inline size_t hpdata_longest_free_range_get(const hpdata_t *hpdata) { return hpdata->h_longest_free_range; } static inline void hpdata_longest_free_range_set(hpdata_t *hpdata, size_t longest_free_range) { assert(longest_free_range <= HUGEPAGE_PAGES); hpdata->h_longest_free_range = longest_free_range; } static inline size_t hpdata_nactive_get(hpdata_t *hpdata) { return hpdata->h_nactive; } static inline size_t hpdata_ntouched_get(hpdata_t *hpdata) { return hpdata->h_ntouched; } static inline size_t hpdata_ndirty_get(hpdata_t *hpdata) { return hpdata->h_ntouched - hpdata->h_nactive; } static inline size_t hpdata_nretained_get(hpdata_t *hpdata) { return HUGEPAGE_PAGES - hpdata->h_ntouched; } static inline void hpdata_assert_empty(hpdata_t *hpdata) { assert(fb_empty(hpdata->active_pages, HUGEPAGE_PAGES)); assert(hpdata->h_nactive == 0); } /* * Only used in tests, and in hpdata_assert_consistent, below. Verifies some * consistency properties of the hpdata (e.g. that cached counts of page stats * match computed ones). */ static inline bool hpdata_consistent(hpdata_t *hpdata) { if(fb_urange_longest(hpdata->active_pages, HUGEPAGE_PAGES) != hpdata_longest_free_range_get(hpdata)) { return false; } if (fb_scount(hpdata->active_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES) != hpdata->h_nactive) { return false; } if (fb_scount(hpdata->touched_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES) != hpdata->h_ntouched) { return false; } if (hpdata->h_ntouched < hpdata->h_nactive) { return false; } if (hpdata->h_huge && hpdata->h_ntouched != HUGEPAGE_PAGES) { return false; } if (hpdata_changing_state_get(hpdata) && ((hpdata->h_purge_allowed) || hpdata->h_hugify_allowed)) { return false; } if (hpdata_hugify_allowed_get(hpdata) != hpdata_in_psset_hugify_container_get(hpdata)) { return false; } return true; } static inline void hpdata_assert_consistent(hpdata_t *hpdata) { assert(hpdata_consistent(hpdata)); } static inline bool hpdata_empty(hpdata_t *hpdata) { return hpdata->h_nactive == 0; } static inline bool hpdata_full(hpdata_t *hpdata) { return hpdata->h_nactive == HUGEPAGE_PAGES; } void hpdata_init(hpdata_t *hpdata, void *addr, uint64_t age); /* * Given an hpdata which can serve an allocation request, pick and reserve an * offset within that allocation. */ void *hpdata_reserve_alloc(hpdata_t *hpdata, size_t sz); void hpdata_unreserve(hpdata_t *hpdata, void *begin, size_t sz); /* * The hpdata_purge_prepare_t allows grabbing the metadata required to purge * subranges of a hugepage while holding a lock, drop the lock during the actual * purging of them, and reacquire it to update the metadata again. */ typedef struct hpdata_purge_state_s hpdata_purge_state_t; struct hpdata_purge_state_s { size_t npurged; size_t ndirty_to_purge; fb_group_t to_purge[FB_NGROUPS(HUGEPAGE_PAGES)]; size_t next_purge_search_begin; }; /* * Initializes purge state. The access to hpdata must be externally * synchronized with other hpdata_* calls. * * You can tell whether or not a thread is purging or hugifying a given hpdata * via hpdata_changing_state_get(hpdata). Racing hugification or purging * operations aren't allowed. * * Once you begin purging, you have to follow through and call hpdata_purge_next * until you're done, and then end. Allocating out of an hpdata undergoing * purging is not allowed. * * Returns the number of dirty pages that will be purged. */ size_t hpdata_purge_begin(hpdata_t *hpdata, hpdata_purge_state_t *purge_state); /* * If there are more extents to purge, sets *r_purge_addr and *r_purge_size to * true, and returns true. Otherwise, returns false to indicate that we're * done. * * This requires exclusive access to the purge state, but *not* to the hpdata. * In particular, unreserve calls are allowed while purging (i.e. you can dalloc * into one part of the hpdata while purging a different part). */ bool hpdata_purge_next(hpdata_t *hpdata, hpdata_purge_state_t *purge_state, void **r_purge_addr, size_t *r_purge_size); /* * Updates the hpdata metadata after all purging is done. Needs external * synchronization. */ void hpdata_purge_end(hpdata_t *hpdata, hpdata_purge_state_t *purge_state); void hpdata_hugify(hpdata_t *hpdata); void hpdata_dehugify(hpdata_t *hpdata); #endif /* JEMALLOC_INTERNAL_HPDATA_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/inspect.h000066400000000000000000000022341501533116600242150ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_INSPECT_H #define JEMALLOC_INTERNAL_INSPECT_H /* * This module contains the heap introspection capabilities. For now they are * exposed purely through mallctl APIs in the experimental namespace, but this * may change over time. */ /* * The following two structs are for experimental purposes. See * experimental_utilization_query_ctl and * experimental_utilization_batch_query_ctl in src/ctl.c. */ typedef struct inspect_extent_util_stats_s inspect_extent_util_stats_t; struct inspect_extent_util_stats_s { size_t nfree; size_t nregs; size_t size; }; typedef struct inspect_extent_util_stats_verbose_s inspect_extent_util_stats_verbose_t; struct inspect_extent_util_stats_verbose_s { void *slabcur_addr; size_t nfree; size_t nregs; size_t size; size_t bin_nfree; size_t bin_nregs; }; void inspect_extent_util_stats_get(tsdn_t *tsdn, const void *ptr, size_t *nfree, size_t *nregs, size_t *size); void inspect_extent_util_stats_verbose_get(tsdn_t *tsdn, const void *ptr, size_t *nfree, size_t *nregs, size_t *size, size_t *bin_nfree, size_t *bin_nregs, void **slabcur_addr); #endif /* JEMALLOC_INTERNAL_INSPECT_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_decls.h000066400000000000000000000047621501533116600274140ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_DECLS_H #define JEMALLOC_INTERNAL_DECLS_H #include #ifdef _WIN32 # include # include "msvc_compat/windows_extra.h" # include "msvc_compat/strings.h" # ifdef _WIN64 # if LG_VADDR <= 32 # error Generate the headers using x64 vcargs # endif # else # if LG_VADDR > 32 # undef LG_VADDR # define LG_VADDR 32 # endif # endif #else # include # include # if !defined(__pnacl__) && !defined(__native_client__) # include # if !defined(SYS_write) && defined(__NR_write) # define SYS_write __NR_write # endif # if defined(SYS_open) && defined(__aarch64__) /* Android headers may define SYS_open to __NR_open even though * __NR_open may not exist on AArch64 (superseded by __NR_openat). */ # undef SYS_open # endif # include # endif # include # if defined(__FreeBSD__) || defined(__DragonFly__) # include # include # if defined(__FreeBSD__) # define cpu_set_t cpuset_t # endif # endif # include # ifdef JEMALLOC_OS_UNFAIR_LOCK # include # endif # ifdef JEMALLOC_GLIBC_MALLOC_HOOK # include # endif # include # include # include # ifdef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME # include # endif #endif #include #include #ifndef SIZE_T_MAX # define SIZE_T_MAX SIZE_MAX #endif #ifndef SSIZE_MAX # define SSIZE_MAX ((ssize_t)(SIZE_T_MAX >> 1)) #endif #include #include #include #include #include #include #ifndef offsetof # define offsetof(type, member) ((size_t)&(((type *)NULL)->member)) #endif #include #include #include #ifdef _MSC_VER # include typedef intptr_t ssize_t; # define PATH_MAX 1024 # define STDERR_FILENO 2 # define __func__ __FUNCTION__ # ifdef JEMALLOC_HAS_RESTRICT # define restrict __restrict # endif /* Disable warnings about deprecated system functions. */ # pragma warning(disable: 4996) #if _MSC_VER < 1800 static int isblank(int c) { return (c == '\t' || c == ' '); } #endif #else # include #endif #include /* * The Win32 midl compiler has #define small char; we don't use midl, but * "small" is a nice identifier to have available when talking about size * classes. */ #ifdef small # undef small #endif #endif /* JEMALLOC_INTERNAL_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h.in000066400000000000000000000274461501533116600276540ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_DEFS_H_ #define JEMALLOC_INTERNAL_DEFS_H_ /* * If JEMALLOC_PREFIX is defined via --with-jemalloc-prefix, it will cause all * public APIs to be prefixed. This makes it possible, with some care, to use * multiple allocators simultaneously. */ #undef JEMALLOC_PREFIX #undef JEMALLOC_CPREFIX /* * Define overrides for non-standard allocator-related functions if they are * present on the system. */ #undef JEMALLOC_OVERRIDE___LIBC_CALLOC #undef JEMALLOC_OVERRIDE___LIBC_FREE #undef JEMALLOC_OVERRIDE___LIBC_MALLOC #undef JEMALLOC_OVERRIDE___LIBC_MEMALIGN #undef JEMALLOC_OVERRIDE___LIBC_REALLOC #undef JEMALLOC_OVERRIDE___LIBC_VALLOC #undef JEMALLOC_OVERRIDE___POSIX_MEMALIGN /* * JEMALLOC_PRIVATE_NAMESPACE is used as a prefix for all library-private APIs. * For shared libraries, symbol visibility mechanisms prevent these symbols * from being exported, but for static libraries, naming collisions are a real * possibility. */ #undef JEMALLOC_PRIVATE_NAMESPACE /* * Hyper-threaded CPUs may need a special instruction inside spin loops in * order to yield to another virtual CPU. */ #undef CPU_SPINWAIT /* 1 if CPU_SPINWAIT is defined, 0 otherwise. */ #undef HAVE_CPU_SPINWAIT /* * Number of significant bits in virtual addresses. This may be less than the * total number of bits in a pointer, e.g. on x64, for which the uppermost 16 * bits are the same as bit 47. */ #undef LG_VADDR /* Defined if C11 atomics are available. */ #undef JEMALLOC_C11_ATOMICS /* Defined if GCC __atomic atomics are available. */ #undef JEMALLOC_GCC_ATOMIC_ATOMICS /* and the 8-bit variant support. */ #undef JEMALLOC_GCC_U8_ATOMIC_ATOMICS /* Defined if GCC __sync atomics are available. */ #undef JEMALLOC_GCC_SYNC_ATOMICS /* and the 8-bit variant support. */ #undef JEMALLOC_GCC_U8_SYNC_ATOMICS /* * Defined if __builtin_clz() and __builtin_clzl() are available. */ #undef JEMALLOC_HAVE_BUILTIN_CLZ /* * Defined if os_unfair_lock_*() functions are available, as provided by Darwin. */ #undef JEMALLOC_OS_UNFAIR_LOCK /* Defined if syscall(2) is usable. */ #undef JEMALLOC_USE_SYSCALL /* * Defined if secure_getenv(3) is available. */ #undef JEMALLOC_HAVE_SECURE_GETENV /* * Defined if issetugid(2) is available. */ #undef JEMALLOC_HAVE_ISSETUGID /* Defined if pthread_atfork(3) is available. */ #undef JEMALLOC_HAVE_PTHREAD_ATFORK /* Defined if pthread_setname_np(3) is available. */ #undef JEMALLOC_HAVE_PTHREAD_SETNAME_NP /* Defined if pthread_getname_np(3) is available. */ #undef JEMALLOC_HAVE_PTHREAD_GETNAME_NP /* Defined if pthread_get_name_np(3) is available. */ #undef JEMALLOC_HAVE_PTHREAD_GET_NAME_NP /* * Defined if clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is available. */ #undef JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE /* * Defined if clock_gettime(CLOCK_MONOTONIC, ...) is available. */ #undef JEMALLOC_HAVE_CLOCK_MONOTONIC /* * Defined if mach_absolute_time() is available. */ #undef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME /* * Defined if clock_gettime(CLOCK_REALTIME, ...) is available. */ #undef JEMALLOC_HAVE_CLOCK_REALTIME /* * Defined if _malloc_thread_cleanup() exists. At least in the case of * FreeBSD, pthread_key_create() allocates, which if used during malloc * bootstrapping will cause recursion into the pthreads library. Therefore, if * _malloc_thread_cleanup() exists, use it as the basis for thread cleanup in * malloc_tsd. */ #undef JEMALLOC_MALLOC_THREAD_CLEANUP /* * Defined if threaded initialization is known to be safe on this platform. * Among other things, it must be possible to initialize a mutex without * triggering allocation in order for threaded allocation to be safe. */ #undef JEMALLOC_THREADED_INIT /* * Defined if the pthreads implementation defines * _pthread_mutex_init_calloc_cb(), in which case the function is used in order * to avoid recursive allocation during mutex initialization. */ #undef JEMALLOC_MUTEX_INIT_CB /* Non-empty if the tls_model attribute is supported. */ #undef JEMALLOC_TLS_MODEL /* * JEMALLOC_DEBUG enables assertions and other sanity checks, and disables * inline functions. */ #undef JEMALLOC_DEBUG /* JEMALLOC_STATS enables statistics calculation. */ #undef JEMALLOC_STATS /* JEMALLOC_EXPERIMENTAL_SMALLOCX_API enables experimental smallocx API. */ #undef JEMALLOC_EXPERIMENTAL_SMALLOCX_API /* JEMALLOC_PROF enables allocation profiling. */ #undef JEMALLOC_PROF /* Use libunwind for profile backtracing if defined. */ #undef JEMALLOC_PROF_LIBUNWIND /* Use libgcc for profile backtracing if defined. */ #undef JEMALLOC_PROF_LIBGCC /* Use gcc intrinsics for profile backtracing if defined. */ #undef JEMALLOC_PROF_GCC /* * JEMALLOC_DSS enables use of sbrk(2) to allocate extents from the data storage * segment (DSS). */ #undef JEMALLOC_DSS /* Support memory filling (junk/zero). */ #undef JEMALLOC_FILL /* Support utrace(2)-based tracing. */ #undef JEMALLOC_UTRACE /* Support utrace(2)-based tracing (label based signature). */ #undef JEMALLOC_UTRACE_LABEL /* Support optional abort() on OOM. */ #undef JEMALLOC_XMALLOC /* Support lazy locking (avoid locking unless a second thread is launched). */ #undef JEMALLOC_LAZY_LOCK /* * Minimum allocation alignment is 2^LG_QUANTUM bytes (ignoring tiny size * classes). */ #undef LG_QUANTUM /* One page is 2^LG_PAGE bytes. */ #undef LG_PAGE /* Maximum number of regions in a slab. */ #undef CONFIG_LG_SLAB_MAXREGS /* * One huge page is 2^LG_HUGEPAGE bytes. Note that this is defined even if the * system does not explicitly support huge pages; system calls that require * explicit huge page support are separately configured. */ #undef LG_HUGEPAGE /* * If defined, adjacent virtual memory mappings with identical attributes * automatically coalesce, and they fragment when changes are made to subranges. * This is the normal order of things for mmap()/munmap(), but on Windows * VirtualAlloc()/VirtualFree() operations must be precisely matched, i.e. * mappings do *not* coalesce/fragment. */ #undef JEMALLOC_MAPS_COALESCE /* * If defined, retain memory for later reuse by default rather than using e.g. * munmap() to unmap freed extents. This is enabled on 64-bit Linux because * common sequences of mmap()/munmap() calls will cause virtual memory map * holes. */ #undef JEMALLOC_RETAIN /* TLS is used to map arenas and magazine caches to threads. */ #undef JEMALLOC_TLS /* * Used to mark unreachable code to quiet "end of non-void" compiler warnings. * Don't use this directly; instead use unreachable() from util.h */ #undef JEMALLOC_INTERNAL_UNREACHABLE /* * ffs*() functions to use for bitmapping. Don't use these directly; instead, * use ffs_*() from util.h. */ #undef JEMALLOC_INTERNAL_FFSLL #undef JEMALLOC_INTERNAL_FFSL #undef JEMALLOC_INTERNAL_FFS /* * popcount*() functions to use for bitmapping. */ #undef JEMALLOC_INTERNAL_POPCOUNTL #undef JEMALLOC_INTERNAL_POPCOUNT /* * If defined, explicitly attempt to more uniformly distribute large allocation * pointer alignments across all cache indices. */ #undef JEMALLOC_CACHE_OBLIVIOUS /* * If defined, enable logging facilities. We make this a configure option to * avoid taking extra branches everywhere. */ #undef JEMALLOC_LOG /* * If defined, use readlinkat() (instead of readlink()) to follow * /etc/malloc_conf. */ #undef JEMALLOC_READLINKAT /* * Darwin (OS X) uses zones to work around Mach-O symbol override shortcomings. */ #undef JEMALLOC_ZONE /* * Methods for determining whether the OS overcommits. * JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY: Linux's * /proc/sys/vm.overcommit_memory file. * JEMALLOC_SYSCTL_VM_OVERCOMMIT: FreeBSD's vm.overcommit sysctl. */ #undef JEMALLOC_SYSCTL_VM_OVERCOMMIT #undef JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY /* Defined if madvise(2) is available. */ #undef JEMALLOC_HAVE_MADVISE /* * Defined if transparent huge pages are supported via the MADV_[NO]HUGEPAGE * arguments to madvise(2). */ #undef JEMALLOC_HAVE_MADVISE_HUGE /* * Methods for purging unused pages differ between operating systems. * * madvise(..., MADV_FREE) : This marks pages as being unused, such that they * will be discarded rather than swapped out. * madvise(..., MADV_DONTNEED) : If JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS is * defined, this immediately discards pages, * such that new pages will be demand-zeroed if * the address region is later touched; * otherwise this behaves similarly to * MADV_FREE, though typically with higher * system overhead. */ #undef JEMALLOC_PURGE_MADVISE_FREE #undef JEMALLOC_PURGE_MADVISE_DONTNEED #undef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS /* Defined if madvise(2) is available but MADV_FREE is not (x86 Linux only). */ #undef JEMALLOC_DEFINE_MADVISE_FREE /* * Defined if MADV_DO[NT]DUMP is supported as an argument to madvise. */ #undef JEMALLOC_MADVISE_DONTDUMP /* * Defined if MADV_[NO]CORE is supported as an argument to madvise. */ #undef JEMALLOC_MADVISE_NOCORE /* Defined if mprotect(2) is available. */ #undef JEMALLOC_HAVE_MPROTECT /* * Defined if transparent huge pages (THPs) are supported via the * MADV_[NO]HUGEPAGE arguments to madvise(2), and THP support is enabled. */ #undef JEMALLOC_THP /* Defined if posix_madvise is available. */ #undef JEMALLOC_HAVE_POSIX_MADVISE /* * Method for purging unused pages using posix_madvise. * * posix_madvise(..., POSIX_MADV_DONTNEED) */ #undef JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED #undef JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED_ZEROS /* * Defined if memcntl page admin call is supported */ #undef JEMALLOC_HAVE_MEMCNTL /* * Defined if malloc_size is supported */ #undef JEMALLOC_HAVE_MALLOC_SIZE /* Define if operating system has alloca.h header. */ #undef JEMALLOC_HAS_ALLOCA_H /* C99 restrict keyword supported. */ #undef JEMALLOC_HAS_RESTRICT /* For use by hash code. */ #undef JEMALLOC_BIG_ENDIAN /* sizeof(int) == 2^LG_SIZEOF_INT. */ #undef LG_SIZEOF_INT /* sizeof(long) == 2^LG_SIZEOF_LONG. */ #undef LG_SIZEOF_LONG /* sizeof(long long) == 2^LG_SIZEOF_LONG_LONG. */ #undef LG_SIZEOF_LONG_LONG /* sizeof(intmax_t) == 2^LG_SIZEOF_INTMAX_T. */ #undef LG_SIZEOF_INTMAX_T /* glibc malloc hooks (__malloc_hook, __realloc_hook, __free_hook). */ #undef JEMALLOC_GLIBC_MALLOC_HOOK /* glibc memalign hook. */ #undef JEMALLOC_GLIBC_MEMALIGN_HOOK /* pthread support */ #undef JEMALLOC_HAVE_PTHREAD /* dlsym() support */ #undef JEMALLOC_HAVE_DLSYM /* Adaptive mutex support in pthreads. */ #undef JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP /* GNU specific sched_getcpu support */ #undef JEMALLOC_HAVE_SCHED_GETCPU /* GNU specific sched_setaffinity support */ #undef JEMALLOC_HAVE_SCHED_SETAFFINITY /* * If defined, all the features necessary for background threads are present. */ #undef JEMALLOC_BACKGROUND_THREAD /* * If defined, jemalloc symbols are not exported (doesn't work when * JEMALLOC_PREFIX is not defined). */ #undef JEMALLOC_EXPORT /* config.malloc_conf options string. */ #undef JEMALLOC_CONFIG_MALLOC_CONF /* If defined, jemalloc takes the malloc/free/etc. symbol names. */ #undef JEMALLOC_IS_MALLOC /* * Defined if strerror_r returns char * if _GNU_SOURCE is defined. */ #undef JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE /* Performs additional safety checks when defined. */ #undef JEMALLOC_OPT_SAFETY_CHECKS /* Is C++ support being built? */ #undef JEMALLOC_ENABLE_CXX /* Performs additional size checks when defined. */ #undef JEMALLOC_OPT_SIZE_CHECKS /* Allows sampled junk and stash for checking use-after-free when defined. */ #undef JEMALLOC_UAF_DETECTION /* Darwin VM_MAKE_TAG support */ #undef JEMALLOC_HAVE_VM_MAKE_TAG /* If defined, realloc(ptr, 0) defaults to "free" instead of "alloc". */ #undef JEMALLOC_ZERO_REALLOC_DEFAULT_FREE #endif /* JEMALLOC_INTERNAL_DEFS_H_ */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_externs.h000066400000000000000000000047231501533116600300070ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_EXTERNS_H #define JEMALLOC_INTERNAL_EXTERNS_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/hpa_opts.h" #include "jemalloc/internal/sec_opts.h" #include "jemalloc/internal/tsd_types.h" #include "jemalloc/internal/nstime.h" /* TSD checks this to set thread local slow state accordingly. */ extern bool malloc_slow; /* Run-time options. */ extern bool opt_abort; extern bool opt_abort_conf; extern bool opt_trust_madvise; extern bool opt_confirm_conf; extern bool opt_hpa; extern hpa_shard_opts_t opt_hpa_opts; extern sec_opts_t opt_hpa_sec_opts; extern const char *opt_junk; extern bool opt_junk_alloc; extern bool opt_junk_free; extern void (*junk_free_callback)(void *ptr, size_t size); extern void (*junk_alloc_callback)(void *ptr, size_t size); extern bool opt_utrace; extern bool opt_xmalloc; extern bool opt_experimental_infallible_new; extern bool opt_zero; extern unsigned opt_narenas; extern zero_realloc_action_t opt_zero_realloc_action; extern malloc_init_t malloc_init_state; extern const char *zero_realloc_mode_names[]; extern atomic_zu_t zero_realloc_count; extern bool opt_cache_oblivious; /* Escape free-fastpath when ptr & mask == 0 (for sanitization purpose). */ extern uintptr_t san_cache_bin_nonfast_mask; /* Number of CPUs. */ extern unsigned ncpus; /* Number of arenas used for automatic multiplexing of threads and arenas. */ extern unsigned narenas_auto; /* Base index for manual arenas. */ extern unsigned manual_arena_base; /* * Arenas that are used to service external requests. Not all elements of the * arenas array are necessarily used; arenas are created lazily as needed. */ extern atomic_p_t arenas[]; void *a0malloc(size_t size); void a0dalloc(void *ptr); void *bootstrap_malloc(size_t size); void *bootstrap_calloc(size_t num, size_t size); void bootstrap_free(void *ptr); void arena_set(unsigned ind, arena_t *arena); unsigned narenas_total_get(void); arena_t *arena_init(tsdn_t *tsdn, unsigned ind, const arena_config_t *config); arena_t *arena_choose_hard(tsd_t *tsd, bool internal); void arena_migrate(tsd_t *tsd, arena_t *oldarena, arena_t *newarena); void iarena_cleanup(tsd_t *tsd); void arena_cleanup(tsd_t *tsd); size_t batch_alloc(void **ptrs, size_t num, size_t size, int flags); void jemalloc_prefork(void); void jemalloc_postfork_parent(void); void jemalloc_postfork_child(void); void je_sdallocx_noflags(void *ptr, size_t size); void *malloc_default(size_t size); #endif /* JEMALLOC_INTERNAL_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_includes.h000066400000000000000000000073761501533116600301340ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_INCLUDES_H #define JEMALLOC_INTERNAL_INCLUDES_H /* * jemalloc can conceptually be broken into components (arena, tcache, etc.), * but there are circular dependencies that cannot be broken without * substantial performance degradation. * * Historically, we dealt with this by each header into four sections (types, * structs, externs, and inlines), and included each header file multiple times * in this file, picking out the portion we want on each pass using the * following #defines: * JEMALLOC_H_TYPES : Preprocessor-defined constants and pseudo-opaque data * types. * JEMALLOC_H_STRUCTS : Data structures. * JEMALLOC_H_EXTERNS : Extern data declarations and function prototypes. * JEMALLOC_H_INLINES : Inline functions. * * We're moving toward a world in which the dependencies are explicit; each file * will #include the headers it depends on (rather than relying on them being * implicitly available via this file including every header file in the * project). * * We're now in an intermediate state: we've broken up the header files to avoid * having to include each one multiple times, but have not yet moved the * dependency information into the header files (i.e. we still rely on the * ordering in this file to ensure all a header's dependencies are available in * its translation unit). Each component is now broken up into multiple header * files, corresponding to the sections above (e.g. instead of "foo.h", we now * have "foo_types.h", "foo_structs.h", "foo_externs.h", "foo_inlines.h"). * * Those files which have been converted to explicitly include their * inter-component dependencies are now in the initial HERMETIC HEADERS * section. All headers may still rely on jemalloc_preamble.h (which, by fiat, * must be included first in every translation unit) for system headers and * global jemalloc definitions, however. */ /******************************************************************************/ /* TYPES */ /******************************************************************************/ #include "jemalloc/internal/arena_types.h" #include "jemalloc/internal/tcache_types.h" #include "jemalloc/internal/prof_types.h" /******************************************************************************/ /* STRUCTS */ /******************************************************************************/ #include "jemalloc/internal/prof_structs.h" #include "jemalloc/internal/arena_structs.h" #include "jemalloc/internal/tcache_structs.h" #include "jemalloc/internal/background_thread_structs.h" /******************************************************************************/ /* EXTERNS */ /******************************************************************************/ #include "jemalloc/internal/jemalloc_internal_externs.h" #include "jemalloc/internal/arena_externs.h" #include "jemalloc/internal/large_externs.h" #include "jemalloc/internal/tcache_externs.h" #include "jemalloc/internal/prof_externs.h" #include "jemalloc/internal/background_thread_externs.h" /******************************************************************************/ /* INLINES */ /******************************************************************************/ #include "jemalloc/internal/jemalloc_internal_inlines_a.h" /* * Include portions of arena code interleaved with tcache code in order to * resolve circular dependencies. */ #include "jemalloc/internal/arena_inlines_a.h" #include "jemalloc/internal/jemalloc_internal_inlines_b.h" #include "jemalloc/internal/tcache_inlines.h" #include "jemalloc/internal/arena_inlines_b.h" #include "jemalloc/internal/jemalloc_internal_inlines_c.h" #include "jemalloc/internal/prof_inlines.h" #include "jemalloc/internal/background_thread_inlines.h" #endif /* JEMALLOC_INTERNAL_INCLUDES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_a.h000066400000000000000000000057261501533116600302640ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_INLINES_A_H #define JEMALLOC_INTERNAL_INLINES_A_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/bit_util.h" #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/ticker.h" JEMALLOC_ALWAYS_INLINE malloc_cpuid_t malloc_getcpu(void) { assert(have_percpu_arena); #if defined(_WIN32) return GetCurrentProcessorNumber(); #elif defined(JEMALLOC_HAVE_SCHED_GETCPU) return (malloc_cpuid_t)sched_getcpu(); #else not_reached(); return -1; #endif } /* Return the chosen arena index based on current cpu. */ JEMALLOC_ALWAYS_INLINE unsigned percpu_arena_choose(void) { assert(have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)); malloc_cpuid_t cpuid = malloc_getcpu(); assert(cpuid >= 0); unsigned arena_ind; if ((opt_percpu_arena == percpu_arena) || ((unsigned)cpuid < ncpus / 2)) { arena_ind = cpuid; } else { assert(opt_percpu_arena == per_phycpu_arena); /* Hyper threads on the same physical CPU share arena. */ arena_ind = cpuid - ncpus / 2; } return arena_ind; } /* Return the limit of percpu auto arena range, i.e. arenas[0...ind_limit). */ JEMALLOC_ALWAYS_INLINE unsigned percpu_arena_ind_limit(percpu_arena_mode_t mode) { assert(have_percpu_arena && PERCPU_ARENA_ENABLED(mode)); if (mode == per_phycpu_arena && ncpus > 1) { if (ncpus % 2) { /* This likely means a misconfig. */ return ncpus / 2 + 1; } return ncpus / 2; } else { return ncpus; } } static inline arena_t * arena_get(tsdn_t *tsdn, unsigned ind, bool init_if_missing) { arena_t *ret; assert(ind < MALLOCX_ARENA_LIMIT); ret = (arena_t *)atomic_load_p(&arenas[ind], ATOMIC_ACQUIRE); if (unlikely(ret == NULL)) { if (init_if_missing) { ret = arena_init(tsdn, ind, &arena_config_default); } } return ret; } JEMALLOC_ALWAYS_INLINE bool tcache_available(tsd_t *tsd) { /* * Thread specific auto tcache might be unavailable if: 1) during tcache * initialization, or 2) disabled through thread.tcache.enabled mallctl * or config options. This check covers all cases. */ if (likely(tsd_tcache_enabled_get(tsd))) { /* Associated arena == NULL implies tcache init in progress. */ if (config_debug && tsd_tcache_slowp_get(tsd)->arena != NULL) { tcache_assert_initialized(tsd_tcachep_get(tsd)); } return true; } return false; } JEMALLOC_ALWAYS_INLINE tcache_t * tcache_get(tsd_t *tsd) { if (!tcache_available(tsd)) { return NULL; } return tsd_tcachep_get(tsd); } JEMALLOC_ALWAYS_INLINE tcache_slow_t * tcache_slow_get(tsd_t *tsd) { if (!tcache_available(tsd)) { return NULL; } return tsd_tcache_slowp_get(tsd); } static inline void pre_reentrancy(tsd_t *tsd, arena_t *arena) { /* arena is the current context. Reentry from a0 is not allowed. */ assert(arena != arena_get(tsd_tsdn(tsd), 0, false)); tsd_pre_reentrancy_raw(tsd); } static inline void post_reentrancy(tsd_t *tsd) { tsd_post_reentrancy_raw(tsd); } #endif /* JEMALLOC_INTERNAL_INLINES_A_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_b.h000066400000000000000000000053361501533116600302620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_INLINES_B_H #define JEMALLOC_INTERNAL_INLINES_B_H #include "jemalloc/internal/extent.h" static inline void percpu_arena_update(tsd_t *tsd, unsigned cpu) { assert(have_percpu_arena); arena_t *oldarena = tsd_arena_get(tsd); assert(oldarena != NULL); unsigned oldind = arena_ind_get(oldarena); if (oldind != cpu) { unsigned newind = cpu; arena_t *newarena = arena_get(tsd_tsdn(tsd), newind, true); assert(newarena != NULL); /* Set new arena/tcache associations. */ arena_migrate(tsd, oldarena, newarena); tcache_t *tcache = tcache_get(tsd); if (tcache != NULL) { tcache_slow_t *tcache_slow = tsd_tcache_slowp_get(tsd); tcache_arena_reassociate(tsd_tsdn(tsd), tcache_slow, tcache, newarena); } } } /* Choose an arena based on a per-thread value. */ static inline arena_t * arena_choose_impl(tsd_t *tsd, arena_t *arena, bool internal) { arena_t *ret; if (arena != NULL) { return arena; } /* During reentrancy, arena 0 is the safest bet. */ if (unlikely(tsd_reentrancy_level_get(tsd) > 0)) { return arena_get(tsd_tsdn(tsd), 0, true); } ret = internal ? tsd_iarena_get(tsd) : tsd_arena_get(tsd); if (unlikely(ret == NULL)) { ret = arena_choose_hard(tsd, internal); assert(ret); if (tcache_available(tsd)) { tcache_slow_t *tcache_slow = tsd_tcache_slowp_get(tsd); tcache_t *tcache = tsd_tcachep_get(tsd); if (tcache_slow->arena != NULL) { /* See comments in tsd_tcache_data_init().*/ assert(tcache_slow->arena == arena_get(tsd_tsdn(tsd), 0, false)); if (tcache_slow->arena != ret) { tcache_arena_reassociate(tsd_tsdn(tsd), tcache_slow, tcache, ret); } } else { tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, tcache, ret); } } } /* * Note that for percpu arena, if the current arena is outside of the * auto percpu arena range, (i.e. thread is assigned to a manually * managed arena), then percpu arena is skipped. */ if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena) && !internal && (arena_ind_get(ret) < percpu_arena_ind_limit(opt_percpu_arena)) && (ret->last_thd != tsd_tsdn(tsd))) { unsigned ind = percpu_arena_choose(); if (arena_ind_get(ret) != ind) { percpu_arena_update(tsd, ind); ret = tsd_arena_get(tsd); } ret->last_thd = tsd_tsdn(tsd); } return ret; } static inline arena_t * arena_choose(tsd_t *tsd, arena_t *arena) { return arena_choose_impl(tsd, arena, false); } static inline arena_t * arena_ichoose(tsd_t *tsd, arena_t *arena) { return arena_choose_impl(tsd, arena, true); } static inline bool arena_is_auto(arena_t *arena) { assert(narenas_auto > 0); return (arena_ind_get(arena) < manual_arena_base); } #endif /* JEMALLOC_INTERNAL_INLINES_B_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h000066400000000000000000000323351501533116600302620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_INLINES_C_H #define JEMALLOC_INTERNAL_INLINES_C_H #include "jemalloc/internal/hook.h" #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/log.h" #include "jemalloc/internal/sz.h" #include "jemalloc/internal/thread_event.h" #include "jemalloc/internal/witness.h" /* * Translating the names of the 'i' functions: * Abbreviations used in the first part of the function name (before * alloc/dalloc) describe what that function accomplishes: * a: arena (query) * s: size (query, or sized deallocation) * e: extent (query) * p: aligned (allocates) * vs: size (query, without knowing that the pointer is into the heap) * r: rallocx implementation * x: xallocx implementation * Abbreviations used in the second part of the function name (after * alloc/dalloc) describe the arguments it takes * z: whether to return zeroed memory * t: accepts a tcache_t * parameter * m: accepts an arena_t * parameter */ JEMALLOC_ALWAYS_INLINE arena_t * iaalloc(tsdn_t *tsdn, const void *ptr) { assert(ptr != NULL); return arena_aalloc(tsdn, ptr); } JEMALLOC_ALWAYS_INLINE size_t isalloc(tsdn_t *tsdn, const void *ptr) { assert(ptr != NULL); return arena_salloc(tsdn, ptr); } JEMALLOC_ALWAYS_INLINE void * iallocztm(tsdn_t *tsdn, size_t size, szind_t ind, bool zero, tcache_t *tcache, bool is_internal, arena_t *arena, bool slow_path) { void *ret; assert(!is_internal || tcache == NULL); assert(!is_internal || arena == NULL || arena_is_auto(arena)); if (!tsdn_null(tsdn) && tsd_reentrancy_level_get(tsdn_tsd(tsdn)) == 0) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); } ret = arena_malloc(tsdn, arena, size, ind, zero, tcache, slow_path); if (config_stats && is_internal && likely(ret != NULL)) { arena_internal_add(iaalloc(tsdn, ret), isalloc(tsdn, ret)); } return ret; } JEMALLOC_ALWAYS_INLINE void * ialloc(tsd_t *tsd, size_t size, szind_t ind, bool zero, bool slow_path) { return iallocztm(tsd_tsdn(tsd), size, ind, zero, tcache_get(tsd), false, NULL, slow_path); } JEMALLOC_ALWAYS_INLINE void * ipallocztm(tsdn_t *tsdn, size_t usize, size_t alignment, bool zero, tcache_t *tcache, bool is_internal, arena_t *arena) { void *ret; assert(usize != 0); assert(usize == sz_sa2u(usize, alignment)); assert(!is_internal || tcache == NULL); assert(!is_internal || arena == NULL || arena_is_auto(arena)); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); ret = arena_palloc(tsdn, arena, usize, alignment, zero, tcache); assert(ALIGNMENT_ADDR2BASE(ret, alignment) == ret); if (config_stats && is_internal && likely(ret != NULL)) { arena_internal_add(iaalloc(tsdn, ret), isalloc(tsdn, ret)); } return ret; } JEMALLOC_ALWAYS_INLINE void * ipalloct(tsdn_t *tsdn, size_t usize, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena) { return ipallocztm(tsdn, usize, alignment, zero, tcache, false, arena); } JEMALLOC_ALWAYS_INLINE void * ipalloc(tsd_t *tsd, size_t usize, size_t alignment, bool zero) { return ipallocztm(tsd_tsdn(tsd), usize, alignment, zero, tcache_get(tsd), false, NULL); } JEMALLOC_ALWAYS_INLINE size_t ivsalloc(tsdn_t *tsdn, const void *ptr) { return arena_vsalloc(tsdn, ptr); } JEMALLOC_ALWAYS_INLINE void idalloctm(tsdn_t *tsdn, void *ptr, tcache_t *tcache, emap_alloc_ctx_t *alloc_ctx, bool is_internal, bool slow_path) { assert(ptr != NULL); assert(!is_internal || tcache == NULL); assert(!is_internal || arena_is_auto(iaalloc(tsdn, ptr))); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (config_stats && is_internal) { arena_internal_sub(iaalloc(tsdn, ptr), isalloc(tsdn, ptr)); } if (!is_internal && !tsdn_null(tsdn) && tsd_reentrancy_level_get(tsdn_tsd(tsdn)) != 0) { assert(tcache == NULL); } arena_dalloc(tsdn, ptr, tcache, alloc_ctx, slow_path); } JEMALLOC_ALWAYS_INLINE void idalloc(tsd_t *tsd, void *ptr) { idalloctm(tsd_tsdn(tsd), ptr, tcache_get(tsd), NULL, false, true); } JEMALLOC_ALWAYS_INLINE void isdalloct(tsdn_t *tsdn, void *ptr, size_t size, tcache_t *tcache, emap_alloc_ctx_t *alloc_ctx, bool slow_path) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); arena_sdalloc(tsdn, ptr, size, tcache, alloc_ctx, slow_path); } JEMALLOC_ALWAYS_INLINE void * iralloct_realign(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena, hook_ralloc_args_t *hook_args) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); void *p; size_t usize, copysize; usize = sz_sa2u(size, alignment); if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { return NULL; } p = ipalloct(tsdn, usize, alignment, zero, tcache, arena); if (p == NULL) { return NULL; } /* * Copy at most size bytes (not size+extra), since the caller has no * expectation that the extra bytes will be reliably preserved. */ copysize = (size < oldsize) ? size : oldsize; memcpy(p, ptr, copysize); hook_invoke_alloc(hook_args->is_realloc ? hook_alloc_realloc : hook_alloc_rallocx, p, (uintptr_t)p, hook_args->args); hook_invoke_dalloc(hook_args->is_realloc ? hook_dalloc_realloc : hook_dalloc_rallocx, ptr, hook_args->args); isdalloct(tsdn, ptr, oldsize, tcache, NULL, true); return p; } /* * is_realloc threads through the knowledge of whether or not this call comes * from je_realloc (as opposed to je_rallocx); this ensures that we pass the * correct entry point into any hooks. * Note that these functions are all force-inlined, so no actual bool gets * passed-around anywhere. */ JEMALLOC_ALWAYS_INLINE void * iralloct(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena, hook_ralloc_args_t *hook_args) { assert(ptr != NULL); assert(size != 0); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (alignment != 0 && ((uintptr_t)ptr & ((uintptr_t)alignment-1)) != 0) { /* * Existing object alignment is inadequate; allocate new space * and copy. */ return iralloct_realign(tsdn, ptr, oldsize, size, alignment, zero, tcache, arena, hook_args); } return arena_ralloc(tsdn, arena, ptr, oldsize, size, alignment, zero, tcache, hook_args); } JEMALLOC_ALWAYS_INLINE void * iralloc(tsd_t *tsd, void *ptr, size_t oldsize, size_t size, size_t alignment, bool zero, hook_ralloc_args_t *hook_args) { return iralloct(tsd_tsdn(tsd), ptr, oldsize, size, alignment, zero, tcache_get(tsd), NULL, hook_args); } JEMALLOC_ALWAYS_INLINE bool ixalloc(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra, size_t alignment, bool zero, size_t *newsize) { assert(ptr != NULL); assert(size != 0); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (alignment != 0 && ((uintptr_t)ptr & ((uintptr_t)alignment-1)) != 0) { /* Existing object alignment is inadequate. */ *newsize = oldsize; return true; } return arena_ralloc_no_move(tsdn, ptr, oldsize, size, extra, zero, newsize); } JEMALLOC_ALWAYS_INLINE void fastpath_success_finish(tsd_t *tsd, uint64_t allocated_after, cache_bin_t *bin, void *ret) { thread_allocated_set(tsd, allocated_after); if (config_stats) { bin->tstats.nrequests++; } LOG("core.malloc.exit", "result: %p", ret); } JEMALLOC_ALWAYS_INLINE bool malloc_initialized(void) { return (malloc_init_state == malloc_init_initialized); } /* * malloc() fastpath. Included here so that we can inline it into operator new; * function call overhead there is non-negligible as a fraction of total CPU in * allocation-heavy C++ programs. We take the fallback alloc to allow malloc * (which can return NULL) to differ in its behavior from operator new (which * can't). It matches the signature of malloc / operator new so that we can * tail-call the fallback allocator, allowing us to avoid setting up the call * frame in the common case. * * Fastpath assumes size <= SC_LOOKUP_MAXCLASS, and that we hit * tcache. If either of these is false, we tail-call to the slowpath, * malloc_default(). Tail-calling is used to avoid any caller-saved * registers. * * fastpath supports ticker and profiling, both of which will also * tail-call to the slowpath if they fire. */ JEMALLOC_ALWAYS_INLINE void * imalloc_fastpath(size_t size, void *(fallback_alloc)(size_t)) { LOG("core.malloc.entry", "size: %zu", size); if (tsd_get_allocates() && unlikely(!malloc_initialized())) { return fallback_alloc(size); } tsd_t *tsd = tsd_get(false); if (unlikely((size > SC_LOOKUP_MAXCLASS) || tsd == NULL)) { return fallback_alloc(size); } /* * The code below till the branch checking the next_event threshold may * execute before malloc_init(), in which case the threshold is 0 to * trigger slow path and initialization. * * Note that when uninitialized, only the fast-path variants of the sz / * tsd facilities may be called. */ szind_t ind; /* * The thread_allocated counter in tsd serves as a general purpose * accumulator for bytes of allocation to trigger different types of * events. usize is always needed to advance thread_allocated, though * it's not always needed in the core allocation logic. */ size_t usize; sz_size2index_usize_fastpath(size, &ind, &usize); /* Fast path relies on size being a bin. */ assert(ind < SC_NBINS); assert((SC_LOOKUP_MAXCLASS < SC_SMALL_MAXCLASS) && (size <= SC_SMALL_MAXCLASS)); uint64_t allocated, threshold; te_malloc_fastpath_ctx(tsd, &allocated, &threshold); uint64_t allocated_after = allocated + usize; /* * The ind and usize might be uninitialized (or partially) before * malloc_init(). The assertions check for: 1) full correctness (usize * & ind) when initialized; and 2) guaranteed slow-path (threshold == 0) * when !initialized. */ if (!malloc_initialized()) { assert(threshold == 0); } else { assert(ind == sz_size2index(size)); assert(usize > 0 && usize == sz_index2size(ind)); } /* * Check for events and tsd non-nominal (fast_threshold will be set to * 0) in a single branch. */ if (unlikely(allocated_after >= threshold)) { return fallback_alloc(size); } assert(tsd_fast(tsd)); tcache_t *tcache = tsd_tcachep_get(tsd); assert(tcache == tcache_get(tsd)); cache_bin_t *bin = &tcache->bins[ind]; bool tcache_success; void *ret; /* * We split up the code this way so that redundant low-water * computation doesn't happen on the (more common) case in which we * don't touch the low water mark. The compiler won't do this * duplication on its own. */ ret = cache_bin_alloc_easy(bin, &tcache_success); if (tcache_success) { fastpath_success_finish(tsd, allocated_after, bin, ret); return ret; } ret = cache_bin_alloc(bin, &tcache_success); if (tcache_success) { fastpath_success_finish(tsd, allocated_after, bin, ret); return ret; } return fallback_alloc(size); } JEMALLOC_ALWAYS_INLINE int iget_defrag_hint(tsdn_t *tsdn, void* ptr) { int defrag = 0; emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx); if (likely(alloc_ctx.slab)) { /* Small allocation. */ edata_t *slab = emap_edata_lookup(tsdn, &arena_emap_global, ptr); arena_t *arena = arena_get_from_edata(slab); szind_t binind = edata_szind_get(slab); unsigned binshard = edata_binshard_get(slab); bin_t *bin = arena_get_bin(arena, binind, binshard); malloc_mutex_lock(tsdn, &bin->lock); arena_dalloc_bin_locked_info_t info; arena_dalloc_bin_locked_begin(&info, binind); /* Don't bother moving allocations from the slab currently used for new allocations */ if (slab != bin->slabcur) { int free_in_slab = edata_nfree_get(slab); if (free_in_slab) { const bin_info_t *bin_info = &bin_infos[binind]; /* Find number of non-full slabs and the number of regs in them */ unsigned long curslabs = 0; size_t curregs = 0; /* Run on all bin shards (usually just one) */ for (uint32_t i=0; i< bin_info->n_shards; i++) { bin_t *bb = arena_get_bin(arena, binind, i); curslabs += bb->stats.nonfull_slabs; /* Deduct the regs in full slabs (they're not part of the game) */ unsigned long full_slabs = bb->stats.curslabs - bb->stats.nonfull_slabs; curregs += bb->stats.curregs - full_slabs * bin_info->nregs; if (bb->slabcur) { /* Remove slabcur from the overall utilization (not a candidate to nove from) */ curregs -= bin_info->nregs - edata_nfree_get(bb->slabcur); curslabs -= 1; } } /* Compare the utilization ratio of the slab in question to the total average * among non-full slabs. To avoid precision loss in division, we do that by * extrapolating the usage of the slab as if all slabs have the same usage. * If this slab is less used than the average, we'll prefer to move the data * to hopefully more used ones. To avoid stagnation when all slabs have the same * utilization, we give additional 12.5% weight to the decision to defrag. */ defrag = (bin_info->nregs - free_in_slab) * curslabs <= curregs + curregs / 8; } } arena_dalloc_bin_locked_finish(tsdn, arena, bin, &info); malloc_mutex_unlock(tsdn, &bin->lock); } return defrag; } #endif /* JEMALLOC_INTERNAL_INLINES_C_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_macros.h000066400000000000000000000075431501533116600276060ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_MACROS_H #define JEMALLOC_INTERNAL_MACROS_H #ifdef JEMALLOC_DEBUG # define JEMALLOC_ALWAYS_INLINE static inline #else # ifdef _MSC_VER # define JEMALLOC_ALWAYS_INLINE static __forceinline # else # define JEMALLOC_ALWAYS_INLINE JEMALLOC_ATTR(always_inline) static inline # endif #endif #ifdef _MSC_VER # define inline _inline #endif #define UNUSED JEMALLOC_ATTR(unused) #define ZU(z) ((size_t)z) #define ZD(z) ((ssize_t)z) #define QU(q) ((uint64_t)q) #define QD(q) ((int64_t)q) #define KZU(z) ZU(z##ULL) #define KZD(z) ZD(z##LL) #define KQU(q) QU(q##ULL) #define KQD(q) QI(q##LL) #ifndef __DECONST # define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) #endif #if !defined(JEMALLOC_HAS_RESTRICT) || defined(__cplusplus) # define restrict #endif /* Various function pointers are static and immutable except during testing. */ #ifdef JEMALLOC_JET # define JET_MUTABLE #else # define JET_MUTABLE const #endif #define JEMALLOC_VA_ARGS_HEAD(head, ...) head #define JEMALLOC_VA_ARGS_TAIL(head, ...) __VA_ARGS__ /* Diagnostic suppression macros */ #if defined(_MSC_VER) && !defined(__clang__) # define JEMALLOC_DIAGNOSTIC_PUSH __pragma(warning(push)) # define JEMALLOC_DIAGNOSTIC_POP __pragma(warning(pop)) # define JEMALLOC_DIAGNOSTIC_IGNORE(W) __pragma(warning(disable:W)) # define JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS # define JEMALLOC_DIAGNOSTIC_IGNORE_TYPE_LIMITS # define JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN # define JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS /* #pragma GCC diagnostic first appeared in gcc 4.6. */ #elif (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && \ (__GNUC_MINOR__ > 5)))) || defined(__clang__) /* * The JEMALLOC_PRAGMA__ macro is an implementation detail of the GCC and Clang * diagnostic suppression macros and should not be used anywhere else. */ # define JEMALLOC_PRAGMA__(X) _Pragma(#X) # define JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_PRAGMA__(GCC diagnostic push) # define JEMALLOC_DIAGNOSTIC_POP JEMALLOC_PRAGMA__(GCC diagnostic pop) # define JEMALLOC_DIAGNOSTIC_IGNORE(W) \ JEMALLOC_PRAGMA__(GCC diagnostic ignored W) /* * The -Wmissing-field-initializers warning is buggy in GCC versions < 5.1 and * all clang versions up to version 7 (currently trunk, unreleased). This macro * suppresses the warning for the affected compiler versions only. */ # if ((defined(__GNUC__) && !defined(__clang__)) && (__GNUC__ < 5)) || \ defined(__clang__) # define JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS \ JEMALLOC_DIAGNOSTIC_IGNORE("-Wmissing-field-initializers") # else # define JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS # endif # define JEMALLOC_DIAGNOSTIC_IGNORE_TYPE_LIMITS \ JEMALLOC_DIAGNOSTIC_IGNORE("-Wtype-limits") # define JEMALLOC_DIAGNOSTIC_IGNORE_UNUSED_PARAMETER \ JEMALLOC_DIAGNOSTIC_IGNORE("-Wunused-parameter") # if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7) # define JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN \ JEMALLOC_DIAGNOSTIC_IGNORE("-Walloc-size-larger-than=") # else # define JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN # endif # define JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS \ JEMALLOC_DIAGNOSTIC_PUSH \ JEMALLOC_DIAGNOSTIC_IGNORE_UNUSED_PARAMETER #else # define JEMALLOC_DIAGNOSTIC_PUSH # define JEMALLOC_DIAGNOSTIC_POP # define JEMALLOC_DIAGNOSTIC_IGNORE(W) # define JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS # define JEMALLOC_DIAGNOSTIC_IGNORE_TYPE_LIMITS # define JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN # define JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS #endif /* * Disables spurious diagnostics for all headers. Since these headers are not * included by users directly, it does not affect their diagnostic settings. */ JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS #endif /* JEMALLOC_INTERNAL_MACROS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_types.h000066400000000000000000000077421501533116600274670ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TYPES_H #define JEMALLOC_INTERNAL_TYPES_H #include "jemalloc/internal/quantum.h" /* Processor / core id type. */ typedef int malloc_cpuid_t; /* When realloc(non-null-ptr, 0) is called, what happens? */ enum zero_realloc_action_e { /* Realloc(ptr, 0) is free(ptr); return malloc(0); */ zero_realloc_action_alloc = 0, /* Realloc(ptr, 0) is free(ptr); */ zero_realloc_action_free = 1, /* Realloc(ptr, 0) aborts. */ zero_realloc_action_abort = 2 }; typedef enum zero_realloc_action_e zero_realloc_action_t; /* Signature of write callback. */ typedef void (write_cb_t)(void *, const char *); enum malloc_init_e { malloc_init_uninitialized = 3, malloc_init_a0_initialized = 2, malloc_init_recursible = 1, malloc_init_initialized = 0 /* Common case --> jnz. */ }; typedef enum malloc_init_e malloc_init_t; /* * Flags bits: * * a: arena * t: tcache * 0: unused * z: zero * n: alignment * * aaaaaaaa aaaatttt tttttttt 0znnnnnn */ #define MALLOCX_ARENA_BITS 12 #define MALLOCX_TCACHE_BITS 12 #define MALLOCX_LG_ALIGN_BITS 6 #define MALLOCX_ARENA_SHIFT 20 #define MALLOCX_TCACHE_SHIFT 8 #define MALLOCX_ARENA_MASK \ (((1 << MALLOCX_ARENA_BITS) - 1) << MALLOCX_ARENA_SHIFT) /* NB: Arena index bias decreases the maximum number of arenas by 1. */ #define MALLOCX_ARENA_LIMIT ((1 << MALLOCX_ARENA_BITS) - 1) #define MALLOCX_TCACHE_MASK \ (((1 << MALLOCX_TCACHE_BITS) - 1) << MALLOCX_TCACHE_SHIFT) #define MALLOCX_TCACHE_MAX ((1 << MALLOCX_TCACHE_BITS) - 3) #define MALLOCX_LG_ALIGN_MASK ((1 << MALLOCX_LG_ALIGN_BITS) - 1) /* Use MALLOCX_ALIGN_GET() if alignment may not be specified in flags. */ #define MALLOCX_ALIGN_GET_SPECIFIED(flags) \ (ZU(1) << (flags & MALLOCX_LG_ALIGN_MASK)) #define MALLOCX_ALIGN_GET(flags) \ (MALLOCX_ALIGN_GET_SPECIFIED(flags) & (SIZE_T_MAX-1)) #define MALLOCX_ZERO_GET(flags) \ ((bool)(flags & MALLOCX_ZERO)) #define MALLOCX_TCACHE_GET(flags) \ (((unsigned)((flags & MALLOCX_TCACHE_MASK) >> MALLOCX_TCACHE_SHIFT)) - 2) #define MALLOCX_ARENA_GET(flags) \ (((unsigned)(((unsigned)flags) >> MALLOCX_ARENA_SHIFT)) - 1) /* Smallest size class to support. */ #define TINY_MIN (1U << LG_TINY_MIN) #define LONG ((size_t)(1U << LG_SIZEOF_LONG)) #define LONG_MASK (LONG - 1) /* Return the smallest long multiple that is >= a. */ #define LONG_CEILING(a) \ (((a) + LONG_MASK) & ~LONG_MASK) #define SIZEOF_PTR (1U << LG_SIZEOF_PTR) #define PTR_MASK (SIZEOF_PTR - 1) /* Return the smallest (void *) multiple that is >= a. */ #define PTR_CEILING(a) \ (((a) + PTR_MASK) & ~PTR_MASK) /* * Maximum size of L1 cache line. This is used to avoid cache line aliasing. * In addition, this controls the spacing of cacheline-spaced size classes. * * CACHELINE cannot be based on LG_CACHELINE because __declspec(align()) can * only handle raw constants. */ #define LG_CACHELINE 6 #define CACHELINE 64 #define CACHELINE_MASK (CACHELINE - 1) /* Return the smallest cacheline multiple that is >= s. */ #define CACHELINE_CEILING(s) \ (((s) + CACHELINE_MASK) & ~CACHELINE_MASK) /* Return the nearest aligned address at or below a. */ #define ALIGNMENT_ADDR2BASE(a, alignment) \ ((void *)((uintptr_t)(a) & ((~(alignment)) + 1))) /* Return the offset between a and the nearest aligned address at or below a. */ #define ALIGNMENT_ADDR2OFFSET(a, alignment) \ ((size_t)((uintptr_t)(a) & (alignment - 1))) /* Return the smallest alignment multiple that is >= s. */ #define ALIGNMENT_CEILING(s, alignment) \ (((s) + (alignment - 1)) & ((~(alignment)) + 1)) /* Declare a variable-length array. */ #if __STDC_VERSION__ < 199901L # ifdef _MSC_VER # include # define alloca _alloca # else # ifdef JEMALLOC_HAS_ALLOCA_H # include # else # include # endif # endif # define VARIABLE_ARRAY(type, name, count) \ type *name = alloca(sizeof(type) * (count)) #else # define VARIABLE_ARRAY(type, name, count) type name[(count)] #endif #endif /* JEMALLOC_INTERNAL_TYPES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/jemalloc_preamble.h.in000066400000000000000000000116501501533116600266140ustar00rootroot00000000000000#ifndef JEMALLOC_PREAMBLE_H #define JEMALLOC_PREAMBLE_H #include "jemalloc_internal_defs.h" #include "jemalloc/internal/jemalloc_internal_decls.h" #if defined(JEMALLOC_UTRACE) || defined(JEMALLOC_UTRACE_LABEL) #include # if defined(JEMALLOC_UTRACE) # define UTRACE_CALL(p, l) utrace(p, l) # else # define UTRACE_CALL(p, l) utrace("jemalloc_process", p, l) # define JEMALLOC_UTRACE # endif #endif #define JEMALLOC_NO_DEMANGLE #ifdef JEMALLOC_JET # undef JEMALLOC_IS_MALLOC # define JEMALLOC_N(n) jet_##n # include "jemalloc/internal/public_namespace.h" # define JEMALLOC_NO_RENAME # include "../jemalloc@install_suffix@.h" # undef JEMALLOC_NO_RENAME #else # define JEMALLOC_N(n) @private_namespace@##n # include "../jemalloc@install_suffix@.h" #endif #if defined(JEMALLOC_OSATOMIC) #include #endif #ifdef JEMALLOC_ZONE #include #include #include #endif #include "jemalloc/internal/jemalloc_internal_macros.h" /* * Note that the ordering matters here; the hook itself is name-mangled. We * want the inclusion of hooks to happen early, so that we hook as much as * possible. */ #ifndef JEMALLOC_NO_PRIVATE_NAMESPACE # ifndef JEMALLOC_JET # include "jemalloc/internal/private_namespace.h" # else # include "jemalloc/internal/private_namespace_jet.h" # endif #endif #include "jemalloc/internal/test_hooks.h" #ifdef JEMALLOC_DEFINE_MADVISE_FREE # define JEMALLOC_MADV_FREE 8 #endif static const bool config_debug = #ifdef JEMALLOC_DEBUG true #else false #endif ; static const bool have_dss = #ifdef JEMALLOC_DSS true #else false #endif ; static const bool have_madvise_huge = #ifdef JEMALLOC_HAVE_MADVISE_HUGE true #else false #endif ; static const bool config_fill = #ifdef JEMALLOC_FILL true #else false #endif ; static const bool config_lazy_lock = #ifdef JEMALLOC_LAZY_LOCK true #else false #endif ; static const char * const config_malloc_conf = JEMALLOC_CONFIG_MALLOC_CONF; static const bool config_prof = #ifdef JEMALLOC_PROF true #else false #endif ; static const bool config_prof_libgcc = #ifdef JEMALLOC_PROF_LIBGCC true #else false #endif ; static const bool config_prof_libunwind = #ifdef JEMALLOC_PROF_LIBUNWIND true #else false #endif ; static const bool maps_coalesce = #ifdef JEMALLOC_MAPS_COALESCE true #else false #endif ; static const bool config_stats = #ifdef JEMALLOC_STATS true #else false #endif ; static const bool config_tls = #ifdef JEMALLOC_TLS true #else false #endif ; static const bool config_utrace = #ifdef JEMALLOC_UTRACE true #else false #endif ; static const bool config_xmalloc = #ifdef JEMALLOC_XMALLOC true #else false #endif ; static const bool config_cache_oblivious = #ifdef JEMALLOC_CACHE_OBLIVIOUS true #else false #endif ; /* * Undocumented, for jemalloc development use only at the moment. See the note * in jemalloc/internal/log.h. */ static const bool config_log = #ifdef JEMALLOC_LOG true #else false #endif ; /* * Are extra safety checks enabled; things like checking the size of sized * deallocations, double-frees, etc. */ static const bool config_opt_safety_checks = #ifdef JEMALLOC_OPT_SAFETY_CHECKS true #elif defined(JEMALLOC_DEBUG) /* * This lets us only guard safety checks by one flag instead of two; fast * checks can guard solely by config_opt_safety_checks and run in debug mode * too. */ true #else false #endif ; /* * Extra debugging of sized deallocations too onerous to be included in the * general safety checks. */ static const bool config_opt_size_checks = #if defined(JEMALLOC_OPT_SIZE_CHECKS) || defined(JEMALLOC_DEBUG) true #else false #endif ; static const bool config_uaf_detection = #if defined(JEMALLOC_UAF_DETECTION) || defined(JEMALLOC_DEBUG) true #else false #endif ; /* Whether or not the C++ extensions are enabled. */ static const bool config_enable_cxx = #ifdef JEMALLOC_ENABLE_CXX true #else false #endif ; #if defined(_WIN32) || defined(JEMALLOC_HAVE_SCHED_GETCPU) /* Currently percpu_arena depends on sched_getcpu. */ #define JEMALLOC_PERCPU_ARENA #endif static const bool have_percpu_arena = #ifdef JEMALLOC_PERCPU_ARENA true #else false #endif ; /* * Undocumented, and not recommended; the application should take full * responsibility for tracking provenance. */ static const bool force_ivsalloc = #ifdef JEMALLOC_FORCE_IVSALLOC true #else false #endif ; static const bool have_background_thread = #ifdef JEMALLOC_BACKGROUND_THREAD true #else false #endif ; static const bool config_high_res_timer = #ifdef JEMALLOC_HAVE_CLOCK_REALTIME true #else false #endif ; static const bool have_memcntl = #ifdef JEMALLOC_HAVE_MEMCNTL true #else false #endif ; #endif /* JEMALLOC_PREAMBLE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/large_externs.h000066400000000000000000000020361501533116600254120ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_LARGE_EXTERNS_H #define JEMALLOC_INTERNAL_LARGE_EXTERNS_H #include "jemalloc/internal/hook.h" void *large_malloc(tsdn_t *tsdn, arena_t *arena, size_t usize, bool zero); void *large_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero); bool large_ralloc_no_move(tsdn_t *tsdn, edata_t *edata, size_t usize_min, size_t usize_max, bool zero); void *large_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t usize, size_t alignment, bool zero, tcache_t *tcache, hook_ralloc_args_t *hook_args); void large_dalloc_prep_locked(tsdn_t *tsdn, edata_t *edata); void large_dalloc_finish(tsdn_t *tsdn, edata_t *edata); void large_dalloc(tsdn_t *tsdn, edata_t *edata); size_t large_salloc(tsdn_t *tsdn, const edata_t *edata); void large_prof_info_get(tsd_t *tsd, edata_t *edata, prof_info_t *prof_info, bool reset_recent); void large_prof_tctx_reset(edata_t *edata); void large_prof_info_set(edata_t *edata, prof_tctx_t *tctx, size_t size); #endif /* JEMALLOC_INTERNAL_LARGE_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/lockedint.h000066400000000000000000000131031501533116600245210ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_LOCKEDINT_H #define JEMALLOC_INTERNAL_LOCKEDINT_H /* * In those architectures that support 64-bit atomics, we use atomic updates for * our 64-bit values. Otherwise, we use a plain uint64_t and synchronize * externally. */ typedef struct locked_u64_s locked_u64_t; #ifdef JEMALLOC_ATOMIC_U64 struct locked_u64_s { atomic_u64_t val; }; #else /* Must hold the associated mutex. */ struct locked_u64_s { uint64_t val; }; #endif typedef struct locked_zu_s locked_zu_t; struct locked_zu_s { atomic_zu_t val; }; #ifndef JEMALLOC_ATOMIC_U64 # define LOCKEDINT_MTX_DECLARE(name) malloc_mutex_t name; # define LOCKEDINT_MTX_INIT(mu, name, rank, rank_mode) \ malloc_mutex_init(&(mu), name, rank, rank_mode) # define LOCKEDINT_MTX(mtx) (&(mtx)) # define LOCKEDINT_MTX_LOCK(tsdn, mu) malloc_mutex_lock(tsdn, &(mu)) # define LOCKEDINT_MTX_UNLOCK(tsdn, mu) malloc_mutex_unlock(tsdn, &(mu)) # define LOCKEDINT_MTX_PREFORK(tsdn, mu) malloc_mutex_prefork(tsdn, &(mu)) # define LOCKEDINT_MTX_POSTFORK_PARENT(tsdn, mu) \ malloc_mutex_postfork_parent(tsdn, &(mu)) # define LOCKEDINT_MTX_POSTFORK_CHILD(tsdn, mu) \ malloc_mutex_postfork_child(tsdn, &(mu)) #else # define LOCKEDINT_MTX_DECLARE(name) # define LOCKEDINT_MTX(mtx) NULL # define LOCKEDINT_MTX_INIT(mu, name, rank, rank_mode) false # define LOCKEDINT_MTX_LOCK(tsdn, mu) # define LOCKEDINT_MTX_UNLOCK(tsdn, mu) # define LOCKEDINT_MTX_PREFORK(tsdn, mu) # define LOCKEDINT_MTX_POSTFORK_PARENT(tsdn, mu) # define LOCKEDINT_MTX_POSTFORK_CHILD(tsdn, mu) #endif #ifdef JEMALLOC_ATOMIC_U64 # define LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx) assert((mtx) == NULL) #else # define LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx) \ malloc_mutex_assert_owner(tsdn, (mtx)) #endif static inline uint64_t locked_read_u64(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_u64_t *p) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); #ifdef JEMALLOC_ATOMIC_U64 return atomic_load_u64(&p->val, ATOMIC_RELAXED); #else return p->val; #endif } static inline void locked_inc_u64(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_u64_t *p, uint64_t x) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); #ifdef JEMALLOC_ATOMIC_U64 atomic_fetch_add_u64(&p->val, x, ATOMIC_RELAXED); #else p->val += x; #endif } static inline void locked_dec_u64(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_u64_t *p, uint64_t x) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); #ifdef JEMALLOC_ATOMIC_U64 uint64_t r = atomic_fetch_sub_u64(&p->val, x, ATOMIC_RELAXED); assert(r - x <= r); #else p->val -= x; assert(p->val + x >= p->val); #endif } /* Increment and take modulus. Returns whether the modulo made any change. */ static inline bool locked_inc_mod_u64(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_u64_t *p, const uint64_t x, const uint64_t modulus) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); uint64_t before, after; bool overflow; #ifdef JEMALLOC_ATOMIC_U64 before = atomic_load_u64(&p->val, ATOMIC_RELAXED); do { after = before + x; assert(after >= before); overflow = (after >= modulus); if (overflow) { after %= modulus; } } while (!atomic_compare_exchange_weak_u64(&p->val, &before, after, ATOMIC_RELAXED, ATOMIC_RELAXED)); #else before = p->val; after = before + x; overflow = (after >= modulus); if (overflow) { after %= modulus; } p->val = after; #endif return overflow; } /* * Non-atomically sets *dst += src. *dst needs external synchronization. * This lets us avoid the cost of a fetch_add when its unnecessary (note that * the types here are atomic). */ static inline void locked_inc_u64_unsynchronized(locked_u64_t *dst, uint64_t src) { #ifdef JEMALLOC_ATOMIC_U64 uint64_t cur_dst = atomic_load_u64(&dst->val, ATOMIC_RELAXED); atomic_store_u64(&dst->val, src + cur_dst, ATOMIC_RELAXED); #else dst->val += src; #endif } static inline uint64_t locked_read_u64_unsynchronized(locked_u64_t *p) { #ifdef JEMALLOC_ATOMIC_U64 return atomic_load_u64(&p->val, ATOMIC_RELAXED); #else return p->val; #endif } static inline void locked_init_u64_unsynchronized(locked_u64_t *p, uint64_t x) { #ifdef JEMALLOC_ATOMIC_U64 atomic_store_u64(&p->val, x, ATOMIC_RELAXED); #else p->val = x; #endif } static inline size_t locked_read_zu(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_zu_t *p) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); #ifdef JEMALLOC_ATOMIC_U64 return atomic_load_zu(&p->val, ATOMIC_RELAXED); #else return atomic_load_zu(&p->val, ATOMIC_RELAXED); #endif } static inline void locked_inc_zu(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_zu_t *p, size_t x) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); #ifdef JEMALLOC_ATOMIC_U64 atomic_fetch_add_zu(&p->val, x, ATOMIC_RELAXED); #else size_t cur = atomic_load_zu(&p->val, ATOMIC_RELAXED); atomic_store_zu(&p->val, cur + x, ATOMIC_RELAXED); #endif } static inline void locked_dec_zu(tsdn_t *tsdn, malloc_mutex_t *mtx, locked_zu_t *p, size_t x) { LOCKEDINT_MTX_ASSERT_INTERNAL(tsdn, mtx); #ifdef JEMALLOC_ATOMIC_U64 size_t r = atomic_fetch_sub_zu(&p->val, x, ATOMIC_RELAXED); assert(r - x <= r); #else size_t cur = atomic_load_zu(&p->val, ATOMIC_RELAXED); atomic_store_zu(&p->val, cur - x, ATOMIC_RELAXED); #endif } /* Like the _u64 variant, needs an externally synchronized *dst. */ static inline void locked_inc_zu_unsynchronized(locked_zu_t *dst, size_t src) { size_t cur_dst = atomic_load_zu(&dst->val, ATOMIC_RELAXED); atomic_store_zu(&dst->val, src + cur_dst, ATOMIC_RELAXED); } /* * Unlike the _u64 variant, this is safe to call unconditionally. */ static inline size_t locked_read_atomic_zu(locked_zu_t *p) { return atomic_load_zu(&p->val, ATOMIC_RELAXED); } #endif /* JEMALLOC_INTERNAL_LOCKEDINT_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/log.h000066400000000000000000000072521501533116600233360ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_LOG_H #define JEMALLOC_INTERNAL_LOG_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/mutex.h" #ifdef JEMALLOC_LOG # define JEMALLOC_LOG_VAR_BUFSIZE 1000 #else # define JEMALLOC_LOG_VAR_BUFSIZE 1 #endif #define JEMALLOC_LOG_BUFSIZE 4096 /* * The log malloc_conf option is a '|'-delimited list of log_var name segments * which should be logged. The names are themselves hierarchical, with '.' as * the delimiter (a "segment" is just a prefix in the log namespace). So, if * you have: * * log("arena", "log msg for arena"); // 1 * log("arena.a", "log msg for arena.a"); // 2 * log("arena.b", "log msg for arena.b"); // 3 * log("arena.a.a", "log msg for arena.a.a"); // 4 * log("extent.a", "log msg for extent.a"); // 5 * log("extent.b", "log msg for extent.b"); // 6 * * And your malloc_conf option is "log=arena.a|extent", then lines 2, 4, 5, and * 6 will print at runtime. You can enable logging from all log vars by * writing "log=.". * * None of this should be regarded as a stable API for right now. It's intended * as a debugging interface, to let us keep around some of our printf-debugging * statements. */ extern char log_var_names[JEMALLOC_LOG_VAR_BUFSIZE]; extern atomic_b_t log_init_done; typedef struct log_var_s log_var_t; struct log_var_s { /* * Lowest bit is "inited", second lowest is "enabled". Putting them in * a single word lets us avoid any fences on weak architectures. */ atomic_u_t state; const char *name; }; #define LOG_NOT_INITIALIZED 0U #define LOG_INITIALIZED_NOT_ENABLED 1U #define LOG_ENABLED 2U #define LOG_VAR_INIT(name_str) {ATOMIC_INIT(LOG_NOT_INITIALIZED), name_str} /* * Returns the value we should assume for state (which is not necessarily * accurate; if logging is done before logging has finished initializing, then * we default to doing the safe thing by logging everything). */ unsigned log_var_update_state(log_var_t *log_var); /* We factor out the metadata management to allow us to test more easily. */ #define log_do_begin(log_var) \ if (config_log) { \ unsigned log_state = atomic_load_u(&(log_var).state, \ ATOMIC_RELAXED); \ if (unlikely(log_state == LOG_NOT_INITIALIZED)) { \ log_state = log_var_update_state(&(log_var)); \ assert(log_state != LOG_NOT_INITIALIZED); \ } \ if (log_state == LOG_ENABLED) { \ { /* User code executes here. */ #define log_do_end(log_var) \ } \ } \ } /* * MSVC has some preprocessor bugs in its expansion of __VA_ARGS__ during * preprocessing. To work around this, we take all potential extra arguments in * a var-args functions. Since a varargs macro needs at least one argument in * the "...", we accept the format string there, and require that the first * argument in this "..." is a const char *. */ static inline void log_impl_varargs(const char *name, ...) { char buf[JEMALLOC_LOG_BUFSIZE]; va_list ap; va_start(ap, name); const char *format = va_arg(ap, const char *); size_t dst_offset = 0; dst_offset += malloc_snprintf(buf, JEMALLOC_LOG_BUFSIZE, "%s: ", name); dst_offset += malloc_vsnprintf(buf + dst_offset, JEMALLOC_LOG_BUFSIZE - dst_offset, format, ap); dst_offset += malloc_snprintf(buf + dst_offset, JEMALLOC_LOG_BUFSIZE - dst_offset, "\n"); va_end(ap); malloc_write(buf); } /* Call as log("log.var.str", "format_string %d", arg_for_format_string); */ #define LOG(log_var_str, ...) \ do { \ static log_var_t log_var = LOG_VAR_INIT(log_var_str); \ log_do_begin(log_var) \ log_impl_varargs((log_var).name, __VA_ARGS__); \ log_do_end(log_var) \ } while (0) #endif /* JEMALLOC_INTERNAL_LOG_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/malloc_io.h000066400000000000000000000057071501533116600245160ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_MALLOC_IO_H #define JEMALLOC_INTERNAL_MALLOC_IO_H #include "jemalloc/internal/jemalloc_internal_types.h" #ifdef _WIN32 # ifdef _WIN64 # define FMT64_PREFIX "ll" # define FMTPTR_PREFIX "ll" # else # define FMT64_PREFIX "ll" # define FMTPTR_PREFIX "" # endif # define FMTd32 "d" # define FMTu32 "u" # define FMTx32 "x" # define FMTd64 FMT64_PREFIX "d" # define FMTu64 FMT64_PREFIX "u" # define FMTx64 FMT64_PREFIX "x" # define FMTdPTR FMTPTR_PREFIX "d" # define FMTuPTR FMTPTR_PREFIX "u" # define FMTxPTR FMTPTR_PREFIX "x" #else # include # define FMTd32 PRId32 # define FMTu32 PRIu32 # define FMTx32 PRIx32 # define FMTd64 PRId64 # define FMTu64 PRIu64 # define FMTx64 PRIx64 # define FMTdPTR PRIdPTR # define FMTuPTR PRIuPTR # define FMTxPTR PRIxPTR #endif /* Size of stack-allocated buffer passed to buferror(). */ #define BUFERROR_BUF 64 /* * Size of stack-allocated buffer used by malloc_{,v,vc}printf(). This must be * large enough for all possible uses within jemalloc. */ #define MALLOC_PRINTF_BUFSIZE 4096 write_cb_t wrtmessage; int buferror(int err, char *buf, size_t buflen); uintmax_t malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base); void malloc_write(const char *s); /* * malloc_vsnprintf() supports a subset of snprintf(3) that avoids floating * point math. */ size_t malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap); size_t malloc_snprintf(char *str, size_t size, const char *format, ...) JEMALLOC_FORMAT_PRINTF(3, 4); /* * The caller can set write_cb to null to choose to print with the * je_malloc_message hook. */ void malloc_vcprintf(write_cb_t *write_cb, void *cbopaque, const char *format, va_list ap); void malloc_cprintf(write_cb_t *write_cb, void *cbopaque, const char *format, ...) JEMALLOC_FORMAT_PRINTF(3, 4); void malloc_printf(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2); static inline ssize_t malloc_write_fd(int fd, const void *buf, size_t count) { #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_write) /* * Use syscall(2) rather than write(2) when possible in order to avoid * the possibility of memory allocation within libc. This is necessary * on FreeBSD; most operating systems do not have this problem though. * * syscall() returns long or int, depending on platform, so capture the * result in the widest plausible type to avoid compiler warnings. */ long result = syscall(SYS_write, fd, buf, count); #else ssize_t result = (ssize_t)write(fd, buf, #ifdef _WIN32 (unsigned int) #endif count); #endif return (ssize_t)result; } static inline ssize_t malloc_read_fd(int fd, void *buf, size_t count) { #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_read) long result = syscall(SYS_read, fd, buf, count); #else ssize_t result = read(fd, buf, #ifdef _WIN32 (unsigned int) #endif count); #endif return (ssize_t)result; } #endif /* JEMALLOC_INTERNAL_MALLOC_IO_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/mpsc_queue.h000066400000000000000000000116511501533116600247210ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_MPSC_QUEUE_H #define JEMALLOC_INTERNAL_MPSC_QUEUE_H #include "jemalloc/internal/atomic.h" /* * A concurrent implementation of a multi-producer, single-consumer queue. It * supports three concurrent operations: * - Push * - Push batch * - Pop batch * * These operations are all lock-free. * * The implementation is the simple two-stack queue built on a Treiber stack. * It's not terribly efficient, but this isn't expected to go into anywhere with * hot code. In fact, we don't really even need queue semantics in any * anticipated use cases; we could get away with just the stack. But this way * lets us frame the API in terms of the existing list types, which is a nice * convenience. We can save on cache misses by introducing our own (parallel) * single-linked list type here, and dropping FIFO semantics, if we need this to * get faster. Since we're currently providing queue semantics though, we use * the prev field in the link rather than the next field for Treiber-stack * linkage, so that we can preserve order for bash-pushed lists (recall that the * two-stack tricks reverses orders in the lock-free first stack). */ #define mpsc_queue(a_type) \ struct { \ atomic_p_t tail; \ } #define mpsc_queue_proto(a_attr, a_prefix, a_queue_type, a_type, \ a_list_type) \ /* Initialize a queue. */ \ a_attr void \ a_prefix##new(a_queue_type *queue); \ /* Insert all items in src into the queue, clearing src. */ \ a_attr void \ a_prefix##push_batch(a_queue_type *queue, a_list_type *src); \ /* Insert node into the queue. */ \ a_attr void \ a_prefix##push(a_queue_type *queue, a_type *node); \ /* \ * Pop all items in the queue into the list at dst. dst should already \ * be initialized (and may contain existing items, which then remain \ * in dst). \ */ \ a_attr void \ a_prefix##pop_batch(a_queue_type *queue, a_list_type *dst); #define mpsc_queue_gen(a_attr, a_prefix, a_queue_type, a_type, \ a_list_type, a_link) \ a_attr void \ a_prefix##new(a_queue_type *queue) { \ atomic_store_p(&queue->tail, NULL, ATOMIC_RELAXED); \ } \ a_attr void \ a_prefix##push_batch(a_queue_type *queue, a_list_type *src) { \ /* \ * Reuse the ql list next field as the Treiber stack next \ * field. \ */ \ a_type *first = ql_first(src); \ a_type *last = ql_last(src, a_link); \ void* cur_tail = atomic_load_p(&queue->tail, ATOMIC_RELAXED); \ do { \ /* \ * Note that this breaks the queue ring structure; \ * it's not a ring any more! \ */ \ first->a_link.qre_prev = cur_tail; \ /* \ * Note: the upcoming CAS doesn't need an atomic; every \ * push only needs to synchronize with the next pop, \ * which we get from the release sequence rules. \ */ \ } while (!atomic_compare_exchange_weak_p(&queue->tail, \ &cur_tail, last, ATOMIC_RELEASE, ATOMIC_RELAXED)); \ ql_new(src); \ } \ a_attr void \ a_prefix##push(a_queue_type *queue, a_type *node) { \ ql_elm_new(node, a_link); \ a_list_type list; \ ql_new(&list); \ ql_head_insert(&list, node, a_link); \ a_prefix##push_batch(queue, &list); \ } \ a_attr void \ a_prefix##pop_batch(a_queue_type *queue, a_list_type *dst) { \ a_type *tail = atomic_load_p(&queue->tail, ATOMIC_RELAXED); \ if (tail == NULL) { \ /* \ * In the common special case where there are no \ * pending elements, bail early without a costly RMW. \ */ \ return; \ } \ tail = atomic_exchange_p(&queue->tail, NULL, ATOMIC_ACQUIRE); \ /* \ * It's a single-consumer queue, so if cur started non-NULL, \ * it'd better stay non-NULL. \ */ \ assert(tail != NULL); \ /* \ * We iterate through the stack and both fix up the link \ * structure (stack insertion broke the list requirement that \ * the list be circularly linked). It's just as efficient at \ * this point to make the queue a "real" queue, so do that as \ * well. \ * If this ever gets to be a hot spot, we can omit this fixup \ * and make the queue a bag (i.e. not necessarily ordered), but \ * that would mean jettisoning the existing list API as the \ * batch pushing/popping interface. \ */ \ a_list_type reversed; \ ql_new(&reversed); \ while (tail != NULL) { \ /* \ * Pop an item off the stack, prepend it onto the list \ * (reversing the order). Recall that we use the \ * list prev field as the Treiber stack next field to \ * preserve order of batch-pushed items when reversed. \ */ \ a_type *next = tail->a_link.qre_prev; \ ql_elm_new(tail, a_link); \ ql_head_insert(&reversed, tail, a_link); \ tail = next; \ } \ ql_concat(dst, &reversed, a_link); \ } #endif /* JEMALLOC_INTERNAL_MPSC_QUEUE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/mutex.h000066400000000000000000000247271501533116600237250ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_MUTEX_H #define JEMALLOC_INTERNAL_MUTEX_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/mutex_prof.h" #include "jemalloc/internal/tsd.h" #include "jemalloc/internal/witness.h" extern int64_t opt_mutex_max_spin; typedef enum { /* Can only acquire one mutex of a given witness rank at a time. */ malloc_mutex_rank_exclusive, /* * Can acquire multiple mutexes of the same witness rank, but in * address-ascending order only. */ malloc_mutex_address_ordered } malloc_mutex_lock_order_t; typedef struct malloc_mutex_s malloc_mutex_t; struct malloc_mutex_s { union { struct { /* * prof_data is defined first to reduce cacheline * bouncing: the data is not touched by the mutex holder * during unlocking, while might be modified by * contenders. Having it before the mutex itself could * avoid prefetching a modified cacheline (for the * unlocking thread). */ mutex_prof_data_t prof_data; #ifdef _WIN32 # if _WIN32_WINNT >= 0x0600 SRWLOCK lock; # else CRITICAL_SECTION lock; # endif #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) os_unfair_lock lock; #elif (defined(JEMALLOC_MUTEX_INIT_CB)) pthread_mutex_t lock; malloc_mutex_t *postponed_next; #else pthread_mutex_t lock; #endif /* * Hint flag to avoid exclusive cache line contention * during spin waiting */ atomic_b_t locked; }; /* * We only touch witness when configured w/ debug. However we * keep the field in a union when !debug so that we don't have * to pollute the code base with #ifdefs, while avoid paying the * memory cost. */ #if !defined(JEMALLOC_DEBUG) witness_t witness; malloc_mutex_lock_order_t lock_order; #endif }; #if defined(JEMALLOC_DEBUG) witness_t witness; malloc_mutex_lock_order_t lock_order; #endif }; #ifdef _WIN32 # if _WIN32_WINNT >= 0x0600 # define MALLOC_MUTEX_LOCK(m) AcquireSRWLockExclusive(&(m)->lock) # define MALLOC_MUTEX_UNLOCK(m) ReleaseSRWLockExclusive(&(m)->lock) # define MALLOC_MUTEX_TRYLOCK(m) (!TryAcquireSRWLockExclusive(&(m)->lock)) # else # define MALLOC_MUTEX_LOCK(m) EnterCriticalSection(&(m)->lock) # define MALLOC_MUTEX_UNLOCK(m) LeaveCriticalSection(&(m)->lock) # define MALLOC_MUTEX_TRYLOCK(m) (!TryEnterCriticalSection(&(m)->lock)) # endif #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) # define MALLOC_MUTEX_LOCK(m) os_unfair_lock_lock(&(m)->lock) # define MALLOC_MUTEX_UNLOCK(m) os_unfair_lock_unlock(&(m)->lock) # define MALLOC_MUTEX_TRYLOCK(m) (!os_unfair_lock_trylock(&(m)->lock)) #else # define MALLOC_MUTEX_LOCK(m) pthread_mutex_lock(&(m)->lock) # define MALLOC_MUTEX_UNLOCK(m) pthread_mutex_unlock(&(m)->lock) # define MALLOC_MUTEX_TRYLOCK(m) (pthread_mutex_trylock(&(m)->lock) != 0) #endif #define LOCK_PROF_DATA_INITIALIZER \ {NSTIME_ZERO_INITIALIZER, NSTIME_ZERO_INITIALIZER, 0, 0, 0, \ ATOMIC_INIT(0), 0, NULL, 0} #ifdef _WIN32 # define MALLOC_MUTEX_INITIALIZER #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) # if defined(JEMALLOC_DEBUG) # define MALLOC_MUTEX_INITIALIZER \ {{{LOCK_PROF_DATA_INITIALIZER, OS_UNFAIR_LOCK_INIT, ATOMIC_INIT(false)}}, \ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT), 0} # else # define MALLOC_MUTEX_INITIALIZER \ {{{LOCK_PROF_DATA_INITIALIZER, OS_UNFAIR_LOCK_INIT, ATOMIC_INIT(false)}}, \ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)} # endif #elif (defined(JEMALLOC_MUTEX_INIT_CB)) # if (defined(JEMALLOC_DEBUG)) # define MALLOC_MUTEX_INITIALIZER \ {{{LOCK_PROF_DATA_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, NULL, ATOMIC_INIT(false)}}, \ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT), 0} # else # define MALLOC_MUTEX_INITIALIZER \ {{{LOCK_PROF_DATA_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, NULL, ATOMIC_INIT(false)}}, \ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)} # endif #else # define MALLOC_MUTEX_TYPE PTHREAD_MUTEX_DEFAULT # if defined(JEMALLOC_DEBUG) # define MALLOC_MUTEX_INITIALIZER \ {{{LOCK_PROF_DATA_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, ATOMIC_INIT(false)}}, \ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT), 0} # else # define MALLOC_MUTEX_INITIALIZER \ {{{LOCK_PROF_DATA_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, ATOMIC_INIT(false)}}, \ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)} # endif #endif #ifdef JEMALLOC_LAZY_LOCK extern bool isthreaded; #else # undef isthreaded /* Undo private_namespace.h definition. */ # define isthreaded true #endif bool malloc_mutex_init(malloc_mutex_t *mutex, const char *name, witness_rank_t rank, malloc_mutex_lock_order_t lock_order); void malloc_mutex_prefork(tsdn_t *tsdn, malloc_mutex_t *mutex); void malloc_mutex_postfork_parent(tsdn_t *tsdn, malloc_mutex_t *mutex); void malloc_mutex_postfork_child(tsdn_t *tsdn, malloc_mutex_t *mutex); bool malloc_mutex_boot(void); void malloc_mutex_prof_data_reset(tsdn_t *tsdn, malloc_mutex_t *mutex); void malloc_mutex_lock_slow(malloc_mutex_t *mutex); static inline void malloc_mutex_lock_final(malloc_mutex_t *mutex) { MALLOC_MUTEX_LOCK(mutex); atomic_store_b(&mutex->locked, true, ATOMIC_RELAXED); } static inline bool malloc_mutex_trylock_final(malloc_mutex_t *mutex) { return MALLOC_MUTEX_TRYLOCK(mutex); } static inline void mutex_owner_stats_update(tsdn_t *tsdn, malloc_mutex_t *mutex) { if (config_stats) { mutex_prof_data_t *data = &mutex->prof_data; data->n_lock_ops++; if (data->prev_owner != tsdn) { data->prev_owner = tsdn; data->n_owner_switches++; } } } /* Trylock: return false if the lock is successfully acquired. */ static inline bool malloc_mutex_trylock(tsdn_t *tsdn, malloc_mutex_t *mutex) { witness_assert_not_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness); if (isthreaded) { if (malloc_mutex_trylock_final(mutex)) { atomic_store_b(&mutex->locked, true, ATOMIC_RELAXED); return true; } mutex_owner_stats_update(tsdn, mutex); } witness_lock(tsdn_witness_tsdp_get(tsdn), &mutex->witness); return false; } /* Aggregate lock prof data. */ static inline void malloc_mutex_prof_merge(mutex_prof_data_t *sum, mutex_prof_data_t *data) { nstime_add(&sum->tot_wait_time, &data->tot_wait_time); if (nstime_compare(&sum->max_wait_time, &data->max_wait_time) < 0) { nstime_copy(&sum->max_wait_time, &data->max_wait_time); } sum->n_wait_times += data->n_wait_times; sum->n_spin_acquired += data->n_spin_acquired; if (sum->max_n_thds < data->max_n_thds) { sum->max_n_thds = data->max_n_thds; } uint32_t cur_n_waiting_thds = atomic_load_u32(&sum->n_waiting_thds, ATOMIC_RELAXED); uint32_t new_n_waiting_thds = cur_n_waiting_thds + atomic_load_u32( &data->n_waiting_thds, ATOMIC_RELAXED); atomic_store_u32(&sum->n_waiting_thds, new_n_waiting_thds, ATOMIC_RELAXED); sum->n_owner_switches += data->n_owner_switches; sum->n_lock_ops += data->n_lock_ops; } static inline void malloc_mutex_lock(tsdn_t *tsdn, malloc_mutex_t *mutex) { witness_assert_not_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness); if (isthreaded) { if (malloc_mutex_trylock_final(mutex)) { malloc_mutex_lock_slow(mutex); atomic_store_b(&mutex->locked, true, ATOMIC_RELAXED); } mutex_owner_stats_update(tsdn, mutex); } witness_lock(tsdn_witness_tsdp_get(tsdn), &mutex->witness); } static inline void malloc_mutex_unlock(tsdn_t *tsdn, malloc_mutex_t *mutex) { atomic_store_b(&mutex->locked, false, ATOMIC_RELAXED); witness_unlock(tsdn_witness_tsdp_get(tsdn), &mutex->witness); if (isthreaded) { MALLOC_MUTEX_UNLOCK(mutex); } } static inline void malloc_mutex_assert_owner(tsdn_t *tsdn, malloc_mutex_t *mutex) { witness_assert_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness); } static inline void malloc_mutex_assert_not_owner(tsdn_t *tsdn, malloc_mutex_t *mutex) { witness_assert_not_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness); } static inline void malloc_mutex_prof_copy(mutex_prof_data_t *dst, mutex_prof_data_t *source) { /* * Not *really* allowed (we shouldn't be doing non-atomic loads of * atomic data), but the mutex protection makes this safe, and writing * a member-for-member copy is tedious for this situation. */ *dst = *source; /* n_wait_thds is not reported (modified w/o locking). */ atomic_store_u32(&dst->n_waiting_thds, 0, ATOMIC_RELAXED); } /* Copy the prof data from mutex for processing. */ static inline void malloc_mutex_prof_read(tsdn_t *tsdn, mutex_prof_data_t *data, malloc_mutex_t *mutex) { /* Can only read holding the mutex. */ malloc_mutex_assert_owner(tsdn, mutex); malloc_mutex_prof_copy(data, &mutex->prof_data); } static inline void malloc_mutex_prof_accum(tsdn_t *tsdn, mutex_prof_data_t *data, malloc_mutex_t *mutex) { mutex_prof_data_t *source = &mutex->prof_data; /* Can only read holding the mutex. */ malloc_mutex_assert_owner(tsdn, mutex); nstime_add(&data->tot_wait_time, &source->tot_wait_time); if (nstime_compare(&source->max_wait_time, &data->max_wait_time) > 0) { nstime_copy(&data->max_wait_time, &source->max_wait_time); } data->n_wait_times += source->n_wait_times; data->n_spin_acquired += source->n_spin_acquired; if (data->max_n_thds < source->max_n_thds) { data->max_n_thds = source->max_n_thds; } /* n_wait_thds is not reported. */ atomic_store_u32(&data->n_waiting_thds, 0, ATOMIC_RELAXED); data->n_owner_switches += source->n_owner_switches; data->n_lock_ops += source->n_lock_ops; } /* Compare the prof data and update to the maximum. */ static inline void malloc_mutex_prof_max_update(tsdn_t *tsdn, mutex_prof_data_t *data, malloc_mutex_t *mutex) { mutex_prof_data_t *source = &mutex->prof_data; /* Can only read holding the mutex. */ malloc_mutex_assert_owner(tsdn, mutex); if (nstime_compare(&source->tot_wait_time, &data->tot_wait_time) > 0) { nstime_copy(&data->tot_wait_time, &source->tot_wait_time); } if (nstime_compare(&source->max_wait_time, &data->max_wait_time) > 0) { nstime_copy(&data->max_wait_time, &source->max_wait_time); } if (source->n_wait_times > data->n_wait_times) { data->n_wait_times = source->n_wait_times; } if (source->n_spin_acquired > data->n_spin_acquired) { data->n_spin_acquired = source->n_spin_acquired; } if (source->max_n_thds > data->max_n_thds) { data->max_n_thds = source->max_n_thds; } if (source->n_owner_switches > data->n_owner_switches) { data->n_owner_switches = source->n_owner_switches; } if (source->n_lock_ops > data->n_lock_ops) { data->n_lock_ops = source->n_lock_ops; } /* n_wait_thds is not reported. */ } #endif /* JEMALLOC_INTERNAL_MUTEX_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/mutex_prof.h000066400000000000000000000074731501533116600247520ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_MUTEX_PROF_H #define JEMALLOC_INTERNAL_MUTEX_PROF_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/nstime.h" #include "jemalloc/internal/tsd_types.h" #define MUTEX_PROF_GLOBAL_MUTEXES \ OP(background_thread) \ OP(max_per_bg_thd) \ OP(ctl) \ OP(prof) \ OP(prof_thds_data) \ OP(prof_dump) \ OP(prof_recent_alloc) \ OP(prof_recent_dump) \ OP(prof_stats) typedef enum { #define OP(mtx) global_prof_mutex_##mtx, MUTEX_PROF_GLOBAL_MUTEXES #undef OP mutex_prof_num_global_mutexes } mutex_prof_global_ind_t; #define MUTEX_PROF_ARENA_MUTEXES \ OP(large) \ OP(extent_avail) \ OP(extents_dirty) \ OP(extents_muzzy) \ OP(extents_retained) \ OP(decay_dirty) \ OP(decay_muzzy) \ OP(base) \ OP(tcache_list) \ OP(hpa_shard) \ OP(hpa_shard_grow) \ OP(hpa_sec) typedef enum { #define OP(mtx) arena_prof_mutex_##mtx, MUTEX_PROF_ARENA_MUTEXES #undef OP mutex_prof_num_arena_mutexes } mutex_prof_arena_ind_t; /* * The forth parameter is a boolean value that is true for derived rate counters * and false for real ones. */ #define MUTEX_PROF_UINT64_COUNTERS \ OP(num_ops, uint64_t, "n_lock_ops", false, num_ops) \ OP(num_ops_ps, uint64_t, "(#/sec)", true, num_ops) \ OP(num_wait, uint64_t, "n_waiting", false, num_wait) \ OP(num_wait_ps, uint64_t, "(#/sec)", true, num_wait) \ OP(num_spin_acq, uint64_t, "n_spin_acq", false, num_spin_acq) \ OP(num_spin_acq_ps, uint64_t, "(#/sec)", true, num_spin_acq) \ OP(num_owner_switch, uint64_t, "n_owner_switch", false, num_owner_switch) \ OP(num_owner_switch_ps, uint64_t, "(#/sec)", true, num_owner_switch) \ OP(total_wait_time, uint64_t, "total_wait_ns", false, total_wait_time) \ OP(total_wait_time_ps, uint64_t, "(#/sec)", true, total_wait_time) \ OP(max_wait_time, uint64_t, "max_wait_ns", false, max_wait_time) #define MUTEX_PROF_UINT32_COUNTERS \ OP(max_num_thds, uint32_t, "max_n_thds", false, max_num_thds) #define MUTEX_PROF_COUNTERS \ MUTEX_PROF_UINT64_COUNTERS \ MUTEX_PROF_UINT32_COUNTERS #define OP(counter, type, human, derived, base_counter) mutex_counter_##counter, #define COUNTER_ENUM(counter_list, t) \ typedef enum { \ counter_list \ mutex_prof_num_##t##_counters \ } mutex_prof_##t##_counter_ind_t; COUNTER_ENUM(MUTEX_PROF_UINT64_COUNTERS, uint64_t) COUNTER_ENUM(MUTEX_PROF_UINT32_COUNTERS, uint32_t) #undef COUNTER_ENUM #undef OP typedef struct { /* * Counters touched on the slow path, i.e. when there is lock * contention. We update them once we have the lock. */ /* Total time (in nano seconds) spent waiting on this mutex. */ nstime_t tot_wait_time; /* Max time (in nano seconds) spent on a single lock operation. */ nstime_t max_wait_time; /* # of times have to wait for this mutex (after spinning). */ uint64_t n_wait_times; /* # of times acquired the mutex through local spinning. */ uint64_t n_spin_acquired; /* Max # of threads waiting for the mutex at the same time. */ uint32_t max_n_thds; /* Current # of threads waiting on the lock. Atomic synced. */ atomic_u32_t n_waiting_thds; /* * Data touched on the fast path. These are modified right after we * grab the lock, so it's placed closest to the end (i.e. right before * the lock) so that we have a higher chance of them being on the same * cacheline. */ /* # of times the mutex holder is different than the previous one. */ uint64_t n_owner_switches; /* Previous mutex holder, to facilitate n_owner_switches. */ tsdn_t *prev_owner; /* # of lock() operations in total. */ uint64_t n_lock_ops; } mutex_prof_data_t; #endif /* JEMALLOC_INTERNAL_MUTEX_PROF_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/nstime.h000066400000000000000000000043641501533116600240550ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_NSTIME_H #define JEMALLOC_INTERNAL_NSTIME_H /* Maximum supported number of seconds (~584 years). */ #define NSTIME_SEC_MAX KQU(18446744072) #define NSTIME_MAGIC ((uint32_t)0xb8a9ce37) #ifdef JEMALLOC_DEBUG # define NSTIME_ZERO_INITIALIZER {0, NSTIME_MAGIC} #else # define NSTIME_ZERO_INITIALIZER {0} #endif typedef struct { uint64_t ns; #ifdef JEMALLOC_DEBUG uint32_t magic; /* Tracks if initialized. */ #endif } nstime_t; static const nstime_t nstime_zero = NSTIME_ZERO_INITIALIZER; void nstime_init(nstime_t *time, uint64_t ns); void nstime_init2(nstime_t *time, uint64_t sec, uint64_t nsec); uint64_t nstime_ns(const nstime_t *time); uint64_t nstime_sec(const nstime_t *time); uint64_t nstime_msec(const nstime_t *time); uint64_t nstime_nsec(const nstime_t *time); void nstime_copy(nstime_t *time, const nstime_t *source); int nstime_compare(const nstime_t *a, const nstime_t *b); void nstime_add(nstime_t *time, const nstime_t *addend); void nstime_iadd(nstime_t *time, uint64_t addend); void nstime_subtract(nstime_t *time, const nstime_t *subtrahend); void nstime_isubtract(nstime_t *time, uint64_t subtrahend); void nstime_imultiply(nstime_t *time, uint64_t multiplier); void nstime_idivide(nstime_t *time, uint64_t divisor); uint64_t nstime_divide(const nstime_t *time, const nstime_t *divisor); uint64_t nstime_ns_since(const nstime_t *past); typedef bool (nstime_monotonic_t)(void); extern nstime_monotonic_t *JET_MUTABLE nstime_monotonic; typedef void (nstime_update_t)(nstime_t *); extern nstime_update_t *JET_MUTABLE nstime_update; typedef void (nstime_prof_update_t)(nstime_t *); extern nstime_prof_update_t *JET_MUTABLE nstime_prof_update; void nstime_init_update(nstime_t *time); void nstime_prof_init_update(nstime_t *time); enum prof_time_res_e { prof_time_res_default = 0, prof_time_res_high = 1 }; typedef enum prof_time_res_e prof_time_res_t; extern prof_time_res_t opt_prof_time_res; extern const char *prof_time_res_mode_names[]; JEMALLOC_ALWAYS_INLINE void nstime_init_zero(nstime_t *time) { nstime_copy(time, &nstime_zero); } JEMALLOC_ALWAYS_INLINE bool nstime_equals_zero(nstime_t *time) { int diff = nstime_compare(time, &nstime_zero); assert(diff >= 0); return diff == 0; } #endif /* JEMALLOC_INTERNAL_NSTIME_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/pa.h000066400000000000000000000217361501533116600231600ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PA_H #define JEMALLOC_INTERNAL_PA_H #include "jemalloc/internal/base.h" #include "jemalloc/internal/decay.h" #include "jemalloc/internal/ecache.h" #include "jemalloc/internal/edata_cache.h" #include "jemalloc/internal/emap.h" #include "jemalloc/internal/hpa.h" #include "jemalloc/internal/lockedint.h" #include "jemalloc/internal/pac.h" #include "jemalloc/internal/pai.h" #include "jemalloc/internal/sec.h" /* * The page allocator; responsible for acquiring pages of memory for * allocations. It picks the implementation of the page allocator interface * (i.e. a pai_t) to handle a given page-level allocation request. For now, the * only such implementation is the PAC code ("page allocator classic"), but * others will be coming soon. */ typedef struct pa_central_s pa_central_t; struct pa_central_s { hpa_central_t hpa; }; /* * The stats for a particular pa_shard. Because of the way the ctl module * handles stats epoch data collection (it has its own arena_stats, and merges * the stats from each arena into it), this needs to live in the arena_stats_t; * hence we define it here and let the pa_shard have a pointer (rather than the * more natural approach of just embedding it in the pa_shard itself). * * We follow the arena_stats_t approach of marking the derived fields. These * are the ones that are not maintained on their own; instead, their values are * derived during those stats merges. */ typedef struct pa_shard_stats_s pa_shard_stats_t; struct pa_shard_stats_s { /* Number of edata_t structs allocated by base, but not being used. */ size_t edata_avail; /* Derived. */ /* * Stats specific to the PAC. For now, these are the only stats that * exist, but there will eventually be other page allocators. Things * like edata_avail make sense in a cross-PA sense, but things like * npurges don't. */ pac_stats_t pac_stats; }; /* * The local allocator handle. Keeps the state necessary to satisfy page-sized * allocations. * * The contents are mostly internal to the PA module. The key exception is that * arena decay code is allowed to grab pointers to the dirty and muzzy ecaches * decay_ts, for a couple of queries, passing them back to a PA function, or * acquiring decay.mtx and looking at decay.purging. The reasoning is that, * while PA decides what and how to purge, the arena code decides when and where * (e.g. on what thread). It's allowed to use the presence of another purger to * decide. * (The background thread code also touches some other decay internals, but * that's not fundamental; its' just an artifact of a partial refactoring, and * its accesses could be straightforwardly moved inside the decay module). */ typedef struct pa_shard_s pa_shard_t; struct pa_shard_s { /* The central PA this shard is associated with. */ pa_central_t *central; /* * Number of pages in active extents. * * Synchronization: atomic. */ atomic_zu_t nactive; /* * Whether or not we should prefer the hugepage allocator. Atomic since * it may be concurrently modified by a thread setting extent hooks. * Note that we still may do HPA operations in this arena; if use_hpa is * changed from true to false, we'll free back to the hugepage allocator * for those allocations. */ atomic_b_t use_hpa; /* * If we never used the HPA to begin with, it wasn't initialized, and so * we shouldn't try to e.g. acquire its mutexes during fork. This * tracks that knowledge. */ bool ever_used_hpa; /* Allocates from a PAC. */ pac_t pac; /* * We place a small extent cache in front of the HPA, since we intend * these configurations to use many fewer arenas, and therefore have a * higher risk of hot locks. */ sec_t hpa_sec; hpa_shard_t hpa_shard; /* The source of edata_t objects. */ edata_cache_t edata_cache; unsigned ind; malloc_mutex_t *stats_mtx; pa_shard_stats_t *stats; /* The emap this shard is tied to. */ emap_t *emap; /* The base from which we get the ehooks and allocate metadat. */ base_t *base; }; static inline bool pa_shard_dont_decay_muzzy(pa_shard_t *shard) { return ecache_npages_get(&shard->pac.ecache_muzzy) == 0 && pac_decay_ms_get(&shard->pac, extent_state_muzzy) <= 0; } static inline ehooks_t * pa_shard_ehooks_get(pa_shard_t *shard) { return base_ehooks_get(shard->base); } /* Returns true on error. */ bool pa_central_init(pa_central_t *central, base_t *base, bool hpa, hpa_hooks_t *hpa_hooks); /* Returns true on error. */ bool pa_shard_init(tsdn_t *tsdn, pa_shard_t *shard, pa_central_t *central, emap_t *emap, base_t *base, unsigned ind, pa_shard_stats_t *stats, malloc_mutex_t *stats_mtx, nstime_t *cur_time, size_t oversize_threshold, ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms); /* * This isn't exposed to users; we allow late enablement of the HPA shard so * that we can boot without worrying about the HPA, then turn it on in a0. */ bool pa_shard_enable_hpa(tsdn_t *tsdn, pa_shard_t *shard, const hpa_shard_opts_t *hpa_opts, const sec_opts_t *hpa_sec_opts); /* * We stop using the HPA when custom extent hooks are installed, but still * redirect deallocations to it. */ void pa_shard_disable_hpa(tsdn_t *tsdn, pa_shard_t *shard); /* * This does the PA-specific parts of arena reset (i.e. freeing all active * allocations). */ void pa_shard_reset(tsdn_t *tsdn, pa_shard_t *shard); /* * Destroy all the remaining retained extents. Should only be called after * decaying all active, dirty, and muzzy extents to the retained state, as the * last step in destroying the shard. */ void pa_shard_destroy(tsdn_t *tsdn, pa_shard_t *shard); /* Gets an edata for the given allocation. */ edata_t *pa_alloc(tsdn_t *tsdn, pa_shard_t *shard, size_t size, size_t alignment, bool slab, szind_t szind, bool zero, bool guarded, bool *deferred_work_generated); /* Returns true on error, in which case nothing changed. */ bool pa_expand(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, size_t old_size, size_t new_size, szind_t szind, bool zero, bool *deferred_work_generated); /* * The same. Sets *generated_dirty to true if we produced new dirty pages, and * false otherwise. */ bool pa_shrink(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, size_t old_size, size_t new_size, szind_t szind, bool *deferred_work_generated); /* * Frees the given edata back to the pa. Sets *generated_dirty if we produced * new dirty pages (well, we always set it for now; but this need not be the * case). * (We could make generated_dirty the return value of course, but this is more * consistent with the shrink pathway and our error codes here). */ void pa_dalloc(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, bool *deferred_work_generated); bool pa_decay_ms_set(tsdn_t *tsdn, pa_shard_t *shard, extent_state_t state, ssize_t decay_ms, pac_purge_eagerness_t eagerness); ssize_t pa_decay_ms_get(pa_shard_t *shard, extent_state_t state); /* * Do deferred work on this PA shard. * * Morally, this should do both PAC decay and the HPA deferred work. For now, * though, the arena, background thread, and PAC modules are tightly interwoven * in a way that's tricky to extricate, so we only do the HPA-specific parts. */ void pa_shard_set_deferral_allowed(tsdn_t *tsdn, pa_shard_t *shard, bool deferral_allowed); void pa_shard_do_deferred_work(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_try_deferred_work(tsdn_t *tsdn, pa_shard_t *shard); uint64_t pa_shard_time_until_deferred_work(tsdn_t *tsdn, pa_shard_t *shard); /******************************************************************************/ /* * Various bits of "boring" functionality that are still part of this module, * but that we relegate to pa_extra.c, to keep the core logic in pa.c as * readable as possible. */ /* * These fork phases are synchronized with the arena fork phase numbering to * make it easy to keep straight. That's why there's no prefork1. */ void pa_shard_prefork0(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_prefork2(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_prefork3(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_prefork4(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_prefork5(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_postfork_parent(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_postfork_child(tsdn_t *tsdn, pa_shard_t *shard); void pa_shard_basic_stats_merge(pa_shard_t *shard, size_t *nactive, size_t *ndirty, size_t *nmuzzy); void pa_shard_stats_merge(tsdn_t *tsdn, pa_shard_t *shard, pa_shard_stats_t *pa_shard_stats_out, pac_estats_t *estats_out, hpa_shard_stats_t *hpa_stats_out, sec_stats_t *sec_stats_out, size_t *resident); /* * Reads the PA-owned mutex stats into the output stats array, at the * appropriate positions. Morally, these stats should really live in * pa_shard_stats_t, but the indices are sort of baked into the various mutex * prof macros. This would be a good thing to do at some point. */ void pa_shard_mtx_stats_read(tsdn_t *tsdn, pa_shard_t *shard, mutex_prof_data_t mutex_prof_data[mutex_prof_num_arena_mutexes]); #endif /* JEMALLOC_INTERNAL_PA_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/pac.h000066400000000000000000000123251501533116600233150ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PAC_H #define JEMALLOC_INTERNAL_PAC_H #include "jemalloc/internal/exp_grow.h" #include "jemalloc/internal/pai.h" #include "san_bump.h" /* * Page allocator classic; an implementation of the PAI interface that: * - Can be used for arenas with custom extent hooks. * - Can always satisfy any allocation request (including highly-fragmentary * ones). * - Can use efficient OS-level zeroing primitives for demand-filled pages. */ /* How "eager" decay/purging should be. */ enum pac_purge_eagerness_e { PAC_PURGE_ALWAYS, PAC_PURGE_NEVER, PAC_PURGE_ON_EPOCH_ADVANCE }; typedef enum pac_purge_eagerness_e pac_purge_eagerness_t; typedef struct pac_decay_stats_s pac_decay_stats_t; struct pac_decay_stats_s { /* Total number of purge sweeps. */ locked_u64_t npurge; /* Total number of madvise calls made. */ locked_u64_t nmadvise; /* Total number of pages purged. */ locked_u64_t purged; }; typedef struct pac_estats_s pac_estats_t; struct pac_estats_s { /* * Stats for a given index in the range [0, SC_NPSIZES] in the various * ecache_ts. * We track both bytes and # of extents: two extents in the same bucket * may have different sizes if adjacent size classes differ by more than * a page, so bytes cannot always be derived from # of extents. */ size_t ndirty; size_t dirty_bytes; size_t nmuzzy; size_t muzzy_bytes; size_t nretained; size_t retained_bytes; }; typedef struct pac_stats_s pac_stats_t; struct pac_stats_s { pac_decay_stats_t decay_dirty; pac_decay_stats_t decay_muzzy; /* * Number of unused virtual memory bytes currently retained. Retained * bytes are technically mapped (though always decommitted or purged), * but they are excluded from the mapped statistic (above). */ size_t retained; /* Derived. */ /* * Number of bytes currently mapped, excluding retained memory (and any * base-allocated memory, which is tracked by the arena stats). * * We name this "pac_mapped" to avoid confusion with the arena_stats * "mapped". */ atomic_zu_t pac_mapped; /* VM space had to be leaked (undocumented). Normally 0. */ atomic_zu_t abandoned_vm; }; typedef struct pac_s pac_t; struct pac_s { /* * Must be the first member (we convert it to a PAC given only a * pointer). The handle to the allocation interface. */ pai_t pai; /* * Collections of extents that were previously allocated. These are * used when allocating extents, in an attempt to re-use address space. * * Synchronization: internal. */ ecache_t ecache_dirty; ecache_t ecache_muzzy; ecache_t ecache_retained; base_t *base; emap_t *emap; edata_cache_t *edata_cache; /* The grow info for the retained ecache. */ exp_grow_t exp_grow; malloc_mutex_t grow_mtx; /* Special allocator for guarded frequently reused extents. */ san_bump_alloc_t sba; /* How large extents should be before getting auto-purged. */ atomic_zu_t oversize_threshold; /* * Decay-based purging state, responsible for scheduling extent state * transitions. * * Synchronization: via the internal mutex. */ decay_t decay_dirty; /* dirty --> muzzy */ decay_t decay_muzzy; /* muzzy --> retained */ malloc_mutex_t *stats_mtx; pac_stats_t *stats; /* Extent serial number generator state. */ atomic_zu_t extent_sn_next; }; bool pac_init(tsdn_t *tsdn, pac_t *pac, base_t *base, emap_t *emap, edata_cache_t *edata_cache, nstime_t *cur_time, size_t oversize_threshold, ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms, pac_stats_t *pac_stats, malloc_mutex_t *stats_mtx); static inline size_t pac_mapped(pac_t *pac) { return atomic_load_zu(&pac->stats->pac_mapped, ATOMIC_RELAXED); } static inline ehooks_t * pac_ehooks_get(pac_t *pac) { return base_ehooks_get(pac->base); } /* * All purging functions require holding decay->mtx. This is one of the few * places external modules are allowed to peek inside pa_shard_t internals. */ /* * Decays the number of pages currently in the ecache. This might not leave the * ecache empty if other threads are inserting dirty objects into it * concurrently with the call. */ void pac_decay_all(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay); /* * Updates decay settings for the current time, and conditionally purges in * response (depending on decay_purge_setting). Returns whether or not the * epoch advanced. */ bool pac_maybe_decay_purge(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, pac_purge_eagerness_t eagerness); /* * Gets / sets the maximum amount that we'll grow an arena down the * grow-retained pathways (unless forced to by an allocaction request). * * Set new_limit to NULL if it's just a query, or old_limit to NULL if you don't * care about the previous value. * * Returns true on error (if the new limit is not valid). */ bool pac_retain_grow_limit_get_set(tsdn_t *tsdn, pac_t *pac, size_t *old_limit, size_t *new_limit); bool pac_decay_ms_set(tsdn_t *tsdn, pac_t *pac, extent_state_t state, ssize_t decay_ms, pac_purge_eagerness_t eagerness); ssize_t pac_decay_ms_get(pac_t *pac, extent_state_t state); void pac_reset(tsdn_t *tsdn, pac_t *pac); void pac_destroy(tsdn_t *tsdn, pac_t *pac); #endif /* JEMALLOC_INTERNAL_PAC_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/pages.h000066400000000000000000000076611501533116600236600ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PAGES_EXTERNS_H #define JEMALLOC_INTERNAL_PAGES_EXTERNS_H /* Page size. LG_PAGE is determined by the configure script. */ #ifdef PAGE_MASK # undef PAGE_MASK #endif #define PAGE ((size_t)(1U << LG_PAGE)) #define PAGE_MASK ((size_t)(PAGE - 1)) /* Return the page base address for the page containing address a. */ #define PAGE_ADDR2BASE(a) \ ((void *)((uintptr_t)(a) & ~PAGE_MASK)) /* Return the smallest pagesize multiple that is >= s. */ #define PAGE_CEILING(s) \ (((s) + PAGE_MASK) & ~PAGE_MASK) /* Return the largest pagesize multiple that is <=s. */ #define PAGE_FLOOR(s) \ ((s) & ~PAGE_MASK) /* Huge page size. LG_HUGEPAGE is determined by the configure script. */ #define HUGEPAGE ((size_t)(1U << LG_HUGEPAGE)) #define HUGEPAGE_MASK ((size_t)(HUGEPAGE - 1)) #if LG_HUGEPAGE != 0 # define HUGEPAGE_PAGES (HUGEPAGE / PAGE) #else /* * It's convenient to define arrays (or bitmaps) of HUGEPAGE_PAGES lengths. If * we can't autodetect the hugepage size, it gets treated as 0, in which case * we'll trigger a compiler error in those arrays. Avoid this case by ensuring * that this value is at least 1. (We won't ever run in this degraded state; * hpa_supported() returns false in this case. */ # define HUGEPAGE_PAGES 1 #endif /* Return the huge page base address for the huge page containing address a. */ #define HUGEPAGE_ADDR2BASE(a) \ ((void *)((uintptr_t)(a) & ~HUGEPAGE_MASK)) /* Return the smallest pagesize multiple that is >= s. */ #define HUGEPAGE_CEILING(s) \ (((s) + HUGEPAGE_MASK) & ~HUGEPAGE_MASK) /* PAGES_CAN_PURGE_LAZY is defined if lazy purging is supported. */ #if defined(_WIN32) || defined(JEMALLOC_PURGE_MADVISE_FREE) # define PAGES_CAN_PURGE_LAZY #endif /* * PAGES_CAN_PURGE_FORCED is defined if forced purging is supported. * * The only supported way to hard-purge on Windows is to decommit and then * re-commit, but doing so is racy, and if re-commit fails it's a pain to * propagate the "poisoned" memory state. Since we typically decommit as the * next step after purging on Windows anyway, there's no point in adding such * complexity. */ #if !defined(_WIN32) && ((defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \ defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS)) || \ defined(JEMALLOC_MAPS_COALESCE)) # define PAGES_CAN_PURGE_FORCED #endif static const bool pages_can_purge_lazy = #ifdef PAGES_CAN_PURGE_LAZY true #else false #endif ; static const bool pages_can_purge_forced = #ifdef PAGES_CAN_PURGE_FORCED true #else false #endif ; #if defined(JEMALLOC_HAVE_MADVISE_HUGE) || defined(JEMALLOC_HAVE_MEMCNTL) # define PAGES_CAN_HUGIFY #endif static const bool pages_can_hugify = #ifdef PAGES_CAN_HUGIFY true #else false #endif ; typedef enum { thp_mode_default = 0, /* Do not change hugepage settings. */ thp_mode_always = 1, /* Always set MADV_HUGEPAGE. */ thp_mode_never = 2, /* Always set MADV_NOHUGEPAGE. */ thp_mode_names_limit = 3, /* Used for option processing. */ thp_mode_not_supported = 3 /* No THP support detected. */ } thp_mode_t; #define THP_MODE_DEFAULT thp_mode_default extern thp_mode_t opt_thp; extern thp_mode_t init_system_thp_mode; /* Initial system wide state. */ extern const char *thp_mode_names[]; void *pages_map(void *addr, size_t size, size_t alignment, bool *commit); void pages_unmap(void *addr, size_t size); bool pages_commit(void *addr, size_t size); bool pages_decommit(void *addr, size_t size); bool pages_purge_lazy(void *addr, size_t size); bool pages_purge_forced(void *addr, size_t size); bool pages_huge(void *addr, size_t size); bool pages_nohuge(void *addr, size_t size); bool pages_dontdump(void *addr, size_t size); bool pages_dodump(void *addr, size_t size); bool pages_boot(void); void pages_set_thp_state (void *ptr, size_t size); void pages_mark_guards(void *head, void *tail); void pages_unmark_guards(void *head, void *tail); #endif /* JEMALLOC_INTERNAL_PAGES_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/pai.h000066400000000000000000000066541501533116600233330ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PAI_H #define JEMALLOC_INTERNAL_PAI_H /* An interface for page allocation. */ typedef struct pai_s pai_t; struct pai_s { /* Returns NULL on failure. */ edata_t *(*alloc)(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated); /* * Returns the number of extents added to the list (which may be fewer * than requested, in case of OOM). The list should already be * initialized. The only alignment guarantee is page-alignment, and * the results are not necessarily zeroed. */ size_t (*alloc_batch)(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated); bool (*expand)(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); bool (*shrink)(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated); void (*dalloc)(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated); /* This function empties out list as a side-effect of being called. */ void (*dalloc_batch)(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated); uint64_t (*time_until_deferred_work)(tsdn_t *tsdn, pai_t *self); }; /* * These are just simple convenience functions to avoid having to reference the * same pai_t twice on every invocation. */ static inline edata_t * pai_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated) { return self->alloc(tsdn, self, size, alignment, zero, guarded, frequent_reuse, deferred_work_generated); } static inline size_t pai_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated) { return self->alloc_batch(tsdn, self, size, nallocs, results, deferred_work_generated); } static inline bool pai_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated) { return self->expand(tsdn, self, edata, old_size, new_size, zero, deferred_work_generated); } static inline bool pai_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated) { return self->shrink(tsdn, self, edata, old_size, new_size, deferred_work_generated); } static inline void pai_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated) { self->dalloc(tsdn, self, edata, deferred_work_generated); } static inline void pai_dalloc_batch(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated) { self->dalloc_batch(tsdn, self, list, deferred_work_generated); } static inline uint64_t pai_time_until_deferred_work(tsdn_t *tsdn, pai_t *self) { return self->time_until_deferred_work(tsdn, self); } /* * An implementation of batch allocation that simply calls alloc once for * each item in the list. */ size_t pai_alloc_batch_default(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated); /* Ditto, for dalloc. */ void pai_dalloc_batch_default(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated); #endif /* JEMALLOC_INTERNAL_PAI_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/peak.h000066400000000000000000000017111501533116600234670ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PEAK_H #define JEMALLOC_INTERNAL_PEAK_H typedef struct peak_s peak_t; struct peak_s { /* The highest recorded peak value, after adjustment (see below). */ uint64_t cur_max; /* * The difference between alloc and dalloc at the last set_zero call; * this lets us cancel out the appropriate amount of excess. */ uint64_t adjustment; }; #define PEAK_INITIALIZER {0, 0} static inline uint64_t peak_max(peak_t *peak) { return peak->cur_max; } static inline void peak_update(peak_t *peak, uint64_t alloc, uint64_t dalloc) { int64_t candidate_max = (int64_t)(alloc - dalloc - peak->adjustment); if (candidate_max > (int64_t)peak->cur_max) { peak->cur_max = candidate_max; } } /* Resets the counter to zero; all peaks are now relative to this point. */ static inline void peak_set_zero(peak_t *peak, uint64_t alloc, uint64_t dalloc) { peak->cur_max = 0; peak->adjustment = alloc - dalloc; } #endif /* JEMALLOC_INTERNAL_PEAK_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/peak_event.h000066400000000000000000000015331501533116600246720ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PEAK_EVENT_H #define JEMALLOC_INTERNAL_PEAK_EVENT_H /* * While peak.h contains the simple helper struct that tracks state, this * contains the allocator tie-ins (and knows about tsd, the event module, etc.). */ /* Update the peak with current tsd state. */ void peak_event_update(tsd_t *tsd); /* Set current state to zero. */ void peak_event_zero(tsd_t *tsd); uint64_t peak_event_max(tsd_t *tsd); /* Manual hooks. */ /* The activity-triggered hooks. */ uint64_t peak_alloc_new_event_wait(tsd_t *tsd); uint64_t peak_alloc_postponed_event_wait(tsd_t *tsd); void peak_alloc_event_handler(tsd_t *tsd, uint64_t elapsed); uint64_t peak_dalloc_new_event_wait(tsd_t *tsd); uint64_t peak_dalloc_postponed_event_wait(tsd_t *tsd); void peak_dalloc_event_handler(tsd_t *tsd, uint64_t elapsed); #endif /* JEMALLOC_INTERNAL_PEAK_EVENT_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ph.h000066400000000000000000000355641501533116600231730ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PH_H #define JEMALLOC_INTERNAL_PH_H /* * A Pairing Heap implementation. * * "The Pairing Heap: A New Form of Self-Adjusting Heap" * https://www.cs.cmu.edu/~sleator/papers/pairing-heaps.pdf * * With auxiliary twopass list, described in a follow on paper. * * "Pairing Heaps: Experiments and Analysis" * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.106.2988&rep=rep1&type=pdf * ******************************************************************************* * * We include a non-obvious optimization: * - First, we introduce a new pop-and-link operation; pop the two most * recently-inserted items off the aux-list, link them, and push the resulting * heap. * - We maintain a count of the number of insertions since the last time we * merged the aux-list (i.e. via first() or remove_first()). After N inserts, * we do ffs(N) pop-and-link operations. * * One way to think of this is that we're progressively building up a tree in * the aux-list, rather than a linked-list (think of the series of merges that * will be performed as the aux-count grows). * * There's a couple reasons we benefit from this: * - Ordinarily, after N insertions, the aux-list is of size N. With our * strategy, it's of size O(log(N)). So we decrease the worst-case time of * first() calls, and reduce the average cost of remove_min calls. Since * these almost always occur while holding a lock, we practically reduce the * frequency of unusually long hold times. * - This moves the bulk of the work of merging the aux-list onto the threads * that are inserting into the heap. In some common scenarios, insertions * happen in bulk, from a single thread (think tcache flushing; we potentially * move many slabs from slabs_full to slabs_nonfull). All the nodes in this * case are in the inserting threads cache, and linking them is very cheap * (cache misses dominate linking cost). Without this optimization, linking * happens on the next call to remove_first. Since that remove_first call * likely happens on a different thread (or at least, after the cache has * gotten cold if done on the same thread), deferring linking trades cheap * link operations now for expensive ones later. * * The ffs trick keeps amortized insert cost at constant time. Similar * strategies based on periodically sorting the list after a batch of operations * perform worse than this in practice, even with various fancy tricks; they * all took amortized complexity of an insert from O(1) to O(log(n)). */ typedef int (*ph_cmp_t)(void *, void *); /* Node structure. */ typedef struct phn_link_s phn_link_t; struct phn_link_s { void *prev; void *next; void *lchild; }; typedef struct ph_s ph_t; struct ph_s { void *root; /* * Inserts done since the last aux-list merge. This is not necessarily * the size of the aux-list, since it's possible that removals have * happened since, and we don't track whether or not those removals are * from the aux list. */ size_t auxcount; }; JEMALLOC_ALWAYS_INLINE phn_link_t * phn_link_get(void *phn, size_t offset) { return (phn_link_t *)(((uintptr_t)phn) + offset); } JEMALLOC_ALWAYS_INLINE void phn_link_init(void *phn, size_t offset) { phn_link_get(phn, offset)->prev = NULL; phn_link_get(phn, offset)->next = NULL; phn_link_get(phn, offset)->lchild = NULL; } /* Internal utility helpers. */ JEMALLOC_ALWAYS_INLINE void * phn_lchild_get(void *phn, size_t offset) { return phn_link_get(phn, offset)->lchild; } JEMALLOC_ALWAYS_INLINE void phn_lchild_set(void *phn, void *lchild, size_t offset) { phn_link_get(phn, offset)->lchild = lchild; } JEMALLOC_ALWAYS_INLINE void * phn_next_get(void *phn, size_t offset) { return phn_link_get(phn, offset)->next; } JEMALLOC_ALWAYS_INLINE void phn_next_set(void *phn, void *next, size_t offset) { phn_link_get(phn, offset)->next = next; } JEMALLOC_ALWAYS_INLINE void * phn_prev_get(void *phn, size_t offset) { return phn_link_get(phn, offset)->prev; } JEMALLOC_ALWAYS_INLINE void phn_prev_set(void *phn, void *prev, size_t offset) { phn_link_get(phn, offset)->prev = prev; } JEMALLOC_ALWAYS_INLINE void phn_merge_ordered(void *phn0, void *phn1, size_t offset, ph_cmp_t cmp) { void *phn0child; assert(phn0 != NULL); assert(phn1 != NULL); assert(cmp(phn0, phn1) <= 0); phn_prev_set(phn1, phn0, offset); phn0child = phn_lchild_get(phn0, offset); phn_next_set(phn1, phn0child, offset); if (phn0child != NULL) { phn_prev_set(phn0child, phn1, offset); } phn_lchild_set(phn0, phn1, offset); } JEMALLOC_ALWAYS_INLINE void * phn_merge(void *phn0, void *phn1, size_t offset, ph_cmp_t cmp) { void *result; if (phn0 == NULL) { result = phn1; } else if (phn1 == NULL) { result = phn0; } else if (cmp(phn0, phn1) < 0) { phn_merge_ordered(phn0, phn1, offset, cmp); result = phn0; } else { phn_merge_ordered(phn1, phn0, offset, cmp); result = phn1; } return result; } JEMALLOC_ALWAYS_INLINE void * phn_merge_siblings(void *phn, size_t offset, ph_cmp_t cmp) { void *head = NULL; void *tail = NULL; void *phn0 = phn; void *phn1 = phn_next_get(phn0, offset); /* * Multipass merge, wherein the first two elements of a FIFO * are repeatedly merged, and each result is appended to the * singly linked FIFO, until the FIFO contains only a single * element. We start with a sibling list but no reference to * its tail, so we do a single pass over the sibling list to * populate the FIFO. */ if (phn1 != NULL) { void *phnrest = phn_next_get(phn1, offset); if (phnrest != NULL) { phn_prev_set(phnrest, NULL, offset); } phn_prev_set(phn0, NULL, offset); phn_next_set(phn0, NULL, offset); phn_prev_set(phn1, NULL, offset); phn_next_set(phn1, NULL, offset); phn0 = phn_merge(phn0, phn1, offset, cmp); head = tail = phn0; phn0 = phnrest; while (phn0 != NULL) { phn1 = phn_next_get(phn0, offset); if (phn1 != NULL) { phnrest = phn_next_get(phn1, offset); if (phnrest != NULL) { phn_prev_set(phnrest, NULL, offset); } phn_prev_set(phn0, NULL, offset); phn_next_set(phn0, NULL, offset); phn_prev_set(phn1, NULL, offset); phn_next_set(phn1, NULL, offset); phn0 = phn_merge(phn0, phn1, offset, cmp); phn_next_set(tail, phn0, offset); tail = phn0; phn0 = phnrest; } else { phn_next_set(tail, phn0, offset); tail = phn0; phn0 = NULL; } } phn0 = head; phn1 = phn_next_get(phn0, offset); if (phn1 != NULL) { while (true) { head = phn_next_get(phn1, offset); assert(phn_prev_get(phn0, offset) == NULL); phn_next_set(phn0, NULL, offset); assert(phn_prev_get(phn1, offset) == NULL); phn_next_set(phn1, NULL, offset); phn0 = phn_merge(phn0, phn1, offset, cmp); if (head == NULL) { break; } phn_next_set(tail, phn0, offset); tail = phn0; phn0 = head; phn1 = phn_next_get(phn0, offset); } } } return phn0; } JEMALLOC_ALWAYS_INLINE void ph_merge_aux(ph_t *ph, size_t offset, ph_cmp_t cmp) { ph->auxcount = 0; void *phn = phn_next_get(ph->root, offset); if (phn != NULL) { phn_prev_set(ph->root, NULL, offset); phn_next_set(ph->root, NULL, offset); phn_prev_set(phn, NULL, offset); phn = phn_merge_siblings(phn, offset, cmp); assert(phn_next_get(phn, offset) == NULL); ph->root = phn_merge(ph->root, phn, offset, cmp); } } JEMALLOC_ALWAYS_INLINE void * ph_merge_children(void *phn, size_t offset, ph_cmp_t cmp) { void *result; void *lchild = phn_lchild_get(phn, offset); if (lchild == NULL) { result = NULL; } else { result = phn_merge_siblings(lchild, offset, cmp); } return result; } JEMALLOC_ALWAYS_INLINE void ph_new(ph_t *ph) { ph->root = NULL; ph->auxcount = 0; } JEMALLOC_ALWAYS_INLINE bool ph_empty(ph_t *ph) { return ph->root == NULL; } JEMALLOC_ALWAYS_INLINE void * ph_first(ph_t *ph, size_t offset, ph_cmp_t cmp) { if (ph->root == NULL) { return NULL; } ph_merge_aux(ph, offset, cmp); return ph->root; } JEMALLOC_ALWAYS_INLINE void * ph_any(ph_t *ph, size_t offset) { if (ph->root == NULL) { return NULL; } void *aux = phn_next_get(ph->root, offset); if (aux != NULL) { return aux; } return ph->root; } /* Returns true if we should stop trying to merge. */ JEMALLOC_ALWAYS_INLINE bool ph_try_aux_merge_pair(ph_t *ph, size_t offset, ph_cmp_t cmp) { assert(ph->root != NULL); void *phn0 = phn_next_get(ph->root, offset); if (phn0 == NULL) { return true; } void *phn1 = phn_next_get(phn0, offset); if (phn1 == NULL) { return true; } void *next_phn1 = phn_next_get(phn1, offset); phn_next_set(phn0, NULL, offset); phn_prev_set(phn0, NULL, offset); phn_next_set(phn1, NULL, offset); phn_prev_set(phn1, NULL, offset); phn0 = phn_merge(phn0, phn1, offset, cmp); phn_next_set(phn0, next_phn1, offset); if (next_phn1 != NULL) { phn_prev_set(next_phn1, phn0, offset); } phn_next_set(ph->root, phn0, offset); phn_prev_set(phn0, ph->root, offset); return next_phn1 == NULL; } JEMALLOC_ALWAYS_INLINE void ph_insert(ph_t *ph, void *phn, size_t offset, ph_cmp_t cmp) { phn_link_init(phn, offset); /* * Treat the root as an aux list during insertion, and lazily merge * during a_prefix##remove_first(). For elements that are inserted, * then removed via a_prefix##remove() before the aux list is ever * processed, this makes insert/remove constant-time, whereas eager * merging would make insert O(log n). */ if (ph->root == NULL) { ph->root = phn; } else { /* * As a special case, check to see if we can replace the root. * This is practically common in some important cases, and lets * us defer some insertions (hopefully, until the point where * some of the items in the aux list have been removed, savings * us from linking them at all). */ if (cmp(phn, ph->root) < 0) { phn_lchild_set(phn, ph->root, offset); phn_prev_set(ph->root, phn, offset); ph->root = phn; ph->auxcount = 0; return; } ph->auxcount++; phn_next_set(phn, phn_next_get(ph->root, offset), offset); if (phn_next_get(ph->root, offset) != NULL) { phn_prev_set(phn_next_get(ph->root, offset), phn, offset); } phn_prev_set(phn, ph->root, offset); phn_next_set(ph->root, phn, offset); } if (ph->auxcount > 1) { unsigned nmerges = ffs_zu(ph->auxcount - 1); bool done = false; for (unsigned i = 0; i < nmerges && !done; i++) { done = ph_try_aux_merge_pair(ph, offset, cmp); } } } JEMALLOC_ALWAYS_INLINE void * ph_remove_first(ph_t *ph, size_t offset, ph_cmp_t cmp) { void *ret; if (ph->root == NULL) { return NULL; } ph_merge_aux(ph, offset, cmp); ret = ph->root; ph->root = ph_merge_children(ph->root, offset, cmp); return ret; } JEMALLOC_ALWAYS_INLINE void ph_remove(ph_t *ph, void *phn, size_t offset, ph_cmp_t cmp) { void *replace; void *parent; if (ph->root == phn) { /* * We can delete from aux list without merging it, but we need * to merge if we are dealing with the root node and it has * children. */ if (phn_lchild_get(phn, offset) == NULL) { ph->root = phn_next_get(phn, offset); if (ph->root != NULL) { phn_prev_set(ph->root, NULL, offset); } return; } ph_merge_aux(ph, offset, cmp); if (ph->root == phn) { ph->root = ph_merge_children(ph->root, offset, cmp); return; } } /* Get parent (if phn is leftmost child) before mutating. */ if ((parent = phn_prev_get(phn, offset)) != NULL) { if (phn_lchild_get(parent, offset) != phn) { parent = NULL; } } /* Find a possible replacement node, and link to parent. */ replace = ph_merge_children(phn, offset, cmp); /* Set next/prev for sibling linked list. */ if (replace != NULL) { if (parent != NULL) { phn_prev_set(replace, parent, offset); phn_lchild_set(parent, replace, offset); } else { phn_prev_set(replace, phn_prev_get(phn, offset), offset); if (phn_prev_get(phn, offset) != NULL) { phn_next_set(phn_prev_get(phn, offset), replace, offset); } } phn_next_set(replace, phn_next_get(phn, offset), offset); if (phn_next_get(phn, offset) != NULL) { phn_prev_set(phn_next_get(phn, offset), replace, offset); } } else { if (parent != NULL) { void *next = phn_next_get(phn, offset); phn_lchild_set(parent, next, offset); if (next != NULL) { phn_prev_set(next, parent, offset); } } else { assert(phn_prev_get(phn, offset) != NULL); phn_next_set( phn_prev_get(phn, offset), phn_next_get(phn, offset), offset); } if (phn_next_get(phn, offset) != NULL) { phn_prev_set( phn_next_get(phn, offset), phn_prev_get(phn, offset), offset); } } } #define ph_structs(a_prefix, a_type) \ typedef struct { \ phn_link_t link; \ } a_prefix##_link_t; \ \ typedef struct { \ ph_t ph; \ } a_prefix##_t; /* * The ph_proto() macro generates function prototypes that correspond to the * functions generated by an equivalently parameterized call to ph_gen(). */ #define ph_proto(a_attr, a_prefix, a_type) \ \ a_attr void a_prefix##_new(a_prefix##_t *ph); \ a_attr bool a_prefix##_empty(a_prefix##_t *ph); \ a_attr a_type *a_prefix##_first(a_prefix##_t *ph); \ a_attr a_type *a_prefix##_any(a_prefix##_t *ph); \ a_attr void a_prefix##_insert(a_prefix##_t *ph, a_type *phn); \ a_attr a_type *a_prefix##_remove_first(a_prefix##_t *ph); \ a_attr void a_prefix##_remove(a_prefix##_t *ph, a_type *phn); \ a_attr a_type *a_prefix##_remove_any(a_prefix##_t *ph); /* The ph_gen() macro generates a type-specific pairing heap implementation. */ #define ph_gen(a_attr, a_prefix, a_type, a_field, a_cmp) \ JEMALLOC_ALWAYS_INLINE int \ a_prefix##_ph_cmp(void *a, void *b) { \ return a_cmp((a_type *)a, (a_type *)b); \ } \ \ a_attr void \ a_prefix##_new(a_prefix##_t *ph) { \ ph_new(&ph->ph); \ } \ \ a_attr bool \ a_prefix##_empty(a_prefix##_t *ph) { \ return ph_empty(&ph->ph); \ } \ \ a_attr a_type * \ a_prefix##_first(a_prefix##_t *ph) { \ return ph_first(&ph->ph, offsetof(a_type, a_field), \ &a_prefix##_ph_cmp); \ } \ \ a_attr a_type * \ a_prefix##_any(a_prefix##_t *ph) { \ return ph_any(&ph->ph, offsetof(a_type, a_field)); \ } \ \ a_attr void \ a_prefix##_insert(a_prefix##_t *ph, a_type *phn) { \ ph_insert(&ph->ph, phn, offsetof(a_type, a_field), \ a_prefix##_ph_cmp); \ } \ \ a_attr a_type * \ a_prefix##_remove_first(a_prefix##_t *ph) { \ return ph_remove_first(&ph->ph, offsetof(a_type, a_field), \ a_prefix##_ph_cmp); \ } \ \ a_attr void \ a_prefix##_remove(a_prefix##_t *ph, a_type *phn) { \ ph_remove(&ph->ph, phn, offsetof(a_type, a_field), \ a_prefix##_ph_cmp); \ } \ \ a_attr a_type * \ a_prefix##_remove_any(a_prefix##_t *ph) { \ a_type *ret = a_prefix##_any(ph); \ if (ret != NULL) { \ a_prefix##_remove(ph, ret); \ } \ return ret; \ } #endif /* JEMALLOC_INTERNAL_PH_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/private_namespace.sh000077500000000000000000000001371501533116600264240ustar00rootroot00000000000000#!/bin/sh for symbol in `cat "$@"` ; do echo "#define ${symbol} JEMALLOC_N(${symbol})" done redis-8.0.2/deps/jemalloc/include/jemalloc/internal/private_symbols.sh000077500000000000000000000021741501533116600261630ustar00rootroot00000000000000#!/bin/sh # # Generate private_symbols[_jet].awk. # # Usage: private_symbols.sh * # # is typically "" or "_". sym_prefix=$1 shift cat <' output. # # Handle lines like: # 0000000000000008 D opt_junk # 0000000000007574 T malloc_initialized (NF == 3 && $2 ~ /^[ABCDGRSTVW]$/ && !($3 in exported_symbols) && $3 ~ /^[A-Za-z0-9_]+$/) { print substr($3, 1+length(sym_prefix), length($3)-length(sym_prefix)) } # Process 'dumpbin /SYMBOLS ' output. # # Handle lines like: # 353 00008098 SECT4 notype External | opt_junk # 3F1 00000000 SECT7 notype () External | malloc_initialized ($3 ~ /^SECT[0-9]+/ && $(NF-2) == "External" && !($NF in exported_symbols)) { print $NF } EOF redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prng.h000066400000000000000000000102331501533116600235140ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PRNG_H #define JEMALLOC_INTERNAL_PRNG_H #include "jemalloc/internal/bit_util.h" /* * Simple linear congruential pseudo-random number generator: * * prng(y) = (a*x + c) % m * * where the following constants ensure maximal period: * * a == Odd number (relatively prime to 2^n), and (a-1) is a multiple of 4. * c == Odd number (relatively prime to 2^n). * m == 2^32 * * See Knuth's TAOCP 3rd Ed., Vol. 2, pg. 17 for details on these constraints. * * This choice of m has the disadvantage that the quality of the bits is * proportional to bit position. For example, the lowest bit has a cycle of 2, * the next has a cycle of 4, etc. For this reason, we prefer to use the upper * bits. */ /******************************************************************************/ /* INTERNAL DEFINITIONS -- IGNORE */ /******************************************************************************/ #define PRNG_A_32 UINT32_C(1103515241) #define PRNG_C_32 UINT32_C(12347) #define PRNG_A_64 UINT64_C(6364136223846793005) #define PRNG_C_64 UINT64_C(1442695040888963407) JEMALLOC_ALWAYS_INLINE uint32_t prng_state_next_u32(uint32_t state) { return (state * PRNG_A_32) + PRNG_C_32; } JEMALLOC_ALWAYS_INLINE uint64_t prng_state_next_u64(uint64_t state) { return (state * PRNG_A_64) + PRNG_C_64; } JEMALLOC_ALWAYS_INLINE size_t prng_state_next_zu(size_t state) { #if LG_SIZEOF_PTR == 2 return (state * PRNG_A_32) + PRNG_C_32; #elif LG_SIZEOF_PTR == 3 return (state * PRNG_A_64) + PRNG_C_64; #else #error Unsupported pointer size #endif } /******************************************************************************/ /* BEGIN PUBLIC API */ /******************************************************************************/ /* * The prng_lg_range functions give a uniform int in the half-open range [0, * 2**lg_range). */ JEMALLOC_ALWAYS_INLINE uint32_t prng_lg_range_u32(uint32_t *state, unsigned lg_range) { assert(lg_range > 0); assert(lg_range <= 32); *state = prng_state_next_u32(*state); uint32_t ret = *state >> (32 - lg_range); return ret; } JEMALLOC_ALWAYS_INLINE uint64_t prng_lg_range_u64(uint64_t *state, unsigned lg_range) { assert(lg_range > 0); assert(lg_range <= 64); *state = prng_state_next_u64(*state); uint64_t ret = *state >> (64 - lg_range); return ret; } JEMALLOC_ALWAYS_INLINE size_t prng_lg_range_zu(size_t *state, unsigned lg_range) { assert(lg_range > 0); assert(lg_range <= ZU(1) << (3 + LG_SIZEOF_PTR)); *state = prng_state_next_zu(*state); size_t ret = *state >> ((ZU(1) << (3 + LG_SIZEOF_PTR)) - lg_range); return ret; } /* * The prng_range functions behave like the prng_lg_range, but return a result * in [0, range) instead of [0, 2**lg_range). */ JEMALLOC_ALWAYS_INLINE uint32_t prng_range_u32(uint32_t *state, uint32_t range) { assert(range != 0); /* * If range were 1, lg_range would be 0, so the shift in * prng_lg_range_u32 would be a shift of a 32-bit variable by 32 bits, * which is UB. Just handle this case as a one-off. */ if (range == 1) { return 0; } /* Compute the ceiling of lg(range). */ unsigned lg_range = ffs_u32(pow2_ceil_u32(range)); /* Generate a result in [0..range) via repeated trial. */ uint32_t ret; do { ret = prng_lg_range_u32(state, lg_range); } while (ret >= range); return ret; } JEMALLOC_ALWAYS_INLINE uint64_t prng_range_u64(uint64_t *state, uint64_t range) { assert(range != 0); /* See the note in prng_range_u32. */ if (range == 1) { return 0; } /* Compute the ceiling of lg(range). */ unsigned lg_range = ffs_u64(pow2_ceil_u64(range)); /* Generate a result in [0..range) via repeated trial. */ uint64_t ret; do { ret = prng_lg_range_u64(state, lg_range); } while (ret >= range); return ret; } JEMALLOC_ALWAYS_INLINE size_t prng_range_zu(size_t *state, size_t range) { assert(range != 0); /* See the note in prng_range_u32. */ if (range == 1) { return 0; } /* Compute the ceiling of lg(range). */ unsigned lg_range = ffs_u64(pow2_ceil_u64(range)); /* Generate a result in [0..range) via repeated trial. */ size_t ret; do { ret = prng_lg_range_zu(state, lg_range); } while (ret >= range); return ret; } #endif /* JEMALLOC_INTERNAL_PRNG_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_data.h000066400000000000000000000024701501533116600245110ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_DATA_H #define JEMALLOC_INTERNAL_PROF_DATA_H #include "jemalloc/internal/mutex.h" extern malloc_mutex_t bt2gctx_mtx; extern malloc_mutex_t tdatas_mtx; extern malloc_mutex_t prof_dump_mtx; extern malloc_mutex_t *gctx_locks; extern malloc_mutex_t *tdata_locks; extern size_t prof_unbiased_sz[PROF_SC_NSIZES]; extern size_t prof_shifted_unbiased_cnt[PROF_SC_NSIZES]; void prof_bt_hash(const void *key, size_t r_hash[2]); bool prof_bt_keycomp(const void *k1, const void *k2); bool prof_data_init(tsd_t *tsd); prof_tctx_t *prof_lookup(tsd_t *tsd, prof_bt_t *bt); char *prof_thread_name_alloc(tsd_t *tsd, const char *thread_name); int prof_thread_name_set_impl(tsd_t *tsd, const char *thread_name); void prof_unbias_map_init(); void prof_dump_impl(tsd_t *tsd, write_cb_t *prof_dump_write, void *cbopaque, prof_tdata_t *tdata, bool leakcheck); prof_tdata_t * prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim, char *thread_name, bool active); void prof_tdata_detach(tsd_t *tsd, prof_tdata_t *tdata); void prof_reset(tsd_t *tsd, size_t lg_sample); void prof_tctx_try_destroy(tsd_t *tsd, prof_tctx_t *tctx); /* Used in unit tests. */ size_t prof_tdata_count(void); size_t prof_bt_count(void); void prof_cnt_all(prof_cnt_t *cnt_all); #endif /* JEMALLOC_INTERNAL_PROF_DATA_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_externs.h000066400000000000000000000065701501533116600252750ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_EXTERNS_H #define JEMALLOC_INTERNAL_PROF_EXTERNS_H #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/prof_hook.h" extern bool opt_prof; extern bool opt_prof_active; extern bool opt_prof_thread_active_init; extern size_t opt_lg_prof_sample; /* Mean bytes between samples. */ extern ssize_t opt_lg_prof_interval; /* lg(prof_interval). */ extern bool opt_prof_gdump; /* High-water memory dumping. */ extern bool opt_prof_final; /* Final profile dumping. */ extern bool opt_prof_leak; /* Dump leak summary at exit. */ extern bool opt_prof_leak_error; /* Exit with error code if memory leaked */ extern bool opt_prof_accum; /* Report cumulative bytes. */ extern bool opt_prof_log; /* Turn logging on at boot. */ extern char opt_prof_prefix[ /* Minimize memory bloat for non-prof builds. */ #ifdef JEMALLOC_PROF PATH_MAX + #endif 1]; extern bool opt_prof_unbias; /* For recording recent allocations */ extern ssize_t opt_prof_recent_alloc_max; /* Whether to use thread name provided by the system or by mallctl. */ extern bool opt_prof_sys_thread_name; /* Whether to record per size class counts and request size totals. */ extern bool opt_prof_stats; /* Accessed via prof_active_[gs]et{_unlocked,}(). */ extern bool prof_active_state; /* Accessed via prof_gdump_[gs]et{_unlocked,}(). */ extern bool prof_gdump_val; /* Profile dump interval, measured in bytes allocated. */ extern uint64_t prof_interval; /* * Initialized as opt_lg_prof_sample, and potentially modified during profiling * resets. */ extern size_t lg_prof_sample; extern bool prof_booted; void prof_backtrace_hook_set(prof_backtrace_hook_t hook); prof_backtrace_hook_t prof_backtrace_hook_get(); void prof_dump_hook_set(prof_dump_hook_t hook); prof_dump_hook_t prof_dump_hook_get(); /* Functions only accessed in prof_inlines.h */ prof_tdata_t *prof_tdata_init(tsd_t *tsd); prof_tdata_t *prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata); void prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx); void prof_malloc_sample_object(tsd_t *tsd, const void *ptr, size_t size, size_t usize, prof_tctx_t *tctx); void prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_info_t *prof_info); prof_tctx_t *prof_tctx_create(tsd_t *tsd); void prof_idump(tsdn_t *tsdn); bool prof_mdump(tsd_t *tsd, const char *filename); void prof_gdump(tsdn_t *tsdn); void prof_tdata_cleanup(tsd_t *tsd); bool prof_active_get(tsdn_t *tsdn); bool prof_active_set(tsdn_t *tsdn, bool active); const char *prof_thread_name_get(tsd_t *tsd); int prof_thread_name_set(tsd_t *tsd, const char *thread_name); bool prof_thread_active_get(tsd_t *tsd); bool prof_thread_active_set(tsd_t *tsd, bool active); bool prof_thread_active_init_get(tsdn_t *tsdn); bool prof_thread_active_init_set(tsdn_t *tsdn, bool active_init); bool prof_gdump_get(tsdn_t *tsdn); bool prof_gdump_set(tsdn_t *tsdn, bool active); void prof_boot0(void); void prof_boot1(void); bool prof_boot2(tsd_t *tsd, base_t *base); void prof_prefork0(tsdn_t *tsdn); void prof_prefork1(tsdn_t *tsdn); void prof_postfork_parent(tsdn_t *tsdn); void prof_postfork_child(tsdn_t *tsdn); /* Only accessed by thread event. */ uint64_t prof_sample_new_event_wait(tsd_t *tsd); uint64_t prof_sample_postponed_event_wait(tsd_t *tsd); void prof_sample_event_handler(tsd_t *tsd, uint64_t elapsed); #endif /* JEMALLOC_INTERNAL_PROF_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_hook.h000066400000000000000000000013221501533116600245330ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_HOOK_H #define JEMALLOC_INTERNAL_PROF_HOOK_H /* * The hooks types of which are declared in this file are experimental and * undocumented, thus the typedefs are located in an 'internal' header. */ /* * A hook to mock out backtrace functionality. This can be handy, since it's * otherwise difficult to guarantee that two allocations are reported as coming * from the exact same stack trace in the presence of an optimizing compiler. */ typedef void (*prof_backtrace_hook_t)(void **, unsigned *, unsigned); /* * A callback hook that notifies about recently dumped heap profile. */ typedef void (*prof_dump_hook_t)(const char *filename); #endif /* JEMALLOC_INTERNAL_PROF_HOOK_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_inlines.h000066400000000000000000000160621501533116600252430ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_INLINES_H #define JEMALLOC_INTERNAL_PROF_INLINES_H #include "jemalloc/internal/safety_check.h" #include "jemalloc/internal/sz.h" #include "jemalloc/internal/thread_event.h" JEMALLOC_ALWAYS_INLINE void prof_active_assert() { cassert(config_prof); /* * If opt_prof is off, then prof_active must always be off, regardless * of whether prof_active_mtx is in effect or not. */ assert(opt_prof || !prof_active_state); } JEMALLOC_ALWAYS_INLINE bool prof_active_get_unlocked(void) { prof_active_assert(); /* * Even if opt_prof is true, sampling can be temporarily disabled by * setting prof_active to false. No locking is used when reading * prof_active in the fast path, so there are no guarantees regarding * how long it will take for all threads to notice state changes. */ return prof_active_state; } JEMALLOC_ALWAYS_INLINE bool prof_gdump_get_unlocked(void) { /* * No locking is used when reading prof_gdump_val in the fast path, so * there are no guarantees regarding how long it will take for all * threads to notice state changes. */ return prof_gdump_val; } JEMALLOC_ALWAYS_INLINE prof_tdata_t * prof_tdata_get(tsd_t *tsd, bool create) { prof_tdata_t *tdata; cassert(config_prof); tdata = tsd_prof_tdata_get(tsd); if (create) { assert(tsd_reentrancy_level_get(tsd) == 0); if (unlikely(tdata == NULL)) { if (tsd_nominal(tsd)) { tdata = prof_tdata_init(tsd); tsd_prof_tdata_set(tsd, tdata); } } else if (unlikely(tdata->expired)) { tdata = prof_tdata_reinit(tsd, tdata); tsd_prof_tdata_set(tsd, tdata); } assert(tdata == NULL || tdata->attached); } return tdata; } JEMALLOC_ALWAYS_INLINE void prof_info_get(tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx, prof_info_t *prof_info) { cassert(config_prof); assert(ptr != NULL); assert(prof_info != NULL); arena_prof_info_get(tsd, ptr, alloc_ctx, prof_info, false); } JEMALLOC_ALWAYS_INLINE void prof_info_get_and_reset_recent(tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx, prof_info_t *prof_info) { cassert(config_prof); assert(ptr != NULL); assert(prof_info != NULL); arena_prof_info_get(tsd, ptr, alloc_ctx, prof_info, true); } JEMALLOC_ALWAYS_INLINE void prof_tctx_reset(tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx) { cassert(config_prof); assert(ptr != NULL); arena_prof_tctx_reset(tsd, ptr, alloc_ctx); } JEMALLOC_ALWAYS_INLINE void prof_tctx_reset_sampled(tsd_t *tsd, const void *ptr) { cassert(config_prof); assert(ptr != NULL); arena_prof_tctx_reset_sampled(tsd, ptr); } JEMALLOC_ALWAYS_INLINE void prof_info_set(tsd_t *tsd, edata_t *edata, prof_tctx_t *tctx, size_t size) { cassert(config_prof); assert(edata != NULL); assert((uintptr_t)tctx > (uintptr_t)1U); arena_prof_info_set(tsd, edata, tctx, size); } JEMALLOC_ALWAYS_INLINE bool prof_sample_should_skip(tsd_t *tsd, bool sample_event) { cassert(config_prof); /* Fastpath: no need to load tdata */ if (likely(!sample_event)) { return true; } /* * sample_event is always obtained from the thread event module, and * whenever it's true, it means that the thread event module has * already checked the reentrancy level. */ assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t *tdata = prof_tdata_get(tsd, true); if (unlikely(tdata == NULL)) { return true; } return !tdata->active; } JEMALLOC_ALWAYS_INLINE prof_tctx_t * prof_alloc_prep(tsd_t *tsd, bool prof_active, bool sample_event) { prof_tctx_t *ret; if (!prof_active || likely(prof_sample_should_skip(tsd, sample_event))) { ret = (prof_tctx_t *)(uintptr_t)1U; } else { ret = prof_tctx_create(tsd); } return ret; } JEMALLOC_ALWAYS_INLINE void prof_malloc(tsd_t *tsd, const void *ptr, size_t size, size_t usize, emap_alloc_ctx_t *alloc_ctx, prof_tctx_t *tctx) { cassert(config_prof); assert(ptr != NULL); assert(usize == isalloc(tsd_tsdn(tsd), ptr)); if (unlikely((uintptr_t)tctx > (uintptr_t)1U)) { prof_malloc_sample_object(tsd, ptr, size, usize, tctx); } else { prof_tctx_reset(tsd, ptr, alloc_ctx); } } JEMALLOC_ALWAYS_INLINE void prof_realloc(tsd_t *tsd, const void *ptr, size_t size, size_t usize, prof_tctx_t *tctx, bool prof_active, const void *old_ptr, size_t old_usize, prof_info_t *old_prof_info, bool sample_event) { bool sampled, old_sampled, moved; cassert(config_prof); assert(ptr != NULL || (uintptr_t)tctx <= (uintptr_t)1U); if (prof_active && ptr != NULL) { assert(usize == isalloc(tsd_tsdn(tsd), ptr)); if (prof_sample_should_skip(tsd, sample_event)) { /* * Don't sample. The usize passed to prof_alloc_prep() * was larger than what actually got allocated, so a * backtrace was captured for this allocation, even * though its actual usize was insufficient to cross the * sample threshold. */ prof_alloc_rollback(tsd, tctx); tctx = (prof_tctx_t *)(uintptr_t)1U; } } sampled = ((uintptr_t)tctx > (uintptr_t)1U); old_sampled = ((uintptr_t)old_prof_info->alloc_tctx > (uintptr_t)1U); moved = (ptr != old_ptr); if (unlikely(sampled)) { prof_malloc_sample_object(tsd, ptr, size, usize, tctx); } else if (moved) { prof_tctx_reset(tsd, ptr, NULL); } else if (unlikely(old_sampled)) { /* * prof_tctx_reset() would work for the !moved case as well, * but prof_tctx_reset_sampled() is slightly cheaper, and the * proper thing to do here in the presence of explicit * knowledge re: moved state. */ prof_tctx_reset_sampled(tsd, ptr); } else { prof_info_t prof_info; prof_info_get(tsd, ptr, NULL, &prof_info); assert((uintptr_t)prof_info.alloc_tctx == (uintptr_t)1U); } /* * The prof_free_sampled_object() call must come after the * prof_malloc_sample_object() call, because tctx and old_tctx may be * the same, in which case reversing the call order could cause the tctx * to be prematurely destroyed as a side effect of momentarily zeroed * counters. */ if (unlikely(old_sampled)) { prof_free_sampled_object(tsd, old_usize, old_prof_info); } } JEMALLOC_ALWAYS_INLINE size_t prof_sample_align(size_t orig_align) { /* * Enforce page alignment, so that sampled allocations can be identified * w/o metadata lookup. */ assert(opt_prof); return (opt_cache_oblivious && orig_align < PAGE) ? PAGE : orig_align; } JEMALLOC_ALWAYS_INLINE bool prof_sample_aligned(const void *ptr) { return ((uintptr_t)ptr & PAGE_MASK) == 0; } JEMALLOC_ALWAYS_INLINE bool prof_sampled(tsd_t *tsd, const void *ptr) { prof_info_t prof_info; prof_info_get(tsd, ptr, NULL, &prof_info); bool sampled = (uintptr_t)prof_info.alloc_tctx > (uintptr_t)1U; if (sampled) { assert(prof_sample_aligned(ptr)); } return sampled; } JEMALLOC_ALWAYS_INLINE void prof_free(tsd_t *tsd, const void *ptr, size_t usize, emap_alloc_ctx_t *alloc_ctx) { prof_info_t prof_info; prof_info_get_and_reset_recent(tsd, ptr, alloc_ctx, &prof_info); cassert(config_prof); assert(usize == isalloc(tsd_tsdn(tsd), ptr)); if (unlikely((uintptr_t)prof_info.alloc_tctx > (uintptr_t)1U)) { assert(prof_sample_aligned(ptr)); prof_free_sampled_object(tsd, usize, &prof_info); } } #endif /* JEMALLOC_INTERNAL_PROF_INLINES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_log.h000066400000000000000000000011451501533116600243570ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_LOG_H #define JEMALLOC_INTERNAL_PROF_LOG_H #include "jemalloc/internal/mutex.h" extern malloc_mutex_t log_mtx; void prof_try_log(tsd_t *tsd, size_t usize, prof_info_t *prof_info); bool prof_log_init(tsd_t *tsdn); /* Used in unit tests. */ size_t prof_log_bt_count(void); size_t prof_log_alloc_count(void); size_t prof_log_thr_count(void); bool prof_log_is_logging(void); bool prof_log_rep_check(void); void prof_log_dummy_set(bool new_value); bool prof_log_start(tsdn_t *tsdn, const char *filename); bool prof_log_stop(tsdn_t *tsdn); #endif /* JEMALLOC_INTERNAL_PROF_LOG_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_recent.h000066400000000000000000000017061501533116600250610ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_RECENT_H #define JEMALLOC_INTERNAL_PROF_RECENT_H extern malloc_mutex_t prof_recent_alloc_mtx; extern malloc_mutex_t prof_recent_dump_mtx; bool prof_recent_alloc_prepare(tsd_t *tsd, prof_tctx_t *tctx); void prof_recent_alloc(tsd_t *tsd, edata_t *edata, size_t size, size_t usize); void prof_recent_alloc_reset(tsd_t *tsd, edata_t *edata); bool prof_recent_init(); void edata_prof_recent_alloc_init(edata_t *edata); /* Used in unit tests. */ typedef ql_head(prof_recent_t) prof_recent_list_t; extern prof_recent_list_t prof_recent_alloc_list; edata_t *prof_recent_alloc_edata_get_no_lock_test(const prof_recent_t *node); prof_recent_t *edata_prof_recent_alloc_get_no_lock_test(const edata_t *edata); ssize_t prof_recent_alloc_max_ctl_read(); ssize_t prof_recent_alloc_max_ctl_write(tsd_t *tsd, ssize_t max); void prof_recent_alloc_dump(tsd_t *tsd, write_cb_t *write_cb, void *cbopaque); #endif /* JEMALLOC_INTERNAL_PROF_RECENT_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_stats.h000066400000000000000000000010221501533116600247260ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_STATS_H #define JEMALLOC_INTERNAL_PROF_STATS_H typedef struct prof_stats_s prof_stats_t; struct prof_stats_s { uint64_t req_sum; uint64_t count; }; extern malloc_mutex_t prof_stats_mtx; void prof_stats_inc(tsd_t *tsd, szind_t ind, size_t size); void prof_stats_dec(tsd_t *tsd, szind_t ind, size_t size); void prof_stats_get_live(tsd_t *tsd, szind_t ind, prof_stats_t *stats); void prof_stats_get_accum(tsd_t *tsd, szind_t ind, prof_stats_t *stats); #endif /* JEMALLOC_INTERNAL_PROF_STATS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_structs.h000066400000000000000000000132631501533116600253110ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_STRUCTS_H #define JEMALLOC_INTERNAL_PROF_STRUCTS_H #include "jemalloc/internal/ckh.h" #include "jemalloc/internal/edata.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/prng.h" #include "jemalloc/internal/rb.h" struct prof_bt_s { /* Backtrace, stored as len program counters. */ void **vec; unsigned len; }; #ifdef JEMALLOC_PROF_LIBGCC /* Data structure passed to libgcc _Unwind_Backtrace() callback functions. */ typedef struct { void **vec; unsigned *len; unsigned max; } prof_unwind_data_t; #endif struct prof_cnt_s { /* Profiling counters. */ uint64_t curobjs; uint64_t curobjs_shifted_unbiased; uint64_t curbytes; uint64_t curbytes_unbiased; uint64_t accumobjs; uint64_t accumobjs_shifted_unbiased; uint64_t accumbytes; uint64_t accumbytes_unbiased; }; typedef enum { prof_tctx_state_initializing, prof_tctx_state_nominal, prof_tctx_state_dumping, prof_tctx_state_purgatory /* Dumper must finish destroying. */ } prof_tctx_state_t; struct prof_tctx_s { /* Thread data for thread that performed the allocation. */ prof_tdata_t *tdata; /* * Copy of tdata->thr_{uid,discrim}, necessary because tdata may be * defunct during teardown. */ uint64_t thr_uid; uint64_t thr_discrim; /* * Reference count of how many times this tctx object is referenced in * recent allocation / deallocation records, protected by tdata->lock. */ uint64_t recent_count; /* Profiling counters, protected by tdata->lock. */ prof_cnt_t cnts; /* Associated global context. */ prof_gctx_t *gctx; /* * UID that distinguishes multiple tctx's created by the same thread, * but coexisting in gctx->tctxs. There are two ways that such * coexistence can occur: * - A dumper thread can cause a tctx to be retained in the purgatory * state. * - Although a single "producer" thread must create all tctx's which * share the same thr_uid, multiple "consumers" can each concurrently * execute portions of prof_tctx_destroy(). prof_tctx_destroy() only * gets called once each time cnts.cur{objs,bytes} drop to 0, but this * threshold can be hit again before the first consumer finishes * executing prof_tctx_destroy(). */ uint64_t tctx_uid; /* Linkage into gctx's tctxs. */ rb_node(prof_tctx_t) tctx_link; /* * True during prof_alloc_prep()..prof_malloc_sample_object(), prevents * sample vs destroy race. */ bool prepared; /* Current dump-related state, protected by gctx->lock. */ prof_tctx_state_t state; /* * Copy of cnts snapshotted during early dump phase, protected by * dump_mtx. */ prof_cnt_t dump_cnts; }; typedef rb_tree(prof_tctx_t) prof_tctx_tree_t; struct prof_info_s { /* Time when the allocation was made. */ nstime_t alloc_time; /* Points to the prof_tctx_t corresponding to the allocation. */ prof_tctx_t *alloc_tctx; /* Allocation request size. */ size_t alloc_size; }; struct prof_gctx_s { /* Protects nlimbo, cnt_summed, and tctxs. */ malloc_mutex_t *lock; /* * Number of threads that currently cause this gctx to be in a state of * limbo due to one of: * - Initializing this gctx. * - Initializing per thread counters associated with this gctx. * - Preparing to destroy this gctx. * - Dumping a heap profile that includes this gctx. * nlimbo must be 1 (single destroyer) in order to safely destroy the * gctx. */ unsigned nlimbo; /* * Tree of profile counters, one for each thread that has allocated in * this context. */ prof_tctx_tree_t tctxs; /* Linkage for tree of contexts to be dumped. */ rb_node(prof_gctx_t) dump_link; /* Temporary storage for summation during dump. */ prof_cnt_t cnt_summed; /* Associated backtrace. */ prof_bt_t bt; /* Backtrace vector, variable size, referred to by bt. */ void *vec[1]; }; typedef rb_tree(prof_gctx_t) prof_gctx_tree_t; struct prof_tdata_s { malloc_mutex_t *lock; /* Monotonically increasing unique thread identifier. */ uint64_t thr_uid; /* * Monotonically increasing discriminator among tdata structures * associated with the same thr_uid. */ uint64_t thr_discrim; /* Included in heap profile dumps if non-NULL. */ char *thread_name; bool attached; bool expired; rb_node(prof_tdata_t) tdata_link; /* * Counter used to initialize prof_tctx_t's tctx_uid. No locking is * necessary when incrementing this field, because only one thread ever * does so. */ uint64_t tctx_uid_next; /* * Hash of (prof_bt_t *)-->(prof_tctx_t *). Each thread tracks * backtraces for which it has non-zero allocation/deallocation counters * associated with thread-specific prof_tctx_t objects. Other threads * may write to prof_tctx_t contents when freeing associated objects. */ ckh_t bt2tctx; /* State used to avoid dumping while operating on prof internals. */ bool enq; bool enq_idump; bool enq_gdump; /* * Set to true during an early dump phase for tdata's which are * currently being dumped. New threads' tdata's have this initialized * to false so that they aren't accidentally included in later dump * phases. */ bool dumping; /* * True if profiling is active for this tdata's thread * (thread.prof.active mallctl). */ bool active; /* Temporary storage for summation during dump. */ prof_cnt_t cnt_summed; /* Backtrace vector, used for calls to prof_backtrace(). */ void *vec[PROF_BT_MAX]; }; typedef rb_tree(prof_tdata_t) prof_tdata_tree_t; struct prof_recent_s { nstime_t alloc_time; nstime_t dalloc_time; ql_elm(prof_recent_t) link; size_t size; size_t usize; atomic_p_t alloc_edata; /* NULL means allocation has been freed. */ prof_tctx_t *alloc_tctx; prof_tctx_t *dalloc_tctx; }; #endif /* JEMALLOC_INTERNAL_PROF_STRUCTS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_sys.h000066400000000000000000000022741501533116600244200ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_SYS_H #define JEMALLOC_INTERNAL_PROF_SYS_H extern malloc_mutex_t prof_dump_filename_mtx; extern base_t *prof_base; void bt_init(prof_bt_t *bt, void **vec); void prof_backtrace(tsd_t *tsd, prof_bt_t *bt); void prof_hooks_init(); void prof_unwind_init(); void prof_sys_thread_name_fetch(tsd_t *tsd); int prof_getpid(void); void prof_get_default_filename(tsdn_t *tsdn, char *filename, uint64_t ind); bool prof_prefix_set(tsdn_t *tsdn, const char *prefix); void prof_fdump_impl(tsd_t *tsd); void prof_idump_impl(tsd_t *tsd); bool prof_mdump_impl(tsd_t *tsd, const char *filename); void prof_gdump_impl(tsd_t *tsd); /* Used in unit tests. */ typedef int (prof_sys_thread_name_read_t)(char *buf, size_t limit); extern prof_sys_thread_name_read_t *JET_MUTABLE prof_sys_thread_name_read; typedef int (prof_dump_open_file_t)(const char *, int); extern prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file; typedef ssize_t (prof_dump_write_file_t)(int, const void *, size_t); extern prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file; typedef int (prof_dump_open_maps_t)(); extern prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps; #endif /* JEMALLOC_INTERNAL_PROF_SYS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/prof_types.h000066400000000000000000000042171501533116600247450ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PROF_TYPES_H #define JEMALLOC_INTERNAL_PROF_TYPES_H typedef struct prof_bt_s prof_bt_t; typedef struct prof_cnt_s prof_cnt_t; typedef struct prof_tctx_s prof_tctx_t; typedef struct prof_info_s prof_info_t; typedef struct prof_gctx_s prof_gctx_t; typedef struct prof_tdata_s prof_tdata_t; typedef struct prof_recent_s prof_recent_t; /* Option defaults. */ #ifdef JEMALLOC_PROF # define PROF_PREFIX_DEFAULT "jeprof" #else # define PROF_PREFIX_DEFAULT "" #endif #define LG_PROF_SAMPLE_DEFAULT 19 #define LG_PROF_INTERVAL_DEFAULT -1 /* * Hard limit on stack backtrace depth. The version of prof_backtrace() that * is based on __builtin_return_address() necessarily has a hard-coded number * of backtrace frame handlers, and should be kept in sync with this setting. */ #define PROF_BT_MAX 128 /* Initial hash table size. */ #define PROF_CKH_MINITEMS 64 /* Size of memory buffer to use when writing dump files. */ #ifndef JEMALLOC_PROF /* Minimize memory bloat for non-prof builds. */ # define PROF_DUMP_BUFSIZE 1 #elif defined(JEMALLOC_DEBUG) /* Use a small buffer size in debug build, mainly to facilitate testing. */ # define PROF_DUMP_BUFSIZE 16 #else # define PROF_DUMP_BUFSIZE 65536 #endif /* Size of size class related tables */ #ifdef JEMALLOC_PROF # define PROF_SC_NSIZES SC_NSIZES #else /* Minimize memory bloat for non-prof builds. */ # define PROF_SC_NSIZES 1 #endif /* Size of stack-allocated buffer used by prof_printf(). */ #define PROF_PRINTF_BUFSIZE 128 /* * Number of mutexes shared among all gctx's. No space is allocated for these * unless profiling is enabled, so it's okay to over-provision. */ #define PROF_NCTX_LOCKS 1024 /* * Number of mutexes shared among all tdata's. No space is allocated for these * unless profiling is enabled, so it's okay to over-provision. */ #define PROF_NTDATA_LOCKS 256 /* Minimize memory bloat for non-prof builds. */ #ifdef JEMALLOC_PROF #define PROF_DUMP_FILENAME_LEN (PATH_MAX + 1) #else #define PROF_DUMP_FILENAME_LEN 1 #endif /* Default number of recent allocations to record. */ #define PROF_RECENT_ALLOC_MAX_DEFAULT 0 #endif /* JEMALLOC_INTERNAL_PROF_TYPES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/psset.h000066400000000000000000000102341501533116600237050ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_PSSET_H #define JEMALLOC_INTERNAL_PSSET_H #include "jemalloc/internal/hpdata.h" /* * A page-slab set. What the eset is to PAC, the psset is to HPA. It maintains * a collection of page-slabs (the intent being that they are backed by * hugepages, or at least could be), and handles allocation and deallocation * requests. */ /* * One more than the maximum pszind_t we will serve out of the HPA. * Practically, we expect only the first few to be actually used. This * corresponds to a maximum size of of 512MB on systems with 4k pages and * SC_NGROUP == 4, which is already an unreasonably large maximum. Morally, you * can think of this as being SC_NPSIZES, but there's no sense in wasting that * much space in the arena, making bitmaps that much larger, etc. */ #define PSSET_NPSIZES 64 /* * We keep two purge lists per page size class; one for hugified hpdatas (at * index 2*pszind), and one for the non-hugified hpdatas (at index 2*pszind + * 1). This lets us implement a preference for purging non-hugified hpdatas * among similarly-dirty ones. * We reserve the last two indices for empty slabs, in that case purging * hugified ones (which are definitionally all waste) before non-hugified ones * (i.e. reversing the order). */ #define PSSET_NPURGE_LISTS (2 * PSSET_NPSIZES) typedef struct psset_bin_stats_s psset_bin_stats_t; struct psset_bin_stats_s { /* How many pageslabs are in this bin? */ size_t npageslabs; /* Of them, how many pages are active? */ size_t nactive; /* And how many are dirty? */ size_t ndirty; }; typedef struct psset_stats_s psset_stats_t; struct psset_stats_s { /* * The second index is huge stats; nonfull_slabs[pszind][0] contains * stats for the non-huge slabs in bucket pszind, while * nonfull_slabs[pszind][1] contains stats for the huge slabs. */ psset_bin_stats_t nonfull_slabs[PSSET_NPSIZES][2]; /* * Full slabs don't live in any edata heap, but we still track their * stats. */ psset_bin_stats_t full_slabs[2]; /* Empty slabs are similar. */ psset_bin_stats_t empty_slabs[2]; }; typedef struct psset_s psset_t; struct psset_s { /* * The pageslabs, quantized by the size class of the largest contiguous * free run of pages in a pageslab. */ hpdata_age_heap_t pageslabs[PSSET_NPSIZES]; /* Bitmap for which set bits correspond to non-empty heaps. */ fb_group_t pageslab_bitmap[FB_NGROUPS(PSSET_NPSIZES)]; /* * The sum of all bin stats in stats. This lets us quickly answer * queries for the number of dirty, active, and retained pages in the * entire set. */ psset_bin_stats_t merged_stats; psset_stats_t stats; /* * Slabs with no active allocations, but which are allowed to serve new * allocations. */ hpdata_empty_list_t empty; /* * Slabs which are available to be purged, ordered by how much we want * to purge them (with later indices indicating slabs we want to purge * more). */ hpdata_purge_list_t to_purge[PSSET_NPURGE_LISTS]; /* Bitmap for which set bits correspond to non-empty purge lists. */ fb_group_t purge_bitmap[FB_NGROUPS(PSSET_NPURGE_LISTS)]; /* Slabs which are available to be hugified. */ hpdata_hugify_list_t to_hugify; }; void psset_init(psset_t *psset); void psset_stats_accum(psset_stats_t *dst, psset_stats_t *src); /* * Begin or end updating the given pageslab's metadata. While the pageslab is * being updated, it won't be returned from psset_fit calls. */ void psset_update_begin(psset_t *psset, hpdata_t *ps); void psset_update_end(psset_t *psset, hpdata_t *ps); /* Analogous to the eset_fit; pick a hpdata to serve the request. */ hpdata_t *psset_pick_alloc(psset_t *psset, size_t size); /* Pick one to purge. */ hpdata_t *psset_pick_purge(psset_t *psset); /* Pick one to hugify. */ hpdata_t *psset_pick_hugify(psset_t *psset); void psset_insert(psset_t *psset, hpdata_t *ps); void psset_remove(psset_t *psset, hpdata_t *ps); static inline size_t psset_npageslabs(psset_t *psset) { return psset->merged_stats.npageslabs; } static inline size_t psset_nactive(psset_t *psset) { return psset->merged_stats.nactive; } static inline size_t psset_ndirty(psset_t *psset) { return psset->merged_stats.ndirty; } #endif /* JEMALLOC_INTERNAL_PSSET_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/public_namespace.sh000077500000000000000000000002011501533116600262200ustar00rootroot00000000000000#!/bin/sh for nm in `cat $1` ; do n=`echo ${nm} |tr ':' ' ' |awk '{print $1}'` echo "#define je_${n} JEMALLOC_N(${n})" done redis-8.0.2/deps/jemalloc/include/jemalloc/internal/public_unnamespace.sh000077500000000000000000000001571501533116600265750ustar00rootroot00000000000000#!/bin/sh for nm in `cat $1` ; do n=`echo ${nm} |tr ':' ' ' |awk '{print $1}'` echo "#undef je_${n}" done redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ql.h000066400000000000000000000132301501533116600231620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_QL_H #define JEMALLOC_INTERNAL_QL_H #include "jemalloc/internal/qr.h" /* * A linked-list implementation. * * This is built on top of the ring implementation, but that can be viewed as an * implementation detail (i.e. trying to advance past the tail of the list * doesn't wrap around). * * You define a struct like so: * typedef strucy my_s my_t; * struct my_s { * int data; * ql_elm(my_t) my_link; * }; * * // We wobble between "list" and "head" for this type; we're now mostly * // heading towards "list". * typedef ql_head(my_t) my_list_t; * * You then pass a my_list_t * for a_head arguments, a my_t * for a_elm * arguments, the token "my_link" for a_field arguments, and the token "my_t" * for a_type arguments. */ /* List definitions. */ #define ql_head(a_type) \ struct { \ a_type *qlh_first; \ } /* Static initializer for an empty list. */ #define ql_head_initializer(a_head) {NULL} /* The field definition. */ #define ql_elm(a_type) qr(a_type) /* A pointer to the first element in the list, or NULL if the list is empty. */ #define ql_first(a_head) ((a_head)->qlh_first) /* Dynamically initializes a list. */ #define ql_new(a_head) do { \ ql_first(a_head) = NULL; \ } while (0) /* * Sets dest to be the contents of src (overwriting any elements there), leaving * src empty. */ #define ql_move(a_head_dest, a_head_src) do { \ ql_first(a_head_dest) = ql_first(a_head_src); \ ql_new(a_head_src); \ } while (0) /* True if the list is empty, otherwise false. */ #define ql_empty(a_head) (ql_first(a_head) == NULL) /* * Initializes a ql_elm. Must be called even if the field is about to be * overwritten. */ #define ql_elm_new(a_elm, a_field) qr_new((a_elm), a_field) /* * Obtains the last item in the list. */ #define ql_last(a_head, a_field) \ (ql_empty(a_head) ? NULL : qr_prev(ql_first(a_head), a_field)) /* * Gets a pointer to the next/prev element in the list. Trying to advance past * the end or retreat before the beginning of the list returns NULL. */ #define ql_next(a_head, a_elm, a_field) \ ((ql_last(a_head, a_field) != (a_elm)) \ ? qr_next((a_elm), a_field) : NULL) #define ql_prev(a_head, a_elm, a_field) \ ((ql_first(a_head) != (a_elm)) ? qr_prev((a_elm), a_field) \ : NULL) /* Inserts a_elm before a_qlelm in the list. */ #define ql_before_insert(a_head, a_qlelm, a_elm, a_field) do { \ qr_before_insert((a_qlelm), (a_elm), a_field); \ if (ql_first(a_head) == (a_qlelm)) { \ ql_first(a_head) = (a_elm); \ } \ } while (0) /* Inserts a_elm after a_qlelm in the list. */ #define ql_after_insert(a_qlelm, a_elm, a_field) \ qr_after_insert((a_qlelm), (a_elm), a_field) /* Inserts a_elm as the first item in the list. */ #define ql_head_insert(a_head, a_elm, a_field) do { \ if (!ql_empty(a_head)) { \ qr_before_insert(ql_first(a_head), (a_elm), a_field); \ } \ ql_first(a_head) = (a_elm); \ } while (0) /* Inserts a_elm as the last item in the list. */ #define ql_tail_insert(a_head, a_elm, a_field) do { \ if (!ql_empty(a_head)) { \ qr_before_insert(ql_first(a_head), (a_elm), a_field); \ } \ ql_first(a_head) = qr_next((a_elm), a_field); \ } while (0) /* * Given lists a = [a_1, ..., a_n] and [b_1, ..., b_n], results in: * a = [a1, ..., a_n, b_1, ..., b_n] and b = []. */ #define ql_concat(a_head_a, a_head_b, a_field) do { \ if (ql_empty(a_head_a)) { \ ql_move(a_head_a, a_head_b); \ } else if (!ql_empty(a_head_b)) { \ qr_meld(ql_first(a_head_a), ql_first(a_head_b), \ a_field); \ ql_new(a_head_b); \ } \ } while (0) /* Removes a_elm from the list. */ #define ql_remove(a_head, a_elm, a_field) do { \ if (ql_first(a_head) == (a_elm)) { \ ql_first(a_head) = qr_next(ql_first(a_head), a_field); \ } \ if (ql_first(a_head) != (a_elm)) { \ qr_remove((a_elm), a_field); \ } else { \ ql_new(a_head); \ } \ } while (0) /* Removes the first item in the list. */ #define ql_head_remove(a_head, a_type, a_field) do { \ a_type *t = ql_first(a_head); \ ql_remove((a_head), t, a_field); \ } while (0) /* Removes the last item in the list. */ #define ql_tail_remove(a_head, a_type, a_field) do { \ a_type *t = ql_last(a_head, a_field); \ ql_remove((a_head), t, a_field); \ } while (0) /* * Given a = [a_1, a_2, ..., a_n-1, a_n, a_n+1, ...], * ql_split(a, a_n, b, some_field) results in * a = [a_1, a_2, ..., a_n-1] * and replaces b's contents with: * b = [a_n, a_n+1, ...] */ #define ql_split(a_head_a, a_elm, a_head_b, a_field) do { \ if (ql_first(a_head_a) == (a_elm)) { \ ql_move(a_head_b, a_head_a); \ } else { \ qr_split(ql_first(a_head_a), (a_elm), a_field); \ ql_first(a_head_b) = (a_elm); \ } \ } while (0) /* * An optimized version of: * a_type *t = ql_first(a_head); * ql_remove((a_head), t, a_field); * ql_tail_insert((a_head), t, a_field); */ #define ql_rotate(a_head, a_field) do { \ ql_first(a_head) = qr_next(ql_first(a_head), a_field); \ } while (0) /* * Helper macro to iterate over each element in a list in order, starting from * the head (or in reverse order, starting from the tail). The usage is * (assuming my_t and my_list_t defined as above). * * int sum(my_list_t *list) { * int sum = 0; * my_t *iter; * ql_foreach(iter, list, link) { * sum += iter->data; * } * return sum; * } */ #define ql_foreach(a_var, a_head, a_field) \ qr_foreach((a_var), ql_first(a_head), a_field) #define ql_reverse_foreach(a_var, a_head, a_field) \ qr_reverse_foreach((a_var), ql_first(a_head), a_field) #endif /* JEMALLOC_INTERNAL_QL_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/qr.h000066400000000000000000000101471501533116600231740ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_QR_H #define JEMALLOC_INTERNAL_QR_H /* * A ring implementation based on an embedded circular doubly-linked list. * * You define your struct like so: * * typedef struct my_s my_t; * struct my_s { * int data; * qr(my_t) my_link; * }; * * And then pass a my_t * into macros for a_qr arguments, and the token * "my_link" into a_field fields. */ /* Ring definitions. */ #define qr(a_type) \ struct { \ a_type *qre_next; \ a_type *qre_prev; \ } /* * Initialize a qr link. Every link must be initialized before being used, even * if that initialization is going to be immediately overwritten (say, by being * passed into an insertion macro). */ #define qr_new(a_qr, a_field) do { \ (a_qr)->a_field.qre_next = (a_qr); \ (a_qr)->a_field.qre_prev = (a_qr); \ } while (0) /* * Go forwards or backwards in the ring. Note that (the ring being circular), this * always succeeds -- you just keep looping around and around the ring if you * chase pointers without end. */ #define qr_next(a_qr, a_field) ((a_qr)->a_field.qre_next) #define qr_prev(a_qr, a_field) ((a_qr)->a_field.qre_prev) /* * Given two rings: * a -> a_1 -> ... -> a_n -- * ^ | * |------------------------ * * b -> b_1 -> ... -> b_n -- * ^ | * |------------------------ * * Results in the ring: * a -> a_1 -> ... -> a_n -> b -> b_1 -> ... -> b_n -- * ^ | * |-------------------------------------------------| * * a_qr_a can directly be a qr_next() macro, but a_qr_b cannot. */ #define qr_meld(a_qr_a, a_qr_b, a_field) do { \ (a_qr_b)->a_field.qre_prev->a_field.qre_next = \ (a_qr_a)->a_field.qre_prev; \ (a_qr_a)->a_field.qre_prev = (a_qr_b)->a_field.qre_prev; \ (a_qr_b)->a_field.qre_prev = \ (a_qr_b)->a_field.qre_prev->a_field.qre_next; \ (a_qr_a)->a_field.qre_prev->a_field.qre_next = (a_qr_a); \ (a_qr_b)->a_field.qre_prev->a_field.qre_next = (a_qr_b); \ } while (0) /* * Logically, this is just a meld. The intent, though, is that a_qrelm is a * single-element ring, so that "before" has a more obvious interpretation than * meld. */ #define qr_before_insert(a_qrelm, a_qr, a_field) \ qr_meld((a_qrelm), (a_qr), a_field) /* Ditto, but inserting after rather than before. */ #define qr_after_insert(a_qrelm, a_qr, a_field) \ qr_before_insert(qr_next(a_qrelm, a_field), (a_qr), a_field) /* * Inverts meld; given the ring: * a -> a_1 -> ... -> a_n -> b -> b_1 -> ... -> b_n -- * ^ | * |-------------------------------------------------| * * Results in two rings: * a -> a_1 -> ... -> a_n -- * ^ | * |------------------------ * * b -> b_1 -> ... -> b_n -- * ^ | * |------------------------ * * qr_meld() and qr_split() are functionally equivalent, so there's no need to * have two copies of the code. */ #define qr_split(a_qr_a, a_qr_b, a_field) \ qr_meld((a_qr_a), (a_qr_b), a_field) /* * Splits off a_qr from the rest of its ring, so that it becomes a * single-element ring. */ #define qr_remove(a_qr, a_field) \ qr_split(qr_next(a_qr, a_field), (a_qr), a_field) /* * Helper macro to iterate over each element in a ring exactly once, starting * with a_qr. The usage is (assuming my_t defined as above): * * int sum(my_t *item) { * int sum = 0; * my_t *iter; * qr_foreach(iter, item, link) { * sum += iter->data; * } * return sum; * } */ #define qr_foreach(var, a_qr, a_field) \ for ((var) = (a_qr); \ (var) != NULL; \ (var) = (((var)->a_field.qre_next != (a_qr)) \ ? (var)->a_field.qre_next : NULL)) /* * The same (and with the same usage) as qr_foreach, but in the opposite order, * ending with a_qr. */ #define qr_reverse_foreach(var, a_qr, a_field) \ for ((var) = ((a_qr) != NULL) ? qr_prev(a_qr, a_field) : NULL; \ (var) != NULL; \ (var) = (((var) != (a_qr)) \ ? (var)->a_field.qre_prev : NULL)) #endif /* JEMALLOC_INTERNAL_QR_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/quantum.h000066400000000000000000000037211501533116600242440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_QUANTUM_H #define JEMALLOC_INTERNAL_QUANTUM_H /* * Minimum allocation alignment is 2^LG_QUANTUM bytes (ignoring tiny size * classes). */ #ifndef LG_QUANTUM # if (defined(__i386__) || defined(_M_IX86)) # define LG_QUANTUM 4 # endif # ifdef __ia64__ # define LG_QUANTUM 4 # endif # ifdef __alpha__ # define LG_QUANTUM 4 # endif # if (defined(__sparc64__) || defined(__sparcv9) || defined(__sparc_v9__)) # define LG_QUANTUM 4 # endif # if (defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)) # define LG_QUANTUM 4 # endif # ifdef __arm__ # define LG_QUANTUM 3 # endif # ifdef __aarch64__ # define LG_QUANTUM 4 # endif # ifdef __hppa__ # define LG_QUANTUM 4 # endif # ifdef __loongarch__ # define LG_QUANTUM 4 # endif # ifdef __m68k__ # define LG_QUANTUM 3 # endif # ifdef __mips__ # if defined(__mips_n32) || defined(__mips_n64) # define LG_QUANTUM 4 # else # define LG_QUANTUM 3 # endif # endif # ifdef __nios2__ # define LG_QUANTUM 3 # endif # ifdef __or1k__ # define LG_QUANTUM 3 # endif # ifdef __powerpc__ # define LG_QUANTUM 4 # endif # if defined(__riscv) || defined(__riscv__) # define LG_QUANTUM 4 # endif # ifdef __s390__ # define LG_QUANTUM 4 # endif # if (defined (__SH3E__) || defined(__SH4_SINGLE__) || defined(__SH4__) || \ defined(__SH4_SINGLE_ONLY__)) # define LG_QUANTUM 4 # endif # ifdef __tile__ # define LG_QUANTUM 4 # endif # ifdef __le32__ # define LG_QUANTUM 4 # endif # ifdef __arc__ # define LG_QUANTUM 3 # endif # ifndef LG_QUANTUM # error "Unknown minimum alignment for architecture; specify via " "--with-lg-quantum" # endif #endif #define QUANTUM ((size_t)(1U << LG_QUANTUM)) #define QUANTUM_MASK (QUANTUM - 1) /* Return the smallest quantum multiple that is >= a. */ #define QUANTUM_CEILING(a) \ (((a) + QUANTUM_MASK) & ~QUANTUM_MASK) #endif /* JEMALLOC_INTERNAL_QUANTUM_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/rb.h000066400000000000000000002211751501533116600231620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_RB_H #define JEMALLOC_INTERNAL_RB_H /*- ******************************************************************************* * * cpp macro implementation of left-leaning 2-3 red-black trees. Parent * pointers are not used, and color bits are stored in the least significant * bit of right-child pointers (if RB_COMPACT is defined), thus making node * linkage as compact as is possible for red-black trees. * * Usage: * * #include * #include * #define NDEBUG // (Optional, see assert(3).) * #include * #define RB_COMPACT // (Optional, embed color bits in right-child pointers.) * #include * ... * ******************************************************************************* */ #ifndef __PGI #define RB_COMPACT #endif /* * Each node in the RB tree consumes at least 1 byte of space (for the linkage * if nothing else, so there are a maximum of sizeof(void *) << 3 rb tree nodes * in any process (and thus, at most sizeof(void *) << 3 nodes in any rb tree). * The choice of algorithm bounds the depth of a tree to twice the binary log of * the number of elements in the tree; the following bound follows. */ #define RB_MAX_DEPTH (sizeof(void *) << 4) #ifdef RB_COMPACT /* Node structure. */ #define rb_node(a_type) \ struct { \ a_type *rbn_left; \ a_type *rbn_right_red; \ } #else #define rb_node(a_type) \ struct { \ a_type *rbn_left; \ a_type *rbn_right; \ bool rbn_red; \ } #endif /* Root structure. */ #define rb_tree(a_type) \ struct { \ a_type *rbt_root; \ } /* Left accessors. */ #define rbtn_left_get(a_type, a_field, a_node) \ ((a_node)->a_field.rbn_left) #define rbtn_left_set(a_type, a_field, a_node, a_left) do { \ (a_node)->a_field.rbn_left = a_left; \ } while (0) #ifdef RB_COMPACT /* Right accessors. */ #define rbtn_right_get(a_type, a_field, a_node) \ ((a_type *) (((intptr_t) (a_node)->a_field.rbn_right_red) \ & ((ssize_t)-2))) #define rbtn_right_set(a_type, a_field, a_node, a_right) do { \ (a_node)->a_field.rbn_right_red = (a_type *) (((uintptr_t) a_right) \ | (((uintptr_t) (a_node)->a_field.rbn_right_red) & ((size_t)1))); \ } while (0) /* Color accessors. */ #define rbtn_red_get(a_type, a_field, a_node) \ ((bool) (((uintptr_t) (a_node)->a_field.rbn_right_red) \ & ((size_t)1))) #define rbtn_color_set(a_type, a_field, a_node, a_red) do { \ (a_node)->a_field.rbn_right_red = (a_type *) ((((intptr_t) \ (a_node)->a_field.rbn_right_red) & ((ssize_t)-2)) \ | ((ssize_t)a_red)); \ } while (0) #define rbtn_red_set(a_type, a_field, a_node) do { \ (a_node)->a_field.rbn_right_red = (a_type *) (((uintptr_t) \ (a_node)->a_field.rbn_right_red) | ((size_t)1)); \ } while (0) #define rbtn_black_set(a_type, a_field, a_node) do { \ (a_node)->a_field.rbn_right_red = (a_type *) (((intptr_t) \ (a_node)->a_field.rbn_right_red) & ((ssize_t)-2)); \ } while (0) /* Node initializer. */ #define rbt_node_new(a_type, a_field, a_rbt, a_node) do { \ /* Bookkeeping bit cannot be used by node pointer. */ \ assert(((uintptr_t)(a_node) & 0x1) == 0); \ rbtn_left_set(a_type, a_field, (a_node), NULL); \ rbtn_right_set(a_type, a_field, (a_node), NULL); \ rbtn_red_set(a_type, a_field, (a_node)); \ } while (0) #else /* Right accessors. */ #define rbtn_right_get(a_type, a_field, a_node) \ ((a_node)->a_field.rbn_right) #define rbtn_right_set(a_type, a_field, a_node, a_right) do { \ (a_node)->a_field.rbn_right = a_right; \ } while (0) /* Color accessors. */ #define rbtn_red_get(a_type, a_field, a_node) \ ((a_node)->a_field.rbn_red) #define rbtn_color_set(a_type, a_field, a_node, a_red) do { \ (a_node)->a_field.rbn_red = (a_red); \ } while (0) #define rbtn_red_set(a_type, a_field, a_node) do { \ (a_node)->a_field.rbn_red = true; \ } while (0) #define rbtn_black_set(a_type, a_field, a_node) do { \ (a_node)->a_field.rbn_red = false; \ } while (0) /* Node initializer. */ #define rbt_node_new(a_type, a_field, a_rbt, a_node) do { \ rbtn_left_set(a_type, a_field, (a_node), NULL); \ rbtn_right_set(a_type, a_field, (a_node), NULL); \ rbtn_red_set(a_type, a_field, (a_node)); \ } while (0) #endif /* Tree initializer. */ #define rb_new(a_type, a_field, a_rbt) do { \ (a_rbt)->rbt_root = NULL; \ } while (0) /* Internal utility macros. */ #define rbtn_first(a_type, a_field, a_rbt, a_root, r_node) do { \ (r_node) = (a_root); \ if ((r_node) != NULL) { \ for (; \ rbtn_left_get(a_type, a_field, (r_node)) != NULL; \ (r_node) = rbtn_left_get(a_type, a_field, (r_node))) { \ } \ } \ } while (0) #define rbtn_last(a_type, a_field, a_rbt, a_root, r_node) do { \ (r_node) = (a_root); \ if ((r_node) != NULL) { \ for (; rbtn_right_get(a_type, a_field, (r_node)) != NULL; \ (r_node) = rbtn_right_get(a_type, a_field, (r_node))) { \ } \ } \ } while (0) #define rbtn_rotate_left(a_type, a_field, a_node, r_node) do { \ (r_node) = rbtn_right_get(a_type, a_field, (a_node)); \ rbtn_right_set(a_type, a_field, (a_node), \ rbtn_left_get(a_type, a_field, (r_node))); \ rbtn_left_set(a_type, a_field, (r_node), (a_node)); \ } while (0) #define rbtn_rotate_right(a_type, a_field, a_node, r_node) do { \ (r_node) = rbtn_left_get(a_type, a_field, (a_node)); \ rbtn_left_set(a_type, a_field, (a_node), \ rbtn_right_get(a_type, a_field, (r_node))); \ rbtn_right_set(a_type, a_field, (r_node), (a_node)); \ } while (0) #define rb_summarized_only_false(...) #define rb_summarized_only_true(...) __VA_ARGS__ #define rb_empty_summarize(a_node, a_lchild, a_rchild) false /* * The rb_proto() and rb_summarized_proto() macros generate function prototypes * that correspond to the functions generated by an equivalently parameterized * call to rb_gen() or rb_summarized_gen(), respectively. */ #define rb_proto(a_attr, a_prefix, a_rbt_type, a_type) \ rb_proto_impl(a_attr, a_prefix, a_rbt_type, a_type, false) #define rb_summarized_proto(a_attr, a_prefix, a_rbt_type, a_type) \ rb_proto_impl(a_attr, a_prefix, a_rbt_type, a_type, true) #define rb_proto_impl(a_attr, a_prefix, a_rbt_type, a_type, \ a_is_summarized) \ a_attr void \ a_prefix##new(a_rbt_type *rbtree); \ a_attr bool \ a_prefix##empty(a_rbt_type *rbtree); \ a_attr a_type * \ a_prefix##first(a_rbt_type *rbtree); \ a_attr a_type * \ a_prefix##last(a_rbt_type *rbtree); \ a_attr a_type * \ a_prefix##next(a_rbt_type *rbtree, a_type *node); \ a_attr a_type * \ a_prefix##prev(a_rbt_type *rbtree, a_type *node); \ a_attr a_type * \ a_prefix##search(a_rbt_type *rbtree, const a_type *key); \ a_attr a_type * \ a_prefix##nsearch(a_rbt_type *rbtree, const a_type *key); \ a_attr a_type * \ a_prefix##psearch(a_rbt_type *rbtree, const a_type *key); \ a_attr void \ a_prefix##insert(a_rbt_type *rbtree, a_type *node); \ a_attr void \ a_prefix##remove(a_rbt_type *rbtree, a_type *node); \ a_attr a_type * \ a_prefix##iter(a_rbt_type *rbtree, a_type *start, a_type *(*cb)( \ a_rbt_type *, a_type *, void *), void *arg); \ a_attr a_type * \ a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg); \ a_attr void \ a_prefix##destroy(a_rbt_type *rbtree, void (*cb)(a_type *, void *), \ void *arg); \ /* Extended API */ \ rb_summarized_only_##a_is_summarized( \ a_attr void \ a_prefix##update_summaries(a_rbt_type *rbtree, a_type *node); \ a_attr bool \ a_prefix##empty_filtered(a_rbt_type *rbtree, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##first_filtered(a_rbt_type *rbtree, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##last_filtered(a_rbt_type *rbtree, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##next_filtered(a_rbt_type *rbtree, a_type *node, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##prev_filtered(a_rbt_type *rbtree, a_type *node, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##search_filtered(a_rbt_type *rbtree, const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##nsearch_filtered(a_rbt_type *rbtree, const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##psearch_filtered(a_rbt_type *rbtree, const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##iter_filtered(a_rbt_type *rbtree, a_type *start, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ a_attr a_type * \ a_prefix##reverse_iter_filtered(a_rbt_type *rbtree, a_type *start, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx); \ ) /* * The rb_gen() macro generates a type-specific red-black tree implementation, * based on the above cpp macros. * Arguments: * * a_attr: * Function attribute for generated functions (ex: static). * a_prefix: * Prefix for generated functions (ex: ex_). * a_rb_type: * Type for red-black tree data structure (ex: ex_t). * a_type: * Type for red-black tree node data structure (ex: ex_node_t). * a_field: * Name of red-black tree node linkage (ex: ex_link). * a_cmp: * Node comparison function name, with the following prototype: * * int a_cmp(a_type *a_node, a_type *a_other); * ^^^^^^ * or a_key * Interpretation of comparison function return values: * -1 : a_node < a_other * 0 : a_node == a_other * 1 : a_node > a_other * In all cases, the a_node or a_key macro argument is the first argument to * the comparison function, which makes it possible to write comparison * functions that treat the first argument specially. a_cmp must be a total * order on values inserted into the tree -- duplicates are not allowed. * * Assuming the following setup: * * typedef struct ex_node_s ex_node_t; * struct ex_node_s { * rb_node(ex_node_t) ex_link; * }; * typedef rb_tree(ex_node_t) ex_t; * rb_gen(static, ex_, ex_t, ex_node_t, ex_link, ex_cmp) * * The following API is generated: * * static void * ex_new(ex_t *tree); * Description: Initialize a red-black tree structure. * Args: * tree: Pointer to an uninitialized red-black tree object. * * static bool * ex_empty(ex_t *tree); * Description: Determine whether tree is empty. * Args: * tree: Pointer to an initialized red-black tree object. * Ret: True if tree is empty, false otherwise. * * static ex_node_t * * ex_first(ex_t *tree); * static ex_node_t * * ex_last(ex_t *tree); * Description: Get the first/last node in tree. * Args: * tree: Pointer to an initialized red-black tree object. * Ret: First/last node in tree, or NULL if tree is empty. * * static ex_node_t * * ex_next(ex_t *tree, ex_node_t *node); * static ex_node_t * * ex_prev(ex_t *tree, ex_node_t *node); * Description: Get node's successor/predecessor. * Args: * tree: Pointer to an initialized red-black tree object. * node: A node in tree. * Ret: node's successor/predecessor in tree, or NULL if node is * last/first. * * static ex_node_t * * ex_search(ex_t *tree, const ex_node_t *key); * Description: Search for node that matches key. * Args: * tree: Pointer to an initialized red-black tree object. * key : Search key. * Ret: Node in tree that matches key, or NULL if no match. * * static ex_node_t * * ex_nsearch(ex_t *tree, const ex_node_t *key); * static ex_node_t * * ex_psearch(ex_t *tree, const ex_node_t *key); * Description: Search for node that matches key. If no match is found, * return what would be key's successor/predecessor, were * key in tree. * Args: * tree: Pointer to an initialized red-black tree object. * key : Search key. * Ret: Node in tree that matches key, or if no match, hypothetical node's * successor/predecessor (NULL if no successor/predecessor). * * static void * ex_insert(ex_t *tree, ex_node_t *node); * Description: Insert node into tree. * Args: * tree: Pointer to an initialized red-black tree object. * node: Node to be inserted into tree. * * static void * ex_remove(ex_t *tree, ex_node_t *node); * Description: Remove node from tree. * Args: * tree: Pointer to an initialized red-black tree object. * node: Node in tree to be removed. * * static ex_node_t * * ex_iter(ex_t *tree, ex_node_t *start, ex_node_t *(*cb)(ex_t *, * ex_node_t *, void *), void *arg); * static ex_node_t * * ex_reverse_iter(ex_t *tree, ex_node_t *start, ex_node *(*cb)(ex_t *, * ex_node_t *, void *), void *arg); * Description: Iterate forward/backward over tree, starting at node. If * tree is modified, iteration must be immediately * terminated by the callback function that causes the * modification. * Args: * tree : Pointer to an initialized red-black tree object. * start: Node at which to start iteration, or NULL to start at * first/last node. * cb : Callback function, which is called for each node during * iteration. Under normal circumstances the callback function * should return NULL, which causes iteration to continue. If a * callback function returns non-NULL, iteration is immediately * terminated and the non-NULL return value is returned by the * iterator. This is useful for re-starting iteration after * modifying tree. * arg : Opaque pointer passed to cb(). * Ret: NULL if iteration completed, or the non-NULL callback return value * that caused termination of the iteration. * * static void * ex_destroy(ex_t *tree, void (*cb)(ex_node_t *, void *), void *arg); * Description: Iterate over the tree with post-order traversal, remove * each node, and run the callback if non-null. This is * used for destroying a tree without paying the cost to * rebalance it. The tree must not be otherwise altered * during traversal. * Args: * tree: Pointer to an initialized red-black tree object. * cb : Callback function, which, if non-null, is called for each node * during iteration. There is no way to stop iteration once it * has begun. * arg : Opaque pointer passed to cb(). * * The rb_summarized_gen() macro generates all the functions above, but has an * expanded interface. In introduces the notion of summarizing subtrees, and of * filtering searches in the tree according to the information contained in * those summaries. * The extra macro argument is: * a_summarize: * Tree summarization function name, with the following prototype: * * bool a_summarize(a_type *a_node, const a_type *a_left_child, * const a_type *a_right_child); * * This function should update a_node with the summary of the subtree rooted * there, using the data contained in it and the summaries in a_left_child * and a_right_child. One or both of them may be NULL. When the tree * changes due to an insertion or removal, it updates the summaries of all * nodes whose subtrees have changed (always updating the summaries of * children before their parents). If the user alters a node in the tree in * a way that may change its summary, they can call the generated * update_summaries function to bubble up the summary changes to the root. * It should return true if the summary changed (or may have changed), and * false if it didn't (which will allow the implementation to terminate * "bubbling up" the summaries early). * As the parameter names indicate, the children are ordered as they are in * the tree, a_left_child, if it is not NULL, compares less than a_node, * which in turn compares less than a_right_child (if a_right_child is not * NULL). * * Using the same setup as above but replacing the macro with * rb_summarized_gen(static, ex_, ex_t, ex_node_t, ex_link, ex_cmp, * ex_summarize) * * Generates all the previous functions, but adds some more: * * static void * ex_update_summaries(ex_t *tree, ex_node_t *node); * Description: Recompute all summaries of ancestors of node. * Args: * tree: Pointer to an initialized red-black tree object. * node: The element of the tree whose summary may have changed. * * For each of ex_empty, ex_first, ex_last, ex_next, ex_prev, ex_search, * ex_nsearch, ex_psearch, ex_iter, and ex_reverse_iter, an additional function * is generated as well, with the suffix _filtered (e.g. ex_empty_filtered, * ex_first_filtered, etc.). These use the concept of a "filter"; a binary * property some node either satisfies or does not satisfy. Clever use of the * a_summary argument to rb_summarized_gen can allow efficient computation of * these predicates across whole subtrees of the tree. * The extended API functions accept three additional arguments after the * arguments to the corresponding non-extended equivalent. * * ex_fn(..., bool (*filter_node)(void *, ex_node_t *), * bool (*filter_subtree)(void *, ex_node_t *), void *filter_ctx); * filter_node : Returns true if the node passes the filter. * filter_subtree : Returns true if some node in the subtree rooted at * node passes the filter. * filter_ctx : A context argument passed to the filters. * * For a more concrete example of summarizing and filtering, suppose we're using * the red-black tree to track a set of integers: * * struct ex_node_s { * rb_node(ex_node_t) ex_link; * unsigned data; * }; * * Suppose, for some application-specific reason, we want to be able to quickly * find numbers in the set which are divisible by large powers of 2 (say, for * aligned allocation purposes). We augment the node with a summary field: * * struct ex_node_s { * rb_node(ex_node_t) ex_link; * unsigned data; * unsigned max_subtree_ffs; * } * * and define our summarization function as follows: * * bool * ex_summarize(ex_node_t *node, const ex_node_t *lchild, * const ex_node_t *rchild) { * unsigned new_max_subtree_ffs = ffs(node->data); * if (lchild != NULL && lchild->max_subtree_ffs > new_max_subtree_ffs) { * new_max_subtree_ffs = lchild->max_subtree_ffs; * } * if (rchild != NULL && rchild->max_subtree_ffs > new_max_subtree_ffs) { * new_max_subtree_ffs = rchild->max_subtree_ffs; * } * bool changed = (node->max_subtree_ffs != new_max_subtree_ffs) * node->max_subtree_ffs = new_max_subtree_ffs; * // This could be "return true" without any correctness or big-O * // performance changes; but practically, precisely reporting summary * // changes reduces the amount of work that has to be done when "bubbling * // up" summary changes. * return changed; * } * * We can now implement our filter functions as follows: * bool * ex_filter_node(void *filter_ctx, ex_node_t *node) { * unsigned required_ffs = *(unsigned *)filter_ctx; * return ffs(node->data) >= required_ffs; * } * bool * ex_filter_subtree(void *filter_ctx, ex_node_t *node) { * unsigned required_ffs = *(unsigned *)filter_ctx; * return node->max_subtree_ffs >= required_ffs; * } * * We can now easily search for, e.g., the smallest integer in the set that's * divisible by 128: * ex_node_t * * find_div_128(ex_tree_t *tree) { * unsigned min_ffs = 7; * return ex_first_filtered(tree, &ex_filter_node, &ex_filter_subtree, * &min_ffs); * } * * We could with similar ease: * - Fnd the next multiple of 128 in the set that's larger than 12345 (with * ex_nsearch_filtered) * - Iterate over just those multiples of 64 that are in the set (with * ex_iter_filtered) * - Determine if the set contains any multiples of 1024 (with * ex_empty_filtered). * * Some possibly subtle API notes: * - The node argument to ex_next_filtered and ex_prev_filtered need not pass * the filter; it will find the next/prev node that passes the filter. * - ex_search_filtered will fail even for a node in the tree, if that node does * not pass the filter. ex_psearch_filtered and ex_nsearch_filtered behave * similarly; they may return a node larger/smaller than the key, even if a * node equivalent to the key is in the tree (but does not pass the filter). * - Similarly, if the start argument to a filtered iteration function does not * pass the filter, the callback won't be invoked on it. * * These should make sense after a moment's reflection; each post-condition is * the same as with the unfiltered version, with the added constraint that the * returned node must pass the filter. */ #define rb_gen(a_attr, a_prefix, a_rbt_type, a_type, a_field, a_cmp) \ rb_gen_impl(a_attr, a_prefix, a_rbt_type, a_type, a_field, a_cmp, \ rb_empty_summarize, false) #define rb_summarized_gen(a_attr, a_prefix, a_rbt_type, a_type, \ a_field, a_cmp, a_summarize) \ rb_gen_impl(a_attr, a_prefix, a_rbt_type, a_type, a_field, a_cmp, \ a_summarize, true) #define rb_gen_impl(a_attr, a_prefix, a_rbt_type, a_type, \ a_field, a_cmp, a_summarize, a_is_summarized) \ typedef struct { \ a_type *node; \ int cmp; \ } a_prefix##path_entry_t; \ static inline void \ a_prefix##summarize_range(a_prefix##path_entry_t *rfirst, \ a_prefix##path_entry_t *rlast) { \ while ((uintptr_t)rlast >= (uintptr_t)rfirst) { \ a_type *node = rlast->node; \ /* Avoid a warning when a_summarize is rb_empty_summarize. */ \ (void)node; \ bool changed = a_summarize(node, rbtn_left_get(a_type, a_field, \ node), rbtn_right_get(a_type, a_field, node)); \ if (!changed) { \ break; \ } \ rlast--; \ } \ } \ /* On the remove pathways, we sometimes swap the node being removed */\ /* and its first successor; in such cases we need to do two range */\ /* updates; one from the node to its (former) swapped successor, the */\ /* next from that successor to the root (with either allowed to */\ /* bail out early if appropriate. */\ static inline void \ a_prefix##summarize_swapped_range(a_prefix##path_entry_t *rfirst, \ a_prefix##path_entry_t *rlast, a_prefix##path_entry_t *swap_loc) { \ if (swap_loc == NULL || rlast <= swap_loc) { \ a_prefix##summarize_range(rfirst, rlast); \ } else { \ a_prefix##summarize_range(swap_loc + 1, rlast); \ (void)a_summarize(swap_loc->node, \ rbtn_left_get(a_type, a_field, swap_loc->node), \ rbtn_right_get(a_type, a_field, swap_loc->node)); \ a_prefix##summarize_range(rfirst, swap_loc - 1); \ } \ } \ a_attr void \ a_prefix##new(a_rbt_type *rbtree) { \ rb_new(a_type, a_field, rbtree); \ } \ a_attr bool \ a_prefix##empty(a_rbt_type *rbtree) { \ return (rbtree->rbt_root == NULL); \ } \ a_attr a_type * \ a_prefix##first(a_rbt_type *rbtree) { \ a_type *ret; \ rbtn_first(a_type, a_field, rbtree, rbtree->rbt_root, ret); \ return ret; \ } \ a_attr a_type * \ a_prefix##last(a_rbt_type *rbtree) { \ a_type *ret; \ rbtn_last(a_type, a_field, rbtree, rbtree->rbt_root, ret); \ return ret; \ } \ a_attr a_type * \ a_prefix##next(a_rbt_type *rbtree, a_type *node) { \ a_type *ret; \ if (rbtn_right_get(a_type, a_field, node) != NULL) { \ rbtn_first(a_type, a_field, rbtree, rbtn_right_get(a_type, \ a_field, node), ret); \ } else { \ a_type *tnode = rbtree->rbt_root; \ assert(tnode != NULL); \ ret = NULL; \ while (true) { \ int cmp = (a_cmp)(node, tnode); \ if (cmp < 0) { \ ret = tnode; \ tnode = rbtn_left_get(a_type, a_field, tnode); \ } else if (cmp > 0) { \ tnode = rbtn_right_get(a_type, a_field, tnode); \ } else { \ break; \ } \ assert(tnode != NULL); \ } \ } \ return ret; \ } \ a_attr a_type * \ a_prefix##prev(a_rbt_type *rbtree, a_type *node) { \ a_type *ret; \ if (rbtn_left_get(a_type, a_field, node) != NULL) { \ rbtn_last(a_type, a_field, rbtree, rbtn_left_get(a_type, \ a_field, node), ret); \ } else { \ a_type *tnode = rbtree->rbt_root; \ assert(tnode != NULL); \ ret = NULL; \ while (true) { \ int cmp = (a_cmp)(node, tnode); \ if (cmp < 0) { \ tnode = rbtn_left_get(a_type, a_field, tnode); \ } else if (cmp > 0) { \ ret = tnode; \ tnode = rbtn_right_get(a_type, a_field, tnode); \ } else { \ break; \ } \ assert(tnode != NULL); \ } \ } \ return ret; \ } \ a_attr a_type * \ a_prefix##search(a_rbt_type *rbtree, const a_type *key) { \ a_type *ret; \ int cmp; \ ret = rbtree->rbt_root; \ while (ret != NULL \ && (cmp = (a_cmp)(key, ret)) != 0) { \ if (cmp < 0) { \ ret = rbtn_left_get(a_type, a_field, ret); \ } else { \ ret = rbtn_right_get(a_type, a_field, ret); \ } \ } \ return ret; \ } \ a_attr a_type * \ a_prefix##nsearch(a_rbt_type *rbtree, const a_type *key) { \ a_type *ret; \ a_type *tnode = rbtree->rbt_root; \ ret = NULL; \ while (tnode != NULL) { \ int cmp = (a_cmp)(key, tnode); \ if (cmp < 0) { \ ret = tnode; \ tnode = rbtn_left_get(a_type, a_field, tnode); \ } else if (cmp > 0) { \ tnode = rbtn_right_get(a_type, a_field, tnode); \ } else { \ ret = tnode; \ break; \ } \ } \ return ret; \ } \ a_attr a_type * \ a_prefix##psearch(a_rbt_type *rbtree, const a_type *key) { \ a_type *ret; \ a_type *tnode = rbtree->rbt_root; \ ret = NULL; \ while (tnode != NULL) { \ int cmp = (a_cmp)(key, tnode); \ if (cmp < 0) { \ tnode = rbtn_left_get(a_type, a_field, tnode); \ } else if (cmp > 0) { \ ret = tnode; \ tnode = rbtn_right_get(a_type, a_field, tnode); \ } else { \ ret = tnode; \ break; \ } \ } \ return ret; \ } \ a_attr void \ a_prefix##insert(a_rbt_type *rbtree, a_type *node) { \ a_prefix##path_entry_t path[RB_MAX_DEPTH]; \ a_prefix##path_entry_t *pathp; \ rbt_node_new(a_type, a_field, rbtree, node); \ /* Wind. */ \ path->node = rbtree->rbt_root; \ for (pathp = path; pathp->node != NULL; pathp++) { \ int cmp = pathp->cmp = a_cmp(node, pathp->node); \ assert(cmp != 0); \ if (cmp < 0) { \ pathp[1].node = rbtn_left_get(a_type, a_field, \ pathp->node); \ } else { \ pathp[1].node = rbtn_right_get(a_type, a_field, \ pathp->node); \ } \ } \ pathp->node = node; \ /* A loop invariant we maintain is that all nodes with */\ /* out-of-date summaries live in path[0], path[1], ..., *pathp. */\ /* To maintain this, we have to summarize node, since we */\ /* decrement pathp before the first iteration. */\ assert(rbtn_left_get(a_type, a_field, node) == NULL); \ assert(rbtn_right_get(a_type, a_field, node) == NULL); \ (void)a_summarize(node, NULL, NULL); \ /* Unwind. */ \ for (pathp--; (uintptr_t)pathp >= (uintptr_t)path; pathp--) { \ a_type *cnode = pathp->node; \ if (pathp->cmp < 0) { \ a_type *left = pathp[1].node; \ rbtn_left_set(a_type, a_field, cnode, left); \ if (rbtn_red_get(a_type, a_field, left)) { \ a_type *leftleft = rbtn_left_get(a_type, a_field, left);\ if (leftleft != NULL && rbtn_red_get(a_type, a_field, \ leftleft)) { \ /* Fix up 4-node. */ \ a_type *tnode; \ rbtn_black_set(a_type, a_field, leftleft); \ rbtn_rotate_right(a_type, a_field, cnode, tnode); \ (void)a_summarize(cnode, \ rbtn_left_get(a_type, a_field, cnode), \ rbtn_right_get(a_type, a_field, cnode)); \ cnode = tnode; \ } \ } else { \ a_prefix##summarize_range(path, pathp); \ return; \ } \ } else { \ a_type *right = pathp[1].node; \ rbtn_right_set(a_type, a_field, cnode, right); \ if (rbtn_red_get(a_type, a_field, right)) { \ a_type *left = rbtn_left_get(a_type, a_field, cnode); \ if (left != NULL && rbtn_red_get(a_type, a_field, \ left)) { \ /* Split 4-node. */ \ rbtn_black_set(a_type, a_field, left); \ rbtn_black_set(a_type, a_field, right); \ rbtn_red_set(a_type, a_field, cnode); \ } else { \ /* Lean left. */ \ a_type *tnode; \ bool tred = rbtn_red_get(a_type, a_field, cnode); \ rbtn_rotate_left(a_type, a_field, cnode, tnode); \ rbtn_color_set(a_type, a_field, tnode, tred); \ rbtn_red_set(a_type, a_field, cnode); \ (void)a_summarize(cnode, \ rbtn_left_get(a_type, a_field, cnode), \ rbtn_right_get(a_type, a_field, cnode)); \ cnode = tnode; \ } \ } else { \ a_prefix##summarize_range(path, pathp); \ return; \ } \ } \ pathp->node = cnode; \ (void)a_summarize(cnode, \ rbtn_left_get(a_type, a_field, cnode), \ rbtn_right_get(a_type, a_field, cnode)); \ } \ /* Set root, and make it black. */ \ rbtree->rbt_root = path->node; \ rbtn_black_set(a_type, a_field, rbtree->rbt_root); \ } \ a_attr void \ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \ a_prefix##path_entry_t path[RB_MAX_DEPTH]; \ a_prefix##path_entry_t *pathp; \ a_prefix##path_entry_t *nodep; \ a_prefix##path_entry_t *swap_loc; \ /* This is a "real" sentinel -- NULL means we didn't swap the */\ /* node to be pruned with one of its successors, and so */\ /* summarization can terminate early whenever some summary */\ /* doesn't change. */\ swap_loc = NULL; \ /* This is just to silence a compiler warning. */ \ nodep = NULL; \ /* Wind. */ \ path->node = rbtree->rbt_root; \ for (pathp = path; pathp->node != NULL; pathp++) { \ int cmp = pathp->cmp = a_cmp(node, pathp->node); \ if (cmp < 0) { \ pathp[1].node = rbtn_left_get(a_type, a_field, \ pathp->node); \ } else { \ pathp[1].node = rbtn_right_get(a_type, a_field, \ pathp->node); \ if (cmp == 0) { \ /* Find node's successor, in preparation for swap. */ \ pathp->cmp = 1; \ nodep = pathp; \ for (pathp++; pathp->node != NULL; pathp++) { \ pathp->cmp = -1; \ pathp[1].node = rbtn_left_get(a_type, a_field, \ pathp->node); \ } \ break; \ } \ } \ } \ assert(nodep->node == node); \ pathp--; \ if (pathp->node != node) { \ /* Swap node with its successor. */ \ swap_loc = nodep; \ bool tred = rbtn_red_get(a_type, a_field, pathp->node); \ rbtn_color_set(a_type, a_field, pathp->node, \ rbtn_red_get(a_type, a_field, node)); \ rbtn_left_set(a_type, a_field, pathp->node, \ rbtn_left_get(a_type, a_field, node)); \ /* If node's successor is its right child, the following code */\ /* will do the wrong thing for the right child pointer. */\ /* However, it doesn't matter, because the pointer will be */\ /* properly set when the successor is pruned. */\ rbtn_right_set(a_type, a_field, pathp->node, \ rbtn_right_get(a_type, a_field, node)); \ rbtn_color_set(a_type, a_field, node, tred); \ /* The pruned leaf node's child pointers are never accessed */\ /* again, so don't bother setting them to nil. */\ nodep->node = pathp->node; \ pathp->node = node; \ if (nodep == path) { \ rbtree->rbt_root = nodep->node; \ } else { \ if (nodep[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, nodep[-1].node, \ nodep->node); \ } else { \ rbtn_right_set(a_type, a_field, nodep[-1].node, \ nodep->node); \ } \ } \ } else { \ a_type *left = rbtn_left_get(a_type, a_field, node); \ if (left != NULL) { \ /* node has no successor, but it has a left child. */\ /* Splice node out, without losing the left child. */\ assert(!rbtn_red_get(a_type, a_field, node)); \ assert(rbtn_red_get(a_type, a_field, left)); \ rbtn_black_set(a_type, a_field, left); \ if (pathp == path) { \ rbtree->rbt_root = left; \ /* Nothing to summarize -- the subtree rooted at the */\ /* node's left child hasn't changed, and it's now the */\ /* root. */\ } else { \ if (pathp[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, pathp[-1].node, \ left); \ } else { \ rbtn_right_set(a_type, a_field, pathp[-1].node, \ left); \ } \ a_prefix##summarize_swapped_range(path, &pathp[-1], \ swap_loc); \ } \ return; \ } else if (pathp == path) { \ /* The tree only contained one node. */ \ rbtree->rbt_root = NULL; \ return; \ } \ } \ /* We've now established the invariant that the node has no right */\ /* child (well, morally; we didn't bother nulling it out if we */\ /* swapped it with its successor), and that the only nodes with */\ /* out-of-date summaries live in path[0], path[1], ..., pathp[-1].*/\ if (rbtn_red_get(a_type, a_field, pathp->node)) { \ /* Prune red node, which requires no fixup. */ \ assert(pathp[-1].cmp < 0); \ rbtn_left_set(a_type, a_field, pathp[-1].node, NULL); \ a_prefix##summarize_swapped_range(path, &pathp[-1], swap_loc); \ return; \ } \ /* The node to be pruned is black, so unwind until balance is */\ /* restored. */\ pathp->node = NULL; \ for (pathp--; (uintptr_t)pathp >= (uintptr_t)path; pathp--) { \ assert(pathp->cmp != 0); \ if (pathp->cmp < 0) { \ rbtn_left_set(a_type, a_field, pathp->node, \ pathp[1].node); \ if (rbtn_red_get(a_type, a_field, pathp->node)) { \ a_type *right = rbtn_right_get(a_type, a_field, \ pathp->node); \ a_type *rightleft = rbtn_left_get(a_type, a_field, \ right); \ a_type *tnode; \ if (rightleft != NULL && rbtn_red_get(a_type, a_field, \ rightleft)) { \ /* In the following diagrams, ||, //, and \\ */\ /* indicate the path to the removed node. */\ /* */\ /* || */\ /* pathp(r) */\ /* // \ */\ /* (b) (b) */\ /* / */\ /* (r) */\ /* */\ rbtn_black_set(a_type, a_field, pathp->node); \ rbtn_rotate_right(a_type, a_field, right, tnode); \ rbtn_right_set(a_type, a_field, pathp->node, tnode);\ rbtn_rotate_left(a_type, a_field, pathp->node, \ tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ (void)a_summarize(right, \ rbtn_left_get(a_type, a_field, right), \ rbtn_right_get(a_type, a_field, right)); \ } else { \ /* || */\ /* pathp(r) */\ /* // \ */\ /* (b) (b) */\ /* / */\ /* (b) */\ /* */\ rbtn_rotate_left(a_type, a_field, pathp->node, \ tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ } \ (void)a_summarize(tnode, rbtn_left_get(a_type, a_field, \ tnode), rbtn_right_get(a_type, a_field, tnode)); \ /* Balance restored, but rotation modified subtree */\ /* root. */\ assert((uintptr_t)pathp > (uintptr_t)path); \ if (pathp[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, pathp[-1].node, \ tnode); \ } else { \ rbtn_right_set(a_type, a_field, pathp[-1].node, \ tnode); \ } \ a_prefix##summarize_swapped_range(path, &pathp[-1], \ swap_loc); \ return; \ } else { \ a_type *right = rbtn_right_get(a_type, a_field, \ pathp->node); \ a_type *rightleft = rbtn_left_get(a_type, a_field, \ right); \ if (rightleft != NULL && rbtn_red_get(a_type, a_field, \ rightleft)) { \ /* || */\ /* pathp(b) */\ /* // \ */\ /* (b) (b) */\ /* / */\ /* (r) */\ a_type *tnode; \ rbtn_black_set(a_type, a_field, rightleft); \ rbtn_rotate_right(a_type, a_field, right, tnode); \ rbtn_right_set(a_type, a_field, pathp->node, tnode);\ rbtn_rotate_left(a_type, a_field, pathp->node, \ tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ (void)a_summarize(right, \ rbtn_left_get(a_type, a_field, right), \ rbtn_right_get(a_type, a_field, right)); \ (void)a_summarize(tnode, \ rbtn_left_get(a_type, a_field, tnode), \ rbtn_right_get(a_type, a_field, tnode)); \ /* Balance restored, but rotation modified */\ /* subtree root, which may actually be the tree */\ /* root. */\ if (pathp == path) { \ /* Set root. */ \ rbtree->rbt_root = tnode; \ } else { \ if (pathp[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, \ pathp[-1].node, tnode); \ } else { \ rbtn_right_set(a_type, a_field, \ pathp[-1].node, tnode); \ } \ a_prefix##summarize_swapped_range(path, \ &pathp[-1], swap_loc); \ } \ return; \ } else { \ /* || */\ /* pathp(b) */\ /* // \ */\ /* (b) (b) */\ /* / */\ /* (b) */\ a_type *tnode; \ rbtn_red_set(a_type, a_field, pathp->node); \ rbtn_rotate_left(a_type, a_field, pathp->node, \ tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ (void)a_summarize(tnode, \ rbtn_left_get(a_type, a_field, tnode), \ rbtn_right_get(a_type, a_field, tnode)); \ pathp->node = tnode; \ } \ } \ } else { \ a_type *left; \ rbtn_right_set(a_type, a_field, pathp->node, \ pathp[1].node); \ left = rbtn_left_get(a_type, a_field, pathp->node); \ if (rbtn_red_get(a_type, a_field, left)) { \ a_type *tnode; \ a_type *leftright = rbtn_right_get(a_type, a_field, \ left); \ a_type *leftrightleft = rbtn_left_get(a_type, a_field, \ leftright); \ if (leftrightleft != NULL && rbtn_red_get(a_type, \ a_field, leftrightleft)) { \ /* || */\ /* pathp(b) */\ /* / \\ */\ /* (r) (b) */\ /* \ */\ /* (b) */\ /* / */\ /* (r) */\ a_type *unode; \ rbtn_black_set(a_type, a_field, leftrightleft); \ rbtn_rotate_right(a_type, a_field, pathp->node, \ unode); \ rbtn_rotate_right(a_type, a_field, pathp->node, \ tnode); \ rbtn_right_set(a_type, a_field, unode, tnode); \ rbtn_rotate_left(a_type, a_field, unode, tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ (void)a_summarize(unode, \ rbtn_left_get(a_type, a_field, unode), \ rbtn_right_get(a_type, a_field, unode)); \ } else { \ /* || */\ /* pathp(b) */\ /* / \\ */\ /* (r) (b) */\ /* \ */\ /* (b) */\ /* / */\ /* (b) */\ assert(leftright != NULL); \ rbtn_red_set(a_type, a_field, leftright); \ rbtn_rotate_right(a_type, a_field, pathp->node, \ tnode); \ rbtn_black_set(a_type, a_field, tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ } \ (void)a_summarize(tnode, \ rbtn_left_get(a_type, a_field, tnode), \ rbtn_right_get(a_type, a_field, tnode)); \ /* Balance restored, but rotation modified subtree */\ /* root, which may actually be the tree root. */\ if (pathp == path) { \ /* Set root. */ \ rbtree->rbt_root = tnode; \ } else { \ if (pathp[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, pathp[-1].node, \ tnode); \ } else { \ rbtn_right_set(a_type, a_field, pathp[-1].node, \ tnode); \ } \ a_prefix##summarize_swapped_range(path, &pathp[-1], \ swap_loc); \ } \ return; \ } else if (rbtn_red_get(a_type, a_field, pathp->node)) { \ a_type *leftleft = rbtn_left_get(a_type, a_field, left);\ if (leftleft != NULL && rbtn_red_get(a_type, a_field, \ leftleft)) { \ /* || */\ /* pathp(r) */\ /* / \\ */\ /* (b) (b) */\ /* / */\ /* (r) */\ a_type *tnode; \ rbtn_black_set(a_type, a_field, pathp->node); \ rbtn_red_set(a_type, a_field, left); \ rbtn_black_set(a_type, a_field, leftleft); \ rbtn_rotate_right(a_type, a_field, pathp->node, \ tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ (void)a_summarize(tnode, \ rbtn_left_get(a_type, a_field, tnode), \ rbtn_right_get(a_type, a_field, tnode)); \ /* Balance restored, but rotation modified */\ /* subtree root. */\ assert((uintptr_t)pathp > (uintptr_t)path); \ if (pathp[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, pathp[-1].node, \ tnode); \ } else { \ rbtn_right_set(a_type, a_field, pathp[-1].node, \ tnode); \ } \ a_prefix##summarize_swapped_range(path, &pathp[-1], \ swap_loc); \ return; \ } else { \ /* || */\ /* pathp(r) */\ /* / \\ */\ /* (b) (b) */\ /* / */\ /* (b) */\ rbtn_red_set(a_type, a_field, left); \ rbtn_black_set(a_type, a_field, pathp->node); \ /* Balance restored. */ \ a_prefix##summarize_swapped_range(path, pathp, \ swap_loc); \ return; \ } \ } else { \ a_type *leftleft = rbtn_left_get(a_type, a_field, left);\ if (leftleft != NULL && rbtn_red_get(a_type, a_field, \ leftleft)) { \ /* || */\ /* pathp(b) */\ /* / \\ */\ /* (b) (b) */\ /* / */\ /* (r) */\ a_type *tnode; \ rbtn_black_set(a_type, a_field, leftleft); \ rbtn_rotate_right(a_type, a_field, pathp->node, \ tnode); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ (void)a_summarize(tnode, \ rbtn_left_get(a_type, a_field, tnode), \ rbtn_right_get(a_type, a_field, tnode)); \ /* Balance restored, but rotation modified */\ /* subtree root, which may actually be the tree */\ /* root. */\ if (pathp == path) { \ /* Set root. */ \ rbtree->rbt_root = tnode; \ } else { \ if (pathp[-1].cmp < 0) { \ rbtn_left_set(a_type, a_field, \ pathp[-1].node, tnode); \ } else { \ rbtn_right_set(a_type, a_field, \ pathp[-1].node, tnode); \ } \ a_prefix##summarize_swapped_range(path, \ &pathp[-1], swap_loc); \ } \ return; \ } else { \ /* || */\ /* pathp(b) */\ /* / \\ */\ /* (b) (b) */\ /* / */\ /* (b) */\ rbtn_red_set(a_type, a_field, left); \ (void)a_summarize(pathp->node, \ rbtn_left_get(a_type, a_field, pathp->node), \ rbtn_right_get(a_type, a_field, pathp->node)); \ } \ } \ } \ } \ /* Set root. */ \ rbtree->rbt_root = path->node; \ assert(!rbtn_red_get(a_type, a_field, rbtree->rbt_root)); \ } \ a_attr a_type * \ a_prefix##iter_recurse(a_rbt_type *rbtree, a_type *node, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ if (node == NULL) { \ return NULL; \ } else { \ a_type *ret; \ if ((ret = a_prefix##iter_recurse(rbtree, rbtn_left_get(a_type, \ a_field, node), cb, arg)) != NULL || (ret = cb(rbtree, node, \ arg)) != NULL) { \ return ret; \ } \ return a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \ a_field, node), cb, arg); \ } \ } \ a_attr a_type * \ a_prefix##iter_start(a_rbt_type *rbtree, a_type *start, a_type *node, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ int cmp = a_cmp(start, node); \ if (cmp < 0) { \ a_type *ret; \ if ((ret = a_prefix##iter_start(rbtree, start, \ rbtn_left_get(a_type, a_field, node), cb, arg)) != NULL || \ (ret = cb(rbtree, node, arg)) != NULL) { \ return ret; \ } \ return a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \ a_field, node), cb, arg); \ } else if (cmp > 0) { \ return a_prefix##iter_start(rbtree, start, \ rbtn_right_get(a_type, a_field, node), cb, arg); \ } else { \ a_type *ret; \ if ((ret = cb(rbtree, node, arg)) != NULL) { \ return ret; \ } \ return a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \ a_field, node), cb, arg); \ } \ } \ a_attr a_type * \ a_prefix##iter(a_rbt_type *rbtree, a_type *start, a_type *(*cb)( \ a_rbt_type *, a_type *, void *), void *arg) { \ a_type *ret; \ if (start != NULL) { \ ret = a_prefix##iter_start(rbtree, start, rbtree->rbt_root, \ cb, arg); \ } else { \ ret = a_prefix##iter_recurse(rbtree, rbtree->rbt_root, cb, arg);\ } \ return ret; \ } \ a_attr a_type * \ a_prefix##reverse_iter_recurse(a_rbt_type *rbtree, a_type *node, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ if (node == NULL) { \ return NULL; \ } else { \ a_type *ret; \ if ((ret = a_prefix##reverse_iter_recurse(rbtree, \ rbtn_right_get(a_type, a_field, node), cb, arg)) != NULL || \ (ret = cb(rbtree, node, arg)) != NULL) { \ return ret; \ } \ return a_prefix##reverse_iter_recurse(rbtree, \ rbtn_left_get(a_type, a_field, node), cb, arg); \ } \ } \ a_attr a_type * \ a_prefix##reverse_iter_start(a_rbt_type *rbtree, a_type *start, \ a_type *node, a_type *(*cb)(a_rbt_type *, a_type *, void *), \ void *arg) { \ int cmp = a_cmp(start, node); \ if (cmp > 0) { \ a_type *ret; \ if ((ret = a_prefix##reverse_iter_start(rbtree, start, \ rbtn_right_get(a_type, a_field, node), cb, arg)) != NULL || \ (ret = cb(rbtree, node, arg)) != NULL) { \ return ret; \ } \ return a_prefix##reverse_iter_recurse(rbtree, \ rbtn_left_get(a_type, a_field, node), cb, arg); \ } else if (cmp < 0) { \ return a_prefix##reverse_iter_start(rbtree, start, \ rbtn_left_get(a_type, a_field, node), cb, arg); \ } else { \ a_type *ret; \ if ((ret = cb(rbtree, node, arg)) != NULL) { \ return ret; \ } \ return a_prefix##reverse_iter_recurse(rbtree, \ rbtn_left_get(a_type, a_field, node), cb, arg); \ } \ } \ a_attr a_type * \ a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \ a_type *ret; \ if (start != NULL) { \ ret = a_prefix##reverse_iter_start(rbtree, start, \ rbtree->rbt_root, cb, arg); \ } else { \ ret = a_prefix##reverse_iter_recurse(rbtree, rbtree->rbt_root, \ cb, arg); \ } \ return ret; \ } \ a_attr void \ a_prefix##destroy_recurse(a_rbt_type *rbtree, a_type *node, void (*cb)( \ a_type *, void *), void *arg) { \ if (node == NULL) { \ return; \ } \ a_prefix##destroy_recurse(rbtree, rbtn_left_get(a_type, a_field, \ node), cb, arg); \ rbtn_left_set(a_type, a_field, (node), NULL); \ a_prefix##destroy_recurse(rbtree, rbtn_right_get(a_type, a_field, \ node), cb, arg); \ rbtn_right_set(a_type, a_field, (node), NULL); \ if (cb) { \ cb(node, arg); \ } \ } \ a_attr void \ a_prefix##destroy(a_rbt_type *rbtree, void (*cb)(a_type *, void *), \ void *arg) { \ a_prefix##destroy_recurse(rbtree, rbtree->rbt_root, cb, arg); \ rbtree->rbt_root = NULL; \ } \ /* BEGIN SUMMARIZED-ONLY IMPLEMENTATION */ \ rb_summarized_only_##a_is_summarized( \ static inline a_prefix##path_entry_t * \ a_prefix##wind(a_rbt_type *rbtree, \ a_prefix##path_entry_t path[RB_MAX_DEPTH], a_type *node) { \ a_prefix##path_entry_t *pathp; \ path->node = rbtree->rbt_root; \ for (pathp = path; ; pathp++) { \ assert((size_t)(pathp - path) < RB_MAX_DEPTH); \ pathp->cmp = a_cmp(node, pathp->node); \ if (pathp->cmp < 0) { \ pathp[1].node = rbtn_left_get(a_type, a_field, \ pathp->node); \ } else if (pathp->cmp == 0) { \ return pathp; \ } else { \ pathp[1].node = rbtn_right_get(a_type, a_field, \ pathp->node); \ } \ } \ unreachable(); \ } \ a_attr void \ a_prefix##update_summaries(a_rbt_type *rbtree, a_type *node) { \ a_prefix##path_entry_t path[RB_MAX_DEPTH]; \ a_prefix##path_entry_t *pathp = a_prefix##wind(rbtree, path, node); \ a_prefix##summarize_range(path, pathp); \ } \ a_attr bool \ a_prefix##empty_filtered(a_rbt_type *rbtree, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *node = rbtree->rbt_root; \ return node == NULL || !filter_subtree(filter_ctx, node); \ } \ static inline a_type * \ a_prefix##first_filtered_from_node(a_type *node, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ assert(node != NULL && filter_subtree(filter_ctx, node)); \ while (true) { \ a_type *left = rbtn_left_get(a_type, a_field, node); \ a_type *right = rbtn_right_get(a_type, a_field, node); \ if (left != NULL && filter_subtree(filter_ctx, left)) { \ node = left; \ } else if (filter_node(filter_ctx, node)) { \ return node; \ } else { \ assert(right != NULL \ && filter_subtree(filter_ctx, right)); \ node = right; \ } \ } \ unreachable(); \ } \ a_attr a_type * \ a_prefix##first_filtered(a_rbt_type *rbtree, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *node = rbtree->rbt_root; \ if (node == NULL || !filter_subtree(filter_ctx, node)) { \ return NULL; \ } \ return a_prefix##first_filtered_from_node(node, filter_node, \ filter_subtree, filter_ctx); \ } \ static inline a_type * \ a_prefix##last_filtered_from_node(a_type *node, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ assert(node != NULL && filter_subtree(filter_ctx, node)); \ while (true) { \ a_type *left = rbtn_left_get(a_type, a_field, node); \ a_type *right = rbtn_right_get(a_type, a_field, node); \ if (right != NULL && filter_subtree(filter_ctx, right)) { \ node = right; \ } else if (filter_node(filter_ctx, node)) { \ return node; \ } else { \ assert(left != NULL \ && filter_subtree(filter_ctx, left)); \ node = left; \ } \ } \ unreachable(); \ } \ a_attr a_type * \ a_prefix##last_filtered(a_rbt_type *rbtree, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *node = rbtree->rbt_root; \ if (node == NULL || !filter_subtree(filter_ctx, node)) { \ return NULL; \ } \ return a_prefix##last_filtered_from_node(node, filter_node, \ filter_subtree, filter_ctx); \ } \ /* Internal implementation function. Search for a node comparing */\ /* equal to key matching the filter. If such a node is in the tree, */\ /* return it. Additionally, the caller has the option to ask for */\ /* bounds on the next / prev node in the tree passing the filter. */\ /* If nextbound is true, then this function will do one of the */\ /* following: */\ /* - Fill in *nextbound_node with the smallest node in the tree */\ /* greater than key passing the filter, and NULL-out */\ /* *nextbound_subtree. */\ /* - Fill in *nextbound_subtree with a parent of that node which is */\ /* not a parent of the searched-for node, and NULL-out */\ /* *nextbound_node. */\ /* - NULL-out both *nextbound_node and *nextbound_subtree, in which */\ /* case no node greater than key but passing the filter is in the */\ /* tree. */\ /* The prevbound case is similar. If the caller knows that key is in */\ /* the tree and that the subtree rooted at key does not contain a */\ /* node satisfying the bound being searched for, then they can pass */\ /* false for include_subtree, in which case we won't bother searching */\ /* there (risking a cache miss). */\ /* */\ /* This API is unfortunately complex; but the logic for filtered */\ /* searches is very subtle, and otherwise we would have to repeat it */\ /* multiple times for filtered search, nsearch, psearch, next, and */\ /* prev. */\ static inline a_type * \ a_prefix##search_with_filter_bounds(a_rbt_type *rbtree, \ const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx, \ bool include_subtree, \ bool nextbound, a_type **nextbound_node, a_type **nextbound_subtree, \ bool prevbound, a_type **prevbound_node, a_type **prevbound_subtree) {\ if (nextbound) { \ *nextbound_node = NULL; \ *nextbound_subtree = NULL; \ } \ if (prevbound) { \ *prevbound_node = NULL; \ *prevbound_subtree = NULL; \ } \ a_type *tnode = rbtree->rbt_root; \ while (tnode != NULL && filter_subtree(filter_ctx, tnode)) { \ int cmp = a_cmp(key, tnode); \ a_type *tleft = rbtn_left_get(a_type, a_field, tnode); \ a_type *tright = rbtn_right_get(a_type, a_field, tnode); \ if (cmp < 0) { \ if (nextbound) { \ if (filter_node(filter_ctx, tnode)) { \ *nextbound_node = tnode; \ *nextbound_subtree = NULL; \ } else if (tright != NULL && filter_subtree( \ filter_ctx, tright)) { \ *nextbound_node = NULL; \ *nextbound_subtree = tright; \ } \ } \ tnode = tleft; \ } else if (cmp > 0) { \ if (prevbound) { \ if (filter_node(filter_ctx, tnode)) { \ *prevbound_node = tnode; \ *prevbound_subtree = NULL; \ } else if (tleft != NULL && filter_subtree( \ filter_ctx, tleft)) { \ *prevbound_node = NULL; \ *prevbound_subtree = tleft; \ } \ } \ tnode = tright; \ } else { \ if (filter_node(filter_ctx, tnode)) { \ return tnode; \ } \ if (include_subtree) { \ if (prevbound && tleft != NULL && filter_subtree( \ filter_ctx, tleft)) { \ *prevbound_node = NULL; \ *prevbound_subtree = tleft; \ } \ if (nextbound && tright != NULL && filter_subtree( \ filter_ctx, tright)) { \ *nextbound_node = NULL; \ *nextbound_subtree = tright; \ } \ } \ return NULL; \ } \ } \ return NULL; \ } \ a_attr a_type * \ a_prefix##next_filtered(a_rbt_type *rbtree, a_type *node, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *nright = rbtn_right_get(a_type, a_field, node); \ if (nright != NULL && filter_subtree(filter_ctx, nright)) { \ return a_prefix##first_filtered_from_node(nright, filter_node, \ filter_subtree, filter_ctx); \ } \ a_type *node_candidate; \ a_type *subtree_candidate; \ a_type *search_result = a_prefix##search_with_filter_bounds( \ rbtree, node, filter_node, filter_subtree, filter_ctx, \ /* include_subtree */ false, \ /* nextbound */ true, &node_candidate, &subtree_candidate, \ /* prevbound */ false, NULL, NULL); \ assert(node == search_result \ || !filter_node(filter_ctx, node)); \ if (node_candidate != NULL) { \ return node_candidate; \ } \ if (subtree_candidate != NULL) { \ return a_prefix##first_filtered_from_node( \ subtree_candidate, filter_node, filter_subtree, \ filter_ctx); \ } \ return NULL; \ } \ a_attr a_type * \ a_prefix##prev_filtered(a_rbt_type *rbtree, a_type *node, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *nleft = rbtn_left_get(a_type, a_field, node); \ if (nleft != NULL && filter_subtree(filter_ctx, nleft)) { \ return a_prefix##last_filtered_from_node(nleft, filter_node, \ filter_subtree, filter_ctx); \ } \ a_type *node_candidate; \ a_type *subtree_candidate; \ a_type *search_result = a_prefix##search_with_filter_bounds( \ rbtree, node, filter_node, filter_subtree, filter_ctx, \ /* include_subtree */ false, \ /* nextbound */ false, NULL, NULL, \ /* prevbound */ true, &node_candidate, &subtree_candidate); \ assert(node == search_result \ || !filter_node(filter_ctx, node)); \ if (node_candidate != NULL) { \ return node_candidate; \ } \ if (subtree_candidate != NULL) { \ return a_prefix##last_filtered_from_node( \ subtree_candidate, filter_node, filter_subtree, \ filter_ctx); \ } \ return NULL; \ } \ a_attr a_type * \ a_prefix##search_filtered(a_rbt_type *rbtree, const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *result = a_prefix##search_with_filter_bounds(rbtree, key, \ filter_node, filter_subtree, filter_ctx, \ /* include_subtree */ false, \ /* nextbound */ false, NULL, NULL, \ /* prevbound */ false, NULL, NULL); \ return result; \ } \ a_attr a_type * \ a_prefix##nsearch_filtered(a_rbt_type *rbtree, const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *node_candidate; \ a_type *subtree_candidate; \ a_type *result = a_prefix##search_with_filter_bounds(rbtree, key, \ filter_node, filter_subtree, filter_ctx, \ /* include_subtree */ true, \ /* nextbound */ true, &node_candidate, &subtree_candidate, \ /* prevbound */ false, NULL, NULL); \ if (result != NULL) { \ return result; \ } \ if (node_candidate != NULL) { \ return node_candidate; \ } \ if (subtree_candidate != NULL) { \ return a_prefix##first_filtered_from_node( \ subtree_candidate, filter_node, filter_subtree, \ filter_ctx); \ } \ return NULL; \ } \ a_attr a_type * \ a_prefix##psearch_filtered(a_rbt_type *rbtree, const a_type *key, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *node_candidate; \ a_type *subtree_candidate; \ a_type *result = a_prefix##search_with_filter_bounds(rbtree, key, \ filter_node, filter_subtree, filter_ctx, \ /* include_subtree */ true, \ /* nextbound */ false, NULL, NULL, \ /* prevbound */ true, &node_candidate, &subtree_candidate); \ if (result != NULL) { \ return result; \ } \ if (node_candidate != NULL) { \ return node_candidate; \ } \ if (subtree_candidate != NULL) { \ return a_prefix##last_filtered_from_node( \ subtree_candidate, filter_node, filter_subtree, \ filter_ctx); \ } \ return NULL; \ } \ a_attr a_type * \ a_prefix##iter_recurse_filtered(a_rbt_type *rbtree, a_type *node, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ if (node == NULL || !filter_subtree(filter_ctx, node)) { \ return NULL; \ } \ a_type *ret; \ a_type *left = rbtn_left_get(a_type, a_field, node); \ a_type *right = rbtn_right_get(a_type, a_field, node); \ ret = a_prefix##iter_recurse_filtered(rbtree, left, cb, arg, \ filter_node, filter_subtree, filter_ctx); \ if (ret != NULL) { \ return ret; \ } \ if (filter_node(filter_ctx, node)) { \ ret = cb(rbtree, node, arg); \ } \ if (ret != NULL) { \ return ret; \ } \ return a_prefix##iter_recurse_filtered(rbtree, right, cb, arg, \ filter_node, filter_subtree, filter_ctx); \ } \ a_attr a_type * \ a_prefix##iter_start_filtered(a_rbt_type *rbtree, a_type *start, \ a_type *node, a_type *(*cb)(a_rbt_type *, a_type *, void *), \ void *arg, bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ if (!filter_subtree(filter_ctx, node)) { \ return NULL; \ } \ int cmp = a_cmp(start, node); \ a_type *ret; \ a_type *left = rbtn_left_get(a_type, a_field, node); \ a_type *right = rbtn_right_get(a_type, a_field, node); \ if (cmp < 0) { \ ret = a_prefix##iter_start_filtered(rbtree, start, left, cb, \ arg, filter_node, filter_subtree, filter_ctx); \ if (ret != NULL) { \ return ret; \ } \ if (filter_node(filter_ctx, node)) { \ ret = cb(rbtree, node, arg); \ if (ret != NULL) { \ return ret; \ } \ } \ return a_prefix##iter_recurse_filtered(rbtree, right, cb, arg, \ filter_node, filter_subtree, filter_ctx); \ } else if (cmp > 0) { \ return a_prefix##iter_start_filtered(rbtree, start, right, \ cb, arg, filter_node, filter_subtree, filter_ctx); \ } else { \ if (filter_node(filter_ctx, node)) { \ ret = cb(rbtree, node, arg); \ if (ret != NULL) { \ return ret; \ } \ } \ return a_prefix##iter_recurse_filtered(rbtree, right, cb, arg, \ filter_node, filter_subtree, filter_ctx); \ } \ } \ a_attr a_type * \ a_prefix##iter_filtered(a_rbt_type *rbtree, a_type *start, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *ret; \ if (start != NULL) { \ ret = a_prefix##iter_start_filtered(rbtree, start, \ rbtree->rbt_root, cb, arg, filter_node, filter_subtree, \ filter_ctx); \ } else { \ ret = a_prefix##iter_recurse_filtered(rbtree, rbtree->rbt_root, \ cb, arg, filter_node, filter_subtree, filter_ctx); \ } \ return ret; \ } \ a_attr a_type * \ a_prefix##reverse_iter_recurse_filtered(a_rbt_type *rbtree, \ a_type *node, a_type *(*cb)(a_rbt_type *, a_type *, void *), \ void *arg, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ if (node == NULL || !filter_subtree(filter_ctx, node)) { \ return NULL; \ } \ a_type *ret; \ a_type *left = rbtn_left_get(a_type, a_field, node); \ a_type *right = rbtn_right_get(a_type, a_field, node); \ ret = a_prefix##reverse_iter_recurse_filtered(rbtree, right, cb, \ arg, filter_node, filter_subtree, filter_ctx); \ if (ret != NULL) { \ return ret; \ } \ if (filter_node(filter_ctx, node)) { \ ret = cb(rbtree, node, arg); \ } \ if (ret != NULL) { \ return ret; \ } \ return a_prefix##reverse_iter_recurse_filtered(rbtree, left, cb, \ arg, filter_node, filter_subtree, filter_ctx); \ } \ a_attr a_type * \ a_prefix##reverse_iter_start_filtered(a_rbt_type *rbtree, a_type *start,\ a_type *node, a_type *(*cb)(a_rbt_type *, a_type *, void *), \ void *arg, bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ if (!filter_subtree(filter_ctx, node)) { \ return NULL; \ } \ int cmp = a_cmp(start, node); \ a_type *ret; \ a_type *left = rbtn_left_get(a_type, a_field, node); \ a_type *right = rbtn_right_get(a_type, a_field, node); \ if (cmp > 0) { \ ret = a_prefix##reverse_iter_start_filtered(rbtree, start, \ right, cb, arg, filter_node, filter_subtree, filter_ctx); \ if (ret != NULL) { \ return ret; \ } \ if (filter_node(filter_ctx, node)) { \ ret = cb(rbtree, node, arg); \ if (ret != NULL) { \ return ret; \ } \ } \ return a_prefix##reverse_iter_recurse_filtered(rbtree, left, cb,\ arg, filter_node, filter_subtree, filter_ctx); \ } else if (cmp < 0) { \ return a_prefix##reverse_iter_start_filtered(rbtree, start, \ left, cb, arg, filter_node, filter_subtree, filter_ctx); \ } else { \ if (filter_node(filter_ctx, node)) { \ ret = cb(rbtree, node, arg); \ if (ret != NULL) { \ return ret; \ } \ } \ return a_prefix##reverse_iter_recurse_filtered(rbtree, left, cb,\ arg, filter_node, filter_subtree, filter_ctx); \ } \ } \ a_attr a_type * \ a_prefix##reverse_iter_filtered(a_rbt_type *rbtree, a_type *start, \ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg, \ bool (*filter_node)(void *, a_type *), \ bool (*filter_subtree)(void *, a_type *), \ void *filter_ctx) { \ a_type *ret; \ if (start != NULL) { \ ret = a_prefix##reverse_iter_start_filtered(rbtree, start, \ rbtree->rbt_root, cb, arg, filter_node, filter_subtree, \ filter_ctx); \ } else { \ ret = a_prefix##reverse_iter_recurse_filtered(rbtree, \ rbtree->rbt_root, cb, arg, filter_node, filter_subtree, \ filter_ctx); \ } \ return ret; \ } \ ) /* end rb_summarized_only */ #endif /* JEMALLOC_INTERNAL_RB_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/rtree.h000066400000000000000000000436571501533116600237070ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_RTREE_H #define JEMALLOC_INTERNAL_RTREE_H #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/rtree_tsd.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/tsd.h" /* * This radix tree implementation is tailored to the singular purpose of * associating metadata with extents that are currently owned by jemalloc. * ******************************************************************************* */ /* Number of high insignificant bits. */ #define RTREE_NHIB ((1U << (LG_SIZEOF_PTR+3)) - LG_VADDR) /* Number of low insigificant bits. */ #define RTREE_NLIB LG_PAGE /* Number of significant bits. */ #define RTREE_NSB (LG_VADDR - RTREE_NLIB) /* Number of levels in radix tree. */ #if RTREE_NSB <= 10 # define RTREE_HEIGHT 1 #elif RTREE_NSB <= 36 # define RTREE_HEIGHT 2 #elif RTREE_NSB <= 52 # define RTREE_HEIGHT 3 #else # error Unsupported number of significant virtual address bits #endif /* Use compact leaf representation if virtual address encoding allows. */ #if RTREE_NHIB >= LG_CEIL(SC_NSIZES) # define RTREE_LEAF_COMPACT #endif typedef struct rtree_node_elm_s rtree_node_elm_t; struct rtree_node_elm_s { atomic_p_t child; /* (rtree_{node,leaf}_elm_t *) */ }; typedef struct rtree_metadata_s rtree_metadata_t; struct rtree_metadata_s { szind_t szind; extent_state_t state; /* Mirrors edata->state. */ bool is_head; /* Mirrors edata->is_head. */ bool slab; }; typedef struct rtree_contents_s rtree_contents_t; struct rtree_contents_s { edata_t *edata; rtree_metadata_t metadata; }; #define RTREE_LEAF_STATE_WIDTH EDATA_BITS_STATE_WIDTH #define RTREE_LEAF_STATE_SHIFT 2 #define RTREE_LEAF_STATE_MASK MASK(RTREE_LEAF_STATE_WIDTH, RTREE_LEAF_STATE_SHIFT) struct rtree_leaf_elm_s { #ifdef RTREE_LEAF_COMPACT /* * Single pointer-width field containing all three leaf element fields. * For example, on a 64-bit x64 system with 48 significant virtual * memory address bits, the index, edata, and slab fields are packed as * such: * * x: index * e: edata * s: state * h: is_head * b: slab * * 00000000 xxxxxxxx eeeeeeee [...] eeeeeeee e00ssshb */ atomic_p_t le_bits; #else atomic_p_t le_edata; /* (edata_t *) */ /* * From high to low bits: szind (8 bits), state (4 bits), is_head, slab */ atomic_u_t le_metadata; #endif }; typedef struct rtree_level_s rtree_level_t; struct rtree_level_s { /* Number of key bits distinguished by this level. */ unsigned bits; /* * Cumulative number of key bits distinguished by traversing to * corresponding tree level. */ unsigned cumbits; }; typedef struct rtree_s rtree_t; struct rtree_s { base_t *base; malloc_mutex_t init_lock; /* Number of elements based on rtree_levels[0].bits. */ #if RTREE_HEIGHT > 1 rtree_node_elm_t root[1U << (RTREE_NSB/RTREE_HEIGHT)]; #else rtree_leaf_elm_t root[1U << (RTREE_NSB/RTREE_HEIGHT)]; #endif }; /* * Split the bits into one to three partitions depending on number of * significant bits. It the number of bits does not divide evenly into the * number of levels, place one remainder bit per level starting at the leaf * level. */ static const rtree_level_t rtree_levels[] = { #if RTREE_HEIGHT == 1 {RTREE_NSB, RTREE_NHIB + RTREE_NSB} #elif RTREE_HEIGHT == 2 {RTREE_NSB/2, RTREE_NHIB + RTREE_NSB/2}, {RTREE_NSB/2 + RTREE_NSB%2, RTREE_NHIB + RTREE_NSB} #elif RTREE_HEIGHT == 3 {RTREE_NSB/3, RTREE_NHIB + RTREE_NSB/3}, {RTREE_NSB/3 + RTREE_NSB%3/2, RTREE_NHIB + RTREE_NSB/3*2 + RTREE_NSB%3/2}, {RTREE_NSB/3 + RTREE_NSB%3 - RTREE_NSB%3/2, RTREE_NHIB + RTREE_NSB} #else # error Unsupported rtree height #endif }; bool rtree_new(rtree_t *rtree, base_t *base, bool zeroed); rtree_leaf_elm_t *rtree_leaf_elm_lookup_hard(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, bool dependent, bool init_missing); JEMALLOC_ALWAYS_INLINE unsigned rtree_leaf_maskbits(void) { unsigned ptrbits = ZU(1) << (LG_SIZEOF_PTR+3); unsigned cumbits = (rtree_levels[RTREE_HEIGHT-1].cumbits - rtree_levels[RTREE_HEIGHT-1].bits); return ptrbits - cumbits; } JEMALLOC_ALWAYS_INLINE uintptr_t rtree_leafkey(uintptr_t key) { uintptr_t mask = ~((ZU(1) << rtree_leaf_maskbits()) - 1); return (key & mask); } JEMALLOC_ALWAYS_INLINE size_t rtree_cache_direct_map(uintptr_t key) { return (size_t)((key >> rtree_leaf_maskbits()) & (RTREE_CTX_NCACHE - 1)); } JEMALLOC_ALWAYS_INLINE uintptr_t rtree_subkey(uintptr_t key, unsigned level) { unsigned ptrbits = ZU(1) << (LG_SIZEOF_PTR+3); unsigned cumbits = rtree_levels[level].cumbits; unsigned shiftbits = ptrbits - cumbits; unsigned maskbits = rtree_levels[level].bits; uintptr_t mask = (ZU(1) << maskbits) - 1; return ((key >> shiftbits) & mask); } /* * Atomic getters. * * dependent: Reading a value on behalf of a pointer to a valid allocation * is guaranteed to be a clean read even without synchronization, * because the rtree update became visible in memory before the * pointer came into existence. * !dependent: An arbitrary read, e.g. on behalf of ivsalloc(), may not be * dependent on a previous rtree write, which means a stale read * could result if synchronization were omitted here. */ # ifdef RTREE_LEAF_COMPACT JEMALLOC_ALWAYS_INLINE uintptr_t rtree_leaf_elm_bits_read(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm, bool dependent) { return (uintptr_t)atomic_load_p(&elm->le_bits, dependent ? ATOMIC_RELAXED : ATOMIC_ACQUIRE); } JEMALLOC_ALWAYS_INLINE uintptr_t rtree_leaf_elm_bits_encode(rtree_contents_t contents) { assert((uintptr_t)contents.edata % (uintptr_t)EDATA_ALIGNMENT == 0); uintptr_t edata_bits = (uintptr_t)contents.edata & (((uintptr_t)1 << LG_VADDR) - 1); uintptr_t szind_bits = (uintptr_t)contents.metadata.szind << LG_VADDR; uintptr_t slab_bits = (uintptr_t)contents.metadata.slab; uintptr_t is_head_bits = (uintptr_t)contents.metadata.is_head << 1; uintptr_t state_bits = (uintptr_t)contents.metadata.state << RTREE_LEAF_STATE_SHIFT; uintptr_t metadata_bits = szind_bits | state_bits | is_head_bits | slab_bits; assert((edata_bits & metadata_bits) == 0); return edata_bits | metadata_bits; } JEMALLOC_ALWAYS_INLINE rtree_contents_t rtree_leaf_elm_bits_decode(uintptr_t bits) { rtree_contents_t contents; /* Do the easy things first. */ contents.metadata.szind = bits >> LG_VADDR; contents.metadata.slab = (bool)(bits & 1); contents.metadata.is_head = (bool)(bits & (1 << 1)); uintptr_t state_bits = (bits & RTREE_LEAF_STATE_MASK) >> RTREE_LEAF_STATE_SHIFT; assert(state_bits <= extent_state_max); contents.metadata.state = (extent_state_t)state_bits; uintptr_t low_bit_mask = ~((uintptr_t)EDATA_ALIGNMENT - 1); # ifdef __aarch64__ /* * aarch64 doesn't sign extend the highest virtual address bit to set * the higher ones. Instead, the high bits get zeroed. */ uintptr_t high_bit_mask = ((uintptr_t)1 << LG_VADDR) - 1; /* Mask off metadata. */ uintptr_t mask = high_bit_mask & low_bit_mask; contents.edata = (edata_t *)(bits & mask); # else /* Restore sign-extended high bits, mask metadata bits. */ contents.edata = (edata_t *)((uintptr_t)((intptr_t)(bits << RTREE_NHIB) >> RTREE_NHIB) & low_bit_mask); # endif assert((uintptr_t)contents.edata % (uintptr_t)EDATA_ALIGNMENT == 0); return contents; } # endif /* RTREE_LEAF_COMPACT */ JEMALLOC_ALWAYS_INLINE rtree_contents_t rtree_leaf_elm_read(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm, bool dependent) { #ifdef RTREE_LEAF_COMPACT uintptr_t bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm, dependent); rtree_contents_t contents = rtree_leaf_elm_bits_decode(bits); return contents; #else rtree_contents_t contents; unsigned metadata_bits = atomic_load_u(&elm->le_metadata, dependent ? ATOMIC_RELAXED : ATOMIC_ACQUIRE); contents.metadata.slab = (bool)(metadata_bits & 1); contents.metadata.is_head = (bool)(metadata_bits & (1 << 1)); uintptr_t state_bits = (metadata_bits & RTREE_LEAF_STATE_MASK) >> RTREE_LEAF_STATE_SHIFT; assert(state_bits <= extent_state_max); contents.metadata.state = (extent_state_t)state_bits; contents.metadata.szind = metadata_bits >> (RTREE_LEAF_STATE_SHIFT + RTREE_LEAF_STATE_WIDTH); contents.edata = (edata_t *)atomic_load_p(&elm->le_edata, dependent ? ATOMIC_RELAXED : ATOMIC_ACQUIRE); return contents; #endif } JEMALLOC_ALWAYS_INLINE void rtree_contents_encode(rtree_contents_t contents, void **bits, unsigned *additional) { #ifdef RTREE_LEAF_COMPACT *bits = (void *)rtree_leaf_elm_bits_encode(contents); #else *additional = (unsigned)contents.metadata.slab | ((unsigned)contents.metadata.is_head << 1) | ((unsigned)contents.metadata.state << RTREE_LEAF_STATE_SHIFT) | ((unsigned)contents.metadata.szind << (RTREE_LEAF_STATE_SHIFT + RTREE_LEAF_STATE_WIDTH)); *bits = contents.edata; #endif } JEMALLOC_ALWAYS_INLINE void rtree_leaf_elm_write_commit(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm, void *bits, unsigned additional) { #ifdef RTREE_LEAF_COMPACT atomic_store_p(&elm->le_bits, bits, ATOMIC_RELEASE); #else atomic_store_u(&elm->le_metadata, additional, ATOMIC_RELEASE); /* * Write edata last, since the element is atomically considered valid * as soon as the edata field is non-NULL. */ atomic_store_p(&elm->le_edata, bits, ATOMIC_RELEASE); #endif } JEMALLOC_ALWAYS_INLINE void rtree_leaf_elm_write(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm, rtree_contents_t contents) { assert((uintptr_t)contents.edata % EDATA_ALIGNMENT == 0); void *bits; unsigned additional; rtree_contents_encode(contents, &bits, &additional); rtree_leaf_elm_write_commit(tsdn, rtree, elm, bits, additional); } /* The state field can be updated independently (and more frequently). */ JEMALLOC_ALWAYS_INLINE void rtree_leaf_elm_state_update(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm1, rtree_leaf_elm_t *elm2, extent_state_t state) { assert(elm1 != NULL); #ifdef RTREE_LEAF_COMPACT uintptr_t bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm1, /* dependent */ true); bits &= ~RTREE_LEAF_STATE_MASK; bits |= state << RTREE_LEAF_STATE_SHIFT; atomic_store_p(&elm1->le_bits, (void *)bits, ATOMIC_RELEASE); if (elm2 != NULL) { atomic_store_p(&elm2->le_bits, (void *)bits, ATOMIC_RELEASE); } #else unsigned bits = atomic_load_u(&elm1->le_metadata, ATOMIC_RELAXED); bits &= ~RTREE_LEAF_STATE_MASK; bits |= state << RTREE_LEAF_STATE_SHIFT; atomic_store_u(&elm1->le_metadata, bits, ATOMIC_RELEASE); if (elm2 != NULL) { atomic_store_u(&elm2->le_metadata, bits, ATOMIC_RELEASE); } #endif } /* * Tries to look up the key in the L1 cache, returning false if there's a hit, or * true if there's a miss. * Key is allowed to be NULL; returns true in this case. */ JEMALLOC_ALWAYS_INLINE bool rtree_leaf_elm_lookup_fast(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, rtree_leaf_elm_t **elm) { size_t slot = rtree_cache_direct_map(key); uintptr_t leafkey = rtree_leafkey(key); assert(leafkey != RTREE_LEAFKEY_INVALID); if (unlikely(rtree_ctx->cache[slot].leafkey != leafkey)) { return true; } rtree_leaf_elm_t *leaf = rtree_ctx->cache[slot].leaf; assert(leaf != NULL); uintptr_t subkey = rtree_subkey(key, RTREE_HEIGHT-1); *elm = &leaf[subkey]; return false; } JEMALLOC_ALWAYS_INLINE rtree_leaf_elm_t * rtree_leaf_elm_lookup(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, bool dependent, bool init_missing) { assert(key != 0); assert(!dependent || !init_missing); size_t slot = rtree_cache_direct_map(key); uintptr_t leafkey = rtree_leafkey(key); assert(leafkey != RTREE_LEAFKEY_INVALID); /* Fast path: L1 direct mapped cache. */ if (likely(rtree_ctx->cache[slot].leafkey == leafkey)) { rtree_leaf_elm_t *leaf = rtree_ctx->cache[slot].leaf; assert(leaf != NULL); uintptr_t subkey = rtree_subkey(key, RTREE_HEIGHT-1); return &leaf[subkey]; } /* * Search the L2 LRU cache. On hit, swap the matching element into the * slot in L1 cache, and move the position in L2 up by 1. */ #define RTREE_CACHE_CHECK_L2(i) do { \ if (likely(rtree_ctx->l2_cache[i].leafkey == leafkey)) { \ rtree_leaf_elm_t *leaf = rtree_ctx->l2_cache[i].leaf; \ assert(leaf != NULL); \ if (i > 0) { \ /* Bubble up by one. */ \ rtree_ctx->l2_cache[i].leafkey = \ rtree_ctx->l2_cache[i - 1].leafkey; \ rtree_ctx->l2_cache[i].leaf = \ rtree_ctx->l2_cache[i - 1].leaf; \ rtree_ctx->l2_cache[i - 1].leafkey = \ rtree_ctx->cache[slot].leafkey; \ rtree_ctx->l2_cache[i - 1].leaf = \ rtree_ctx->cache[slot].leaf; \ } else { \ rtree_ctx->l2_cache[0].leafkey = \ rtree_ctx->cache[slot].leafkey; \ rtree_ctx->l2_cache[0].leaf = \ rtree_ctx->cache[slot].leaf; \ } \ rtree_ctx->cache[slot].leafkey = leafkey; \ rtree_ctx->cache[slot].leaf = leaf; \ uintptr_t subkey = rtree_subkey(key, RTREE_HEIGHT-1); \ return &leaf[subkey]; \ } \ } while (0) /* Check the first cache entry. */ RTREE_CACHE_CHECK_L2(0); /* Search the remaining cache elements. */ for (unsigned i = 1; i < RTREE_CTX_NCACHE_L2; i++) { RTREE_CACHE_CHECK_L2(i); } #undef RTREE_CACHE_CHECK_L2 return rtree_leaf_elm_lookup_hard(tsdn, rtree, rtree_ctx, key, dependent, init_missing); } /* * Returns true on lookup failure. */ static inline bool rtree_read_independent(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, rtree_contents_t *r_contents) { rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, key, /* dependent */ false, /* init_missing */ false); if (elm == NULL) { return true; } *r_contents = rtree_leaf_elm_read(tsdn, rtree, elm, /* dependent */ false); return false; } static inline rtree_contents_t rtree_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key) { rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, key, /* dependent */ true, /* init_missing */ false); assert(elm != NULL); return rtree_leaf_elm_read(tsdn, rtree, elm, /* dependent */ true); } static inline rtree_metadata_t rtree_metadata_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key) { rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, key, /* dependent */ true, /* init_missing */ false); assert(elm != NULL); return rtree_leaf_elm_read(tsdn, rtree, elm, /* dependent */ true).metadata; } /* * Returns true when the request cannot be fulfilled by fastpath. */ static inline bool rtree_metadata_try_read_fast(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, rtree_metadata_t *r_rtree_metadata) { rtree_leaf_elm_t *elm; /* * Should check the bool return value (lookup success or not) instead of * elm == NULL (which will result in an extra branch). This is because * when the cache lookup succeeds, there will never be a NULL pointer * returned (which is unknown to the compiler). */ if (rtree_leaf_elm_lookup_fast(tsdn, rtree, rtree_ctx, key, &elm)) { return true; } assert(elm != NULL); *r_rtree_metadata = rtree_leaf_elm_read(tsdn, rtree, elm, /* dependent */ true).metadata; return false; } JEMALLOC_ALWAYS_INLINE void rtree_write_range_impl(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t base, uintptr_t end, rtree_contents_t contents, bool clearing) { assert((base & PAGE_MASK) == 0 && (end & PAGE_MASK) == 0); /* * Only used for emap_(de)register_interior, which implies the * boundaries have been registered already. Therefore all the lookups * are dependent w/o init_missing, assuming the range spans across at * most 2 rtree leaf nodes (each covers 1 GiB of vaddr). */ void *bits; unsigned additional; rtree_contents_encode(contents, &bits, &additional); rtree_leaf_elm_t *elm = NULL; /* Dead store. */ for (uintptr_t addr = base; addr <= end; addr += PAGE) { if (addr == base || (addr & ((ZU(1) << rtree_leaf_maskbits()) - 1)) == 0) { elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, addr, /* dependent */ true, /* init_missing */ false); assert(elm != NULL); } assert(elm == rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, addr, /* dependent */ true, /* init_missing */ false)); assert(!clearing || rtree_leaf_elm_read(tsdn, rtree, elm, /* dependent */ true).edata != NULL); rtree_leaf_elm_write_commit(tsdn, rtree, elm, bits, additional); elm++; } } JEMALLOC_ALWAYS_INLINE void rtree_write_range(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t base, uintptr_t end, rtree_contents_t contents) { rtree_write_range_impl(tsdn, rtree, rtree_ctx, base, end, contents, /* clearing */ false); } JEMALLOC_ALWAYS_INLINE bool rtree_write(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, rtree_contents_t contents) { rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, key, /* dependent */ false, /* init_missing */ true); if (elm == NULL) { return true; } rtree_leaf_elm_write(tsdn, rtree, elm, contents); return false; } static inline void rtree_clear(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key) { rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx, key, /* dependent */ true, /* init_missing */ false); assert(elm != NULL); assert(rtree_leaf_elm_read(tsdn, rtree, elm, /* dependent */ true).edata != NULL); rtree_contents_t contents; contents.edata = NULL; contents.metadata.szind = SC_NSIZES; contents.metadata.slab = false; contents.metadata.is_head = false; contents.metadata.state = (extent_state_t)0; rtree_leaf_elm_write(tsdn, rtree, elm, contents); } static inline void rtree_clear_range(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t base, uintptr_t end) { rtree_contents_t contents; contents.edata = NULL; contents.metadata.szind = SC_NSIZES; contents.metadata.slab = false; contents.metadata.is_head = false; contents.metadata.state = (extent_state_t)0; rtree_write_range_impl(tsdn, rtree, rtree_ctx, base, end, contents, /* clearing */ true); } #endif /* JEMALLOC_INTERNAL_RTREE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/rtree_tsd.h000066400000000000000000000047641501533116600245550ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_RTREE_CTX_H #define JEMALLOC_INTERNAL_RTREE_CTX_H /* * Number of leafkey/leaf pairs to cache in L1 and L2 level respectively. Each * entry supports an entire leaf, so the cache hit rate is typically high even * with a small number of entries. In rare cases extent activity will straddle * the boundary between two leaf nodes. Furthermore, an arena may use a * combination of dss and mmap. Note that as memory usage grows past the amount * that this cache can directly cover, the cache will become less effective if * locality of reference is low, but the consequence is merely cache misses * while traversing the tree nodes. * * The L1 direct mapped cache offers consistent and low cost on cache hit. * However collision could affect hit rate negatively. This is resolved by * combining with a L2 LRU cache, which requires linear search and re-ordering * on access but suffers no collision. Note that, the cache will itself suffer * cache misses if made overly large, plus the cost of linear search in the LRU * cache. */ #define RTREE_CTX_NCACHE 16 #define RTREE_CTX_NCACHE_L2 8 /* Needed for initialization only. */ #define RTREE_LEAFKEY_INVALID ((uintptr_t)1) #define RTREE_CTX_CACHE_ELM_INVALID {RTREE_LEAFKEY_INVALID, NULL} #define RTREE_CTX_INIT_ELM_1 RTREE_CTX_CACHE_ELM_INVALID #define RTREE_CTX_INIT_ELM_2 RTREE_CTX_INIT_ELM_1, RTREE_CTX_INIT_ELM_1 #define RTREE_CTX_INIT_ELM_4 RTREE_CTX_INIT_ELM_2, RTREE_CTX_INIT_ELM_2 #define RTREE_CTX_INIT_ELM_8 RTREE_CTX_INIT_ELM_4, RTREE_CTX_INIT_ELM_4 #define RTREE_CTX_INIT_ELM_16 RTREE_CTX_INIT_ELM_8, RTREE_CTX_INIT_ELM_8 #define _RTREE_CTX_INIT_ELM_DATA(n) RTREE_CTX_INIT_ELM_##n #define RTREE_CTX_INIT_ELM_DATA(n) _RTREE_CTX_INIT_ELM_DATA(n) /* * Static initializer (to invalidate the cache entries) is required because the * free fastpath may access the rtree cache before a full tsd initialization. */ #define RTREE_CTX_INITIALIZER {{RTREE_CTX_INIT_ELM_DATA(RTREE_CTX_NCACHE)}, \ {RTREE_CTX_INIT_ELM_DATA(RTREE_CTX_NCACHE_L2)}} typedef struct rtree_leaf_elm_s rtree_leaf_elm_t; typedef struct rtree_ctx_cache_elm_s rtree_ctx_cache_elm_t; struct rtree_ctx_cache_elm_s { uintptr_t leafkey; rtree_leaf_elm_t *leaf; }; typedef struct rtree_ctx_s rtree_ctx_t; struct rtree_ctx_s { /* Direct mapped cache. */ rtree_ctx_cache_elm_t cache[RTREE_CTX_NCACHE]; /* L2 LRU cache. */ rtree_ctx_cache_elm_t l2_cache[RTREE_CTX_NCACHE_L2]; }; void rtree_ctx_data_init(rtree_ctx_t *ctx); #endif /* JEMALLOC_INTERNAL_RTREE_CTX_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/safety_check.h000066400000000000000000000017451501533116600252060ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SAFETY_CHECK_H #define JEMALLOC_INTERNAL_SAFETY_CHECK_H void safety_check_fail_sized_dealloc(bool current_dealloc, const void *ptr, size_t true_size, size_t input_size); void safety_check_fail(const char *format, ...); typedef void (*safety_check_abort_hook_t)(const char *message); /* Can set to NULL for a default. */ void safety_check_set_abort(safety_check_abort_hook_t abort_fn); JEMALLOC_ALWAYS_INLINE void safety_check_set_redzone(void *ptr, size_t usize, size_t bumped_usize) { assert(usize < bumped_usize); for (size_t i = usize; i < bumped_usize && i < usize + 32; ++i) { *((unsigned char *)ptr + i) = 0xBC; } } JEMALLOC_ALWAYS_INLINE void safety_check_verify_redzone(const void *ptr, size_t usize, size_t bumped_usize) { for (size_t i = usize; i < bumped_usize && i < usize + 32; ++i) { if (unlikely(*((unsigned char *)ptr + i) != 0xBC)) { safety_check_fail("Use after free error\n"); } } } #endif /*JEMALLOC_INTERNAL_SAFETY_CHECK_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/san.h000066400000000000000000000127111501533116600233320ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_GUARD_H #define JEMALLOC_INTERNAL_GUARD_H #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/emap.h" #define SAN_PAGE_GUARD PAGE #define SAN_PAGE_GUARDS_SIZE (SAN_PAGE_GUARD * 2) #define SAN_GUARD_LARGE_EVERY_N_EXTENTS_DEFAULT 0 #define SAN_GUARD_SMALL_EVERY_N_EXTENTS_DEFAULT 0 #define SAN_LG_UAF_ALIGN_DEFAULT (-1) #define SAN_CACHE_BIN_NONFAST_MASK_DEFAULT (uintptr_t)(-1) static const uintptr_t uaf_detect_junk = (uintptr_t)0x5b5b5b5b5b5b5b5bULL; /* 0 means disabled, i.e. never guarded. */ extern size_t opt_san_guard_large; extern size_t opt_san_guard_small; /* -1 means disabled, i.e. never check for use-after-free. */ extern ssize_t opt_lg_san_uaf_align; void san_guard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, bool left, bool right, bool remap); void san_unguard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, bool left, bool right); /* * Unguard the extent, but don't modify emap boundaries. Must be called on an * extent that has been erased from emap and shouldn't be placed back. */ void san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap); void san_check_stashed_ptrs(void **ptrs, size_t nstashed, size_t usize); void tsd_san_init(tsd_t *tsd); void san_init(ssize_t lg_san_uaf_align); static inline void san_guard_pages_two_sided(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, bool remap) { san_guard_pages(tsdn, ehooks, edata, emap, true, true, remap); } static inline void san_unguard_pages_two_sided(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap) { san_unguard_pages(tsdn, ehooks, edata, emap, true, true); } static inline size_t san_two_side_unguarded_sz(size_t size) { assert(size % PAGE == 0); assert(size >= SAN_PAGE_GUARDS_SIZE); return size - SAN_PAGE_GUARDS_SIZE; } static inline size_t san_two_side_guarded_sz(size_t size) { assert(size % PAGE == 0); return size + SAN_PAGE_GUARDS_SIZE; } static inline size_t san_one_side_unguarded_sz(size_t size) { assert(size % PAGE == 0); assert(size >= SAN_PAGE_GUARD); return size - SAN_PAGE_GUARD; } static inline size_t san_one_side_guarded_sz(size_t size) { assert(size % PAGE == 0); return size + SAN_PAGE_GUARD; } static inline bool san_guard_enabled(void) { return (opt_san_guard_large != 0 || opt_san_guard_small != 0); } static inline bool san_large_extent_decide_guard(tsdn_t *tsdn, ehooks_t *ehooks, size_t size, size_t alignment) { if (opt_san_guard_large == 0 || ehooks_guard_will_fail(ehooks) || tsdn_null(tsdn)) { return false; } tsd_t *tsd = tsdn_tsd(tsdn); uint64_t n = tsd_san_extents_until_guard_large_get(tsd); assert(n >= 1); if (n > 1) { /* * Subtract conditionally because the guard may not happen due * to alignment or size restriction below. */ *tsd_san_extents_until_guard_largep_get(tsd) = n - 1; } if (n == 1 && (alignment <= PAGE) && (san_two_side_guarded_sz(size) <= SC_LARGE_MAXCLASS)) { *tsd_san_extents_until_guard_largep_get(tsd) = opt_san_guard_large; return true; } else { assert(tsd_san_extents_until_guard_large_get(tsd) >= 1); return false; } } static inline bool san_slab_extent_decide_guard(tsdn_t *tsdn, ehooks_t *ehooks) { if (opt_san_guard_small == 0 || ehooks_guard_will_fail(ehooks) || tsdn_null(tsdn)) { return false; } tsd_t *tsd = tsdn_tsd(tsdn); uint64_t n = tsd_san_extents_until_guard_small_get(tsd); assert(n >= 1); if (n == 1) { *tsd_san_extents_until_guard_smallp_get(tsd) = opt_san_guard_small; return true; } else { *tsd_san_extents_until_guard_smallp_get(tsd) = n - 1; assert(tsd_san_extents_until_guard_small_get(tsd) >= 1); return false; } } static inline void san_junk_ptr_locations(void *ptr, size_t usize, void **first, void **mid, void **last) { size_t ptr_sz = sizeof(void *); *first = ptr; *mid = (void *)((uintptr_t)ptr + ((usize >> 1) & ~(ptr_sz - 1))); assert(*first != *mid || usize == ptr_sz); assert((uintptr_t)*first <= (uintptr_t)*mid); /* * When usize > 32K, the gap between requested_size and usize might be * greater than 4K -- this means the last write may access an * likely-untouched page (default settings w/ 4K pages). However by * default the tcache only goes up to the 32K size class, and is usually * tuned lower instead of higher, which makes it less of a concern. */ *last = (void *)((uintptr_t)ptr + usize - sizeof(uaf_detect_junk)); assert(*first != *last || usize == ptr_sz); assert(*mid != *last || usize <= ptr_sz * 2); assert((uintptr_t)*mid <= (uintptr_t)*last); } static inline bool san_junk_ptr_should_slow(void) { /* * The latter condition (pointer size greater than the min size class) * is not expected -- fall back to the slow path for simplicity. */ return config_debug || (LG_SIZEOF_PTR > SC_LG_TINY_MIN); } static inline void san_junk_ptr(void *ptr, size_t usize) { if (san_junk_ptr_should_slow()) { memset(ptr, (char)uaf_detect_junk, usize); return; } void *first, *mid, *last; san_junk_ptr_locations(ptr, usize, &first, &mid, &last); *(uintptr_t *)first = uaf_detect_junk; *(uintptr_t *)mid = uaf_detect_junk; *(uintptr_t *)last = uaf_detect_junk; } static inline bool san_uaf_detection_enabled(void) { bool ret = config_uaf_detection && (opt_lg_san_uaf_align != -1); if (config_uaf_detection && ret) { assert(san_cache_bin_nonfast_mask == ((uintptr_t)1 << opt_lg_san_uaf_align) - 1); } return ret; } #endif /* JEMALLOC_INTERNAL_GUARD_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/san_bump.h000066400000000000000000000026511501533116600243570ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SAN_BUMP_H #define JEMALLOC_INTERNAL_SAN_BUMP_H #include "jemalloc/internal/edata.h" #include "jemalloc/internal/exp_grow.h" #include "jemalloc/internal/mutex.h" #define SBA_RETAINED_ALLOC_SIZE ((size_t)4 << 20) extern bool opt_retain; typedef struct ehooks_s ehooks_t; typedef struct pac_s pac_t; typedef struct san_bump_alloc_s san_bump_alloc_t; struct san_bump_alloc_s { malloc_mutex_t mtx; edata_t *curr_reg; }; static inline bool san_bump_enabled() { /* * We enable san_bump allocator only when it's possible to break up a * mapping and unmap a part of it (maps_coalesce). This is needed to * ensure the arena destruction process can destroy all retained guarded * extents one by one and to unmap a trailing part of a retained guarded * region when it's too small to fit a pending allocation. * opt_retain is required, because this allocator retains a large * virtual memory mapping and returns smaller parts of it. */ return maps_coalesce && opt_retain; } static inline bool san_bump_alloc_init(san_bump_alloc_t* sba) { bool err = malloc_mutex_init(&sba->mtx, "sanitizer_bump_allocator", WITNESS_RANK_SAN_BUMP_ALLOC, malloc_mutex_rank_exclusive); if (err) { return true; } sba->curr_reg = NULL; return false; } edata_t * san_bump_alloc(tsdn_t *tsdn, san_bump_alloc_t* sba, pac_t *pac, ehooks_t *ehooks, size_t size, bool zero); #endif /* JEMALLOC_INTERNAL_SAN_BUMP_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/sc.h000066400000000000000000000333121501533116600231560ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SC_H #define JEMALLOC_INTERNAL_SC_H #include "jemalloc/internal/jemalloc_internal_types.h" /* * Size class computations: * * These are a little tricky; we'll first start by describing how things * generally work, and then describe some of the details. * * Ignore the first few size classes for a moment. We can then split all the * remaining size classes into groups. The size classes in a group are spaced * such that they cover allocation request sizes in a power-of-2 range. The * power of two is called the base of the group, and the size classes in it * satisfy allocations in the half-open range (base, base * 2]. There are * SC_NGROUP size classes in each group, equally spaced in the range, so that * each one covers allocations for base / SC_NGROUP possible allocation sizes. * We call that value (base / SC_NGROUP) the delta of the group. Each size class * is delta larger than the one before it (including the initial size class in a * group, which is delta larger than base, the largest size class in the * previous group). * To make the math all work out nicely, we require that SC_NGROUP is a power of * two, and define it in terms of SC_LG_NGROUP. We'll often talk in terms of * lg_base and lg_delta. For each of these groups then, we have that * lg_delta == lg_base - SC_LG_NGROUP. * The size classes in a group with a given lg_base and lg_delta (which, recall, * can be computed from lg_base for these groups) are therefore: * base + 1 * delta * which covers allocations in (base, base + 1 * delta] * base + 2 * delta * which covers allocations in (base + 1 * delta, base + 2 * delta]. * base + 3 * delta * which covers allocations in (base + 2 * delta, base + 3 * delta]. * ... * base + SC_NGROUP * delta ( == 2 * base) * which covers allocations in (base + (SC_NGROUP - 1) * delta, 2 * base]. * (Note that currently SC_NGROUP is always 4, so the "..." is empty in * practice.) * Note that the last size class in the group is the next power of two (after * base), so that we've set up the induction correctly for the next group's * selection of delta. * * Now, let's start considering the first few size classes. Two extra constants * come into play here: LG_QUANTUM and SC_LG_TINY_MIN. LG_QUANTUM ensures * correct platform alignment; all objects of size (1 << LG_QUANTUM) or larger * are at least (1 << LG_QUANTUM) aligned; this can be used to ensure that we * never return improperly aligned memory, by making (1 << LG_QUANTUM) equal the * highest required alignment of a platform. For allocation sizes smaller than * (1 << LG_QUANTUM) though, we can be more relaxed (since we don't support * platforms with types with alignment larger than their size). To allow such * allocations (without wasting space unnecessarily), we introduce tiny size * classes; one per power of two, up until we hit the quantum size. There are * therefore LG_QUANTUM - SC_LG_TINY_MIN such size classes. * * Next, we have a size class of size (1 << LG_QUANTUM). This can't be the * start of a group in the sense we described above (covering a power of two * range) since, if we divided into it to pick a value of delta, we'd get a * delta smaller than (1 << LG_QUANTUM) for sizes >= (1 << LG_QUANTUM), which * is against the rules. * * The first base we can divide by SC_NGROUP while still being at least * (1 << LG_QUANTUM) is SC_NGROUP * (1 << LG_QUANTUM). We can get there by * having SC_NGROUP size classes, spaced (1 << LG_QUANTUM) apart. These size * classes are: * 1 * (1 << LG_QUANTUM) * 2 * (1 << LG_QUANTUM) * 3 * (1 << LG_QUANTUM) * ... (although, as above, this "..." is empty in practice) * SC_NGROUP * (1 << LG_QUANTUM). * * There are SC_NGROUP of these size classes, so we can regard it as a sort of * pseudo-group, even though it spans multiple powers of 2, is divided * differently, and both starts and ends on a power of 2 (as opposed to just * ending). SC_NGROUP is itself a power of two, so the first group after the * pseudo-group has the power-of-two base SC_NGROUP * (1 << LG_QUANTUM), for a * lg_base of LG_QUANTUM + SC_LG_NGROUP. We can divide this base into SC_NGROUP * sizes without violating our LG_QUANTUM requirements, so we can safely set * lg_delta = lg_base - SC_LG_GROUP (== LG_QUANTUM). * * So, in order, the size classes are: * * Tiny size classes: * - Count: LG_QUANTUM - SC_LG_TINY_MIN. * - Sizes: * 1 << SC_LG_TINY_MIN * 1 << (SC_LG_TINY_MIN + 1) * 1 << (SC_LG_TINY_MIN + 2) * ... * 1 << (LG_QUANTUM - 1) * * Initial pseudo-group: * - Count: SC_NGROUP * - Sizes: * 1 * (1 << LG_QUANTUM) * 2 * (1 << LG_QUANTUM) * 3 * (1 << LG_QUANTUM) * ... * SC_NGROUP * (1 << LG_QUANTUM) * * Regular group 0: * - Count: SC_NGROUP * - Sizes: * (relative to lg_base of LG_QUANTUM + SC_LG_NGROUP and lg_delta of * lg_base - SC_LG_NGROUP) * (1 << lg_base) + 1 * (1 << lg_delta) * (1 << lg_base) + 2 * (1 << lg_delta) * (1 << lg_base) + 3 * (1 << lg_delta) * ... * (1 << lg_base) + SC_NGROUP * (1 << lg_delta) [ == (1 << (lg_base + 1)) ] * * Regular group 1: * - Count: SC_NGROUP * - Sizes: * (relative to lg_base of LG_QUANTUM + SC_LG_NGROUP + 1 and lg_delta of * lg_base - SC_LG_NGROUP) * (1 << lg_base) + 1 * (1 << lg_delta) * (1 << lg_base) + 2 * (1 << lg_delta) * (1 << lg_base) + 3 * (1 << lg_delta) * ... * (1 << lg_base) + SC_NGROUP * (1 << lg_delta) [ == (1 << (lg_base + 1)) ] * * ... * * Regular group N: * - Count: SC_NGROUP * - Sizes: * (relative to lg_base of LG_QUANTUM + SC_LG_NGROUP + N and lg_delta of * lg_base - SC_LG_NGROUP) * (1 << lg_base) + 1 * (1 << lg_delta) * (1 << lg_base) + 2 * (1 << lg_delta) * (1 << lg_base) + 3 * (1 << lg_delta) * ... * (1 << lg_base) + SC_NGROUP * (1 << lg_delta) [ == (1 << (lg_base + 1)) ] * * * Representation of metadata: * To make the math easy, we'll mostly work in lg quantities. We record lg_base, * lg_delta, and ndelta (i.e. number of deltas above the base) on a * per-size-class basis, and maintain the invariant that, across all size * classes, size == (1 << lg_base) + ndelta * (1 << lg_delta). * * For regular groups (i.e. those with lg_base >= LG_QUANTUM + SC_LG_NGROUP), * lg_delta is lg_base - SC_LG_NGROUP, and ndelta goes from 1 to SC_NGROUP. * * For the initial tiny size classes (if any), lg_base is lg(size class size). * lg_delta is lg_base for the first size class, and lg_base - 1 for all * subsequent ones. ndelta is always 0. * * For the pseudo-group, if there are no tiny size classes, then we set * lg_base == LG_QUANTUM, lg_delta == LG_QUANTUM, and have ndelta range from 0 * to SC_NGROUP - 1. (Note that delta == base, so base + (SC_NGROUP - 1) * delta * is just SC_NGROUP * base, or (1 << (SC_LG_NGROUP + LG_QUANTUM)), so we do * indeed get a power of two that way). If there *are* tiny size classes, then * the first size class needs to have lg_delta relative to the largest tiny size * class. We therefore set lg_base == LG_QUANTUM - 1, * lg_delta == LG_QUANTUM - 1, and ndelta == 1, keeping the rest of the * pseudo-group the same. * * * Other terminology: * "Small" size classes mean those that are allocated out of bins, which is the * same as those that are slab allocated. * "Large" size classes are those that are not small. The cutoff for counting as * large is page size * group size. */ /* * Size class N + (1 << SC_LG_NGROUP) twice the size of size class N. */ #define SC_LG_NGROUP 2 #define SC_LG_TINY_MIN 3 #if SC_LG_TINY_MIN == 0 /* The div module doesn't support division by 1, which this would require. */ #error "Unsupported LG_TINY_MIN" #endif /* * The definitions below are all determined by the above settings and system * characteristics. */ #define SC_NGROUP (1ULL << SC_LG_NGROUP) #define SC_PTR_BITS ((1ULL << LG_SIZEOF_PTR) * 8) #define SC_NTINY (LG_QUANTUM - SC_LG_TINY_MIN) #define SC_LG_TINY_MAXCLASS (LG_QUANTUM > SC_LG_TINY_MIN ? LG_QUANTUM - 1 : -1) #define SC_NPSEUDO SC_NGROUP #define SC_LG_FIRST_REGULAR_BASE (LG_QUANTUM + SC_LG_NGROUP) /* * We cap allocations to be less than 2 ** (ptr_bits - 1), so the highest base * we need is 2 ** (ptr_bits - 2). (This also means that the last group is 1 * size class shorter than the others). * We could probably save some space in arenas by capping this at LG_VADDR size. */ #define SC_LG_BASE_MAX (SC_PTR_BITS - 2) #define SC_NREGULAR (SC_NGROUP * \ (SC_LG_BASE_MAX - SC_LG_FIRST_REGULAR_BASE + 1) - 1) #define SC_NSIZES (SC_NTINY + SC_NPSEUDO + SC_NREGULAR) /* * The number of size classes that are a multiple of the page size. * * Here are the first few bases that have a page-sized SC. * * lg(base) | base | highest SC | page-multiple SCs * --------------|------------------------------------------ * LG_PAGE - 1 | PAGE / 2 | PAGE | 1 * LG_PAGE | PAGE | 2 * PAGE | 1 * LG_PAGE + 1 | 2 * PAGE | 4 * PAGE | 2 * LG_PAGE + 2 | 4 * PAGE | 8 * PAGE | 4 * * The number of page-multiple SCs continues to grow in powers of two, up until * lg_delta == lg_page, which corresponds to setting lg_base to lg_page + * SC_LG_NGROUP. So, then, the number of size classes that are multiples of the * page size whose lg_delta is less than the page size are * is 1 + (2**0 + 2**1 + ... + 2**(lg_ngroup - 1) == 2**lg_ngroup. * * For each base with lg_base in [lg_page + lg_ngroup, lg_base_max), there are * NGROUP page-sized size classes, and when lg_base == lg_base_max, there are * NGROUP - 1. * * This gives us the quantity we seek. */ #define SC_NPSIZES ( \ SC_NGROUP \ + (SC_LG_BASE_MAX - (LG_PAGE + SC_LG_NGROUP)) * SC_NGROUP \ + SC_NGROUP - 1) /* * We declare a size class is binnable if size < page size * group. Or, in other * words, lg(size) < lg(page size) + lg(group size). */ #define SC_NBINS ( \ /* Sub-regular size classes. */ \ SC_NTINY + SC_NPSEUDO \ /* Groups with lg_regular_min_base <= lg_base <= lg_base_max */ \ + SC_NGROUP * (LG_PAGE + SC_LG_NGROUP - SC_LG_FIRST_REGULAR_BASE) \ /* Last SC of the last group hits the bound exactly; exclude it. */ \ - 1) /* * The size2index_tab lookup table uses uint8_t to encode each bin index, so we * cannot support more than 256 small size classes. */ #if (SC_NBINS > 256) # error "Too many small size classes" #endif /* The largest size class in the lookup table, and its binary log. */ #define SC_LG_MAX_LOOKUP 12 #define SC_LOOKUP_MAXCLASS (1 << SC_LG_MAX_LOOKUP) /* Internal, only used for the definition of SC_SMALL_MAXCLASS. */ #define SC_SMALL_MAX_BASE (1 << (LG_PAGE + SC_LG_NGROUP - 1)) #define SC_SMALL_MAX_DELTA (1 << (LG_PAGE - 1)) /* The largest size class allocated out of a slab. */ #define SC_SMALL_MAXCLASS (SC_SMALL_MAX_BASE \ + (SC_NGROUP - 1) * SC_SMALL_MAX_DELTA) /* The fastpath assumes all lookup-able sizes are small. */ #if (SC_SMALL_MAXCLASS < SC_LOOKUP_MAXCLASS) # error "Lookup table sizes must be small" #endif /* The smallest size class not allocated out of a slab. */ #define SC_LARGE_MINCLASS ((size_t)1ULL << (LG_PAGE + SC_LG_NGROUP)) #define SC_LG_LARGE_MINCLASS (LG_PAGE + SC_LG_NGROUP) /* Internal; only used for the definition of SC_LARGE_MAXCLASS. */ #define SC_MAX_BASE ((size_t)1 << (SC_PTR_BITS - 2)) #define SC_MAX_DELTA ((size_t)1 << (SC_PTR_BITS - 2 - SC_LG_NGROUP)) /* The largest size class supported. */ #define SC_LARGE_MAXCLASS (SC_MAX_BASE + (SC_NGROUP - 1) * SC_MAX_DELTA) /* Maximum number of regions in one slab. */ #ifndef CONFIG_LG_SLAB_MAXREGS # define SC_LG_SLAB_MAXREGS (LG_PAGE - SC_LG_TINY_MIN) #else # if CONFIG_LG_SLAB_MAXREGS < (LG_PAGE - SC_LG_TINY_MIN) # error "Unsupported SC_LG_SLAB_MAXREGS" # else # define SC_LG_SLAB_MAXREGS CONFIG_LG_SLAB_MAXREGS # endif #endif #define SC_SLAB_MAXREGS (1U << SC_LG_SLAB_MAXREGS) typedef struct sc_s sc_t; struct sc_s { /* Size class index, or -1 if not a valid size class. */ int index; /* Lg group base size (no deltas added). */ int lg_base; /* Lg delta to previous size class. */ int lg_delta; /* Delta multiplier. size == 1<bytes += src->bytes; } /* A collections of free extents, all of the same size. */ typedef struct sec_bin_s sec_bin_t; struct sec_bin_s { /* * When we fail to fulfill an allocation, we do a batch-alloc on the * underlying allocator to fill extra items, as well. We drop the SEC * lock while doing so, to allow operations on other bins to succeed. * That introduces the possibility of other threads also trying to * allocate out of this bin, failing, and also going to the backing * allocator. To avoid a thundering herd problem in which lots of * threads do batch allocs and overfill this bin as a result, we only * allow one batch allocation at a time for a bin. This bool tracks * whether or not some thread is already batch allocating. * * Eventually, the right answer may be a smarter sharding policy for the * bins (e.g. a mutex per bin, which would also be more scalable * generally; the batch-allocating thread could hold it while * batch-allocating). */ bool being_batch_filled; /* * Number of bytes in this particular bin (as opposed to the * sec_shard_t's bytes_cur. This isn't user visible or reported in * stats; rather, it allows us to quickly determine the change in the * centralized counter when flushing. */ size_t bytes_cur; edata_list_active_t freelist; }; typedef struct sec_shard_s sec_shard_t; struct sec_shard_s { /* * We don't keep per-bin mutexes, even though that would allow more * sharding; this allows global cache-eviction, which in turn allows for * better balancing across free lists. */ malloc_mutex_t mtx; /* * A SEC may need to be shut down (i.e. flushed of its contents and * prevented from further caching). To avoid tricky synchronization * issues, we just track enabled-status in each shard, guarded by a * mutex. In practice, this is only ever checked during brief races, * since the arena-level atomic boolean tracking HPA enabled-ness means * that we won't go down these pathways very often after custom extent * hooks are installed. */ bool enabled; sec_bin_t *bins; /* Number of bytes in all bins in the shard. */ size_t bytes_cur; /* The next pszind to flush in the flush-some pathways. */ pszind_t to_flush_next; }; typedef struct sec_s sec_t; struct sec_s { pai_t pai; pai_t *fallback; sec_opts_t opts; sec_shard_t *shards; pszind_t npsizes; }; bool sec_init(tsdn_t *tsdn, sec_t *sec, base_t *base, pai_t *fallback, const sec_opts_t *opts); void sec_flush(tsdn_t *tsdn, sec_t *sec); void sec_disable(tsdn_t *tsdn, sec_t *sec); /* * Morally, these two stats methods probably ought to be a single one (and the * mutex_prof_data ought to live in the sec_stats_t. But splitting them apart * lets them fit easily into the pa_shard stats framework (which also has this * split), which simplifies the stats management. */ void sec_stats_merge(tsdn_t *tsdn, sec_t *sec, sec_stats_t *stats); void sec_mutex_stats_read(tsdn_t *tsdn, sec_t *sec, mutex_prof_data_t *mutex_prof_data); /* * We use the arena lock ordering; these are acquired in phase 2 of forking, but * should be acquired before the underlying allocator mutexes. */ void sec_prefork2(tsdn_t *tsdn, sec_t *sec); void sec_postfork_parent(tsdn_t *tsdn, sec_t *sec); void sec_postfork_child(tsdn_t *tsdn, sec_t *sec); #endif /* JEMALLOC_INTERNAL_SEC_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/sec_opts.h000066400000000000000000000035061501533116600243720ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SEC_OPTS_H #define JEMALLOC_INTERNAL_SEC_OPTS_H /* * The configuration settings used by an sec_t. Morally, this is part of the * SEC interface, but we put it here for header-ordering reasons. */ typedef struct sec_opts_s sec_opts_t; struct sec_opts_s { /* * We don't necessarily always use all the shards; requests are * distributed across shards [0, nshards - 1). */ size_t nshards; /* * We'll automatically refuse to cache any objects in this sec if * they're larger than max_alloc bytes, instead forwarding such objects * directly to the fallback. */ size_t max_alloc; /* * Exceeding this amount of cached extents in a shard causes us to start * flushing bins in that shard until we fall below bytes_after_flush. */ size_t max_bytes; /* * The number of bytes (in all bins) we flush down to when we exceed * bytes_cur. We want this to be less than bytes_cur, because * otherwise we could get into situations where a shard undergoing * net-deallocation keeps bytes_cur very near to max_bytes, so that * most deallocations get immediately forwarded to the underlying PAI * implementation, defeating the point of the SEC. */ size_t bytes_after_flush; /* * When we can't satisfy an allocation out of the SEC because there are * no available ones cached, we allocate multiple of that size out of * the fallback allocator. Eventually we might want to do something * cleverer, but for now we just grab a fixed number. */ size_t batch_fill_extra; }; #define SEC_OPTS_DEFAULT { \ /* nshards */ \ 4, \ /* max_alloc */ \ (32 * 1024) < PAGE ? PAGE : (32 * 1024), \ /* max_bytes */ \ 256 * 1024, \ /* bytes_after_flush */ \ 128 * 1024, \ /* batch_fill_extra */ \ 0 \ } #endif /* JEMALLOC_INTERNAL_SEC_OPTS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/seq.h000066400000000000000000000034711501533116600233440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SEQ_H #define JEMALLOC_INTERNAL_SEQ_H #include "jemalloc/internal/atomic.h" /* * A simple seqlock implementation. */ #define seq_define(type, short_type) \ typedef struct { \ atomic_zu_t seq; \ atomic_zu_t data[ \ (sizeof(type) + sizeof(size_t) - 1) / sizeof(size_t)]; \ } seq_##short_type##_t; \ \ /* \ * No internal synchronization -- the caller must ensure that there's \ * only a single writer at a time. \ */ \ static inline void \ seq_store_##short_type(seq_##short_type##_t *dst, type *src) { \ size_t buf[sizeof(dst->data) / sizeof(size_t)]; \ buf[sizeof(buf) / sizeof(size_t) - 1] = 0; \ memcpy(buf, src, sizeof(type)); \ size_t old_seq = atomic_load_zu(&dst->seq, ATOMIC_RELAXED); \ atomic_store_zu(&dst->seq, old_seq + 1, ATOMIC_RELAXED); \ atomic_fence(ATOMIC_RELEASE); \ for (size_t i = 0; i < sizeof(buf) / sizeof(size_t); i++) { \ atomic_store_zu(&dst->data[i], buf[i], ATOMIC_RELAXED); \ } \ atomic_store_zu(&dst->seq, old_seq + 2, ATOMIC_RELEASE); \ } \ \ /* Returns whether or not the read was consistent. */ \ static inline bool \ seq_try_load_##short_type(type *dst, seq_##short_type##_t *src) { \ size_t buf[sizeof(src->data) / sizeof(size_t)]; \ size_t seq1 = atomic_load_zu(&src->seq, ATOMIC_ACQUIRE); \ if (seq1 % 2 != 0) { \ return false; \ } \ for (size_t i = 0; i < sizeof(buf) / sizeof(size_t); i++) { \ buf[i] = atomic_load_zu(&src->data[i], ATOMIC_RELAXED); \ } \ atomic_fence(ATOMIC_ACQUIRE); \ size_t seq2 = atomic_load_zu(&src->seq, ATOMIC_RELAXED); \ if (seq1 != seq2) { \ return false; \ } \ memcpy(dst, buf, sizeof(type)); \ return true; \ } #endif /* JEMALLOC_INTERNAL_SEQ_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/slab_data.h000066400000000000000000000004651501533116600244660ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SLAB_DATA_H #define JEMALLOC_INTERNAL_SLAB_DATA_H #include "jemalloc/internal/bitmap.h" typedef struct slab_data_s slab_data_t; struct slab_data_s { /* Per region allocated/deallocated bitmap. */ bitmap_t bitmap[BITMAP_GROUPS_MAX]; }; #endif /* JEMALLOC_INTERNAL_SLAB_DATA_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/smoothstep.h000066400000000000000000000364121501533116600247620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SMOOTHSTEP_H #define JEMALLOC_INTERNAL_SMOOTHSTEP_H /* * This file was generated by the following command: * sh smoothstep.sh smoother 200 24 3 15 */ /******************************************************************************/ /* * This header defines a precomputed table based on the smoothstep family of * sigmoidal curves (https://en.wikipedia.org/wiki/Smoothstep) that grow from 0 * to 1 in 0 <= x <= 1. The table is stored as integer fixed point values so * that floating point math can be avoided. * * 3 2 * smoothstep(x) = -2x + 3x * * 5 4 3 * smootherstep(x) = 6x - 15x + 10x * * 7 6 5 4 * smootheststep(x) = -20x + 70x - 84x + 35x */ #define SMOOTHSTEP_VARIANT "smoother" #define SMOOTHSTEP_NSTEPS 200 #define SMOOTHSTEP_BFP 24 #define SMOOTHSTEP \ /* STEP(step, h, x, y) */ \ STEP( 1, UINT64_C(0x0000000000000014), 0.005, 0.000001240643750) \ STEP( 2, UINT64_C(0x00000000000000a5), 0.010, 0.000009850600000) \ STEP( 3, UINT64_C(0x0000000000000229), 0.015, 0.000032995181250) \ STEP( 4, UINT64_C(0x0000000000000516), 0.020, 0.000077619200000) \ STEP( 5, UINT64_C(0x00000000000009dc), 0.025, 0.000150449218750) \ STEP( 6, UINT64_C(0x00000000000010e8), 0.030, 0.000257995800000) \ STEP( 7, UINT64_C(0x0000000000001aa4), 0.035, 0.000406555756250) \ STEP( 8, UINT64_C(0x0000000000002777), 0.040, 0.000602214400000) \ STEP( 9, UINT64_C(0x00000000000037c2), 0.045, 0.000850847793750) \ STEP( 10, UINT64_C(0x0000000000004be6), 0.050, 0.001158125000000) \ STEP( 11, UINT64_C(0x000000000000643c), 0.055, 0.001529510331250) \ STEP( 12, UINT64_C(0x000000000000811f), 0.060, 0.001970265600000) \ STEP( 13, UINT64_C(0x000000000000a2e2), 0.065, 0.002485452368750) \ STEP( 14, UINT64_C(0x000000000000c9d8), 0.070, 0.003079934200000) \ STEP( 15, UINT64_C(0x000000000000f64f), 0.075, 0.003758378906250) \ STEP( 16, UINT64_C(0x0000000000012891), 0.080, 0.004525260800000) \ STEP( 17, UINT64_C(0x00000000000160e7), 0.085, 0.005384862943750) \ STEP( 18, UINT64_C(0x0000000000019f95), 0.090, 0.006341279400000) \ STEP( 19, UINT64_C(0x000000000001e4dc), 0.095, 0.007398417481250) \ STEP( 20, UINT64_C(0x00000000000230fc), 0.100, 0.008560000000000) \ STEP( 21, UINT64_C(0x0000000000028430), 0.105, 0.009829567518750) \ STEP( 22, UINT64_C(0x000000000002deb0), 0.110, 0.011210480600000) \ STEP( 23, UINT64_C(0x00000000000340b1), 0.115, 0.012705922056250) \ STEP( 24, UINT64_C(0x000000000003aa67), 0.120, 0.014318899200000) \ STEP( 25, UINT64_C(0x0000000000041c00), 0.125, 0.016052246093750) \ STEP( 26, UINT64_C(0x00000000000495a8), 0.130, 0.017908625800000) \ STEP( 27, UINT64_C(0x000000000005178b), 0.135, 0.019890532631250) \ STEP( 28, UINT64_C(0x000000000005a1cf), 0.140, 0.022000294400000) \ STEP( 29, UINT64_C(0x0000000000063498), 0.145, 0.024240074668750) \ STEP( 30, UINT64_C(0x000000000006d009), 0.150, 0.026611875000000) \ STEP( 31, UINT64_C(0x000000000007743f), 0.155, 0.029117537206250) \ STEP( 32, UINT64_C(0x0000000000082157), 0.160, 0.031758745600000) \ STEP( 33, UINT64_C(0x000000000008d76b), 0.165, 0.034537029243750) \ STEP( 34, UINT64_C(0x0000000000099691), 0.170, 0.037453764200000) \ STEP( 35, UINT64_C(0x00000000000a5edf), 0.175, 0.040510175781250) \ STEP( 36, UINT64_C(0x00000000000b3067), 0.180, 0.043707340800000) \ STEP( 37, UINT64_C(0x00000000000c0b38), 0.185, 0.047046189818750) \ STEP( 38, UINT64_C(0x00000000000cef5e), 0.190, 0.050527509400000) \ STEP( 39, UINT64_C(0x00000000000ddce6), 0.195, 0.054151944356250) \ STEP( 40, UINT64_C(0x00000000000ed3d8), 0.200, 0.057920000000000) \ STEP( 41, UINT64_C(0x00000000000fd439), 0.205, 0.061832044393750) \ STEP( 42, UINT64_C(0x000000000010de0e), 0.210, 0.065888310600000) \ STEP( 43, UINT64_C(0x000000000011f158), 0.215, 0.070088898931250) \ STEP( 44, UINT64_C(0x0000000000130e17), 0.220, 0.074433779200000) \ STEP( 45, UINT64_C(0x0000000000143448), 0.225, 0.078922792968750) \ STEP( 46, UINT64_C(0x00000000001563e7), 0.230, 0.083555655800000) \ STEP( 47, UINT64_C(0x0000000000169cec), 0.235, 0.088331959506250) \ STEP( 48, UINT64_C(0x000000000017df4f), 0.240, 0.093251174400000) \ STEP( 49, UINT64_C(0x0000000000192b04), 0.245, 0.098312651543750) \ STEP( 50, UINT64_C(0x00000000001a8000), 0.250, 0.103515625000000) \ STEP( 51, UINT64_C(0x00000000001bde32), 0.255, 0.108859214081250) \ STEP( 52, UINT64_C(0x00000000001d458b), 0.260, 0.114342425600000) \ STEP( 53, UINT64_C(0x00000000001eb5f8), 0.265, 0.119964156118750) \ STEP( 54, UINT64_C(0x0000000000202f65), 0.270, 0.125723194200000) \ STEP( 55, UINT64_C(0x000000000021b1bb), 0.275, 0.131618222656250) \ STEP( 56, UINT64_C(0x0000000000233ce3), 0.280, 0.137647820800000) \ STEP( 57, UINT64_C(0x000000000024d0c3), 0.285, 0.143810466693750) \ STEP( 58, UINT64_C(0x0000000000266d40), 0.290, 0.150104539400000) \ STEP( 59, UINT64_C(0x000000000028123d), 0.295, 0.156528321231250) \ STEP( 60, UINT64_C(0x000000000029bf9c), 0.300, 0.163080000000000) \ STEP( 61, UINT64_C(0x00000000002b753d), 0.305, 0.169757671268750) \ STEP( 62, UINT64_C(0x00000000002d32fe), 0.310, 0.176559340600000) \ STEP( 63, UINT64_C(0x00000000002ef8bc), 0.315, 0.183482925806250) \ STEP( 64, UINT64_C(0x000000000030c654), 0.320, 0.190526259200000) \ STEP( 65, UINT64_C(0x0000000000329b9f), 0.325, 0.197687089843750) \ STEP( 66, UINT64_C(0x0000000000347875), 0.330, 0.204963085800000) \ STEP( 67, UINT64_C(0x0000000000365cb0), 0.335, 0.212351836381250) \ STEP( 68, UINT64_C(0x0000000000384825), 0.340, 0.219850854400000) \ STEP( 69, UINT64_C(0x00000000003a3aa8), 0.345, 0.227457578418750) \ STEP( 70, UINT64_C(0x00000000003c340f), 0.350, 0.235169375000000) \ STEP( 71, UINT64_C(0x00000000003e342b), 0.355, 0.242983540956250) \ STEP( 72, UINT64_C(0x0000000000403ace), 0.360, 0.250897305600000) \ STEP( 73, UINT64_C(0x00000000004247c8), 0.365, 0.258907832993750) \ STEP( 74, UINT64_C(0x0000000000445ae9), 0.370, 0.267012224200000) \ STEP( 75, UINT64_C(0x0000000000467400), 0.375, 0.275207519531250) \ STEP( 76, UINT64_C(0x00000000004892d8), 0.380, 0.283490700800000) \ STEP( 77, UINT64_C(0x00000000004ab740), 0.385, 0.291858693568750) \ STEP( 78, UINT64_C(0x00000000004ce102), 0.390, 0.300308369400000) \ STEP( 79, UINT64_C(0x00000000004f0fe9), 0.395, 0.308836548106250) \ STEP( 80, UINT64_C(0x00000000005143bf), 0.400, 0.317440000000000) \ STEP( 81, UINT64_C(0x0000000000537c4d), 0.405, 0.326115448143750) \ STEP( 82, UINT64_C(0x000000000055b95b), 0.410, 0.334859570600000) \ STEP( 83, UINT64_C(0x000000000057fab1), 0.415, 0.343669002681250) \ STEP( 84, UINT64_C(0x00000000005a4015), 0.420, 0.352540339200000) \ STEP( 85, UINT64_C(0x00000000005c894e), 0.425, 0.361470136718750) \ STEP( 86, UINT64_C(0x00000000005ed622), 0.430, 0.370454915800000) \ STEP( 87, UINT64_C(0x0000000000612655), 0.435, 0.379491163256250) \ STEP( 88, UINT64_C(0x00000000006379ac), 0.440, 0.388575334400000) \ STEP( 89, UINT64_C(0x000000000065cfeb), 0.445, 0.397703855293750) \ STEP( 90, UINT64_C(0x00000000006828d6), 0.450, 0.406873125000000) \ STEP( 91, UINT64_C(0x00000000006a842f), 0.455, 0.416079517831250) \ STEP( 92, UINT64_C(0x00000000006ce1bb), 0.460, 0.425319385600000) \ STEP( 93, UINT64_C(0x00000000006f413a), 0.465, 0.434589059868750) \ STEP( 94, UINT64_C(0x000000000071a270), 0.470, 0.443884854200000) \ STEP( 95, UINT64_C(0x000000000074051d), 0.475, 0.453203066406250) \ STEP( 96, UINT64_C(0x0000000000766905), 0.480, 0.462539980800000) \ STEP( 97, UINT64_C(0x000000000078cde7), 0.485, 0.471891870443750) \ STEP( 98, UINT64_C(0x00000000007b3387), 0.490, 0.481254999400000) \ STEP( 99, UINT64_C(0x00000000007d99a4), 0.495, 0.490625624981250) \ STEP( 100, UINT64_C(0x0000000000800000), 0.500, 0.500000000000000) \ STEP( 101, UINT64_C(0x000000000082665b), 0.505, 0.509374375018750) \ STEP( 102, UINT64_C(0x000000000084cc78), 0.510, 0.518745000600000) \ STEP( 103, UINT64_C(0x0000000000873218), 0.515, 0.528108129556250) \ STEP( 104, UINT64_C(0x00000000008996fa), 0.520, 0.537460019200000) \ STEP( 105, UINT64_C(0x00000000008bfae2), 0.525, 0.546796933593750) \ STEP( 106, UINT64_C(0x00000000008e5d8f), 0.530, 0.556115145800000) \ STEP( 107, UINT64_C(0x000000000090bec5), 0.535, 0.565410940131250) \ STEP( 108, UINT64_C(0x0000000000931e44), 0.540, 0.574680614400000) \ STEP( 109, UINT64_C(0x0000000000957bd0), 0.545, 0.583920482168750) \ STEP( 110, UINT64_C(0x000000000097d729), 0.550, 0.593126875000000) \ STEP( 111, UINT64_C(0x00000000009a3014), 0.555, 0.602296144706250) \ STEP( 112, UINT64_C(0x00000000009c8653), 0.560, 0.611424665600000) \ STEP( 113, UINT64_C(0x00000000009ed9aa), 0.565, 0.620508836743750) \ STEP( 114, UINT64_C(0x0000000000a129dd), 0.570, 0.629545084200000) \ STEP( 115, UINT64_C(0x0000000000a376b1), 0.575, 0.638529863281250) \ STEP( 116, UINT64_C(0x0000000000a5bfea), 0.580, 0.647459660800000) \ STEP( 117, UINT64_C(0x0000000000a8054e), 0.585, 0.656330997318750) \ STEP( 118, UINT64_C(0x0000000000aa46a4), 0.590, 0.665140429400000) \ STEP( 119, UINT64_C(0x0000000000ac83b2), 0.595, 0.673884551856250) \ STEP( 120, UINT64_C(0x0000000000aebc40), 0.600, 0.682560000000000) \ STEP( 121, UINT64_C(0x0000000000b0f016), 0.605, 0.691163451893750) \ STEP( 122, UINT64_C(0x0000000000b31efd), 0.610, 0.699691630600000) \ STEP( 123, UINT64_C(0x0000000000b548bf), 0.615, 0.708141306431250) \ STEP( 124, UINT64_C(0x0000000000b76d27), 0.620, 0.716509299200000) \ STEP( 125, UINT64_C(0x0000000000b98c00), 0.625, 0.724792480468750) \ STEP( 126, UINT64_C(0x0000000000bba516), 0.630, 0.732987775800000) \ STEP( 127, UINT64_C(0x0000000000bdb837), 0.635, 0.741092167006250) \ STEP( 128, UINT64_C(0x0000000000bfc531), 0.640, 0.749102694400000) \ STEP( 129, UINT64_C(0x0000000000c1cbd4), 0.645, 0.757016459043750) \ STEP( 130, UINT64_C(0x0000000000c3cbf0), 0.650, 0.764830625000000) \ STEP( 131, UINT64_C(0x0000000000c5c557), 0.655, 0.772542421581250) \ STEP( 132, UINT64_C(0x0000000000c7b7da), 0.660, 0.780149145600000) \ STEP( 133, UINT64_C(0x0000000000c9a34f), 0.665, 0.787648163618750) \ STEP( 134, UINT64_C(0x0000000000cb878a), 0.670, 0.795036914200000) \ STEP( 135, UINT64_C(0x0000000000cd6460), 0.675, 0.802312910156250) \ STEP( 136, UINT64_C(0x0000000000cf39ab), 0.680, 0.809473740800000) \ STEP( 137, UINT64_C(0x0000000000d10743), 0.685, 0.816517074193750) \ STEP( 138, UINT64_C(0x0000000000d2cd01), 0.690, 0.823440659400000) \ STEP( 139, UINT64_C(0x0000000000d48ac2), 0.695, 0.830242328731250) \ STEP( 140, UINT64_C(0x0000000000d64063), 0.700, 0.836920000000000) \ STEP( 141, UINT64_C(0x0000000000d7edc2), 0.705, 0.843471678768750) \ STEP( 142, UINT64_C(0x0000000000d992bf), 0.710, 0.849895460600000) \ STEP( 143, UINT64_C(0x0000000000db2f3c), 0.715, 0.856189533306250) \ STEP( 144, UINT64_C(0x0000000000dcc31c), 0.720, 0.862352179200000) \ STEP( 145, UINT64_C(0x0000000000de4e44), 0.725, 0.868381777343750) \ STEP( 146, UINT64_C(0x0000000000dfd09a), 0.730, 0.874276805800000) \ STEP( 147, UINT64_C(0x0000000000e14a07), 0.735, 0.880035843881250) \ STEP( 148, UINT64_C(0x0000000000e2ba74), 0.740, 0.885657574400000) \ STEP( 149, UINT64_C(0x0000000000e421cd), 0.745, 0.891140785918750) \ STEP( 150, UINT64_C(0x0000000000e58000), 0.750, 0.896484375000000) \ STEP( 151, UINT64_C(0x0000000000e6d4fb), 0.755, 0.901687348456250) \ STEP( 152, UINT64_C(0x0000000000e820b0), 0.760, 0.906748825600000) \ STEP( 153, UINT64_C(0x0000000000e96313), 0.765, 0.911668040493750) \ STEP( 154, UINT64_C(0x0000000000ea9c18), 0.770, 0.916444344200000) \ STEP( 155, UINT64_C(0x0000000000ebcbb7), 0.775, 0.921077207031250) \ STEP( 156, UINT64_C(0x0000000000ecf1e8), 0.780, 0.925566220800000) \ STEP( 157, UINT64_C(0x0000000000ee0ea7), 0.785, 0.929911101068750) \ STEP( 158, UINT64_C(0x0000000000ef21f1), 0.790, 0.934111689400000) \ STEP( 159, UINT64_C(0x0000000000f02bc6), 0.795, 0.938167955606250) \ STEP( 160, UINT64_C(0x0000000000f12c27), 0.800, 0.942080000000000) \ STEP( 161, UINT64_C(0x0000000000f22319), 0.805, 0.945848055643750) \ STEP( 162, UINT64_C(0x0000000000f310a1), 0.810, 0.949472490600000) \ STEP( 163, UINT64_C(0x0000000000f3f4c7), 0.815, 0.952953810181250) \ STEP( 164, UINT64_C(0x0000000000f4cf98), 0.820, 0.956292659200000) \ STEP( 165, UINT64_C(0x0000000000f5a120), 0.825, 0.959489824218750) \ STEP( 166, UINT64_C(0x0000000000f6696e), 0.830, 0.962546235800000) \ STEP( 167, UINT64_C(0x0000000000f72894), 0.835, 0.965462970756250) \ STEP( 168, UINT64_C(0x0000000000f7dea8), 0.840, 0.968241254400000) \ STEP( 169, UINT64_C(0x0000000000f88bc0), 0.845, 0.970882462793750) \ STEP( 170, UINT64_C(0x0000000000f92ff6), 0.850, 0.973388125000000) \ STEP( 171, UINT64_C(0x0000000000f9cb67), 0.855, 0.975759925331250) \ STEP( 172, UINT64_C(0x0000000000fa5e30), 0.860, 0.977999705600000) \ STEP( 173, UINT64_C(0x0000000000fae874), 0.865, 0.980109467368750) \ STEP( 174, UINT64_C(0x0000000000fb6a57), 0.870, 0.982091374200000) \ STEP( 175, UINT64_C(0x0000000000fbe400), 0.875, 0.983947753906250) \ STEP( 176, UINT64_C(0x0000000000fc5598), 0.880, 0.985681100800000) \ STEP( 177, UINT64_C(0x0000000000fcbf4e), 0.885, 0.987294077943750) \ STEP( 178, UINT64_C(0x0000000000fd214f), 0.890, 0.988789519400000) \ STEP( 179, UINT64_C(0x0000000000fd7bcf), 0.895, 0.990170432481250) \ STEP( 180, UINT64_C(0x0000000000fdcf03), 0.900, 0.991440000000000) \ STEP( 181, UINT64_C(0x0000000000fe1b23), 0.905, 0.992601582518750) \ STEP( 182, UINT64_C(0x0000000000fe606a), 0.910, 0.993658720600000) \ STEP( 183, UINT64_C(0x0000000000fe9f18), 0.915, 0.994615137056250) \ STEP( 184, UINT64_C(0x0000000000fed76e), 0.920, 0.995474739200000) \ STEP( 185, UINT64_C(0x0000000000ff09b0), 0.925, 0.996241621093750) \ STEP( 186, UINT64_C(0x0000000000ff3627), 0.930, 0.996920065800000) \ STEP( 187, UINT64_C(0x0000000000ff5d1d), 0.935, 0.997514547631250) \ STEP( 188, UINT64_C(0x0000000000ff7ee0), 0.940, 0.998029734400000) \ STEP( 189, UINT64_C(0x0000000000ff9bc3), 0.945, 0.998470489668750) \ STEP( 190, UINT64_C(0x0000000000ffb419), 0.950, 0.998841875000000) \ STEP( 191, UINT64_C(0x0000000000ffc83d), 0.955, 0.999149152206250) \ STEP( 192, UINT64_C(0x0000000000ffd888), 0.960, 0.999397785600000) \ STEP( 193, UINT64_C(0x0000000000ffe55b), 0.965, 0.999593444243750) \ STEP( 194, UINT64_C(0x0000000000ffef17), 0.970, 0.999742004200000) \ STEP( 195, UINT64_C(0x0000000000fff623), 0.975, 0.999849550781250) \ STEP( 196, UINT64_C(0x0000000000fffae9), 0.980, 0.999922380800000) \ STEP( 197, UINT64_C(0x0000000000fffdd6), 0.985, 0.999967004818750) \ STEP( 198, UINT64_C(0x0000000000ffff5a), 0.990, 0.999990149400000) \ STEP( 199, UINT64_C(0x0000000000ffffeb), 0.995, 0.999998759356250) \ STEP( 200, UINT64_C(0x0000000001000000), 1.000, 1.000000000000000) \ #endif /* JEMALLOC_INTERNAL_SMOOTHSTEP_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/smoothstep.sh000077500000000000000000000056321501533116600251500ustar00rootroot00000000000000#!/bin/sh # # Generate a discrete lookup table for a sigmoid function in the smoothstep # family (https://en.wikipedia.org/wiki/Smoothstep), where the lookup table # entries correspond to x in [1/nsteps, 2/nsteps, ..., nsteps/nsteps]. Encode # the entries using a binary fixed point representation. # # Usage: smoothstep.sh # # is in {smooth, smoother, smoothest}. # must be greater than zero. # must be in [0..62]; reasonable values are roughly [10..30]. # is x decimal precision. # is y decimal precision. #set -x cmd="sh smoothstep.sh $*" variant=$1 nsteps=$2 bfp=$3 xprec=$4 yprec=$5 case "${variant}" in smooth) ;; smoother) ;; smoothest) ;; *) echo "Unsupported variant" exit 1 ;; esac smooth() { step=$1 y=`echo ${yprec} k ${step} ${nsteps} / sx _2 lx 3 ^ '*' 3 lx 2 ^ '*' + p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'` h=`echo ${yprec} k 2 ${bfp} ^ ${y} '*' p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g' | tr '.' ' ' | awk '{print $1}' ` } smoother() { step=$1 y=`echo ${yprec} k ${step} ${nsteps} / sx 6 lx 5 ^ '*' _15 lx 4 ^ '*' + 10 lx 3 ^ '*' + p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'` h=`echo ${yprec} k 2 ${bfp} ^ ${y} '*' p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g' | tr '.' ' ' | awk '{print $1}' ` } smoothest() { step=$1 y=`echo ${yprec} k ${step} ${nsteps} / sx _20 lx 7 ^ '*' 70 lx 6 ^ '*' + _84 lx 5 ^ '*' + 35 lx 4 ^ '*' + p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'` h=`echo ${yprec} k 2 ${bfp} ^ ${y} '*' p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g' | tr '.' ' ' | awk '{print $1}' ` } cat <iteration < 5) { for (i = 0; i < (1U << spin->iteration); i++) { spin_cpu_spinwait(); } spin->iteration++; } else { #ifdef _WIN32 SwitchToThread(); #else sched_yield(); #endif } } #undef SPIN_INLINE #endif /* JEMALLOC_INTERNAL_SPIN_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/stats.h000066400000000000000000000035371501533116600237150ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_STATS_H #define JEMALLOC_INTERNAL_STATS_H /* OPTION(opt, var_name, default, set_value_to) */ #define STATS_PRINT_OPTIONS \ OPTION('J', json, false, true) \ OPTION('g', general, true, false) \ OPTION('m', merged, config_stats, false) \ OPTION('d', destroyed, config_stats, false) \ OPTION('a', unmerged, config_stats, false) \ OPTION('b', bins, true, false) \ OPTION('l', large, true, false) \ OPTION('x', mutex, true, false) \ OPTION('e', extents, true, false) \ OPTION('h', hpa, config_stats, false) enum { #define OPTION(o, v, d, s) stats_print_option_num_##v, STATS_PRINT_OPTIONS #undef OPTION stats_print_tot_num_options }; /* Options for stats_print. */ extern bool opt_stats_print; extern char opt_stats_print_opts[stats_print_tot_num_options+1]; /* Utilities for stats_interval. */ extern int64_t opt_stats_interval; extern char opt_stats_interval_opts[stats_print_tot_num_options+1]; #define STATS_INTERVAL_DEFAULT -1 /* * Batch-increment the counter to reduce synchronization overhead. Each thread * merges after (interval >> LG_BATCH_SIZE) bytes of allocations; also limit the * BATCH_MAX for accuracy when the interval is huge (which is expected). */ #define STATS_INTERVAL_ACCUM_LG_BATCH_SIZE 6 #define STATS_INTERVAL_ACCUM_BATCH_MAX (4 << 20) /* Only accessed by thread event. */ uint64_t stats_interval_new_event_wait(tsd_t *tsd); uint64_t stats_interval_postponed_event_wait(tsd_t *tsd); void stats_interval_event_handler(tsd_t *tsd, uint64_t elapsed); /* Implements je_malloc_stats_print. */ void stats_print(write_cb_t *write_cb, void *cbopaque, const char *opts); bool stats_boot(void); void stats_prefork(tsdn_t *tsdn); void stats_postfork_parent(tsdn_t *tsdn); void stats_postfork_child(tsdn_t *tsdn); #endif /* JEMALLOC_INTERNAL_STATS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/sz.h000066400000000000000000000240561501533116600232120ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_SIZE_H #define JEMALLOC_INTERNAL_SIZE_H #include "jemalloc/internal/bit_util.h" #include "jemalloc/internal/pages.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/util.h" /* * sz module: Size computations. * * Some abbreviations used here: * p: Page * ind: Index * s, sz: Size * u: Usable size * a: Aligned * * These are not always used completely consistently, but should be enough to * interpret function names. E.g. sz_psz2ind converts page size to page size * index; sz_sa2u converts a (size, alignment) allocation request to the usable * size that would result from such an allocation. */ /* Page size index type. */ typedef unsigned pszind_t; /* Size class index type. */ typedef unsigned szind_t; /* * sz_pind2sz_tab encodes the same information as could be computed by * sz_pind2sz_compute(). */ extern size_t sz_pind2sz_tab[SC_NPSIZES + 1]; /* * sz_index2size_tab encodes the same information as could be computed (at * unacceptable cost in some code paths) by sz_index2size_compute(). */ extern size_t sz_index2size_tab[SC_NSIZES]; /* * sz_size2index_tab is a compact lookup table that rounds request sizes up to * size classes. In order to reduce cache footprint, the table is compressed, * and all accesses are via sz_size2index(). */ extern uint8_t sz_size2index_tab[]; /* * Padding for large allocations: PAGE when opt_cache_oblivious == true (to * enable cache index randomization); 0 otherwise. */ extern size_t sz_large_pad; extern void sz_boot(const sc_data_t *sc_data, bool cache_oblivious); JEMALLOC_ALWAYS_INLINE pszind_t sz_psz2ind(size_t psz) { assert(psz > 0); if (unlikely(psz > SC_LARGE_MAXCLASS)) { return SC_NPSIZES; } /* x is the lg of the first base >= psz. */ pszind_t x = lg_ceil(psz); /* * sc.h introduces a lot of size classes. These size classes are divided * into different size class groups. There is a very special size class * group, each size class in or after it is an integer multiple of PAGE. * We call it first_ps_rg. It means first page size regular group. The * range of first_ps_rg is (base, base * 2], and base == PAGE * * SC_NGROUP. off_to_first_ps_rg begins from 1, instead of 0. e.g. * off_to_first_ps_rg is 1 when psz is (PAGE * SC_NGROUP + 1). */ pszind_t off_to_first_ps_rg = (x < SC_LG_NGROUP + LG_PAGE) ? 0 : x - (SC_LG_NGROUP + LG_PAGE); /* * Same as sc_s::lg_delta. * Delta for off_to_first_ps_rg == 1 is PAGE, * for each increase in offset, it's multiplied by two. * Therefore, lg_delta = LG_PAGE + (off_to_first_ps_rg - 1). */ pszind_t lg_delta = (off_to_first_ps_rg == 0) ? LG_PAGE : LG_PAGE + (off_to_first_ps_rg - 1); /* * Let's write psz in binary, e.g. 0011 for 0x3, 0111 for 0x7. * The leftmost bits whose len is lg_base decide the base of psz. * The rightmost bits whose len is lg_delta decide (pgz % PAGE). * The middle bits whose len is SC_LG_NGROUP decide ndelta. * ndelta is offset to the first size class in the size class group, * starts from 1. * If you don't know lg_base, ndelta or lg_delta, see sc.h. * |xxxxxxxxxxxxxxxxxxxx|------------------------|yyyyyyyyyyyyyyyyyyyyy| * |<-- len: lg_base -->|<-- len: SC_LG_NGROUP-->|<-- len: lg_delta -->| * |<-- ndelta -->| * rg_inner_off = ndelta - 1 * Why use (psz - 1)? * To handle case: psz % (1 << lg_delta) == 0. */ pszind_t rg_inner_off = (((psz - 1)) >> lg_delta) & (SC_NGROUP - 1); pszind_t base_ind = off_to_first_ps_rg << SC_LG_NGROUP; pszind_t ind = base_ind + rg_inner_off; return ind; } static inline size_t sz_pind2sz_compute(pszind_t pind) { if (unlikely(pind == SC_NPSIZES)) { return SC_LARGE_MAXCLASS + PAGE; } size_t grp = pind >> SC_LG_NGROUP; size_t mod = pind & ((ZU(1) << SC_LG_NGROUP) - 1); size_t grp_size_mask = ~((!!grp)-1); size_t grp_size = ((ZU(1) << (LG_PAGE + (SC_LG_NGROUP-1))) << grp) & grp_size_mask; size_t shift = (grp == 0) ? 1 : grp; size_t lg_delta = shift + (LG_PAGE-1); size_t mod_size = (mod+1) << lg_delta; size_t sz = grp_size + mod_size; return sz; } static inline size_t sz_pind2sz_lookup(pszind_t pind) { size_t ret = (size_t)sz_pind2sz_tab[pind]; assert(ret == sz_pind2sz_compute(pind)); return ret; } static inline size_t sz_pind2sz(pszind_t pind) { assert(pind < SC_NPSIZES + 1); return sz_pind2sz_lookup(pind); } static inline size_t sz_psz2u(size_t psz) { if (unlikely(psz > SC_LARGE_MAXCLASS)) { return SC_LARGE_MAXCLASS + PAGE; } size_t x = lg_floor((psz<<1)-1); size_t lg_delta = (x < SC_LG_NGROUP + LG_PAGE + 1) ? LG_PAGE : x - SC_LG_NGROUP - 1; size_t delta = ZU(1) << lg_delta; size_t delta_mask = delta - 1; size_t usize = (psz + delta_mask) & ~delta_mask; return usize; } static inline szind_t sz_size2index_compute(size_t size) { if (unlikely(size > SC_LARGE_MAXCLASS)) { return SC_NSIZES; } if (size == 0) { return 0; } #if (SC_NTINY != 0) if (size <= (ZU(1) << SC_LG_TINY_MAXCLASS)) { szind_t lg_tmin = SC_LG_TINY_MAXCLASS - SC_NTINY + 1; szind_t lg_ceil = lg_floor(pow2_ceil_zu(size)); return (lg_ceil < lg_tmin ? 0 : lg_ceil - lg_tmin); } #endif { szind_t x = lg_floor((size<<1)-1); szind_t shift = (x < SC_LG_NGROUP + LG_QUANTUM) ? 0 : x - (SC_LG_NGROUP + LG_QUANTUM); szind_t grp = shift << SC_LG_NGROUP; szind_t lg_delta = (x < SC_LG_NGROUP + LG_QUANTUM + 1) ? LG_QUANTUM : x - SC_LG_NGROUP - 1; size_t delta_inverse_mask = ZU(-1) << lg_delta; szind_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) & ((ZU(1) << SC_LG_NGROUP) - 1); szind_t index = SC_NTINY + grp + mod; return index; } } JEMALLOC_ALWAYS_INLINE szind_t sz_size2index_lookup_impl(size_t size) { assert(size <= SC_LOOKUP_MAXCLASS); return sz_size2index_tab[(size + (ZU(1) << SC_LG_TINY_MIN) - 1) >> SC_LG_TINY_MIN]; } JEMALLOC_ALWAYS_INLINE szind_t sz_size2index_lookup(size_t size) { szind_t ret = sz_size2index_lookup_impl(size); assert(ret == sz_size2index_compute(size)); return ret; } JEMALLOC_ALWAYS_INLINE szind_t sz_size2index(size_t size) { if (likely(size <= SC_LOOKUP_MAXCLASS)) { return sz_size2index_lookup(size); } return sz_size2index_compute(size); } static inline size_t sz_index2size_compute(szind_t index) { #if (SC_NTINY > 0) if (index < SC_NTINY) { return (ZU(1) << (SC_LG_TINY_MAXCLASS - SC_NTINY + 1 + index)); } #endif { size_t reduced_index = index - SC_NTINY; size_t grp = reduced_index >> SC_LG_NGROUP; size_t mod = reduced_index & ((ZU(1) << SC_LG_NGROUP) - 1); size_t grp_size_mask = ~((!!grp)-1); size_t grp_size = ((ZU(1) << (LG_QUANTUM + (SC_LG_NGROUP-1))) << grp) & grp_size_mask; size_t shift = (grp == 0) ? 1 : grp; size_t lg_delta = shift + (LG_QUANTUM-1); size_t mod_size = (mod+1) << lg_delta; size_t usize = grp_size + mod_size; return usize; } } JEMALLOC_ALWAYS_INLINE size_t sz_index2size_lookup_impl(szind_t index) { return sz_index2size_tab[index]; } JEMALLOC_ALWAYS_INLINE size_t sz_index2size_lookup(szind_t index) { size_t ret = sz_index2size_lookup_impl(index); assert(ret == sz_index2size_compute(index)); return ret; } JEMALLOC_ALWAYS_INLINE size_t sz_index2size(szind_t index) { assert(index < SC_NSIZES); return sz_index2size_lookup(index); } JEMALLOC_ALWAYS_INLINE void sz_size2index_usize_fastpath(size_t size, szind_t *ind, size_t *usize) { *ind = sz_size2index_lookup_impl(size); *usize = sz_index2size_lookup_impl(*ind); } JEMALLOC_ALWAYS_INLINE size_t sz_s2u_compute(size_t size) { if (unlikely(size > SC_LARGE_MAXCLASS)) { return 0; } if (size == 0) { size++; } #if (SC_NTINY > 0) if (size <= (ZU(1) << SC_LG_TINY_MAXCLASS)) { size_t lg_tmin = SC_LG_TINY_MAXCLASS - SC_NTINY + 1; size_t lg_ceil = lg_floor(pow2_ceil_zu(size)); return (lg_ceil < lg_tmin ? (ZU(1) << lg_tmin) : (ZU(1) << lg_ceil)); } #endif { size_t x = lg_floor((size<<1)-1); size_t lg_delta = (x < SC_LG_NGROUP + LG_QUANTUM + 1) ? LG_QUANTUM : x - SC_LG_NGROUP - 1; size_t delta = ZU(1) << lg_delta; size_t delta_mask = delta - 1; size_t usize = (size + delta_mask) & ~delta_mask; return usize; } } JEMALLOC_ALWAYS_INLINE size_t sz_s2u_lookup(size_t size) { size_t ret = sz_index2size_lookup(sz_size2index_lookup(size)); assert(ret == sz_s2u_compute(size)); return ret; } /* * Compute usable size that would result from allocating an object with the * specified size. */ JEMALLOC_ALWAYS_INLINE size_t sz_s2u(size_t size) { if (likely(size <= SC_LOOKUP_MAXCLASS)) { return sz_s2u_lookup(size); } return sz_s2u_compute(size); } /* * Compute usable size that would result from allocating an object with the * specified size and alignment. */ JEMALLOC_ALWAYS_INLINE size_t sz_sa2u(size_t size, size_t alignment) { size_t usize; assert(alignment != 0 && ((alignment - 1) & alignment) == 0); /* Try for a small size class. */ if (size <= SC_SMALL_MAXCLASS && alignment <= PAGE) { /* * Round size up to the nearest multiple of alignment. * * This done, we can take advantage of the fact that for each * small size class, every object is aligned at the smallest * power of two that is non-zero in the base two representation * of the size. For example: * * Size | Base 2 | Minimum alignment * -----+----------+------------------ * 96 | 1100000 | 32 * 144 | 10100000 | 32 * 192 | 11000000 | 64 */ usize = sz_s2u(ALIGNMENT_CEILING(size, alignment)); if (usize < SC_LARGE_MINCLASS) { return usize; } } /* Large size class. Beware of overflow. */ if (unlikely(alignment > SC_LARGE_MAXCLASS)) { return 0; } /* Make sure result is a large size class. */ if (size <= SC_LARGE_MINCLASS) { usize = SC_LARGE_MINCLASS; } else { usize = sz_s2u(size); if (usize < size) { /* size_t overflow. */ return 0; } } /* * Calculate the multi-page mapping that large_palloc() would need in * order to guarantee the alignment. */ if (usize + sz_large_pad + PAGE_CEILING(alignment) - PAGE < usize) { /* size_t overflow. */ return 0; } return usize; } size_t sz_psz_quantize_floor(size_t size); size_t sz_psz_quantize_ceil(size_t size); #endif /* JEMALLOC_INTERNAL_SIZE_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tcache_externs.h000066400000000000000000000060331501533116600255500ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TCACHE_EXTERNS_H #define JEMALLOC_INTERNAL_TCACHE_EXTERNS_H extern bool opt_tcache; extern size_t opt_tcache_max; extern ssize_t opt_lg_tcache_nslots_mul; extern unsigned opt_tcache_nslots_small_min; extern unsigned opt_tcache_nslots_small_max; extern unsigned opt_tcache_nslots_large; extern ssize_t opt_lg_tcache_shift; extern size_t opt_tcache_gc_incr_bytes; extern size_t opt_tcache_gc_delay_bytes; extern unsigned opt_lg_tcache_flush_small_div; extern unsigned opt_lg_tcache_flush_large_div; /* * Number of tcache bins. There are SC_NBINS small-object bins, plus 0 or more * large-object bins. */ extern unsigned nhbins; /* Maximum cached size class. */ extern size_t tcache_maxclass; extern cache_bin_info_t *tcache_bin_info; /* * Explicit tcaches, managed via the tcache.{create,flush,destroy} mallctls and * usable via the MALLOCX_TCACHE() flag. The automatic per thread tcaches are * completely disjoint from this data structure. tcaches starts off as a sparse * array, so it has no physical memory footprint until individual pages are * touched. This allows the entire array to be allocated the first time an * explicit tcache is created without a disproportionate impact on memory usage. */ extern tcaches_t *tcaches; size_t tcache_salloc(tsdn_t *tsdn, const void *ptr); void *tcache_alloc_small_hard(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache, cache_bin_t *tbin, szind_t binind, bool *tcache_success); void tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, cache_bin_t *tbin, szind_t binind, unsigned rem); void tcache_bin_flush_large(tsd_t *tsd, tcache_t *tcache, cache_bin_t *tbin, szind_t binind, unsigned rem); void tcache_bin_flush_stashed(tsd_t *tsd, tcache_t *tcache, cache_bin_t *bin, szind_t binind, bool is_small); void tcache_arena_reassociate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, tcache_t *tcache, arena_t *arena); tcache_t *tcache_create_explicit(tsd_t *tsd); void tcache_cleanup(tsd_t *tsd); void tcache_stats_merge(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena); bool tcaches_create(tsd_t *tsd, base_t *base, unsigned *r_ind); void tcaches_flush(tsd_t *tsd, unsigned ind); void tcaches_destroy(tsd_t *tsd, unsigned ind); bool tcache_boot(tsdn_t *tsdn, base_t *base); void tcache_arena_associate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, tcache_t *tcache, arena_t *arena); void tcache_prefork(tsdn_t *tsdn); void tcache_postfork_parent(tsdn_t *tsdn); void tcache_postfork_child(tsdn_t *tsdn); void tcache_flush(tsd_t *tsd); bool tsd_tcache_data_init(tsd_t *tsd); bool tsd_tcache_enabled_data_init(tsd_t *tsd); void tcache_assert_initialized(tcache_t *tcache); /* Only accessed by thread event. */ uint64_t tcache_gc_new_event_wait(tsd_t *tsd); uint64_t tcache_gc_postponed_event_wait(tsd_t *tsd); void tcache_gc_event_handler(tsd_t *tsd, uint64_t elapsed); uint64_t tcache_gc_dalloc_new_event_wait(tsd_t *tsd); uint64_t tcache_gc_dalloc_postponed_event_wait(tsd_t *tsd); void tcache_gc_dalloc_event_handler(tsd_t *tsd, uint64_t elapsed); #endif /* JEMALLOC_INTERNAL_TCACHE_EXTERNS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tcache_inlines.h000066400000000000000000000125411501533116600255220ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TCACHE_INLINES_H #define JEMALLOC_INTERNAL_TCACHE_INLINES_H #include "jemalloc/internal/bin.h" #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/sz.h" #include "jemalloc/internal/util.h" static inline bool tcache_enabled_get(tsd_t *tsd) { return tsd_tcache_enabled_get(tsd); } static inline void tcache_enabled_set(tsd_t *tsd, bool enabled) { bool was_enabled = tsd_tcache_enabled_get(tsd); if (!was_enabled && enabled) { tsd_tcache_data_init(tsd); } else if (was_enabled && !enabled) { tcache_cleanup(tsd); } /* Commit the state last. Above calls check current state. */ tsd_tcache_enabled_set(tsd, enabled); tsd_slow_update(tsd); } JEMALLOC_ALWAYS_INLINE bool tcache_small_bin_disabled(szind_t ind, cache_bin_t *bin) { assert(ind < SC_NBINS); bool ret = (cache_bin_info_ncached_max(&tcache_bin_info[ind]) == 0); if (ret && bin != NULL) { /* small size class but cache bin disabled. */ assert(ind >= nhbins); assert((uintptr_t)(*bin->stack_head) == cache_bin_preceding_junk); } return ret; } JEMALLOC_ALWAYS_INLINE void * tcache_alloc_small(tsd_t *tsd, arena_t *arena, tcache_t *tcache, size_t size, szind_t binind, bool zero, bool slow_path) { void *ret; bool tcache_success; assert(binind < SC_NBINS); cache_bin_t *bin = &tcache->bins[binind]; ret = cache_bin_alloc(bin, &tcache_success); assert(tcache_success == (ret != NULL)); if (unlikely(!tcache_success)) { bool tcache_hard_success; arena = arena_choose(tsd, arena); if (unlikely(arena == NULL)) { return NULL; } if (unlikely(tcache_small_bin_disabled(binind, bin))) { /* stats and zero are handled directly by the arena. */ return arena_malloc_hard(tsd_tsdn(tsd), arena, size, binind, zero); } tcache_bin_flush_stashed(tsd, tcache, bin, binind, /* is_small */ true); ret = tcache_alloc_small_hard(tsd_tsdn(tsd), arena, tcache, bin, binind, &tcache_hard_success); if (tcache_hard_success == false) { return NULL; } } assert(ret); if (unlikely(zero)) { size_t usize = sz_index2size(binind); assert(tcache_salloc(tsd_tsdn(tsd), ret) == usize); memset(ret, 0, usize); } if (config_stats) { bin->tstats.nrequests++; } return ret; } JEMALLOC_ALWAYS_INLINE void * tcache_alloc_large(tsd_t *tsd, arena_t *arena, tcache_t *tcache, size_t size, szind_t binind, bool zero, bool slow_path) { void *ret; bool tcache_success; assert(binind >= SC_NBINS && binind < nhbins); cache_bin_t *bin = &tcache->bins[binind]; ret = cache_bin_alloc(bin, &tcache_success); assert(tcache_success == (ret != NULL)); if (unlikely(!tcache_success)) { /* * Only allocate one large object at a time, because it's quite * expensive to create one and not use it. */ arena = arena_choose(tsd, arena); if (unlikely(arena == NULL)) { return NULL; } tcache_bin_flush_stashed(tsd, tcache, bin, binind, /* is_small */ false); ret = large_malloc(tsd_tsdn(tsd), arena, sz_s2u(size), zero); if (ret == NULL) { return NULL; } } else { if (unlikely(zero)) { size_t usize = sz_index2size(binind); assert(usize <= tcache_maxclass); memset(ret, 0, usize); } if (config_stats) { bin->tstats.nrequests++; } } return ret; } JEMALLOC_ALWAYS_INLINE void tcache_dalloc_small(tsd_t *tsd, tcache_t *tcache, void *ptr, szind_t binind, bool slow_path) { assert(tcache_salloc(tsd_tsdn(tsd), ptr) <= SC_SMALL_MAXCLASS); cache_bin_t *bin = &tcache->bins[binind]; /* * Not marking the branch unlikely because this is past free_fastpath() * (which handles the most common cases), i.e. at this point it's often * uncommon cases. */ if (cache_bin_nonfast_aligned(ptr)) { /* Junk unconditionally, even if bin is full. */ san_junk_ptr(ptr, sz_index2size(binind)); if (cache_bin_stash(bin, ptr)) { return; } assert(cache_bin_full(bin)); /* Bin full; fall through into the flush branch. */ } if (unlikely(!cache_bin_dalloc_easy(bin, ptr))) { if (unlikely(tcache_small_bin_disabled(binind, bin))) { arena_dalloc_small(tsd_tsdn(tsd), ptr); return; } cache_bin_sz_t max = cache_bin_info_ncached_max( &tcache_bin_info[binind]); unsigned remain = max >> opt_lg_tcache_flush_small_div; tcache_bin_flush_small(tsd, tcache, bin, binind, remain); bool ret = cache_bin_dalloc_easy(bin, ptr); assert(ret); } } JEMALLOC_ALWAYS_INLINE void tcache_dalloc_large(tsd_t *tsd, tcache_t *tcache, void *ptr, szind_t binind, bool slow_path) { assert(tcache_salloc(tsd_tsdn(tsd), ptr) > SC_SMALL_MAXCLASS); assert(tcache_salloc(tsd_tsdn(tsd), ptr) <= tcache_maxclass); cache_bin_t *bin = &tcache->bins[binind]; if (unlikely(!cache_bin_dalloc_easy(bin, ptr))) { unsigned remain = cache_bin_info_ncached_max( &tcache_bin_info[binind]) >> opt_lg_tcache_flush_large_div; tcache_bin_flush_large(tsd, tcache, bin, binind, remain); bool ret = cache_bin_dalloc_easy(bin, ptr); assert(ret); } } JEMALLOC_ALWAYS_INLINE tcache_t * tcaches_get(tsd_t *tsd, unsigned ind) { tcaches_t *elm = &tcaches[ind]; if (unlikely(elm->tcache == NULL)) { malloc_printf(": invalid tcache id (%u).\n", ind); abort(); } else if (unlikely(elm->tcache == TCACHES_ELM_NEED_REINIT)) { elm->tcache = tcache_create_explicit(tsd); } return elm->tcache; } #endif /* JEMALLOC_INTERNAL_TCACHE_INLINES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tcache_structs.h000066400000000000000000000041721501533116600255710ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TCACHE_STRUCTS_H #define JEMALLOC_INTERNAL_TCACHE_STRUCTS_H #include "jemalloc/internal/cache_bin.h" #include "jemalloc/internal/ql.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/ticker.h" #include "jemalloc/internal/tsd_types.h" /* * The tcache state is split into the slow and hot path data. Each has a * pointer to the other, and the data always comes in pairs. The layout of each * of them varies in practice; tcache_slow lives in the TSD for the automatic * tcache, and as part of a dynamic allocation for manual allocations. Keeping * a pointer to tcache_slow lets us treat these cases uniformly, rather than * splitting up the tcache [de]allocation code into those paths called with the * TSD tcache and those called with a manual tcache. */ struct tcache_slow_s { /* Lets us track all the tcaches in an arena. */ ql_elm(tcache_slow_t) link; /* * The descriptor lets the arena find our cache bins without seeing the * tcache definition. This enables arenas to aggregate stats across * tcaches without having a tcache dependency. */ cache_bin_array_descriptor_t cache_bin_array_descriptor; /* The arena this tcache is associated with. */ arena_t *arena; /* Next bin to GC. */ szind_t next_gc_bin; /* For small bins, fill (ncached_max >> lg_fill_div). */ uint8_t lg_fill_div[SC_NBINS]; /* For small bins, whether has been refilled since last GC. */ bool bin_refilled[SC_NBINS]; /* * For small bins, the number of items we can pretend to flush before * actually flushing. */ uint8_t bin_flush_delay_items[SC_NBINS]; /* * The start of the allocation containing the dynamic allocation for * either the cache bins alone, or the cache bin memory as well as this * tcache_slow_t and its associated tcache_t. */ void *dyn_alloc; /* The associated bins. */ tcache_t *tcache; }; struct tcache_s { tcache_slow_t *tcache_slow; cache_bin_t bins[TCACHE_NBINS_MAX]; }; /* Linkage for list of available (previously used) explicit tcache IDs. */ struct tcaches_s { union { tcache_t *tcache; tcaches_t *next; }; }; #endif /* JEMALLOC_INTERNAL_TCACHE_STRUCTS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tcache_types.h000066400000000000000000000025301501533116600252220ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TCACHE_TYPES_H #define JEMALLOC_INTERNAL_TCACHE_TYPES_H #include "jemalloc/internal/sc.h" typedef struct tcache_slow_s tcache_slow_t; typedef struct tcache_s tcache_t; typedef struct tcaches_s tcaches_t; /* * tcache pointers close to NULL are used to encode state information that is * used for two purposes: preventing thread caching on a per thread basis and * cleaning up during thread shutdown. */ #define TCACHE_STATE_DISABLED ((tcache_t *)(uintptr_t)1) #define TCACHE_STATE_REINCARNATED ((tcache_t *)(uintptr_t)2) #define TCACHE_STATE_PURGATORY ((tcache_t *)(uintptr_t)3) #define TCACHE_STATE_MAX TCACHE_STATE_PURGATORY /* Used in TSD static initializer only. Real init in tsd_tcache_data_init(). */ #define TCACHE_ZERO_INITIALIZER {0} #define TCACHE_SLOW_ZERO_INITIALIZER {0} /* Used in TSD static initializer only. Will be initialized to opt_tcache. */ #define TCACHE_ENABLED_ZERO_INITIALIZER false /* Used for explicit tcache only. Means flushed but not destroyed. */ #define TCACHES_ELM_NEED_REINIT ((tcache_t *)(uintptr_t)1) #define TCACHE_LG_MAXCLASS_LIMIT 23 /* tcache_maxclass = 8M */ #define TCACHE_MAXCLASS_LIMIT ((size_t)1 << TCACHE_LG_MAXCLASS_LIMIT) #define TCACHE_NBINS_MAX (SC_NBINS + SC_NGROUP * \ (TCACHE_LG_MAXCLASS_LIMIT - SC_LG_LARGE_MINCLASS) + 1) #endif /* JEMALLOC_INTERNAL_TCACHE_TYPES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/test_hooks.h000066400000000000000000000020341501533116600247300ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TEST_HOOKS_H #define JEMALLOC_INTERNAL_TEST_HOOKS_H extern JEMALLOC_EXPORT void (*test_hooks_arena_new_hook)(); extern JEMALLOC_EXPORT void (*test_hooks_libc_hook)(); #if defined(JEMALLOC_JET) || defined(JEMALLOC_UNIT_TEST) # define JEMALLOC_TEST_HOOK(fn, hook) ((void)(hook != NULL && (hook(), 0)), fn) # define open JEMALLOC_TEST_HOOK(open, test_hooks_libc_hook) # define read JEMALLOC_TEST_HOOK(read, test_hooks_libc_hook) # define write JEMALLOC_TEST_HOOK(write, test_hooks_libc_hook) # define readlink JEMALLOC_TEST_HOOK(readlink, test_hooks_libc_hook) # define close JEMALLOC_TEST_HOOK(close, test_hooks_libc_hook) # define creat JEMALLOC_TEST_HOOK(creat, test_hooks_libc_hook) # define secure_getenv JEMALLOC_TEST_HOOK(secure_getenv, test_hooks_libc_hook) /* Note that this is undef'd and re-define'd in src/prof.c. */ # define _Unwind_Backtrace JEMALLOC_TEST_HOOK(_Unwind_Backtrace, test_hooks_libc_hook) #else # define JEMALLOC_TEST_HOOK(fn, hook) fn #endif #endif /* JEMALLOC_INTERNAL_TEST_HOOKS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/thread_event.h000066400000000000000000000226341501533116600252260ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_THREAD_EVENT_H #define JEMALLOC_INTERNAL_THREAD_EVENT_H #include "jemalloc/internal/tsd.h" /* "te" is short for "thread_event" */ /* * TE_MIN_START_WAIT should not exceed the minimal allocation usize. */ #define TE_MIN_START_WAIT ((uint64_t)1U) #define TE_MAX_START_WAIT UINT64_MAX /* * Maximum threshold on thread_(de)allocated_next_event_fast, so that there is * no need to check overflow in malloc fast path. (The allocation size in malloc * fast path never exceeds SC_LOOKUP_MAXCLASS.) */ #define TE_NEXT_EVENT_FAST_MAX (UINT64_MAX - SC_LOOKUP_MAXCLASS + 1U) /* * The max interval helps make sure that malloc stays on the fast path in the * common case, i.e. thread_allocated < thread_allocated_next_event_fast. When * thread_allocated is within an event's distance to TE_NEXT_EVENT_FAST_MAX * above, thread_allocated_next_event_fast is wrapped around and we fall back to * the medium-fast path. The max interval makes sure that we're not staying on * the fallback case for too long, even if there's no active event or if all * active events have long wait times. */ #define TE_MAX_INTERVAL ((uint64_t)(4U << 20)) /* * Invalid elapsed time, for situations where elapsed time is not needed. See * comments in thread_event.c for more info. */ #define TE_INVALID_ELAPSED UINT64_MAX typedef struct te_ctx_s { bool is_alloc; uint64_t *current; uint64_t *last_event; uint64_t *next_event; uint64_t *next_event_fast; } te_ctx_t; void te_assert_invariants_debug(tsd_t *tsd); void te_event_trigger(tsd_t *tsd, te_ctx_t *ctx); void te_recompute_fast_threshold(tsd_t *tsd); void tsd_te_init(tsd_t *tsd); /* * List of all events, in the following format: * E(event, (condition), is_alloc_event) */ #define ITERATE_OVER_ALL_EVENTS \ E(tcache_gc, (opt_tcache_gc_incr_bytes > 0), true) \ E(prof_sample, (config_prof && opt_prof), true) \ E(stats_interval, (opt_stats_interval >= 0), true) \ E(tcache_gc_dalloc, (opt_tcache_gc_incr_bytes > 0), false) \ E(peak_alloc, config_stats, true) \ E(peak_dalloc, config_stats, false) #define E(event, condition_unused, is_alloc_event_unused) \ C(event##_event_wait) /* List of all thread event counters. */ #define ITERATE_OVER_ALL_COUNTERS \ C(thread_allocated) \ C(thread_allocated_last_event) \ ITERATE_OVER_ALL_EVENTS \ C(prof_sample_last_event) \ C(stats_interval_last_event) /* Getters directly wrap TSD getters. */ #define C(counter) \ JEMALLOC_ALWAYS_INLINE uint64_t \ counter##_get(tsd_t *tsd) { \ return tsd_##counter##_get(tsd); \ } ITERATE_OVER_ALL_COUNTERS #undef C /* * Setters call the TSD pointer getters rather than the TSD setters, so that * the counters can be modified even when TSD state is reincarnated or * minimal_initialized: if an event is triggered in such cases, we will * temporarily delay the event and let it be immediately triggered at the next * allocation call. */ #define C(counter) \ JEMALLOC_ALWAYS_INLINE void \ counter##_set(tsd_t *tsd, uint64_t v) { \ *tsd_##counter##p_get(tsd) = v; \ } ITERATE_OVER_ALL_COUNTERS #undef C /* * For generating _event_wait getter / setter functions for each individual * event. */ #undef E /* * The malloc and free fastpath getters -- use the unsafe getters since tsd may * be non-nominal, in which case the fast_threshold will be set to 0. This * allows checking for events and tsd non-nominal in a single branch. * * Note that these can only be used on the fastpath. */ JEMALLOC_ALWAYS_INLINE void te_malloc_fastpath_ctx(tsd_t *tsd, uint64_t *allocated, uint64_t *threshold) { *allocated = *tsd_thread_allocatedp_get_unsafe(tsd); *threshold = *tsd_thread_allocated_next_event_fastp_get_unsafe(tsd); assert(*threshold <= TE_NEXT_EVENT_FAST_MAX); } JEMALLOC_ALWAYS_INLINE void te_free_fastpath_ctx(tsd_t *tsd, uint64_t *deallocated, uint64_t *threshold) { /* Unsafe getters since this may happen before tsd_init. */ *deallocated = *tsd_thread_deallocatedp_get_unsafe(tsd); *threshold = *tsd_thread_deallocated_next_event_fastp_get_unsafe(tsd); assert(*threshold <= TE_NEXT_EVENT_FAST_MAX); } JEMALLOC_ALWAYS_INLINE bool te_ctx_is_alloc(te_ctx_t *ctx) { return ctx->is_alloc; } JEMALLOC_ALWAYS_INLINE uint64_t te_ctx_current_bytes_get(te_ctx_t *ctx) { return *ctx->current; } JEMALLOC_ALWAYS_INLINE void te_ctx_current_bytes_set(te_ctx_t *ctx, uint64_t v) { *ctx->current = v; } JEMALLOC_ALWAYS_INLINE uint64_t te_ctx_last_event_get(te_ctx_t *ctx) { return *ctx->last_event; } JEMALLOC_ALWAYS_INLINE void te_ctx_last_event_set(te_ctx_t *ctx, uint64_t v) { *ctx->last_event = v; } /* Below 3 for next_event_fast. */ JEMALLOC_ALWAYS_INLINE uint64_t te_ctx_next_event_fast_get(te_ctx_t *ctx) { uint64_t v = *ctx->next_event_fast; assert(v <= TE_NEXT_EVENT_FAST_MAX); return v; } JEMALLOC_ALWAYS_INLINE void te_ctx_next_event_fast_set(te_ctx_t *ctx, uint64_t v) { assert(v <= TE_NEXT_EVENT_FAST_MAX); *ctx->next_event_fast = v; } JEMALLOC_ALWAYS_INLINE void te_next_event_fast_set_non_nominal(tsd_t *tsd) { /* * Set the fast thresholds to zero when tsd is non-nominal. Use the * unsafe getter as this may get called during tsd init and clean up. */ *tsd_thread_allocated_next_event_fastp_get_unsafe(tsd) = 0; *tsd_thread_deallocated_next_event_fastp_get_unsafe(tsd) = 0; } /* For next_event. Setter also updates the fast threshold. */ JEMALLOC_ALWAYS_INLINE uint64_t te_ctx_next_event_get(te_ctx_t *ctx) { return *ctx->next_event; } JEMALLOC_ALWAYS_INLINE void te_ctx_next_event_set(tsd_t *tsd, te_ctx_t *ctx, uint64_t v) { *ctx->next_event = v; te_recompute_fast_threshold(tsd); } /* * The function checks in debug mode whether the thread event counters are in * a consistent state, which forms the invariants before and after each round * of thread event handling that we can rely on and need to promise. * The invariants are only temporarily violated in the middle of * te_event_advance() if an event is triggered (the te_event_trigger() call at * the end will restore the invariants). */ JEMALLOC_ALWAYS_INLINE void te_assert_invariants(tsd_t *tsd) { if (config_debug) { te_assert_invariants_debug(tsd); } } JEMALLOC_ALWAYS_INLINE void te_ctx_get(tsd_t *tsd, te_ctx_t *ctx, bool is_alloc) { ctx->is_alloc = is_alloc; if (is_alloc) { ctx->current = tsd_thread_allocatedp_get(tsd); ctx->last_event = tsd_thread_allocated_last_eventp_get(tsd); ctx->next_event = tsd_thread_allocated_next_eventp_get(tsd); ctx->next_event_fast = tsd_thread_allocated_next_event_fastp_get(tsd); } else { ctx->current = tsd_thread_deallocatedp_get(tsd); ctx->last_event = tsd_thread_deallocated_last_eventp_get(tsd); ctx->next_event = tsd_thread_deallocated_next_eventp_get(tsd); ctx->next_event_fast = tsd_thread_deallocated_next_event_fastp_get(tsd); } } /* * The lookahead functionality facilitates events to be able to lookahead, i.e. * without touching the event counters, to determine whether an event would be * triggered. The event counters are not advanced until the end of the * allocation / deallocation calls, so the lookahead can be useful if some * preparation work for some event must be done early in the allocation / * deallocation calls. * * Currently only the profiling sampling event needs the lookahead * functionality, so we don't yet define general purpose lookahead functions. * * Surplus is a terminology referring to the amount of bytes beyond what's * needed for triggering an event, which can be a useful quantity to have in * general when lookahead is being called. */ JEMALLOC_ALWAYS_INLINE bool te_prof_sample_event_lookahead_surplus(tsd_t *tsd, size_t usize, size_t *surplus) { if (surplus != NULL) { /* * This is a dead store: the surplus will be overwritten before * any read. The initialization suppresses compiler warnings. * Meanwhile, using SIZE_MAX to initialize is good for * debugging purpose, because a valid surplus value is strictly * less than usize, which is at most SIZE_MAX. */ *surplus = SIZE_MAX; } if (unlikely(!tsd_nominal(tsd) || tsd_reentrancy_level_get(tsd) > 0)) { return false; } /* The subtraction is intentionally susceptible to underflow. */ uint64_t accumbytes = tsd_thread_allocated_get(tsd) + usize - tsd_thread_allocated_last_event_get(tsd); uint64_t sample_wait = tsd_prof_sample_event_wait_get(tsd); if (accumbytes < sample_wait) { return false; } assert(accumbytes - sample_wait < (uint64_t)usize); if (surplus != NULL) { *surplus = (size_t)(accumbytes - sample_wait); } return true; } JEMALLOC_ALWAYS_INLINE bool te_prof_sample_event_lookahead(tsd_t *tsd, size_t usize) { return te_prof_sample_event_lookahead_surplus(tsd, usize, NULL); } JEMALLOC_ALWAYS_INLINE void te_event_advance(tsd_t *tsd, size_t usize, bool is_alloc) { te_assert_invariants(tsd); te_ctx_t ctx; te_ctx_get(tsd, &ctx, is_alloc); uint64_t bytes_before = te_ctx_current_bytes_get(&ctx); te_ctx_current_bytes_set(&ctx, bytes_before + usize); /* The subtraction is intentionally susceptible to underflow. */ if (likely(usize < te_ctx_next_event_get(&ctx) - bytes_before)) { te_assert_invariants(tsd); } else { te_event_trigger(tsd, &ctx); } } JEMALLOC_ALWAYS_INLINE void thread_dalloc_event(tsd_t *tsd, size_t usize) { te_event_advance(tsd, usize, false); } JEMALLOC_ALWAYS_INLINE void thread_alloc_event(tsd_t *tsd, size_t usize) { te_event_advance(tsd, usize, true); } #endif /* JEMALLOC_INTERNAL_THREAD_EVENT_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/ticker.h000066400000000000000000000117571501533116600240430ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TICKER_H #define JEMALLOC_INTERNAL_TICKER_H #include "jemalloc/internal/prng.h" #include "jemalloc/internal/util.h" /** * A ticker makes it easy to count-down events until some limit. You * ticker_init the ticker to trigger every nticks events. You then notify it * that an event has occurred with calls to ticker_tick (or that nticks events * have occurred with a call to ticker_ticks), which will return true (and reset * the counter) if the countdown hit zero. */ typedef struct ticker_s ticker_t; struct ticker_s { int32_t tick; int32_t nticks; }; static inline void ticker_init(ticker_t *ticker, int32_t nticks) { ticker->tick = nticks; ticker->nticks = nticks; } static inline void ticker_copy(ticker_t *ticker, const ticker_t *other) { *ticker = *other; } static inline int32_t ticker_read(const ticker_t *ticker) { return ticker->tick; } /* * Not intended to be a public API. Unfortunately, on x86, neither gcc nor * clang seems smart enough to turn * ticker->tick -= nticks; * if (unlikely(ticker->tick < 0)) { * fixup ticker * return true; * } * return false; * into * subq %nticks_reg, (%ticker_reg) * js fixup ticker * * unless we force "fixup ticker" out of line. In that case, gcc gets it right, * but clang now does worse than before. So, on x86 with gcc, we force it out * of line, but otherwise let the inlining occur. Ordinarily this wouldn't be * worth the hassle, but this is on the fast path of both malloc and free (via * tcache_event). */ #if defined(__GNUC__) && !defined(__clang__) \ && (defined(__x86_64__) || defined(__i386__)) JEMALLOC_NOINLINE #endif static bool ticker_fixup(ticker_t *ticker) { ticker->tick = ticker->nticks; return true; } static inline bool ticker_ticks(ticker_t *ticker, int32_t nticks) { ticker->tick -= nticks; if (unlikely(ticker->tick < 0)) { return ticker_fixup(ticker); } return false; } static inline bool ticker_tick(ticker_t *ticker) { return ticker_ticks(ticker, 1); } /* * Try to tick. If ticker would fire, return true, but rely on * slowpath to reset ticker. */ static inline bool ticker_trytick(ticker_t *ticker) { --ticker->tick; if (unlikely(ticker->tick < 0)) { return true; } return false; } /* * The ticker_geom_t is much like the ticker_t, except that instead of ticker * having a constant countdown, it has an approximate one; each tick has * approximately a 1/nticks chance of triggering the count. * * The motivation is in triggering arena decay. With a naive strategy, each * thread would maintain a ticker per arena, and check if decay is necessary * each time that the arena's ticker fires. This has two costs: * - Since under reasonable assumptions both threads and arenas can scale * linearly with the number of CPUs, maintaining per-arena data in each thread * scales quadratically with the number of CPUs. * - These tickers are often a cache miss down tcache flush pathways. * * By giving each tick a 1/nticks chance of firing, we still maintain the same * average number of ticks-until-firing per arena, with only a single ticker's * worth of metadata. */ /* See ticker.c for an explanation of these constants. */ #define TICKER_GEOM_NBITS 6 #define TICKER_GEOM_MUL 61 extern const uint8_t ticker_geom_table[1 << TICKER_GEOM_NBITS]; /* Not actually any different from ticker_t; just for type safety. */ typedef struct ticker_geom_s ticker_geom_t; struct ticker_geom_s { int32_t tick; int32_t nticks; }; /* * Just pick the average delay for the first counter. We're more concerned with * the behavior over long periods of time rather than the exact timing of the * initial ticks. */ #define TICKER_GEOM_INIT(nticks) {nticks, nticks} static inline void ticker_geom_init(ticker_geom_t *ticker, int32_t nticks) { /* * Make sure there's no overflow possible. This shouldn't really be a * problem for reasonable nticks choices, which are all static and * relatively small. */ assert((uint64_t)nticks * (uint64_t)255 / (uint64_t)TICKER_GEOM_MUL <= (uint64_t)INT32_MAX); ticker->tick = nticks; ticker->nticks = nticks; } static inline int32_t ticker_geom_read(const ticker_geom_t *ticker) { return ticker->tick; } /* Same deal as above. */ #if defined(__GNUC__) && !defined(__clang__) \ && (defined(__x86_64__) || defined(__i386__)) JEMALLOC_NOINLINE #endif static bool ticker_geom_fixup(ticker_geom_t *ticker, uint64_t *prng_state) { uint64_t idx = prng_lg_range_u64(prng_state, TICKER_GEOM_NBITS); ticker->tick = (uint32_t)( (uint64_t)ticker->nticks * (uint64_t)ticker_geom_table[idx] / (uint64_t)TICKER_GEOM_MUL); return true; } static inline bool ticker_geom_ticks(ticker_geom_t *ticker, uint64_t *prng_state, int32_t nticks) { ticker->tick -= nticks; if (unlikely(ticker->tick < 0)) { return ticker_geom_fixup(ticker, prng_state); } return false; } static inline bool ticker_geom_tick(ticker_geom_t *ticker, uint64_t *prng_state) { return ticker_geom_ticks(ticker, prng_state, 1); } #endif /* JEMALLOC_INTERNAL_TICKER_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tsd.h000066400000000000000000000377641501533116600233620ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TSD_H #define JEMALLOC_INTERNAL_TSD_H #include "jemalloc/internal/activity_callback.h" #include "jemalloc/internal/arena_types.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/bin_types.h" #include "jemalloc/internal/jemalloc_internal_externs.h" #include "jemalloc/internal/peak.h" #include "jemalloc/internal/prof_types.h" #include "jemalloc/internal/ql.h" #include "jemalloc/internal/rtree_tsd.h" #include "jemalloc/internal/tcache_types.h" #include "jemalloc/internal/tcache_structs.h" #include "jemalloc/internal/util.h" #include "jemalloc/internal/witness.h" /* * Thread-Specific-Data layout * * At least some thread-local data gets touched on the fast-path of almost all * malloc operations. But much of it is only necessary down slow-paths, or * testing. We want to colocate the fast-path data so that it can live on the * same cacheline if possible. So we define three tiers of hotness: * TSD_DATA_FAST: Touched on the alloc/dalloc fast paths. * TSD_DATA_SLOW: Touched down slow paths. "Slow" here is sort of general; * there are "semi-slow" paths like "not a sized deallocation, but can still * live in the tcache". We'll want to keep these closer to the fast-path * data. * TSD_DATA_SLOWER: Only touched in test or debug modes, or not touched at all. * * An additional concern is that the larger tcache bins won't be used (we have a * bin per size class, but by default only cache relatively small objects). So * the earlier bins are in the TSD_DATA_FAST tier, but the later ones are in the * TSD_DATA_SLOWER tier. * * As a result of all this, we put the slow data first, then the fast data, then * the slower data, while keeping the tcache as the last element of the fast * data (so that the fast -> slower transition happens midway through the * tcache). While we don't yet play alignment tricks to guarantee it, this * increases our odds of getting some cache/page locality on fast paths. */ #ifdef JEMALLOC_JET typedef void (*test_callback_t)(int *); # define MALLOC_TSD_TEST_DATA_INIT 0x72b65c10 # define MALLOC_TEST_TSD \ O(test_data, int, int) \ O(test_callback, test_callback_t, int) # define MALLOC_TEST_TSD_INITIALIZER , MALLOC_TSD_TEST_DATA_INIT, NULL #else # define MALLOC_TEST_TSD # define MALLOC_TEST_TSD_INITIALIZER #endif typedef ql_elm(tsd_t) tsd_link_t; /* O(name, type, nullable type) */ #define TSD_DATA_SLOW \ O(tcache_enabled, bool, bool) \ O(reentrancy_level, int8_t, int8_t) \ O(thread_allocated_last_event, uint64_t, uint64_t) \ O(thread_allocated_next_event, uint64_t, uint64_t) \ O(thread_deallocated_last_event, uint64_t, uint64_t) \ O(thread_deallocated_next_event, uint64_t, uint64_t) \ O(tcache_gc_event_wait, uint64_t, uint64_t) \ O(tcache_gc_dalloc_event_wait, uint64_t, uint64_t) \ O(prof_sample_event_wait, uint64_t, uint64_t) \ O(prof_sample_last_event, uint64_t, uint64_t) \ O(stats_interval_event_wait, uint64_t, uint64_t) \ O(stats_interval_last_event, uint64_t, uint64_t) \ O(peak_alloc_event_wait, uint64_t, uint64_t) \ O(peak_dalloc_event_wait, uint64_t, uint64_t) \ O(prof_tdata, prof_tdata_t *, prof_tdata_t *) \ O(prng_state, uint64_t, uint64_t) \ O(san_extents_until_guard_small, uint64_t, uint64_t) \ O(san_extents_until_guard_large, uint64_t, uint64_t) \ O(iarena, arena_t *, arena_t *) \ O(arena, arena_t *, arena_t *) \ O(arena_decay_ticker, ticker_geom_t, ticker_geom_t) \ O(sec_shard, uint8_t, uint8_t) \ O(binshards, tsd_binshards_t, tsd_binshards_t)\ O(tsd_link, tsd_link_t, tsd_link_t) \ O(in_hook, bool, bool) \ O(peak, peak_t, peak_t) \ O(activity_callback_thunk, activity_callback_thunk_t, \ activity_callback_thunk_t) \ O(tcache_slow, tcache_slow_t, tcache_slow_t) \ O(rtree_ctx, rtree_ctx_t, rtree_ctx_t) #define TSD_DATA_SLOW_INITIALIZER \ /* tcache_enabled */ TCACHE_ENABLED_ZERO_INITIALIZER, \ /* reentrancy_level */ 0, \ /* thread_allocated_last_event */ 0, \ /* thread_allocated_next_event */ 0, \ /* thread_deallocated_last_event */ 0, \ /* thread_deallocated_next_event */ 0, \ /* tcache_gc_event_wait */ 0, \ /* tcache_gc_dalloc_event_wait */ 0, \ /* prof_sample_event_wait */ 0, \ /* prof_sample_last_event */ 0, \ /* stats_interval_event_wait */ 0, \ /* stats_interval_last_event */ 0, \ /* peak_alloc_event_wait */ 0, \ /* peak_dalloc_event_wait */ 0, \ /* prof_tdata */ NULL, \ /* prng_state */ 0, \ /* san_extents_until_guard_small */ 0, \ /* san_extents_until_guard_large */ 0, \ /* iarena */ NULL, \ /* arena */ NULL, \ /* arena_decay_ticker */ \ TICKER_GEOM_INIT(ARENA_DECAY_NTICKS_PER_UPDATE), \ /* sec_shard */ (uint8_t)-1, \ /* binshards */ TSD_BINSHARDS_ZERO_INITIALIZER, \ /* tsd_link */ {NULL}, \ /* in_hook */ false, \ /* peak */ PEAK_INITIALIZER, \ /* activity_callback_thunk */ \ ACTIVITY_CALLBACK_THUNK_INITIALIZER, \ /* tcache_slow */ TCACHE_SLOW_ZERO_INITIALIZER, \ /* rtree_ctx */ RTREE_CTX_INITIALIZER, /* O(name, type, nullable type) */ #define TSD_DATA_FAST \ O(thread_allocated, uint64_t, uint64_t) \ O(thread_allocated_next_event_fast, uint64_t, uint64_t) \ O(thread_deallocated, uint64_t, uint64_t) \ O(thread_deallocated_next_event_fast, uint64_t, uint64_t) \ O(tcache, tcache_t, tcache_t) #define TSD_DATA_FAST_INITIALIZER \ /* thread_allocated */ 0, \ /* thread_allocated_next_event_fast */ 0, \ /* thread_deallocated */ 0, \ /* thread_deallocated_next_event_fast */ 0, \ /* tcache */ TCACHE_ZERO_INITIALIZER, /* O(name, type, nullable type) */ #define TSD_DATA_SLOWER \ O(witness_tsd, witness_tsd_t, witness_tsdn_t) \ MALLOC_TEST_TSD #define TSD_DATA_SLOWER_INITIALIZER \ /* witness */ WITNESS_TSD_INITIALIZER \ /* test data */ MALLOC_TEST_TSD_INITIALIZER #define TSD_INITIALIZER { \ TSD_DATA_SLOW_INITIALIZER \ /* state */ ATOMIC_INIT(tsd_state_uninitialized), \ TSD_DATA_FAST_INITIALIZER \ TSD_DATA_SLOWER_INITIALIZER \ } #if defined(JEMALLOC_MALLOC_THREAD_CLEANUP) || defined(_WIN32) void _malloc_tsd_cleanup_register(bool (*f)(void)); #endif void *malloc_tsd_malloc(size_t size); void malloc_tsd_dalloc(void *wrapper); tsd_t *malloc_tsd_boot0(void); void malloc_tsd_boot1(void); void tsd_cleanup(void *arg); tsd_t *tsd_fetch_slow(tsd_t *tsd, bool internal); void tsd_state_set(tsd_t *tsd, uint8_t new_state); void tsd_slow_update(tsd_t *tsd); void tsd_prefork(tsd_t *tsd); void tsd_postfork_parent(tsd_t *tsd); void tsd_postfork_child(tsd_t *tsd); /* * Call ..._inc when your module wants to take all threads down the slow paths, * and ..._dec when it no longer needs to. */ void tsd_global_slow_inc(tsdn_t *tsdn); void tsd_global_slow_dec(tsdn_t *tsdn); bool tsd_global_slow(); enum { /* Common case --> jnz. */ tsd_state_nominal = 0, /* Initialized but on slow path. */ tsd_state_nominal_slow = 1, /* * Some thread has changed global state in such a way that all nominal * threads need to recompute their fast / slow status the next time they * get a chance. * * Any thread can change another thread's status *to* recompute, but * threads are the only ones who can change their status *from* * recompute. */ tsd_state_nominal_recompute = 2, /* * The above nominal states should be lower values. We use * tsd_nominal_max to separate nominal states from threads in the * process of being born / dying. */ tsd_state_nominal_max = 2, /* * A thread might free() during its death as its only allocator action; * in such scenarios, we need tsd, but set up in such a way that no * cleanup is necessary. */ tsd_state_minimal_initialized = 3, /* States during which we know we're in thread death. */ tsd_state_purgatory = 4, tsd_state_reincarnated = 5, /* * What it says on the tin; tsd that hasn't been initialized. Note * that even when the tsd struct lives in TLS, when need to keep track * of stuff like whether or not our pthread destructors have been * scheduled, so this really truly is different than the nominal state. */ tsd_state_uninitialized = 6 }; /* * Some TSD accesses can only be done in a nominal state. To enforce this, we * wrap TSD member access in a function that asserts on TSD state, and mangle * field names to prevent touching them accidentally. */ #define TSD_MANGLE(n) cant_access_tsd_items_directly_use_a_getter_or_setter_##n #ifdef JEMALLOC_U8_ATOMICS # define tsd_state_t atomic_u8_t # define tsd_atomic_load atomic_load_u8 # define tsd_atomic_store atomic_store_u8 # define tsd_atomic_exchange atomic_exchange_u8 #else # define tsd_state_t atomic_u32_t # define tsd_atomic_load atomic_load_u32 # define tsd_atomic_store atomic_store_u32 # define tsd_atomic_exchange atomic_exchange_u32 #endif /* The actual tsd. */ struct tsd_s { /* * The contents should be treated as totally opaque outside the tsd * module. Access any thread-local state through the getters and * setters below. */ #define O(n, t, nt) \ t TSD_MANGLE(n); TSD_DATA_SLOW /* * We manually limit the state to just a single byte. Unless the 8-bit * atomics are unavailable (which is rare). */ tsd_state_t state; TSD_DATA_FAST TSD_DATA_SLOWER #undef O }; JEMALLOC_ALWAYS_INLINE uint8_t tsd_state_get(tsd_t *tsd) { /* * This should be atomic. Unfortunately, compilers right now can't tell * that this can be done as a memory comparison, and forces a load into * a register that hurts fast-path performance. */ /* return atomic_load_u8(&tsd->state, ATOMIC_RELAXED); */ return *(uint8_t *)&tsd->state; } /* * Wrapper around tsd_t that makes it possible to avoid implicit conversion * between tsd_t and tsdn_t, where tsdn_t is "nullable" and has to be * explicitly converted to tsd_t, which is non-nullable. */ struct tsdn_s { tsd_t tsd; }; #define TSDN_NULL ((tsdn_t *)0) JEMALLOC_ALWAYS_INLINE tsdn_t * tsd_tsdn(tsd_t *tsd) { return (tsdn_t *)tsd; } JEMALLOC_ALWAYS_INLINE bool tsdn_null(const tsdn_t *tsdn) { return tsdn == NULL; } JEMALLOC_ALWAYS_INLINE tsd_t * tsdn_tsd(tsdn_t *tsdn) { assert(!tsdn_null(tsdn)); return &tsdn->tsd; } /* * We put the platform-specific data declarations and inlines into their own * header files to avoid cluttering this file. They define tsd_boot0, * tsd_boot1, tsd_boot, tsd_booted_get, tsd_get_allocates, tsd_get, and tsd_set. */ #ifdef JEMALLOC_MALLOC_THREAD_CLEANUP #include "jemalloc/internal/tsd_malloc_thread_cleanup.h" #elif (defined(JEMALLOC_TLS)) #include "jemalloc/internal/tsd_tls.h" #elif (defined(_WIN32)) #include "jemalloc/internal/tsd_win.h" #else #include "jemalloc/internal/tsd_generic.h" #endif /* * tsd_foop_get_unsafe(tsd) returns a pointer to the thread-local instance of * foo. This omits some safety checks, and so can be used during tsd * initialization and cleanup. */ #define O(n, t, nt) \ JEMALLOC_ALWAYS_INLINE t * \ tsd_##n##p_get_unsafe(tsd_t *tsd) { \ return &tsd->TSD_MANGLE(n); \ } TSD_DATA_SLOW TSD_DATA_FAST TSD_DATA_SLOWER #undef O /* tsd_foop_get(tsd) returns a pointer to the thread-local instance of foo. */ #define O(n, t, nt) \ JEMALLOC_ALWAYS_INLINE t * \ tsd_##n##p_get(tsd_t *tsd) { \ /* \ * Because the state might change asynchronously if it's \ * nominal, we need to make sure that we only read it once. \ */ \ uint8_t state = tsd_state_get(tsd); \ assert(state == tsd_state_nominal || \ state == tsd_state_nominal_slow || \ state == tsd_state_nominal_recompute || \ state == tsd_state_reincarnated || \ state == tsd_state_minimal_initialized); \ return tsd_##n##p_get_unsafe(tsd); \ } TSD_DATA_SLOW TSD_DATA_FAST TSD_DATA_SLOWER #undef O /* * tsdn_foop_get(tsdn) returns either the thread-local instance of foo (if tsdn * isn't NULL), or NULL (if tsdn is NULL), cast to the nullable pointer type. */ #define O(n, t, nt) \ JEMALLOC_ALWAYS_INLINE nt * \ tsdn_##n##p_get(tsdn_t *tsdn) { \ if (tsdn_null(tsdn)) { \ return NULL; \ } \ tsd_t *tsd = tsdn_tsd(tsdn); \ return (nt *)tsd_##n##p_get(tsd); \ } TSD_DATA_SLOW TSD_DATA_FAST TSD_DATA_SLOWER #undef O /* tsd_foo_get(tsd) returns the value of the thread-local instance of foo. */ #define O(n, t, nt) \ JEMALLOC_ALWAYS_INLINE t \ tsd_##n##_get(tsd_t *tsd) { \ return *tsd_##n##p_get(tsd); \ } TSD_DATA_SLOW TSD_DATA_FAST TSD_DATA_SLOWER #undef O /* tsd_foo_set(tsd, val) updates the thread-local instance of foo to be val. */ #define O(n, t, nt) \ JEMALLOC_ALWAYS_INLINE void \ tsd_##n##_set(tsd_t *tsd, t val) { \ assert(tsd_state_get(tsd) != tsd_state_reincarnated && \ tsd_state_get(tsd) != tsd_state_minimal_initialized); \ *tsd_##n##p_get(tsd) = val; \ } TSD_DATA_SLOW TSD_DATA_FAST TSD_DATA_SLOWER #undef O JEMALLOC_ALWAYS_INLINE void tsd_assert_fast(tsd_t *tsd) { /* * Note that our fastness assertion does *not* include global slowness * counters; it's not in general possible to ensure that they won't * change asynchronously from underneath us. */ assert(!malloc_slow && tsd_tcache_enabled_get(tsd) && tsd_reentrancy_level_get(tsd) == 0); } JEMALLOC_ALWAYS_INLINE bool tsd_fast(tsd_t *tsd) { bool fast = (tsd_state_get(tsd) == tsd_state_nominal); if (fast) { tsd_assert_fast(tsd); } return fast; } JEMALLOC_ALWAYS_INLINE tsd_t * tsd_fetch_impl(bool init, bool minimal) { tsd_t *tsd = tsd_get(init); if (!init && tsd_get_allocates() && tsd == NULL) { return NULL; } assert(tsd != NULL); if (unlikely(tsd_state_get(tsd) != tsd_state_nominal)) { return tsd_fetch_slow(tsd, minimal); } assert(tsd_fast(tsd)); tsd_assert_fast(tsd); return tsd; } /* Get a minimal TSD that requires no cleanup. See comments in free(). */ JEMALLOC_ALWAYS_INLINE tsd_t * tsd_fetch_min(void) { return tsd_fetch_impl(true, true); } /* For internal background threads use only. */ JEMALLOC_ALWAYS_INLINE tsd_t * tsd_internal_fetch(void) { tsd_t *tsd = tsd_fetch_min(); /* Use reincarnated state to prevent full initialization. */ tsd_state_set(tsd, tsd_state_reincarnated); return tsd; } JEMALLOC_ALWAYS_INLINE tsd_t * tsd_fetch(void) { return tsd_fetch_impl(true, false); } static inline bool tsd_nominal(tsd_t *tsd) { bool nominal = tsd_state_get(tsd) <= tsd_state_nominal_max; assert(nominal || tsd_reentrancy_level_get(tsd) > 0); return nominal; } JEMALLOC_ALWAYS_INLINE tsdn_t * tsdn_fetch(void) { if (!tsd_booted_get()) { return NULL; } return tsd_tsdn(tsd_fetch_impl(false, false)); } JEMALLOC_ALWAYS_INLINE rtree_ctx_t * tsd_rtree_ctx(tsd_t *tsd) { return tsd_rtree_ctxp_get(tsd); } JEMALLOC_ALWAYS_INLINE rtree_ctx_t * tsdn_rtree_ctx(tsdn_t *tsdn, rtree_ctx_t *fallback) { /* * If tsd cannot be accessed, initialize the fallback rtree_ctx and * return a pointer to it. */ if (unlikely(tsdn_null(tsdn))) { rtree_ctx_data_init(fallback); return fallback; } return tsd_rtree_ctx(tsdn_tsd(tsdn)); } static inline bool tsd_state_nocleanup(tsd_t *tsd) { return tsd_state_get(tsd) == tsd_state_reincarnated || tsd_state_get(tsd) == tsd_state_minimal_initialized; } /* * These "raw" tsd reentrancy functions don't have any debug checking to make * sure that we're not touching arena 0. Better is to call pre_reentrancy and * post_reentrancy if this is possible. */ static inline void tsd_pre_reentrancy_raw(tsd_t *tsd) { bool fast = tsd_fast(tsd); assert(tsd_reentrancy_level_get(tsd) < INT8_MAX); ++*tsd_reentrancy_levelp_get(tsd); if (fast) { /* Prepare slow path for reentrancy. */ tsd_slow_update(tsd); assert(tsd_state_get(tsd) == tsd_state_nominal_slow); } } static inline void tsd_post_reentrancy_raw(tsd_t *tsd) { int8_t *reentrancy_level = tsd_reentrancy_levelp_get(tsd); assert(*reentrancy_level > 0); if (--*reentrancy_level == 0) { tsd_slow_update(tsd); } } #endif /* JEMALLOC_INTERNAL_TSD_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tsd_generic.h000066400000000000000000000101611501533116600250340ustar00rootroot00000000000000#ifdef JEMALLOC_INTERNAL_TSD_GENERIC_H #error This file should be included only once, by tsd.h. #endif #define JEMALLOC_INTERNAL_TSD_GENERIC_H typedef struct tsd_init_block_s tsd_init_block_t; struct tsd_init_block_s { ql_elm(tsd_init_block_t) link; pthread_t thread; void *data; }; /* Defined in tsd.c, to allow the mutex headers to have tsd dependencies. */ typedef struct tsd_init_head_s tsd_init_head_t; typedef struct { bool initialized; tsd_t val; } tsd_wrapper_t; void *tsd_init_check_recursion(tsd_init_head_t *head, tsd_init_block_t *block); void tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block); extern pthread_key_t tsd_tsd; extern tsd_init_head_t tsd_init_head; extern tsd_wrapper_t tsd_boot_wrapper; extern bool tsd_booted; /* Initialization/cleanup. */ JEMALLOC_ALWAYS_INLINE void tsd_cleanup_wrapper(void *arg) { tsd_wrapper_t *wrapper = (tsd_wrapper_t *)arg; if (wrapper->initialized) { wrapper->initialized = false; tsd_cleanup(&wrapper->val); if (wrapper->initialized) { /* Trigger another cleanup round. */ if (pthread_setspecific(tsd_tsd, (void *)wrapper) != 0) { malloc_write(": Error setting TSD\n"); if (opt_abort) { abort(); } } return; } } malloc_tsd_dalloc(wrapper); } JEMALLOC_ALWAYS_INLINE void tsd_wrapper_set(tsd_wrapper_t *wrapper) { if (unlikely(!tsd_booted)) { return; } if (pthread_setspecific(tsd_tsd, (void *)wrapper) != 0) { malloc_write(": Error setting TSD\n"); abort(); } } JEMALLOC_ALWAYS_INLINE tsd_wrapper_t * tsd_wrapper_get(bool init) { tsd_wrapper_t *wrapper; if (unlikely(!tsd_booted)) { return &tsd_boot_wrapper; } wrapper = (tsd_wrapper_t *)pthread_getspecific(tsd_tsd); if (init && unlikely(wrapper == NULL)) { tsd_init_block_t block; wrapper = (tsd_wrapper_t *) tsd_init_check_recursion(&tsd_init_head, &block); if (wrapper) { return wrapper; } wrapper = (tsd_wrapper_t *) malloc_tsd_malloc(sizeof(tsd_wrapper_t)); block.data = (void *)wrapper; if (wrapper == NULL) { malloc_write(": Error allocating TSD\n"); abort(); } else { wrapper->initialized = false; JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS tsd_t initializer = TSD_INITIALIZER; JEMALLOC_DIAGNOSTIC_POP wrapper->val = initializer; } tsd_wrapper_set(wrapper); tsd_init_finish(&tsd_init_head, &block); } return wrapper; } JEMALLOC_ALWAYS_INLINE bool tsd_boot0(void) { tsd_wrapper_t *wrapper; tsd_init_block_t block; wrapper = (tsd_wrapper_t *) tsd_init_check_recursion(&tsd_init_head, &block); if (wrapper) { return false; } block.data = &tsd_boot_wrapper; if (pthread_key_create(&tsd_tsd, tsd_cleanup_wrapper) != 0) { return true; } tsd_booted = true; tsd_wrapper_set(&tsd_boot_wrapper); tsd_init_finish(&tsd_init_head, &block); return false; } JEMALLOC_ALWAYS_INLINE void tsd_boot1(void) { tsd_wrapper_t *wrapper; wrapper = (tsd_wrapper_t *)malloc_tsd_malloc(sizeof(tsd_wrapper_t)); if (wrapper == NULL) { malloc_write(": Error allocating TSD\n"); abort(); } tsd_boot_wrapper.initialized = false; tsd_cleanup(&tsd_boot_wrapper.val); wrapper->initialized = false; JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS tsd_t initializer = TSD_INITIALIZER; JEMALLOC_DIAGNOSTIC_POP wrapper->val = initializer; tsd_wrapper_set(wrapper); } JEMALLOC_ALWAYS_INLINE bool tsd_boot(void) { if (tsd_boot0()) { return true; } tsd_boot1(); return false; } JEMALLOC_ALWAYS_INLINE bool tsd_booted_get(void) { return tsd_booted; } JEMALLOC_ALWAYS_INLINE bool tsd_get_allocates(void) { return true; } /* Get/set. */ JEMALLOC_ALWAYS_INLINE tsd_t * tsd_get(bool init) { tsd_wrapper_t *wrapper; assert(tsd_booted); wrapper = tsd_wrapper_get(init); if (tsd_get_allocates() && !init && wrapper == NULL) { return NULL; } return &wrapper->val; } JEMALLOC_ALWAYS_INLINE void tsd_set(tsd_t *val) { tsd_wrapper_t *wrapper; assert(tsd_booted); wrapper = tsd_wrapper_get(true); if (likely(&wrapper->val != val)) { wrapper->val = *(val); } wrapper->initialized = true; } redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tsd_malloc_thread_cleanup.h000066400000000000000000000023041501533116600277250ustar00rootroot00000000000000#ifdef JEMALLOC_INTERNAL_TSD_MALLOC_THREAD_CLEANUP_H #error This file should be included only once, by tsd.h. #endif #define JEMALLOC_INTERNAL_TSD_MALLOC_THREAD_CLEANUP_H #define JEMALLOC_TSD_TYPE_ATTR(type) __thread type JEMALLOC_TLS_MODEL extern JEMALLOC_TSD_TYPE_ATTR(tsd_t) tsd_tls; extern JEMALLOC_TSD_TYPE_ATTR(bool) tsd_initialized; extern bool tsd_booted; /* Initialization/cleanup. */ JEMALLOC_ALWAYS_INLINE bool tsd_cleanup_wrapper(void) { if (tsd_initialized) { tsd_initialized = false; tsd_cleanup(&tsd_tls); } return tsd_initialized; } JEMALLOC_ALWAYS_INLINE bool tsd_boot0(void) { _malloc_tsd_cleanup_register(&tsd_cleanup_wrapper); tsd_booted = true; return false; } JEMALLOC_ALWAYS_INLINE void tsd_boot1(void) { /* Do nothing. */ } JEMALLOC_ALWAYS_INLINE bool tsd_boot(void) { return tsd_boot0(); } JEMALLOC_ALWAYS_INLINE bool tsd_booted_get(void) { return tsd_booted; } JEMALLOC_ALWAYS_INLINE bool tsd_get_allocates(void) { return false; } /* Get/set. */ JEMALLOC_ALWAYS_INLINE tsd_t * tsd_get(bool init) { return &tsd_tls; } JEMALLOC_ALWAYS_INLINE void tsd_set(tsd_t *val) { assert(tsd_booted); if (likely(&tsd_tls != val)) { tsd_tls = (*val); } tsd_initialized = true; } redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tsd_tls.h000066400000000000000000000021731501533116600242260ustar00rootroot00000000000000#ifdef JEMALLOC_INTERNAL_TSD_TLS_H #error This file should be included only once, by tsd.h. #endif #define JEMALLOC_INTERNAL_TSD_TLS_H #define JEMALLOC_TSD_TYPE_ATTR(type) __thread type JEMALLOC_TLS_MODEL extern JEMALLOC_TSD_TYPE_ATTR(tsd_t) tsd_tls; extern pthread_key_t tsd_tsd; extern bool tsd_booted; /* Initialization/cleanup. */ JEMALLOC_ALWAYS_INLINE bool tsd_boot0(void) { if (pthread_key_create(&tsd_tsd, &tsd_cleanup) != 0) { return true; } tsd_booted = true; return false; } JEMALLOC_ALWAYS_INLINE void tsd_boot1(void) { /* Do nothing. */ } JEMALLOC_ALWAYS_INLINE bool tsd_boot(void) { return tsd_boot0(); } JEMALLOC_ALWAYS_INLINE bool tsd_booted_get(void) { return tsd_booted; } JEMALLOC_ALWAYS_INLINE bool tsd_get_allocates(void) { return false; } /* Get/set. */ JEMALLOC_ALWAYS_INLINE tsd_t * tsd_get(bool init) { return &tsd_tls; } JEMALLOC_ALWAYS_INLINE void tsd_set(tsd_t *val) { assert(tsd_booted); if (likely(&tsd_tls != val)) { tsd_tls = (*val); } if (pthread_setspecific(tsd_tsd, (void *)(&tsd_tls)) != 0) { malloc_write(": Error setting tsd.\n"); if (opt_abort) { abort(); } } } redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tsd_types.h000066400000000000000000000004021501533116600245610ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TSD_TYPES_H #define JEMALLOC_INTERNAL_TSD_TYPES_H #define MALLOC_TSD_CLEANUPS_MAX 4 typedef struct tsd_s tsd_t; typedef struct tsdn_s tsdn_t; typedef bool (*malloc_tsd_cleanup_t)(void); #endif /* JEMALLOC_INTERNAL_TSD_TYPES_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/tsd_win.h000066400000000000000000000057131501533116600242240ustar00rootroot00000000000000#ifdef JEMALLOC_INTERNAL_TSD_WIN_H #error This file should be included only once, by tsd.h. #endif #define JEMALLOC_INTERNAL_TSD_WIN_H typedef struct { bool initialized; tsd_t val; } tsd_wrapper_t; extern DWORD tsd_tsd; extern tsd_wrapper_t tsd_boot_wrapper; extern bool tsd_booted; /* Initialization/cleanup. */ JEMALLOC_ALWAYS_INLINE bool tsd_cleanup_wrapper(void) { DWORD error = GetLastError(); tsd_wrapper_t *wrapper = (tsd_wrapper_t *)TlsGetValue(tsd_tsd); SetLastError(error); if (wrapper == NULL) { return false; } if (wrapper->initialized) { wrapper->initialized = false; tsd_cleanup(&wrapper->val); if (wrapper->initialized) { /* Trigger another cleanup round. */ return true; } } malloc_tsd_dalloc(wrapper); return false; } JEMALLOC_ALWAYS_INLINE void tsd_wrapper_set(tsd_wrapper_t *wrapper) { if (!TlsSetValue(tsd_tsd, (void *)wrapper)) { malloc_write(": Error setting TSD\n"); abort(); } } JEMALLOC_ALWAYS_INLINE tsd_wrapper_t * tsd_wrapper_get(bool init) { DWORD error = GetLastError(); tsd_wrapper_t *wrapper = (tsd_wrapper_t *) TlsGetValue(tsd_tsd); SetLastError(error); if (init && unlikely(wrapper == NULL)) { wrapper = (tsd_wrapper_t *) malloc_tsd_malloc(sizeof(tsd_wrapper_t)); if (wrapper == NULL) { malloc_write(": Error allocating TSD\n"); abort(); } else { wrapper->initialized = false; /* MSVC is finicky about aggregate initialization. */ tsd_t tsd_initializer = TSD_INITIALIZER; wrapper->val = tsd_initializer; } tsd_wrapper_set(wrapper); } return wrapper; } JEMALLOC_ALWAYS_INLINE bool tsd_boot0(void) { tsd_tsd = TlsAlloc(); if (tsd_tsd == TLS_OUT_OF_INDEXES) { return true; } _malloc_tsd_cleanup_register(&tsd_cleanup_wrapper); tsd_wrapper_set(&tsd_boot_wrapper); tsd_booted = true; return false; } JEMALLOC_ALWAYS_INLINE void tsd_boot1(void) { tsd_wrapper_t *wrapper; wrapper = (tsd_wrapper_t *) malloc_tsd_malloc(sizeof(tsd_wrapper_t)); if (wrapper == NULL) { malloc_write(": Error allocating TSD\n"); abort(); } tsd_boot_wrapper.initialized = false; tsd_cleanup(&tsd_boot_wrapper.val); wrapper->initialized = false; tsd_t initializer = TSD_INITIALIZER; wrapper->val = initializer; tsd_wrapper_set(wrapper); } JEMALLOC_ALWAYS_INLINE bool tsd_boot(void) { if (tsd_boot0()) { return true; } tsd_boot1(); return false; } JEMALLOC_ALWAYS_INLINE bool tsd_booted_get(void) { return tsd_booted; } JEMALLOC_ALWAYS_INLINE bool tsd_get_allocates(void) { return true; } /* Get/set. */ JEMALLOC_ALWAYS_INLINE tsd_t * tsd_get(bool init) { tsd_wrapper_t *wrapper; assert(tsd_booted); wrapper = tsd_wrapper_get(init); if (tsd_get_allocates() && !init && wrapper == NULL) { return NULL; } return &wrapper->val; } JEMALLOC_ALWAYS_INLINE void tsd_set(tsd_t *val) { tsd_wrapper_t *wrapper; assert(tsd_booted); wrapper = tsd_wrapper_get(true); if (likely(&wrapper->val != val)) { wrapper->val = *(val); } wrapper->initialized = true; } redis-8.0.2/deps/jemalloc/include/jemalloc/internal/typed_list.h000066400000000000000000000035131501533116600247310ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_TYPED_LIST_H #define JEMALLOC_INTERNAL_TYPED_LIST_H /* * This wraps the ql module to implement a list class in a way that's a little * bit easier to use; it handles ql_elm_new calls and provides type safety. */ #define TYPED_LIST(list_type, el_type, linkage) \ typedef struct { \ ql_head(el_type) head; \ } list_type##_t; \ static inline void \ list_type##_init(list_type##_t *list) { \ ql_new(&list->head); \ } \ static inline el_type * \ list_type##_first(const list_type##_t *list) { \ return ql_first(&list->head); \ } \ static inline el_type * \ list_type##_last(const list_type##_t *list) { \ return ql_last(&list->head, linkage); \ } \ static inline void \ list_type##_append(list_type##_t *list, el_type *item) { \ ql_elm_new(item, linkage); \ ql_tail_insert(&list->head, item, linkage); \ } \ static inline void \ list_type##_prepend(list_type##_t *list, el_type *item) { \ ql_elm_new(item, linkage); \ ql_head_insert(&list->head, item, linkage); \ } \ static inline void \ list_type##_replace(list_type##_t *list, el_type *to_remove, \ el_type *to_insert) { \ ql_elm_new(to_insert, linkage); \ ql_after_insert(to_remove, to_insert, linkage); \ ql_remove(&list->head, to_remove, linkage); \ } \ static inline void \ list_type##_remove(list_type##_t *list, el_type *item) { \ ql_remove(&list->head, item, linkage); \ } \ static inline bool \ list_type##_empty(list_type##_t *list) { \ return ql_empty(&list->head); \ } \ static inline void \ list_type##_concat(list_type##_t *list_a, list_type##_t *list_b) { \ ql_concat(&list_a->head, &list_b->head, linkage); \ } #endif /* JEMALLOC_INTERNAL_TYPED_LIST_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/util.h000066400000000000000000000052241501533116600235270ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_UTIL_H #define JEMALLOC_INTERNAL_UTIL_H #define UTIL_INLINE static inline /* Junk fill patterns. */ #ifndef JEMALLOC_ALLOC_JUNK # define JEMALLOC_ALLOC_JUNK ((uint8_t)0xa5) #endif #ifndef JEMALLOC_FREE_JUNK # define JEMALLOC_FREE_JUNK ((uint8_t)0x5a) #endif /* * Wrap a cpp argument that contains commas such that it isn't broken up into * multiple arguments. */ #define JEMALLOC_ARG_CONCAT(...) __VA_ARGS__ /* cpp macro definition stringification. */ #define STRINGIFY_HELPER(x) #x #define STRINGIFY(x) STRINGIFY_HELPER(x) /* * Silence compiler warnings due to uninitialized values. This is used * wherever the compiler fails to recognize that the variable is never used * uninitialized. */ #define JEMALLOC_CC_SILENCE_INIT(v) = v #ifdef __GNUC__ # define likely(x) __builtin_expect(!!(x), 1) # define unlikely(x) __builtin_expect(!!(x), 0) #else # define likely(x) !!(x) # define unlikely(x) !!(x) #endif #if !defined(JEMALLOC_INTERNAL_UNREACHABLE) # error JEMALLOC_INTERNAL_UNREACHABLE should have been defined by configure #endif #define unreachable() JEMALLOC_INTERNAL_UNREACHABLE() /* Set error code. */ UTIL_INLINE void set_errno(int errnum) { #ifdef _WIN32 SetLastError(errnum); #else errno = errnum; #endif } /* Get last error code. */ UTIL_INLINE int get_errno(void) { #ifdef _WIN32 return GetLastError(); #else return errno; #endif } JEMALLOC_ALWAYS_INLINE void util_assume(bool b) { if (!b) { unreachable(); } } /* ptr should be valid. */ JEMALLOC_ALWAYS_INLINE void util_prefetch_read(void *ptr) { /* * This should arguably be a config check; but any version of GCC so old * that it doesn't support __builtin_prefetch is also too old to build * jemalloc. */ #ifdef __GNUC__ if (config_debug) { /* Enforce the "valid ptr" requirement. */ *(volatile char *)ptr; } __builtin_prefetch(ptr, /* read or write */ 0, /* locality hint */ 3); #else *(volatile char *)ptr; #endif } JEMALLOC_ALWAYS_INLINE void util_prefetch_write(void *ptr) { #ifdef __GNUC__ if (config_debug) { *(volatile char *)ptr; } /* * The only difference from the read variant is that this has a 1 as the * second argument (the write hint). */ __builtin_prefetch(ptr, 1, 3); #else *(volatile char *)ptr; #endif } JEMALLOC_ALWAYS_INLINE void util_prefetch_read_range(void *ptr, size_t sz) { for (size_t i = 0; i < sz; i += CACHELINE) { util_prefetch_read((void *)((uintptr_t)ptr + i)); } } JEMALLOC_ALWAYS_INLINE void util_prefetch_write_range(void *ptr, size_t sz) { for (size_t i = 0; i < sz; i += CACHELINE) { util_prefetch_write((void *)((uintptr_t)ptr + i)); } } #undef UTIL_INLINE #endif /* JEMALLOC_INTERNAL_UTIL_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/internal/witness.h000066400000000000000000000243021501533116600242440ustar00rootroot00000000000000#ifndef JEMALLOC_INTERNAL_WITNESS_H #define JEMALLOC_INTERNAL_WITNESS_H #include "jemalloc/internal/ql.h" /******************************************************************************/ /* LOCK RANKS */ /******************************************************************************/ enum witness_rank_e { /* * Order matters within this enum listing -- higher valued locks can * only be acquired after lower-valued ones. We use the * auto-incrementing-ness of enum values to enforce this. */ /* * Witnesses with rank WITNESS_RANK_OMIT are completely ignored by the * witness machinery. */ WITNESS_RANK_OMIT, WITNESS_RANK_MIN, WITNESS_RANK_INIT = WITNESS_RANK_MIN, WITNESS_RANK_CTL, WITNESS_RANK_TCACHES, WITNESS_RANK_ARENAS, WITNESS_RANK_BACKGROUND_THREAD_GLOBAL, WITNESS_RANK_PROF_DUMP, WITNESS_RANK_PROF_BT2GCTX, WITNESS_RANK_PROF_TDATAS, WITNESS_RANK_PROF_TDATA, WITNESS_RANK_PROF_LOG, WITNESS_RANK_PROF_GCTX, WITNESS_RANK_PROF_RECENT_DUMP, WITNESS_RANK_BACKGROUND_THREAD, /* * Used as an argument to witness_assert_depth_to_rank() in order to * validate depth excluding non-core locks with lower ranks. Since the * rank argument to witness_assert_depth_to_rank() is inclusive rather * than exclusive, this definition can have the same value as the * minimally ranked core lock. */ WITNESS_RANK_CORE, WITNESS_RANK_DECAY = WITNESS_RANK_CORE, WITNESS_RANK_TCACHE_QL, WITNESS_RANK_SEC_SHARD, WITNESS_RANK_EXTENT_GROW, WITNESS_RANK_HPA_SHARD_GROW = WITNESS_RANK_EXTENT_GROW, WITNESS_RANK_SAN_BUMP_ALLOC = WITNESS_RANK_EXTENT_GROW, WITNESS_RANK_EXTENTS, WITNESS_RANK_HPA_SHARD = WITNESS_RANK_EXTENTS, WITNESS_RANK_HPA_CENTRAL_GROW, WITNESS_RANK_HPA_CENTRAL, WITNESS_RANK_EDATA_CACHE, WITNESS_RANK_RTREE, WITNESS_RANK_BASE, WITNESS_RANK_ARENA_LARGE, WITNESS_RANK_HOOK, WITNESS_RANK_LEAF=0x1000, WITNESS_RANK_BIN = WITNESS_RANK_LEAF, WITNESS_RANK_ARENA_STATS = WITNESS_RANK_LEAF, WITNESS_RANK_COUNTER_ACCUM = WITNESS_RANK_LEAF, WITNESS_RANK_DSS = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_ACTIVE = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_DUMP_FILENAME = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_GDUMP = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_NEXT_THR_UID = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_RECENT_ALLOC = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_STATS = WITNESS_RANK_LEAF, WITNESS_RANK_PROF_THREAD_ACTIVE_INIT = WITNESS_RANK_LEAF, }; typedef enum witness_rank_e witness_rank_t; /******************************************************************************/ /* PER-WITNESS DATA */ /******************************************************************************/ #if defined(JEMALLOC_DEBUG) # define WITNESS_INITIALIZER(name, rank) {name, rank, NULL, NULL, {NULL, NULL}} #else # define WITNESS_INITIALIZER(name, rank) #endif typedef struct witness_s witness_t; typedef ql_head(witness_t) witness_list_t; typedef int witness_comp_t (const witness_t *, void *, const witness_t *, void *); struct witness_s { /* Name, used for printing lock order reversal messages. */ const char *name; /* * Witness rank, where 0 is lowest and WITNESS_RANK_LEAF is highest. * Witnesses must be acquired in order of increasing rank. */ witness_rank_t rank; /* * If two witnesses are of equal rank and they have the samp comp * function pointer, it is called as a last attempt to differentiate * between witnesses of equal rank. */ witness_comp_t *comp; /* Opaque data, passed to comp(). */ void *opaque; /* Linkage for thread's currently owned locks. */ ql_elm(witness_t) link; }; /******************************************************************************/ /* PER-THREAD DATA */ /******************************************************************************/ typedef struct witness_tsd_s witness_tsd_t; struct witness_tsd_s { witness_list_t witnesses; bool forking; }; #define WITNESS_TSD_INITIALIZER { ql_head_initializer(witnesses), false } #define WITNESS_TSDN_NULL ((witness_tsdn_t *)0) /******************************************************************************/ /* (PER-THREAD) NULLABILITY HELPERS */ /******************************************************************************/ typedef struct witness_tsdn_s witness_tsdn_t; struct witness_tsdn_s { witness_tsd_t witness_tsd; }; JEMALLOC_ALWAYS_INLINE witness_tsdn_t * witness_tsd_tsdn(witness_tsd_t *witness_tsd) { return (witness_tsdn_t *)witness_tsd; } JEMALLOC_ALWAYS_INLINE bool witness_tsdn_null(witness_tsdn_t *witness_tsdn) { return witness_tsdn == NULL; } JEMALLOC_ALWAYS_INLINE witness_tsd_t * witness_tsdn_tsd(witness_tsdn_t *witness_tsdn) { assert(!witness_tsdn_null(witness_tsdn)); return &witness_tsdn->witness_tsd; } /******************************************************************************/ /* API */ /******************************************************************************/ void witness_init(witness_t *witness, const char *name, witness_rank_t rank, witness_comp_t *comp, void *opaque); typedef void (witness_lock_error_t)(const witness_list_t *, const witness_t *); extern witness_lock_error_t *JET_MUTABLE witness_lock_error; typedef void (witness_owner_error_t)(const witness_t *); extern witness_owner_error_t *JET_MUTABLE witness_owner_error; typedef void (witness_not_owner_error_t)(const witness_t *); extern witness_not_owner_error_t *JET_MUTABLE witness_not_owner_error; typedef void (witness_depth_error_t)(const witness_list_t *, witness_rank_t rank_inclusive, unsigned depth); extern witness_depth_error_t *JET_MUTABLE witness_depth_error; void witnesses_cleanup(witness_tsd_t *witness_tsd); void witness_prefork(witness_tsd_t *witness_tsd); void witness_postfork_parent(witness_tsd_t *witness_tsd); void witness_postfork_child(witness_tsd_t *witness_tsd); /* Helper, not intended for direct use. */ static inline bool witness_owner(witness_tsd_t *witness_tsd, const witness_t *witness) { witness_list_t *witnesses; witness_t *w; cassert(config_debug); witnesses = &witness_tsd->witnesses; ql_foreach(w, witnesses, link) { if (w == witness) { return true; } } return false; } static inline void witness_assert_owner(witness_tsdn_t *witness_tsdn, const witness_t *witness) { witness_tsd_t *witness_tsd; if (!config_debug) { return; } if (witness_tsdn_null(witness_tsdn)) { return; } witness_tsd = witness_tsdn_tsd(witness_tsdn); if (witness->rank == WITNESS_RANK_OMIT) { return; } if (witness_owner(witness_tsd, witness)) { return; } witness_owner_error(witness); } static inline void witness_assert_not_owner(witness_tsdn_t *witness_tsdn, const witness_t *witness) { witness_tsd_t *witness_tsd; witness_list_t *witnesses; witness_t *w; if (!config_debug) { return; } if (witness_tsdn_null(witness_tsdn)) { return; } witness_tsd = witness_tsdn_tsd(witness_tsdn); if (witness->rank == WITNESS_RANK_OMIT) { return; } witnesses = &witness_tsd->witnesses; ql_foreach(w, witnesses, link) { if (w == witness) { witness_not_owner_error(witness); } } } /* Returns depth. Not intended for direct use. */ static inline unsigned witness_depth_to_rank(witness_list_t *witnesses, witness_rank_t rank_inclusive) { unsigned d = 0; witness_t *w = ql_last(witnesses, link); if (w != NULL) { ql_reverse_foreach(w, witnesses, link) { if (w->rank < rank_inclusive) { break; } d++; } } return d; } static inline void witness_assert_depth_to_rank(witness_tsdn_t *witness_tsdn, witness_rank_t rank_inclusive, unsigned depth) { if (!config_debug || witness_tsdn_null(witness_tsdn)) { return; } witness_list_t *witnesses = &witness_tsdn_tsd(witness_tsdn)->witnesses; unsigned d = witness_depth_to_rank(witnesses, rank_inclusive); if (d != depth) { witness_depth_error(witnesses, rank_inclusive, depth); } } static inline void witness_assert_depth(witness_tsdn_t *witness_tsdn, unsigned depth) { witness_assert_depth_to_rank(witness_tsdn, WITNESS_RANK_MIN, depth); } static inline void witness_assert_lockless(witness_tsdn_t *witness_tsdn) { witness_assert_depth(witness_tsdn, 0); } static inline void witness_assert_positive_depth_to_rank(witness_tsdn_t *witness_tsdn, witness_rank_t rank_inclusive) { if (!config_debug || witness_tsdn_null(witness_tsdn)) { return; } witness_list_t *witnesses = &witness_tsdn_tsd(witness_tsdn)->witnesses; unsigned d = witness_depth_to_rank(witnesses, rank_inclusive); if (d == 0) { witness_depth_error(witnesses, rank_inclusive, 1); } } static inline void witness_lock(witness_tsdn_t *witness_tsdn, witness_t *witness) { witness_tsd_t *witness_tsd; witness_list_t *witnesses; witness_t *w; if (!config_debug) { return; } if (witness_tsdn_null(witness_tsdn)) { return; } witness_tsd = witness_tsdn_tsd(witness_tsdn); if (witness->rank == WITNESS_RANK_OMIT) { return; } witness_assert_not_owner(witness_tsdn, witness); witnesses = &witness_tsd->witnesses; w = ql_last(witnesses, link); if (w == NULL) { /* No other locks; do nothing. */ } else if (witness_tsd->forking && w->rank <= witness->rank) { /* Forking, and relaxed ranking satisfied. */ } else if (w->rank > witness->rank) { /* Not forking, rank order reversal. */ witness_lock_error(witnesses, witness); } else if (w->rank == witness->rank && (w->comp == NULL || w->comp != witness->comp || w->comp(w, w->opaque, witness, witness->opaque) > 0)) { /* * Missing/incompatible comparison function, or comparison * function indicates rank order reversal. */ witness_lock_error(witnesses, witness); } ql_elm_new(witness, link); ql_tail_insert(witnesses, witness, link); } static inline void witness_unlock(witness_tsdn_t *witness_tsdn, witness_t *witness) { witness_tsd_t *witness_tsd; witness_list_t *witnesses; if (!config_debug) { return; } if (witness_tsdn_null(witness_tsdn)) { return; } witness_tsd = witness_tsdn_tsd(witness_tsdn); if (witness->rank == WITNESS_RANK_OMIT) { return; } /* * Check whether owner before removal, rather than relying on * witness_assert_owner() to abort, so that unit tests can test this * function's failure mode without causing undefined behavior. */ if (witness_owner(witness_tsd, witness)) { witnesses = &witness_tsd->witnesses; ql_remove(witnesses, witness, link); } else { witness_assert_owner(witness_tsdn, witness); } } #endif /* JEMALLOC_INTERNAL_WITNESS_H */ redis-8.0.2/deps/jemalloc/include/jemalloc/jemalloc.sh000077500000000000000000000007111501533116600227060ustar00rootroot00000000000000#!/bin/sh objroot=$1 cat < #include #include #include #include #define JEMALLOC_VERSION "@jemalloc_version@" #define JEMALLOC_VERSION_MAJOR @jemalloc_version_major@ #define JEMALLOC_VERSION_MINOR @jemalloc_version_minor@ #define JEMALLOC_VERSION_BUGFIX @jemalloc_version_bugfix@ #define JEMALLOC_VERSION_NREV @jemalloc_version_nrev@ #define JEMALLOC_VERSION_GID "@jemalloc_version_gid@" #define JEMALLOC_VERSION_GID_IDENT @jemalloc_version_gid@ #define MALLOCX_LG_ALIGN(la) ((int)(la)) #if LG_SIZEOF_PTR == 2 # define MALLOCX_ALIGN(a) ((int)(ffs((int)(a))-1)) #else # define MALLOCX_ALIGN(a) \ ((int)(((size_t)(a) < (size_t)INT_MAX) ? ffs((int)(a))-1 : \ ffs((int)(((size_t)(a))>>32))+31)) #endif #define MALLOCX_ZERO ((int)0x40) /* * Bias tcache index bits so that 0 encodes "automatic tcache management", and 1 * encodes MALLOCX_TCACHE_NONE. */ #define MALLOCX_TCACHE(tc) ((int)(((tc)+2) << 8)) #define MALLOCX_TCACHE_NONE MALLOCX_TCACHE(-1) /* * Bias arena index bits so that 0 encodes "use an automatically chosen arena". */ #define MALLOCX_ARENA(a) ((((int)(a))+1) << 20) /* * Use as arena index in "arena..{purge,decay,dss}" and * "stats.arenas..*" mallctl interfaces to select all arenas. This * definition is intentionally specified in raw decimal format to support * cpp-based string concatenation, e.g. * * #define STRINGIFY_HELPER(x) #x * #define STRINGIFY(x) STRINGIFY_HELPER(x) * * mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".purge", NULL, NULL, NULL, * 0); */ #define MALLCTL_ARENAS_ALL 4096 /* * Use as arena index in "stats.arenas..*" mallctl interfaces to select * destroyed arenas. */ #define MALLCTL_ARENAS_DESTROYED 4097 #if defined(__cplusplus) && defined(JEMALLOC_USE_CXX_THROW) # define JEMALLOC_CXX_THROW throw() #else # define JEMALLOC_CXX_THROW #endif #if defined(_MSC_VER) # define JEMALLOC_ATTR(s) # define JEMALLOC_ALIGNED(s) __declspec(align(s)) # define JEMALLOC_ALLOC_SIZE(s) # define JEMALLOC_ALLOC_SIZE2(s1, s2) # ifndef JEMALLOC_EXPORT # ifdef DLLEXPORT # define JEMALLOC_EXPORT __declspec(dllexport) # else # define JEMALLOC_EXPORT __declspec(dllimport) # endif # endif # define JEMALLOC_FORMAT_ARG(i) # define JEMALLOC_FORMAT_PRINTF(s, i) # define JEMALLOC_FALLTHROUGH # define JEMALLOC_NOINLINE __declspec(noinline) # ifdef __cplusplus # define JEMALLOC_NOTHROW __declspec(nothrow) # else # define JEMALLOC_NOTHROW # endif # define JEMALLOC_SECTION(s) __declspec(allocate(s)) # define JEMALLOC_RESTRICT_RETURN __declspec(restrict) # if _MSC_VER >= 1900 && !defined(__EDG__) # define JEMALLOC_ALLOCATOR __declspec(allocator) # else # define JEMALLOC_ALLOCATOR # endif # define JEMALLOC_COLD #elif defined(JEMALLOC_HAVE_ATTR) # define JEMALLOC_ATTR(s) __attribute__((s)) # define JEMALLOC_ALIGNED(s) JEMALLOC_ATTR(aligned(s)) # ifdef JEMALLOC_HAVE_ATTR_ALLOC_SIZE # define JEMALLOC_ALLOC_SIZE(s) JEMALLOC_ATTR(alloc_size(s)) # define JEMALLOC_ALLOC_SIZE2(s1, s2) JEMALLOC_ATTR(alloc_size(s1, s2)) # else # define JEMALLOC_ALLOC_SIZE(s) # define JEMALLOC_ALLOC_SIZE2(s1, s2) # endif # ifndef JEMALLOC_EXPORT # define JEMALLOC_EXPORT JEMALLOC_ATTR(visibility("default")) # endif # ifdef JEMALLOC_HAVE_ATTR_FORMAT_ARG # define JEMALLOC_FORMAT_ARG(i) JEMALLOC_ATTR(__format_arg__(3)) # else # define JEMALLOC_FORMAT_ARG(i) # endif # ifdef JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF # define JEMALLOC_FORMAT_PRINTF(s, i) JEMALLOC_ATTR(format(gnu_printf, s, i)) # elif defined(JEMALLOC_HAVE_ATTR_FORMAT_PRINTF) # define JEMALLOC_FORMAT_PRINTF(s, i) JEMALLOC_ATTR(format(printf, s, i)) # else # define JEMALLOC_FORMAT_PRINTF(s, i) # endif # ifdef JEMALLOC_HAVE_ATTR_FALLTHROUGH # define JEMALLOC_FALLTHROUGH JEMALLOC_ATTR(fallthrough) # else # define JEMALLOC_FALLTHROUGH # endif # define JEMALLOC_NOINLINE JEMALLOC_ATTR(noinline) # define JEMALLOC_NOTHROW JEMALLOC_ATTR(nothrow) # define JEMALLOC_SECTION(s) JEMALLOC_ATTR(section(s)) # define JEMALLOC_RESTRICT_RETURN # define JEMALLOC_ALLOCATOR # ifdef JEMALLOC_HAVE_ATTR_COLD # define JEMALLOC_COLD JEMALLOC_ATTR(__cold__) # else # define JEMALLOC_COLD # endif #else # define JEMALLOC_ATTR(s) # define JEMALLOC_ALIGNED(s) # define JEMALLOC_ALLOC_SIZE(s) # define JEMALLOC_ALLOC_SIZE2(s1, s2) # define JEMALLOC_EXPORT # define JEMALLOC_FORMAT_PRINTF(s, i) # define JEMALLOC_FALLTHROUGH # define JEMALLOC_NOINLINE # define JEMALLOC_NOTHROW # define JEMALLOC_SECTION(s) # define JEMALLOC_RESTRICT_RETURN # define JEMALLOC_ALLOCATOR # define JEMALLOC_COLD #endif #if (defined(__APPLE__) || defined(__FreeBSD__)) && !defined(JEMALLOC_NO_RENAME) # define JEMALLOC_SYS_NOTHROW #else # define JEMALLOC_SYS_NOTHROW JEMALLOC_NOTHROW #endif /* This version of Jemalloc, modified for Redis, has the je_get_defrag_hint() * function. */ #define JEMALLOC_FRAG_HINT redis-8.0.2/deps/jemalloc/include/jemalloc/jemalloc_mangle.sh000077500000000000000000000023561501533116600242400ustar00rootroot00000000000000#!/bin/sh -eu public_symbols_txt=$1 symbol_prefix=$2 cat < /* MSVC doesn't define _Bool or bool in C, but does have BOOL */ /* Note this doesn't pass autoconf's test because (bool) 0.5 != true */ /* Clang-cl uses MSVC headers, so needs msvc_compat, but has _Bool as * a built-in type. */ #ifndef __clang__ typedef BOOL _Bool; #endif #define bool _Bool #define true 1 #define false 0 #define __bool_true_false_are_defined 1 #endif /* stdbool_h */ redis-8.0.2/deps/jemalloc/include/msvc_compat/C99/stdint.h000066400000000000000000000170601501533116600233350ustar00rootroot00000000000000// ISO C9x compliant stdint.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // // Copyright (c) 2006-2008 Alexander Chemeris // // 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. // // 3. The name of the author may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. // /////////////////////////////////////////////////////////////////////////////// #ifndef _MSC_VER // [ #error "Use this header only with Microsoft Visual C++ compilers!" #endif // _MSC_VER ] #ifndef _MSC_STDINT_H_ // [ #define _MSC_STDINT_H_ #if _MSC_VER > 1000 #pragma once #endif #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we should wrap include with 'extern "C++" {}' // or compiler give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #ifdef __cplusplus extern "C" { #endif # include #ifdef __cplusplus } #endif // Define _W64 macros to mark types changing their size, like intptr_t. #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 # else # define _W64 # endif #endif // 7.18.1 Integer types // 7.18.1.1 Exact-width integer types // Visual Studio 6 and Embedded Visual C++ 4 doesn't // realize that, e.g. char has the same size as __int8 // so we give up on __intX for them. #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #endif typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; // 7.18.1.2 Minimum-width integer types typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; typedef int64_t int_least64_t; typedef uint8_t uint_least8_t; typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; // 7.18.1.3 Fastest minimum-width integer types typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; typedef int64_t int_fast64_t; typedef uint8_t uint_fast8_t; typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; // 7.18.1.4 Integer types capable of holding object pointers #ifdef _WIN64 // [ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; #else // _WIN64 ][ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; #endif // _WIN64 ] // 7.18.1.5 Greatest-width integer types typedef int64_t intmax_t; typedef uint64_t uintmax_t; // 7.18.2 Limits of specified-width integer types #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 // 7.18.2.1 Limits of exact-width integer types #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) #define INT16_MAX _I16_MAX #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX #define UINT8_MAX _UI8_MAX #define UINT16_MAX _UI16_MAX #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX // 7.18.2.2 Limits of minimum-width integer types #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN #define INT_LEAST16_MAX INT16_MAX #define INT_LEAST32_MIN INT32_MIN #define INT_LEAST32_MAX INT32_MAX #define INT_LEAST64_MIN INT64_MIN #define INT_LEAST64_MAX INT64_MAX #define UINT_LEAST8_MAX UINT8_MAX #define UINT_LEAST16_MAX UINT16_MAX #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX // 7.18.2.3 Limits of fastest minimum-width integer types #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN #define INT_FAST16_MAX INT16_MAX #define INT_FAST32_MIN INT32_MIN #define INT_FAST32_MAX INT32_MAX #define INT_FAST64_MIN INT64_MIN #define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX UINT16_MAX #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX // 7.18.2.4 Limits of integer types capable of holding object pointers #ifdef _WIN64 // [ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX #else // _WIN64 ][ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX #endif // _WIN64 ] // 7.18.2.5 Limits of greatest-width integer types #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX // 7.18.3 Limits of other integer types #ifdef _WIN64 // [ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX #else // _WIN64 ][ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX #endif // _WIN64 ] #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX #ifndef SIZE_MAX // [ # ifdef _WIN64 // [ # define SIZE_MAX _UI64_MAX # else // _WIN64 ][ # define SIZE_MAX _UI32_MAX # endif // _WIN64 ] #endif // SIZE_MAX ] // WCHAR_MIN and WCHAR_MAX are also defined in #ifndef WCHAR_MIN // [ # define WCHAR_MIN 0 #endif // WCHAR_MIN ] #ifndef WCHAR_MAX // [ # define WCHAR_MAX _UI16_MAX #endif // WCHAR_MAX ] #define WINT_MIN 0 #define WINT_MAX _UI16_MAX #endif // __STDC_LIMIT_MACROS ] // 7.18.4 Limits of other integer types #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants #define INTMAX_C INT64_C #define UINTMAX_C UINT64_C #endif // __STDC_CONSTANT_MACROS ] #endif // _MSC_STDINT_H_ ] redis-8.0.2/deps/jemalloc/include/msvc_compat/strings.h000066400000000000000000000020311501533116600231450ustar00rootroot00000000000000#ifndef strings_h #define strings_h /* MSVC doesn't define ffs/ffsl. This dummy strings.h header is provided * for both */ #ifdef _MSC_VER # include # pragma intrinsic(_BitScanForward) static __forceinline int ffsl(long x) { unsigned long i; if (_BitScanForward(&i, x)) { return i + 1; } return 0; } static __forceinline int ffs(int x) { return ffsl(x); } # ifdef _M_X64 # pragma intrinsic(_BitScanForward64) # endif static __forceinline int ffsll(unsigned __int64 x) { unsigned long i; #ifdef _M_X64 if (_BitScanForward64(&i, x)) { return i + 1; } return 0; #else // Fallback for 32-bit build where 64-bit version not available // assuming little endian union { unsigned __int64 ll; unsigned long l[2]; } s; s.ll = x; if (_BitScanForward(&i, s.l[0])) { return i + 1; } else if(_BitScanForward(&i, s.l[1])) { return i + 33; } return 0; #endif } #else # define ffsll(x) __builtin_ffsll(x) # define ffsl(x) __builtin_ffsl(x) # define ffs(x) __builtin_ffs(x) #endif #endif /* strings_h */ redis-8.0.2/deps/jemalloc/include/msvc_compat/windows_extra.h000066400000000000000000000002061501533116600243530ustar00rootroot00000000000000#ifndef MSVC_COMPAT_WINDOWS_EXTRA_H #define MSVC_COMPAT_WINDOWS_EXTRA_H #include #endif /* MSVC_COMPAT_WINDOWS_EXTRA_H */ redis-8.0.2/deps/jemalloc/jemalloc.pc.in000066400000000000000000000007211501533116600200700ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ install_suffix=@install_suffix@ Name: jemalloc Description: A general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support. URL: http://jemalloc.net/ Version: @jemalloc_version_major@.@jemalloc_version_minor@.@jemalloc_version_bugfix@_@jemalloc_version_nrev@ Cflags: -I${includedir} Libs: -L${libdir} -ljemalloc${install_suffix} redis-8.0.2/deps/jemalloc/m4/000077500000000000000000000000001501533116600156715ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/m4/ax_cxx_compile_stdcxx.m4000066400000000000000000000456471501533116600225520ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) # or '14' (for the C++14 standard). # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 11 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], [$2], [noext], [], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done fi]) m4_if([$2], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi]) AC_LANG_POP([C++]) if test x$ax_cxx_compile_cxx$1_required = xtrue; then if test x$ac_success = xno; then AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) fi fi if test x$ac_success = xno; then HAVE_CXX$1=0 AC_MSG_NOTICE([No compiler with C++$1 support was found]) else HAVE_CXX$1=1 AC_DEFINE(HAVE_CXX$1,1, [define if the compiler supports basic C++$1 syntax]) fi AC_SUBST(HAVE_CXX$1) ]) dnl Test body for checking C++11 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 ) dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201103L #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { virtual ~Derived() override {} virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L ]]) dnl Tests for new features in C++14 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L ]]) dnl Tests for new features in C++17 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201703L #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // __cplusplus < 201703L ]]) redis-8.0.2/deps/jemalloc/msvc/000077500000000000000000000000001501533116600163215ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/ReadMe.txt000066400000000000000000000010331501533116600202140ustar00rootroot00000000000000 How to build jemalloc for Windows ================================= 1. Install Cygwin with at least the following packages: * autoconf * autogen * gawk * grep * sed 2. Install Visual Studio 2015 or 2017 with Visual C++ 3. Add Cygwin\bin to the PATH environment variable 4. Open "x64 Native Tools Command Prompt for VS 2017" (note: x86/x64 doesn't matter at this point) 5. Generate header files: sh -c "CC=cl ./autogen.sh" 6. Now the project can be opened and built in Visual Studio: msvc\jemalloc_vc2017.sln redis-8.0.2/deps/jemalloc/msvc/jemalloc_vc2015.sln000066400000000000000000000074501501533116600216330ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{70A99006-6DE9-472B-8F83-4CEE6C616DF3}" ProjectSection(SolutionItems) = preProject ReadMe.txt = ReadMe.txt EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jemalloc", "projects\vc2015\jemalloc\jemalloc.vcxproj", "{8D6BB292-9E1C-413D-9F98-4864BDC1514A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_threads", "projects\vc2015\test_threads\test_threads.vcxproj", "{09028CFD-4EB7-491D-869C-0708DB97ED44}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Debug-static|x64 = Debug-static|x64 Debug-static|x86 = Debug-static|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 Release-static|x64 = Release-static|x64 Release-static|x86 = Release-static|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.ActiveCfg = Debug|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.Build.0 = Debug|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.ActiveCfg = Debug|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.Build.0 = Debug|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.ActiveCfg = Debug-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.Build.0 = Debug-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.ActiveCfg = Debug-static|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.Build.0 = Debug-static|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.ActiveCfg = Release|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.Build.0 = Release|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.ActiveCfg = Release|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.Build.0 = Release|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.ActiveCfg = Release-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.Build.0 = Release-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.ActiveCfg = Release-static|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.Build.0 = Release-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.ActiveCfg = Debug|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.Build.0 = Debug|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.ActiveCfg = Debug|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.Build.0 = Debug|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.ActiveCfg = Debug-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.Build.0 = Debug-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.ActiveCfg = Debug-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.Build.0 = Debug-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.ActiveCfg = Release|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.Build.0 = Release|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.ActiveCfg = Release|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.Build.0 = Release|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.ActiveCfg = Release-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.Build.0 = Release-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.ActiveCfg = Release-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.Build.0 = Release-static|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal redis-8.0.2/deps/jemalloc/msvc/jemalloc_vc2017.sln000066400000000000000000000074501501533116600216350ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{70A99006-6DE9-472B-8F83-4CEE6C616DF3}" ProjectSection(SolutionItems) = preProject ReadMe.txt = ReadMe.txt EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jemalloc", "projects\vc2017\jemalloc\jemalloc.vcxproj", "{8D6BB292-9E1C-413D-9F98-4864BDC1514A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_threads", "projects\vc2017\test_threads\test_threads.vcxproj", "{09028CFD-4EB7-491D-869C-0708DB97ED44}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Debug-static|x64 = Debug-static|x64 Debug-static|x86 = Debug-static|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 Release-static|x64 = Release-static|x64 Release-static|x86 = Release-static|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.ActiveCfg = Debug|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.Build.0 = Debug|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.ActiveCfg = Debug|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.Build.0 = Debug|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.ActiveCfg = Debug-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.Build.0 = Debug-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.ActiveCfg = Debug-static|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.Build.0 = Debug-static|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.ActiveCfg = Release|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.Build.0 = Release|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.ActiveCfg = Release|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.Build.0 = Release|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.ActiveCfg = Release-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.Build.0 = Release-static|x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.ActiveCfg = Release-static|Win32 {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.Build.0 = Release-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.ActiveCfg = Debug|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.Build.0 = Debug|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.ActiveCfg = Debug|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.Build.0 = Debug|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.ActiveCfg = Debug-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.Build.0 = Debug-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.ActiveCfg = Debug-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.Build.0 = Debug-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.ActiveCfg = Release|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.Build.0 = Release|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.ActiveCfg = Release|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.Build.0 = Release|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.ActiveCfg = Release-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.Build.0 = Release-static|x64 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.ActiveCfg = Release-static|Win32 {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.Build.0 = Release-static|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal redis-8.0.2/deps/jemalloc/msvc/projects/000077500000000000000000000000001501533116600201525ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/000077500000000000000000000000001501533116600210725ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/jemalloc/000077500000000000000000000000001501533116600226605ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj000066400000000000000000000506611501533116600260730ustar00rootroot00000000000000 Debug-static Win32 Debug-static x64 Debug Win32 Release-static Win32 Release-static x64 Release Win32 Debug x64 Release x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A} Win32Proj jemalloc 8.1 DynamicLibrary true v140 MultiByte StaticLibrary true v140 MultiByte DynamicLibrary false v140 true MultiByte StaticLibrary false v140 true MultiByte DynamicLibrary true v140 MultiByte StaticLibrary true v140 MultiByte DynamicLibrary false v140 true MultiByte StaticLibrary false v140 true MultiByte $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)d $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-$(PlatformToolset)-$(Configuration) $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-$(PlatformToolset)-$(Configuration) $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)d $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration) $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration) Level3 Disabled JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true Level3 Disabled JEMALLOC_NO_PRIVATE_NAMESPACE;JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true Level3 Disabled JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true Level3 Disabled JEMALLOC_NO_PRIVATE_NAMESPACE;JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug 4090;4146;4267;4334 OldStyle false Windows true Level3 MaxSpeed true true JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true true true Level3 MaxSpeed true true JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true true true Level3 MaxSpeed true true ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true true true Level3 MaxSpeed true true JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded 4090;4146;4267;4334 OldStyle Windows true true true redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj.filters000066400000000000000000000153301501533116600275340ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/test_threads/000077500000000000000000000000001501533116600235635ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj000066400000000000000000000455261501533116600277050ustar00rootroot00000000000000 Debug-static Win32 Debug-static x64 Debug Win32 Release-static Win32 Release-static x64 Release Win32 Debug x64 Release x64 {09028CFD-4EB7-491D-869C-0708DB97ED44} Win32Proj test_threads 8.1 Application true v140 MultiByte Application true v140 MultiByte Application false v140 true MultiByte Application false v140 true MultiByte Application true v140 MultiByte Application true v140 MultiByte Application false v140 true MultiByte Application false v140 true MultiByte $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true true $(SolutionDir)$(Platform)\$(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true $(SolutionDir)$(Platform)\$(Configuration) jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 Disabled JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug Console true $(SolutionDir)$(Platform)\$(Configuration) jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 Disabled _DEBUG;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(Configuration) Level3 Disabled JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug Console true jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(Configuration) Level3 MaxSpeed true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) {8d6bb292-9e1c-413d-9f98-4864bdc1514a} redis-8.0.2/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj.filters000066400000000000000000000017251501533116600313450ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd Source Files Source Files Header Files redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/000077500000000000000000000000001501533116600210745ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/jemalloc/000077500000000000000000000000001501533116600226625ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj000066400000000000000000000503641501533116600260750ustar00rootroot00000000000000 Debug-static Win32 Debug-static x64 Debug Win32 Release-static Win32 Release-static x64 Release Win32 Debug x64 Release x64 {8D6BB292-9E1C-413D-9F98-4864BDC1514A} Win32Proj jemalloc DynamicLibrary true v141 MultiByte StaticLibrary true v141 MultiByte DynamicLibrary false v141 true MultiByte StaticLibrary false v141 true MultiByte DynamicLibrary true v141 MultiByte StaticLibrary true v141 MultiByte DynamicLibrary false v141 true MultiByte StaticLibrary false v141 true MultiByte $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)d $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-$(PlatformToolset)-$(Configuration) $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-$(PlatformToolset)-$(Configuration) $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)d $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration) $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ $(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration) Level3 Disabled _REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true Level3 Disabled JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true Level3 Disabled JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true Level3 Disabled JEMALLOC_NO_PRIVATE_NAMESPACE;JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug 4090;4146;4267;4334 OldStyle false Windows true Level3 MaxSpeed true true _REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true true true Level3 MaxSpeed true true _REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true true true Level3 MaxSpeed true true ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions) 4090;4146;4267;4334 $(OutputPath)$(TargetName).pdb Windows true true true Level3 MaxSpeed true true JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions) ..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded 4090;4146;4267;4334 OldStyle Windows true true true redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj.filters000066400000000000000000000153301501533116600275360ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/test_threads/000077500000000000000000000000001501533116600235655ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj000066400000000000000000000454211501533116600277010ustar00rootroot00000000000000 Debug-static Win32 Debug-static x64 Debug Win32 Release-static Win32 Release-static x64 Release Win32 Debug x64 Release x64 {09028CFD-4EB7-491D-869C-0708DB97ED44} Win32Proj test_threads Application true v141 MultiByte Application true v141 MultiByte Application false v141 true MultiByte Application false v141 true MultiByte Application true v141 MultiByte Application true v141 MultiByte Application false v141 true MultiByte Application false v141 true MultiByte $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true true $(SolutionDir)$(Platform)\$(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ false Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true $(SolutionDir)$(Platform)\$(Configuration) jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 Disabled JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug Console true $(SolutionDir)$(Platform)\$(Configuration) jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 Disabled _DEBUG;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(Configuration) Level3 Disabled JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreadedDebug Console true jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(Configuration) Level3 MaxSpeed true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) Level3 MaxSpeed true true JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories) MultiThreaded Console true true true $(SolutionDir)$(Platform)\$(Configuration) jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) {8d6bb292-9e1c-413d-9f98-4864bdc1514a} redis-8.0.2/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj.filters000066400000000000000000000017251501533116600313470ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd Source Files Source Files Header Files redis-8.0.2/deps/jemalloc/msvc/test_threads/000077500000000000000000000000001501533116600210125ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/msvc/test_threads/test_threads.cpp000066400000000000000000000062061501533116600242130ustar00rootroot00000000000000// jemalloc C++ threaded test // Author: Rustam Abdullaev // Public Domain #include #include #include #include #include #include #include #define JEMALLOC_NO_DEMANGLE #include using std::vector; using std::thread; using std::uniform_int_distribution; using std::minstd_rand; int test_threads() { je_malloc_conf = "narenas:3"; int narenas = 0; size_t sz = sizeof(narenas); je_mallctl("opt.narenas", (void *)&narenas, &sz, NULL, 0); if (narenas != 3) { printf("Error: unexpected number of arenas: %d\n", narenas); return 1; } static const int sizes[] = { 7, 16, 32, 60, 91, 100, 120, 144, 169, 199, 255, 400, 670, 900, 917, 1025, 3333, 5190, 13131, 49192, 99999, 123123, 255265, 2333111 }; static const int numSizes = (int)(sizeof(sizes) / sizeof(sizes[0])); vector workers; static const int numThreads = narenas + 1, numAllocsMax = 25, numIter1 = 50, numIter2 = 50; je_malloc_stats_print(NULL, NULL, NULL); size_t allocated1; size_t sz1 = sizeof(allocated1); je_mallctl("stats.active", (void *)&allocated1, &sz1, NULL, 0); printf("\nPress Enter to start threads...\n"); getchar(); printf("Starting %d threads x %d x %d iterations...\n", numThreads, numIter1, numIter2); for (int i = 0; i < numThreads; i++) { workers.emplace_back([tid=i]() { uniform_int_distribution sizeDist(0, numSizes - 1); minstd_rand rnd(tid * 17); uint8_t* ptrs[numAllocsMax]; int ptrsz[numAllocsMax]; for (int i = 0; i < numIter1; ++i) { thread t([&]() { for (int i = 0; i < numIter2; ++i) { const int numAllocs = numAllocsMax - sizeDist(rnd); for (int j = 0; j < numAllocs; j += 64) { const int x = sizeDist(rnd); const int sz = sizes[x]; ptrsz[j] = sz; ptrs[j] = (uint8_t*)je_malloc(sz); if (!ptrs[j]) { printf("Unable to allocate %d bytes in thread %d, iter %d, alloc %d. %d\n", sz, tid, i, j, x); exit(1); } for (int k = 0; k < sz; k++) ptrs[j][k] = tid + k; } for (int j = 0; j < numAllocs; j += 64) { for (int k = 0, sz = ptrsz[j]; k < sz; k++) if (ptrs[j][k] != (uint8_t)(tid + k)) { printf("Memory error in thread %d, iter %d, alloc %d @ %d : %02X!=%02X\n", tid, i, j, k, ptrs[j][k], (uint8_t)(tid + k)); exit(1); } je_free(ptrs[j]); } } }); t.join(); } }); } for (thread& t : workers) { t.join(); } je_malloc_stats_print(NULL, NULL, NULL); size_t allocated2; je_mallctl("stats.active", (void *)&allocated2, &sz1, NULL, 0); size_t leaked = allocated2 - allocated1; printf("\nDone. Leaked: %zd bytes\n", leaked); bool failed = leaked > 65536; // in case C++ runtime allocated something (e.g. iostream locale or facet) printf("\nTest %s!\n", (failed ? "FAILED" : "successful")); printf("\nPress Enter to continue...\n"); getchar(); return failed ? 1 : 0; } redis-8.0.2/deps/jemalloc/msvc/test_threads/test_threads.h000066400000000000000000000000421501533116600236500ustar00rootroot00000000000000#pragma once int test_threads(); redis-8.0.2/deps/jemalloc/msvc/test_threads/test_threads_main.cpp000066400000000000000000000003101501533116600252050ustar00rootroot00000000000000#include "test_threads.h" #include #include #include using namespace std::chrono_literals; int main(int argc, char** argv) { int rc = test_threads(); return rc; } redis-8.0.2/deps/jemalloc/run_tests.sh000077500000000000000000000000601501533116600177320ustar00rootroot00000000000000$(dirname "$)")/scripts/gen_run_tests.py | bash redis-8.0.2/deps/jemalloc/scripts/000077500000000000000000000000001501533116600170405ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/scripts/check-formatting.sh000077500000000000000000000013701501533116600226250ustar00rootroot00000000000000#!/bin/bash # The files that need to be properly formatted. We'll grow this incrementally # until it includes all the jemalloc source files (as we convert things over), # and then just replace it with # find -name '*.c' -o -name '*.h' -o -name '*.cpp FILES=( ) if command -v clang-format &> /dev/null; then CLANG_FORMAT="clang-format" elif command -v clang-format-8 &> /dev/null; then CLANG_FORMAT="clang-format-8" else echo "Couldn't find clang-format." fi if ! $CLANG_FORMAT -version | grep "version 8\." &> /dev/null; then echo "clang-format is the wrong version." exit 1 fi for file in ${FILES[@]}; do if ! cmp --silent $file <($CLANG_FORMAT $file) &> /dev/null; then echo "Error: $file is not clang-formatted" exit 1 fi done redis-8.0.2/deps/jemalloc/scripts/freebsd/000077500000000000000000000000001501533116600204525ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/scripts/freebsd/before_install.sh000066400000000000000000000000601501533116600237720ustar00rootroot00000000000000#!/bin/tcsh su -m root -c 'pkg install -y git' redis-8.0.2/deps/jemalloc/scripts/freebsd/before_script.sh000066400000000000000000000006031501533116600236330ustar00rootroot00000000000000#!/bin/tcsh autoconf # We don't perfectly track freebsd stdlib.h definitions. This is fine when # we count as a system header, but breaks otherwise, like during these # tests. ./configure --with-jemalloc-prefix=ci_ ${COMPILER_FLAGS:+ CC="$CC $COMPILER_FLAGS" CXX="$CXX $COMPILER_FLAGS"} $CONFIGURE_FLAGS JE_NCPUS=`sysctl -n kern.smp.cpus` gmake -j${JE_NCPUS} gmake -j${JE_NCPUS} tests redis-8.0.2/deps/jemalloc/scripts/freebsd/script.sh000066400000000000000000000000311501533116600223040ustar00rootroot00000000000000#!/bin/tcsh gmake check redis-8.0.2/deps/jemalloc/scripts/gen_run_tests.py000077500000000000000000000077171501533116600223100ustar00rootroot00000000000000#!/usr/bin/env python3 import sys from itertools import combinations from os import uname from multiprocessing import cpu_count from subprocess import call # Later, we want to test extended vaddr support. Apparently, the "real" way of # checking this is flaky on OS X. bits_64 = sys.maxsize > 2**32 nparallel = cpu_count() * 2 uname = uname()[0] if call("command -v gmake", shell=True) == 0: make_cmd = 'gmake' else: make_cmd = 'make' def powerset(items): result = [] for i in range(len(items) + 1): result += combinations(items, i) return result possible_compilers = [] for cc, cxx in (['gcc', 'g++'], ['clang', 'clang++']): try: cmd_ret = call([cc, "-v"]) if cmd_ret == 0: possible_compilers.append((cc, cxx)) except: pass possible_compiler_opts = [ '-m32', ] possible_config_opts = [ '--enable-debug', '--enable-prof', '--disable-stats', '--enable-opt-safety-checks', '--with-lg-page=16', ] if bits_64: possible_config_opts.append('--with-lg-vaddr=56') possible_malloc_conf_opts = [ 'tcache:false', 'dss:primary', 'percpu_arena:percpu', 'background_thread:true', ] print('set -e') print('if [ -f Makefile ] ; then %(make_cmd)s relclean ; fi' % {'make_cmd': make_cmd}) print('autoconf') print('rm -rf run_tests.out') print('mkdir run_tests.out') print('cd run_tests.out') ind = 0 for cc, cxx in possible_compilers: for compiler_opts in powerset(possible_compiler_opts): for config_opts in powerset(possible_config_opts): for malloc_conf_opts in powerset(possible_malloc_conf_opts): if cc == 'clang' \ and '-m32' in possible_compiler_opts \ and '--enable-prof' in config_opts: continue config_line = ( 'EXTRA_CFLAGS=-Werror EXTRA_CXXFLAGS=-Werror ' + 'CC="{} {}" '.format(cc, " ".join(compiler_opts)) + 'CXX="{} {}" '.format(cxx, " ".join(compiler_opts)) + '../../configure ' + " ".join(config_opts) + (' --with-malloc-conf=' + ",".join(malloc_conf_opts) if len(malloc_conf_opts) > 0 else '') ) # We don't want to test large vaddr spaces in 32-bit mode. if ('-m32' in compiler_opts and '--with-lg-vaddr=56' in config_opts): continue # Per CPU arenas are only supported on Linux. linux_supported = ('percpu_arena:percpu' in malloc_conf_opts \ or 'background_thread:true' in malloc_conf_opts) # Heap profiling and dss are not supported on OS X. darwin_unsupported = ('--enable-prof' in config_opts or \ 'dss:primary' in malloc_conf_opts) if (uname == 'Linux' and linux_supported) \ or (not linux_supported and (uname != 'Darwin' or \ not darwin_unsupported)): print("""cat < run_test_%(ind)d.sh #!/bin/sh set -e abort() { echo "==> Error" >> run_test.log echo "Error; see run_tests.out/run_test_%(ind)d.out/run_test.log" exit 255 # Special exit code tells xargs to terminate. } # Environment variables are not supported. run_cmd() { echo "==> \$@" >> run_test.log \$@ >> run_test.log 2>&1 || abort } echo "=> run_test_%(ind)d: %(config_line)s" mkdir run_test_%(ind)d.out cd run_test_%(ind)d.out echo "==> %(config_line)s" >> run_test.log %(config_line)s >> run_test.log 2>&1 || abort run_cmd %(make_cmd)s all tests run_cmd %(make_cmd)s check run_cmd %(make_cmd)s distclean EOF chmod 755 run_test_%(ind)d.sh""" % {'ind': ind, 'config_line': config_line, 'make_cmd': make_cmd}) ind += 1 print('for i in `seq 0 %(last_ind)d` ; do echo run_test_${i}.sh ; done | xargs' ' -P %(nparallel)d -n 1 sh' % {'last_ind': ind-1, 'nparallel': nparallel}) redis-8.0.2/deps/jemalloc/scripts/gen_travis.py000077500000000000000000000217401501533116600215620ustar00rootroot00000000000000#!/usr/bin/env python3 from itertools import combinations, chain from enum import Enum, auto LINUX = 'linux' OSX = 'osx' WINDOWS = 'windows' FREEBSD = 'freebsd' AMD64 = 'amd64' ARM64 = 'arm64' PPC64LE = 'ppc64le' TRAVIS_TEMPLATE = """\ # This config file is generated by ./scripts/gen_travis.py. # Do not edit by hand. # We use 'minimal', because 'generic' makes Windows VMs hang at startup. Also # the software provided by 'generic' is simply not needed for our tests. # Differences are explained here: # https://docs.travis-ci.com/user/languages/minimal-and-generic/ language: minimal dist: focal jobs: include: {jobs} before_install: - |- if test -f "./scripts/$TRAVIS_OS_NAME/before_install.sh"; then source ./scripts/$TRAVIS_OS_NAME/before_install.sh fi before_script: - |- if test -f "./scripts/$TRAVIS_OS_NAME/before_script.sh"; then source ./scripts/$TRAVIS_OS_NAME/before_script.sh else scripts/gen_travis.py > travis_script && diff .travis.yml travis_script autoconf # If COMPILER_FLAGS are not empty, add them to CC and CXX ./configure ${{COMPILER_FLAGS:+ CC="$CC $COMPILER_FLAGS" \ CXX="$CXX $COMPILER_FLAGS"}} $CONFIGURE_FLAGS make -j3 make -j3 tests fi script: - |- if test -f "./scripts/$TRAVIS_OS_NAME/script.sh"; then source ./scripts/$TRAVIS_OS_NAME/script.sh else make check fi """ class Option(object): class Type: COMPILER = auto() COMPILER_FLAG = auto() CONFIGURE_FLAG = auto() MALLOC_CONF = auto() FEATURE = auto() def __init__(self, type, value): self.type = type self.value = value @staticmethod def as_compiler(value): return Option(Option.Type.COMPILER, value) @staticmethod def as_compiler_flag(value): return Option(Option.Type.COMPILER_FLAG, value) @staticmethod def as_configure_flag(value): return Option(Option.Type.CONFIGURE_FLAG, value) @staticmethod def as_malloc_conf(value): return Option(Option.Type.MALLOC_CONF, value) @staticmethod def as_feature(value): return Option(Option.Type.FEATURE, value) def __eq__(self, obj): return (isinstance(obj, Option) and obj.type == self.type and obj.value == self.value) # The 'default' configuration is gcc, on linux, with no compiler or configure # flags. We also test with clang, -m32, --enable-debug, --enable-prof, # --disable-stats, and --with-malloc-conf=tcache:false. To avoid abusing # travis though, we don't test all 2**7 = 128 possible combinations of these; # instead, we only test combinations of up to 2 'unusual' settings, under the # hope that bugs involving interactions of such settings are rare. MAX_UNUSUAL_OPTIONS = 2 GCC = Option.as_compiler('CC=gcc CXX=g++') CLANG = Option.as_compiler('CC=clang CXX=clang++') CL = Option.as_compiler('CC=cl.exe CXX=cl.exe') compilers_unusual = [CLANG,] CROSS_COMPILE_32BIT = Option.as_feature('CROSS_COMPILE_32BIT') feature_unusuals = [CROSS_COMPILE_32BIT] configure_flag_unusuals = [Option.as_configure_flag(opt) for opt in ( '--enable-debug', '--enable-prof', '--disable-stats', '--disable-libdl', '--enable-opt-safety-checks', '--with-lg-page=16', )] malloc_conf_unusuals = [Option.as_malloc_conf(opt) for opt in ( 'tcache:false', 'dss:primary', 'percpu_arena:percpu', 'background_thread:true', )] all_unusuals = (compilers_unusual + feature_unusuals + configure_flag_unusuals + malloc_conf_unusuals) def get_extra_cflags(os, compiler): if os == FREEBSD: return [] if os == WINDOWS: # For non-CL compilers under Windows (for now it's only MinGW-GCC), # -fcommon needs to be specified to correctly handle multiple # 'malloc_conf' symbols and such, which are declared weak under Linux. # Weak symbols don't work with MinGW-GCC. if compiler != CL.value: return ['-fcommon'] else: return [] # We get some spurious errors when -Warray-bounds is enabled. extra_cflags = ['-Werror', '-Wno-array-bounds'] if compiler == CLANG.value or os == OSX: extra_cflags += [ '-Wno-unknown-warning-option', '-Wno-ignored-attributes' ] if os == OSX: extra_cflags += [ '-Wno-deprecated-declarations', ] return extra_cflags # Formats a job from a combination of flags def format_job(os, arch, combination): compilers = [x.value for x in combination if x.type == Option.Type.COMPILER] assert(len(compilers) <= 1) compiler_flags = [x.value for x in combination if x.type == Option.Type.COMPILER_FLAG] configure_flags = [x.value for x in combination if x.type == Option.Type.CONFIGURE_FLAG] malloc_conf = [x.value for x in combination if x.type == Option.Type.MALLOC_CONF] features = [x.value for x in combination if x.type == Option.Type.FEATURE] if len(malloc_conf) > 0: configure_flags.append('--with-malloc-conf=' + ','.join(malloc_conf)) if not compilers: compiler = GCC.value else: compiler = compilers[0] extra_environment_vars = '' cross_compile = CROSS_COMPILE_32BIT.value in features if os == LINUX and cross_compile: compiler_flags.append('-m32') features_str = ' '.join([' {}=yes'.format(feature) for feature in features]) stringify = lambda arr, name: ' {}="{}"'.format(name, ' '.join(arr)) if arr else '' env_string = '{}{}{}{}{}{}'.format( compiler, features_str, stringify(compiler_flags, 'COMPILER_FLAGS'), stringify(configure_flags, 'CONFIGURE_FLAGS'), stringify(get_extra_cflags(os, compiler), 'EXTRA_CFLAGS'), extra_environment_vars) job = ' - os: {}\n'.format(os) job += ' arch: {}\n'.format(arch) job += ' env: {}'.format(env_string) return job def generate_unusual_combinations(unusuals, max_unusual_opts): """ Generates different combinations of non-standard compilers, compiler flags, configure flags and malloc_conf settings. @param max_unusual_opts: Limit of unusual options per combination. """ return chain.from_iterable( [combinations(unusuals, i) for i in range(max_unusual_opts + 1)]) def included(combination, exclude): """ Checks if the combination of options should be included in the Travis testing matrix. @param exclude: A list of options to be avoided. """ return not any(excluded in combination for excluded in exclude) def generate_jobs(os, arch, exclude, max_unusual_opts, unusuals=all_unusuals): jobs = [] for combination in generate_unusual_combinations(unusuals, max_unusual_opts): if included(combination, exclude): jobs.append(format_job(os, arch, combination)) return '\n'.join(jobs) def generate_linux(arch): os = LINUX # Only generate 2 unusual options for AMD64 to reduce matrix size max_unusual_opts = MAX_UNUSUAL_OPTIONS if arch == AMD64 else 1 exclude = [] if arch == PPC64LE: # Avoid 32 bit builds and clang on PowerPC exclude = (CROSS_COMPILE_32BIT, CLANG,) return generate_jobs(os, arch, exclude, max_unusual_opts) def generate_macos(arch): os = OSX max_unusual_opts = 1 exclude = ([Option.as_malloc_conf(opt) for opt in ( 'dss:primary', 'percpu_arena:percpu', 'background_thread:true')] + [Option.as_configure_flag('--enable-prof')] + [CLANG,]) return generate_jobs(os, arch, exclude, max_unusual_opts) def generate_windows(arch): os = WINDOWS max_unusual_opts = 3 unusuals = ( Option.as_configure_flag('--enable-debug'), CL, CROSS_COMPILE_32BIT, ) return generate_jobs(os, arch, (), max_unusual_opts, unusuals) def generate_freebsd(arch): os = FREEBSD max_unusual_opts = 4 unusuals = ( Option.as_configure_flag('--enable-debug'), Option.as_configure_flag('--enable-prof --enable-prof-libunwind'), Option.as_configure_flag('--with-lg-page=16 --with-malloc-conf=tcache:false'), CROSS_COMPILE_32BIT, ) return generate_jobs(os, arch, (), max_unusual_opts, unusuals) def get_manual_jobs(): return """\ # Development build - os: linux env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug \ --disable-cache-oblivious --enable-stats --enable-log --enable-prof" \ EXTRA_CFLAGS="-Werror -Wno-array-bounds" # --enable-expermental-smallocx: - os: linux env: CC=gcc CXX=g++ CONFIGURE_FLAGS="--enable-debug \ --enable-experimental-smallocx --enable-stats --enable-prof" \ EXTRA_CFLAGS="-Werror -Wno-array-bounds" """ def main(): jobs = '\n'.join(( generate_windows(AMD64), generate_freebsd(AMD64), generate_linux(AMD64), generate_linux(PPC64LE), generate_macos(AMD64), get_manual_jobs(), )) print(TRAVIS_TEMPLATE.format(jobs=jobs)) if __name__ == '__main__': main() redis-8.0.2/deps/jemalloc/scripts/linux/000077500000000000000000000000001501533116600201775ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/scripts/linux/before_install.sh000066400000000000000000000004341501533116600235240ustar00rootroot00000000000000#!/bin/bash set -ev if [[ "$TRAVIS_OS_NAME" != "linux" ]]; then echo "Incorrect \$TRAVIS_OS_NAME: expected linux, got $TRAVIS_OS_NAME" exit 1 fi if [[ "$CROSS_COMPILE_32BIT" == "yes" ]]; then sudo apt-get update sudo apt-get -y install gcc-multilib g++-multilib fi redis-8.0.2/deps/jemalloc/scripts/windows/000077500000000000000000000000001501533116600205325ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/scripts/windows/before_install.sh000066400000000000000000000063641501533116600240670ustar00rootroot00000000000000#!/bin/bash set -e # The purpose of this script is to install build dependencies and set # $build_env to a function that sets appropriate environment variables, # to enable (mingw32|mingw64) environment if we want to compile with gcc, or # (mingw32|mingw64) + vcvarsall.bat if we want to compile with cl.exe if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then echo "Incorrect \$TRAVIS_OS_NAME: expected windows, got $TRAVIS_OS_NAME" exit 1 fi [[ ! -f C:/tools/msys64/msys2_shell.cmd ]] && rm -rf C:/tools/msys64 choco uninstall -y mingw choco upgrade --no-progress -y msys2 msys_shell_cmd="cmd //C RefreshEnv.cmd && set MSYS=winsymlinks:nativestrict && C:\\tools\\msys64\\msys2_shell.cmd" msys2() { $msys_shell_cmd -defterm -no-start -msys2 -c "$*"; } mingw32() { $msys_shell_cmd -defterm -no-start -mingw32 -c "$*"; } mingw64() { $msys_shell_cmd -defterm -no-start -mingw64 -c "$*"; } if [[ "$CROSS_COMPILE_32BIT" == "yes" ]]; then mingw=mingw32 mingw_gcc_package_arch=i686 else mingw=mingw64 mingw_gcc_package_arch=x86_64 fi if [[ "$CC" == *"gcc"* ]]; then $mingw pacman -S --noconfirm --needed \ autotools \ git \ mingw-w64-${mingw_gcc_package_arch}-make \ mingw-w64-${mingw_gcc_package_arch}-gcc \ mingw-w64-${mingw_gcc_package_arch}-binutils build_env=$mingw elif [[ "$CC" == *"cl"* ]]; then $mingw pacman -S --noconfirm --needed \ autotools \ git \ mingw-w64-${mingw_gcc_package_arch}-make \ mingw-w64-${mingw_gcc_package_arch}-binutils # In order to use MSVC compiler (cl.exe), we need to correctly set some environment # variables, namely PATH, INCLUDE, LIB and LIBPATH. The correct values of these # variables are set by a batch script "vcvarsall.bat". The code below generates # a batch script that calls "vcvarsall.bat" and prints the environment variables. # # Then, those environment variables are transformed from cmd to bash format and put # into a script $apply_vsenv. If cl.exe needs to be used from bash, one can # 'source $apply_vsenv' and it will apply the environment variables needed for cl.exe # to be located and function correctly. # # At last, a function "mingw_with_msvc_vars" is generated which forwards user input # into a correct mingw (32 or 64) subshell that automatically performs 'source $apply_vsenv', # making it possible for autotools to discover and use cl.exe. vcvarsall="vcvarsall.tmp.bat" echo "@echo off" > $vcvarsall echo "call \"c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\\\vcvarsall.bat\" $USE_MSVC" >> $vcvarsall echo "set" >> $vcvarsall apply_vsenv="./apply_vsenv.sh" cmd //C $vcvarsall | grep -E "^PATH=" | sed -n -e 's/\(.*\)=\(.*\)/export \1=$PATH:"\2"/g' \ -e 's/\([a-zA-Z]\):[\\\/]/\/\1\//g' \ -e 's/\\/\//g' \ -e 's/;\//:\//gp' > $apply_vsenv cmd //C $vcvarsall | grep -E "^(INCLUDE|LIB|LIBPATH)=" | sed -n -e 's/\(.*\)=\(.*\)/export \1="\2"/gp' >> $apply_vsenv cat $apply_vsenv mingw_with_msvc_vars() { $msys_shell_cmd -defterm -no-start -$mingw -c "source $apply_vsenv && ""$*"; } build_env=mingw_with_msvc_vars rm -f $vcvarsall else echo "Unknown C compiler: $CC" exit 1 fi echo "Build environment function: $build_env" redis-8.0.2/deps/jemalloc/scripts/windows/before_script.sh000066400000000000000000000012541501533116600237160ustar00rootroot00000000000000#!/bin/bash set -e if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then echo "Incorrect \$TRAVIS_OS_NAME: expected windows, got $TRAVIS_OS_NAME" exit 1 fi $build_env autoconf $build_env ./configure $CONFIGURE_FLAGS # mingw32-make simply means "make", unrelated to mingw32 vs mingw64. # Simply disregard the prefix and treat is as "make". $build_env mingw32-make -j3 # At the moment, it's impossible to make tests in parallel, # seemingly due to concurrent writes to '.pdb' file. I don't know why # that happens, because we explicitly supply '/Fs' to the compiler. # Until we figure out how to fix it, we should build tests sequentially # on Windows. $build_env mingw32-make tests redis-8.0.2/deps/jemalloc/scripts/windows/script.sh000066400000000000000000000003001501533116600223630ustar00rootroot00000000000000#!/bin/bash set -e if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then echo "Incorrect \$TRAVIS_OS_NAME: expected windows, got $TRAVIS_OS_NAME" exit 1 fi $build_env mingw32-make -k check redis-8.0.2/deps/jemalloc/src/000077500000000000000000000000001501533116600161405ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/src/arena.c000066400000000000000000001563631501533116600174100ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/decay.h" #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/rtree.h" #include "jemalloc/internal/safety_check.h" #include "jemalloc/internal/util.h" JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS /******************************************************************************/ /* Data. */ /* * Define names for both unininitialized and initialized phases, so that * options and mallctl processing are straightforward. */ const char *percpu_arena_mode_names[] = { "percpu", "phycpu", "disabled", "percpu", "phycpu" }; percpu_arena_mode_t opt_percpu_arena = PERCPU_ARENA_DEFAULT; ssize_t opt_dirty_decay_ms = DIRTY_DECAY_MS_DEFAULT; ssize_t opt_muzzy_decay_ms = MUZZY_DECAY_MS_DEFAULT; static atomic_zd_t dirty_decay_ms_default; static atomic_zd_t muzzy_decay_ms_default; emap_t arena_emap_global; pa_central_t arena_pa_central_global; div_info_t arena_binind_div_info[SC_NBINS]; size_t opt_oversize_threshold = OVERSIZE_THRESHOLD_DEFAULT; size_t oversize_threshold = OVERSIZE_THRESHOLD_DEFAULT; uint32_t arena_bin_offsets[SC_NBINS]; static unsigned nbins_total; static unsigned huge_arena_ind; const arena_config_t arena_config_default = { /* .extent_hooks = */ (extent_hooks_t *)&ehooks_default_extent_hooks, /* .metadata_use_hooks = */ true, }; /******************************************************************************/ /* * Function prototypes for static functions that are referenced prior to * definition. */ static bool arena_decay_dirty(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all); static void arena_bin_lower_slab(tsdn_t *tsdn, arena_t *arena, edata_t *slab, bin_t *bin); static void arena_maybe_do_deferred_work(tsdn_t *tsdn, arena_t *arena, decay_t *decay, size_t npages_new); /******************************************************************************/ void arena_basic_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads, const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms, size_t *nactive, size_t *ndirty, size_t *nmuzzy) { *nthreads += arena_nthreads_get(arena, false); *dss = dss_prec_names[arena_dss_prec_get(arena)]; *dirty_decay_ms = arena_decay_ms_get(arena, extent_state_dirty); *muzzy_decay_ms = arena_decay_ms_get(arena, extent_state_muzzy); pa_shard_basic_stats_merge(&arena->pa_shard, nactive, ndirty, nmuzzy); } void arena_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads, const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms, size_t *nactive, size_t *ndirty, size_t *nmuzzy, arena_stats_t *astats, bin_stats_data_t *bstats, arena_stats_large_t *lstats, pac_estats_t *estats, hpa_shard_stats_t *hpastats, sec_stats_t *secstats) { cassert(config_stats); arena_basic_stats_merge(tsdn, arena, nthreads, dss, dirty_decay_ms, muzzy_decay_ms, nactive, ndirty, nmuzzy); size_t base_allocated, base_resident, base_mapped, metadata_thp; base_stats_get(tsdn, arena->base, &base_allocated, &base_resident, &base_mapped, &metadata_thp); size_t pac_mapped_sz = pac_mapped(&arena->pa_shard.pac); astats->mapped += base_mapped + pac_mapped_sz; astats->resident += base_resident; LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); astats->base += base_allocated; atomic_load_add_store_zu(&astats->internal, arena_internal_get(arena)); astats->metadata_thp += metadata_thp; for (szind_t i = 0; i < SC_NSIZES - SC_NBINS; i++) { uint64_t nmalloc = locked_read_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), &arena->stats.lstats[i].nmalloc); locked_inc_u64_unsynchronized(&lstats[i].nmalloc, nmalloc); astats->nmalloc_large += nmalloc; uint64_t ndalloc = locked_read_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), &arena->stats.lstats[i].ndalloc); locked_inc_u64_unsynchronized(&lstats[i].ndalloc, ndalloc); astats->ndalloc_large += ndalloc; uint64_t nrequests = locked_read_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), &arena->stats.lstats[i].nrequests); locked_inc_u64_unsynchronized(&lstats[i].nrequests, nmalloc + nrequests); astats->nrequests_large += nmalloc + nrequests; /* nfill == nmalloc for large currently. */ locked_inc_u64_unsynchronized(&lstats[i].nfills, nmalloc); astats->nfills_large += nmalloc; uint64_t nflush = locked_read_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), &arena->stats.lstats[i].nflushes); locked_inc_u64_unsynchronized(&lstats[i].nflushes, nflush); astats->nflushes_large += nflush; assert(nmalloc >= ndalloc); assert(nmalloc - ndalloc <= SIZE_T_MAX); size_t curlextents = (size_t)(nmalloc - ndalloc); lstats[i].curlextents += curlextents; astats->allocated_large += curlextents * sz_index2size(SC_NBINS + i); } pa_shard_stats_merge(tsdn, &arena->pa_shard, &astats->pa_shard_stats, estats, hpastats, secstats, &astats->resident); LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); /* Currently cached bytes and sanitizer-stashed bytes in tcache. */ astats->tcache_bytes = 0; astats->tcache_stashed_bytes = 0; malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); cache_bin_array_descriptor_t *descriptor; ql_foreach(descriptor, &arena->cache_bin_array_descriptor_ql, link) { for (szind_t i = 0; i < nhbins; i++) { cache_bin_t *cache_bin = &descriptor->bins[i]; cache_bin_sz_t ncached, nstashed; cache_bin_nitems_get_remote(cache_bin, &tcache_bin_info[i], &ncached, &nstashed); astats->tcache_bytes += ncached * sz_index2size(i); astats->tcache_stashed_bytes += nstashed * sz_index2size(i); } } malloc_mutex_prof_read(tsdn, &astats->mutex_prof_data[arena_prof_mutex_tcache_list], &arena->tcache_ql_mtx); malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); #define READ_ARENA_MUTEX_PROF_DATA(mtx, ind) \ malloc_mutex_lock(tsdn, &arena->mtx); \ malloc_mutex_prof_read(tsdn, &astats->mutex_prof_data[ind], \ &arena->mtx); \ malloc_mutex_unlock(tsdn, &arena->mtx); /* Gather per arena mutex profiling data. */ READ_ARENA_MUTEX_PROF_DATA(large_mtx, arena_prof_mutex_large); READ_ARENA_MUTEX_PROF_DATA(base->mtx, arena_prof_mutex_base); #undef READ_ARENA_MUTEX_PROF_DATA pa_shard_mtx_stats_read(tsdn, &arena->pa_shard, astats->mutex_prof_data); nstime_copy(&astats->uptime, &arena->create_time); nstime_update(&astats->uptime); nstime_subtract(&astats->uptime, &arena->create_time); for (szind_t i = 0; i < SC_NBINS; i++) { for (unsigned j = 0; j < bin_infos[i].n_shards; j++) { bin_stats_merge(tsdn, &bstats[i], arena_get_bin(arena, i, j)); } } } static void arena_background_thread_inactivity_check(tsdn_t *tsdn, arena_t *arena, bool is_background_thread) { if (!background_thread_enabled() || is_background_thread) { return; } background_thread_info_t *info = arena_background_thread_info_get(arena); if (background_thread_indefinite_sleep(info)) { arena_maybe_do_deferred_work(tsdn, arena, &arena->pa_shard.pac.decay_dirty, 0); } } /* * React to deferred work generated by a PAI function. */ void arena_handle_deferred_work(tsdn_t *tsdn, arena_t *arena) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (decay_immediately(&arena->pa_shard.pac.decay_dirty)) { arena_decay_dirty(tsdn, arena, false, true); } arena_background_thread_inactivity_check(tsdn, arena, false); } static void * arena_slab_reg_alloc(edata_t *slab, const bin_info_t *bin_info) { void *ret; slab_data_t *slab_data = edata_slab_data_get(slab); size_t regind; assert(edata_nfree_get(slab) > 0); assert(!bitmap_full(slab_data->bitmap, &bin_info->bitmap_info)); regind = bitmap_sfu(slab_data->bitmap, &bin_info->bitmap_info); ret = (void *)((uintptr_t)edata_addr_get(slab) + (uintptr_t)(bin_info->reg_size * regind)); edata_nfree_dec(slab); return ret; } static void arena_slab_reg_alloc_batch(edata_t *slab, const bin_info_t *bin_info, unsigned cnt, void** ptrs) { slab_data_t *slab_data = edata_slab_data_get(slab); assert(edata_nfree_get(slab) >= cnt); assert(!bitmap_full(slab_data->bitmap, &bin_info->bitmap_info)); #if (! defined JEMALLOC_INTERNAL_POPCOUNTL) || (defined BITMAP_USE_TREE) for (unsigned i = 0; i < cnt; i++) { size_t regind = bitmap_sfu(slab_data->bitmap, &bin_info->bitmap_info); *(ptrs + i) = (void *)((uintptr_t)edata_addr_get(slab) + (uintptr_t)(bin_info->reg_size * regind)); } #else unsigned group = 0; bitmap_t g = slab_data->bitmap[group]; unsigned i = 0; while (i < cnt) { while (g == 0) { g = slab_data->bitmap[++group]; } size_t shift = group << LG_BITMAP_GROUP_NBITS; size_t pop = popcount_lu(g); if (pop > (cnt - i)) { pop = cnt - i; } /* * Load from memory locations only once, outside the * hot loop below. */ uintptr_t base = (uintptr_t)edata_addr_get(slab); uintptr_t regsize = (uintptr_t)bin_info->reg_size; while (pop--) { size_t bit = cfs_lu(&g); size_t regind = shift + bit; *(ptrs + i) = (void *)(base + regsize * regind); i++; } slab_data->bitmap[group] = g; } #endif edata_nfree_sub(slab, cnt); } static void arena_large_malloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t usize) { szind_t index, hindex; cassert(config_stats); if (usize < SC_LARGE_MINCLASS) { usize = SC_LARGE_MINCLASS; } index = sz_size2index(usize); hindex = (index >= SC_NBINS) ? index - SC_NBINS : 0; locked_inc_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), &arena->stats.lstats[hindex].nmalloc, 1); } static void arena_large_dalloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t usize) { szind_t index, hindex; cassert(config_stats); if (usize < SC_LARGE_MINCLASS) { usize = SC_LARGE_MINCLASS; } index = sz_size2index(usize); hindex = (index >= SC_NBINS) ? index - SC_NBINS : 0; locked_inc_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), &arena->stats.lstats[hindex].ndalloc, 1); } static void arena_large_ralloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t oldusize, size_t usize) { arena_large_malloc_stats_update(tsdn, arena, usize); arena_large_dalloc_stats_update(tsdn, arena, oldusize); } edata_t * arena_extent_alloc_large(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero) { bool deferred_work_generated = false; szind_t szind = sz_size2index(usize); size_t esize = usize + sz_large_pad; bool guarded = san_large_extent_decide_guard(tsdn, arena_get_ehooks(arena), esize, alignment); edata_t *edata = pa_alloc(tsdn, &arena->pa_shard, esize, alignment, /* slab */ false, szind, zero, guarded, &deferred_work_generated); assert(deferred_work_generated == false); if (edata != NULL) { if (config_stats) { LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); arena_large_malloc_stats_update(tsdn, arena, usize); LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); } } if (edata != NULL && sz_large_pad != 0) { arena_cache_oblivious_randomize(tsdn, arena, edata, alignment); } return edata; } void arena_extent_dalloc_large_prep(tsdn_t *tsdn, arena_t *arena, edata_t *edata) { if (config_stats) { LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); arena_large_dalloc_stats_update(tsdn, arena, edata_usize_get(edata)); LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); } } void arena_extent_ralloc_large_shrink(tsdn_t *tsdn, arena_t *arena, edata_t *edata, size_t oldusize) { size_t usize = edata_usize_get(edata); if (config_stats) { LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); arena_large_ralloc_stats_update(tsdn, arena, oldusize, usize); LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); } } void arena_extent_ralloc_large_expand(tsdn_t *tsdn, arena_t *arena, edata_t *edata, size_t oldusize) { size_t usize = edata_usize_get(edata); if (config_stats) { LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); arena_large_ralloc_stats_update(tsdn, arena, oldusize, usize); LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); } } /* * In situations where we're not forcing a decay (i.e. because the user * specifically requested it), should we purge ourselves, or wait for the * background thread to get to it. */ static pac_purge_eagerness_t arena_decide_unforced_purge_eagerness(bool is_background_thread) { if (is_background_thread) { return PAC_PURGE_ALWAYS; } else if (!is_background_thread && background_thread_enabled()) { return PAC_PURGE_NEVER; } else { return PAC_PURGE_ON_EPOCH_ADVANCE; } } bool arena_decay_ms_set(tsdn_t *tsdn, arena_t *arena, extent_state_t state, ssize_t decay_ms) { pac_purge_eagerness_t eagerness = arena_decide_unforced_purge_eagerness( /* is_background_thread */ false); return pa_decay_ms_set(tsdn, &arena->pa_shard, state, decay_ms, eagerness); } ssize_t arena_decay_ms_get(arena_t *arena, extent_state_t state) { return pa_decay_ms_get(&arena->pa_shard, state); } static bool arena_decay_impl(tsdn_t *tsdn, arena_t *arena, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, bool is_background_thread, bool all) { if (all) { malloc_mutex_lock(tsdn, &decay->mtx); pac_decay_all(tsdn, &arena->pa_shard.pac, decay, decay_stats, ecache, /* fully_decay */ all); malloc_mutex_unlock(tsdn, &decay->mtx); return false; } if (malloc_mutex_trylock(tsdn, &decay->mtx)) { /* No need to wait if another thread is in progress. */ return true; } pac_purge_eagerness_t eagerness = arena_decide_unforced_purge_eagerness(is_background_thread); bool epoch_advanced = pac_maybe_decay_purge(tsdn, &arena->pa_shard.pac, decay, decay_stats, ecache, eagerness); size_t npages_new; if (epoch_advanced) { /* Backlog is updated on epoch advance. */ npages_new = decay_epoch_npages_delta(decay); } malloc_mutex_unlock(tsdn, &decay->mtx); if (have_background_thread && background_thread_enabled() && epoch_advanced && !is_background_thread) { arena_maybe_do_deferred_work(tsdn, arena, decay, npages_new); } return false; } static bool arena_decay_dirty(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all) { return arena_decay_impl(tsdn, arena, &arena->pa_shard.pac.decay_dirty, &arena->pa_shard.pac.stats->decay_dirty, &arena->pa_shard.pac.ecache_dirty, is_background_thread, all); } static bool arena_decay_muzzy(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all) { if (pa_shard_dont_decay_muzzy(&arena->pa_shard)) { return false; } return arena_decay_impl(tsdn, arena, &arena->pa_shard.pac.decay_muzzy, &arena->pa_shard.pac.stats->decay_muzzy, &arena->pa_shard.pac.ecache_muzzy, is_background_thread, all); } void arena_decay(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all) { if (all) { /* * We should take a purge of "all" to mean "save as much memory * as possible", including flushing any caches (for situations * like thread death, or manual purge calls). */ sec_flush(tsdn, &arena->pa_shard.hpa_sec); } if (arena_decay_dirty(tsdn, arena, is_background_thread, all)) { return; } arena_decay_muzzy(tsdn, arena, is_background_thread, all); } static bool arena_should_decay_early(tsdn_t *tsdn, arena_t *arena, decay_t *decay, background_thread_info_t *info, nstime_t *remaining_sleep, size_t npages_new) { malloc_mutex_assert_owner(tsdn, &info->mtx); if (malloc_mutex_trylock(tsdn, &decay->mtx)) { return false; } if (!decay_gradually(decay)) { malloc_mutex_unlock(tsdn, &decay->mtx); return false; } nstime_init(remaining_sleep, background_thread_wakeup_time_get(info)); if (nstime_compare(remaining_sleep, &decay->epoch) <= 0) { malloc_mutex_unlock(tsdn, &decay->mtx); return false; } nstime_subtract(remaining_sleep, &decay->epoch); if (npages_new > 0) { uint64_t npurge_new = decay_npages_purge_in(decay, remaining_sleep, npages_new); info->npages_to_purge_new += npurge_new; } malloc_mutex_unlock(tsdn, &decay->mtx); return info->npages_to_purge_new > ARENA_DEFERRED_PURGE_NPAGES_THRESHOLD; } /* * Check if deferred work needs to be done sooner than planned. * For decay we might want to wake up earlier because of an influx of dirty * pages. Rather than waiting for previously estimated time, we proactively * purge those pages. * If background thread sleeps indefinitely, always wake up because some * deferred work has been generated. */ static void arena_maybe_do_deferred_work(tsdn_t *tsdn, arena_t *arena, decay_t *decay, size_t npages_new) { background_thread_info_t *info = arena_background_thread_info_get( arena); if (malloc_mutex_trylock(tsdn, &info->mtx)) { /* * Background thread may hold the mutex for a long period of * time. We'd like to avoid the variance on application * threads. So keep this non-blocking, and leave the work to a * future epoch. */ return; } if (!background_thread_is_started(info)) { goto label_done; } nstime_t remaining_sleep; if (background_thread_indefinite_sleep(info)) { background_thread_wakeup_early(info, NULL); } else if (arena_should_decay_early(tsdn, arena, decay, info, &remaining_sleep, npages_new)) { info->npages_to_purge_new = 0; background_thread_wakeup_early(info, &remaining_sleep); } label_done: malloc_mutex_unlock(tsdn, &info->mtx); } /* Called from background threads. */ void arena_do_deferred_work(tsdn_t *tsdn, arena_t *arena) { arena_decay(tsdn, arena, true, false); pa_shard_do_deferred_work(tsdn, &arena->pa_shard); } void arena_slab_dalloc(tsdn_t *tsdn, arena_t *arena, edata_t *slab) { bool deferred_work_generated = false; pa_dalloc(tsdn, &arena->pa_shard, slab, &deferred_work_generated); if (deferred_work_generated) { arena_handle_deferred_work(tsdn, arena); } } static void arena_bin_slabs_nonfull_insert(bin_t *bin, edata_t *slab) { assert(edata_nfree_get(slab) > 0); edata_heap_insert(&bin->slabs_nonfull, slab); if (config_stats) { bin->stats.nonfull_slabs++; } } static void arena_bin_slabs_nonfull_remove(bin_t *bin, edata_t *slab) { edata_heap_remove(&bin->slabs_nonfull, slab); if (config_stats) { bin->stats.nonfull_slabs--; } } static edata_t * arena_bin_slabs_nonfull_tryget(bin_t *bin) { edata_t *slab = edata_heap_remove_first(&bin->slabs_nonfull); if (slab == NULL) { return NULL; } if (config_stats) { bin->stats.reslabs++; bin->stats.nonfull_slabs--; } return slab; } static void arena_bin_slabs_full_insert(arena_t *arena, bin_t *bin, edata_t *slab) { assert(edata_nfree_get(slab) == 0); /* * Tracking extents is required by arena_reset, which is not allowed * for auto arenas. Bypass this step to avoid touching the edata * linkage (often results in cache misses) for auto arenas. */ if (arena_is_auto(arena)) { return; } edata_list_active_append(&bin->slabs_full, slab); } static void arena_bin_slabs_full_remove(arena_t *arena, bin_t *bin, edata_t *slab) { if (arena_is_auto(arena)) { return; } edata_list_active_remove(&bin->slabs_full, slab); } static void arena_bin_reset(tsd_t *tsd, arena_t *arena, bin_t *bin) { edata_t *slab; malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); if (bin->slabcur != NULL) { slab = bin->slabcur; bin->slabcur = NULL; malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); arena_slab_dalloc(tsd_tsdn(tsd), arena, slab); malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); } while ((slab = edata_heap_remove_first(&bin->slabs_nonfull)) != NULL) { malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); arena_slab_dalloc(tsd_tsdn(tsd), arena, slab); malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); } for (slab = edata_list_active_first(&bin->slabs_full); slab != NULL; slab = edata_list_active_first(&bin->slabs_full)) { arena_bin_slabs_full_remove(arena, bin, slab); malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); arena_slab_dalloc(tsd_tsdn(tsd), arena, slab); malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); } if (config_stats) { bin->stats.curregs = 0; bin->stats.curslabs = 0; } malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); } void arena_reset(tsd_t *tsd, arena_t *arena) { /* * Locking in this function is unintuitive. The caller guarantees that * no concurrent operations are happening in this arena, but there are * still reasons that some locking is necessary: * * - Some of the functions in the transitive closure of calls assume * appropriate locks are held, and in some cases these locks are * temporarily dropped to avoid lock order reversal or deadlock due to * reentry. * - mallctl("epoch", ...) may concurrently refresh stats. While * strictly speaking this is a "concurrent operation", disallowing * stats refreshes would impose an inconvenient burden. */ /* Large allocations. */ malloc_mutex_lock(tsd_tsdn(tsd), &arena->large_mtx); for (edata_t *edata = edata_list_active_first(&arena->large); edata != NULL; edata = edata_list_active_first(&arena->large)) { void *ptr = edata_base_get(edata); size_t usize; malloc_mutex_unlock(tsd_tsdn(tsd), &arena->large_mtx); emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind != SC_NSIZES); if (config_stats || (config_prof && opt_prof)) { usize = sz_index2size(alloc_ctx.szind); assert(usize == isalloc(tsd_tsdn(tsd), ptr)); } /* Remove large allocation from prof sample set. */ if (config_prof && opt_prof) { prof_free(tsd, ptr, usize, &alloc_ctx); } large_dalloc(tsd_tsdn(tsd), edata); malloc_mutex_lock(tsd_tsdn(tsd), &arena->large_mtx); } malloc_mutex_unlock(tsd_tsdn(tsd), &arena->large_mtx); /* Bins. */ for (unsigned i = 0; i < SC_NBINS; i++) { for (unsigned j = 0; j < bin_infos[i].n_shards; j++) { arena_bin_reset(tsd, arena, arena_get_bin(arena, i, j)); } } pa_shard_reset(tsd_tsdn(tsd), &arena->pa_shard); } static void arena_prepare_base_deletion_sync_finish(tsd_t *tsd, malloc_mutex_t **mutexes, unsigned n_mtx) { for (unsigned i = 0; i < n_mtx; i++) { malloc_mutex_lock(tsd_tsdn(tsd), mutexes[i]); malloc_mutex_unlock(tsd_tsdn(tsd), mutexes[i]); } } #define ARENA_DESTROY_MAX_DELAYED_MTX 32 static void arena_prepare_base_deletion_sync(tsd_t *tsd, malloc_mutex_t *mtx, malloc_mutex_t **delayed_mtx, unsigned *n_delayed) { if (!malloc_mutex_trylock(tsd_tsdn(tsd), mtx)) { /* No contention. */ malloc_mutex_unlock(tsd_tsdn(tsd), mtx); return; } unsigned n = *n_delayed; assert(n < ARENA_DESTROY_MAX_DELAYED_MTX); /* Add another to the batch. */ delayed_mtx[n++] = mtx; if (n == ARENA_DESTROY_MAX_DELAYED_MTX) { arena_prepare_base_deletion_sync_finish(tsd, delayed_mtx, n); n = 0; } *n_delayed = n; } static void arena_prepare_base_deletion(tsd_t *tsd, base_t *base_to_destroy) { /* * In order to coalesce, emap_try_acquire_edata_neighbor will attempt to * check neighbor edata's state to determine eligibility. This means * under certain conditions, the metadata from an arena can be accessed * w/o holding any locks from that arena. In order to guarantee safe * memory access, the metadata and the underlying base allocator needs * to be kept alive, until all pending accesses are done. * * 1) with opt_retain, the arena boundary implies the is_head state * (tracked in the rtree leaf), and the coalesce flow will stop at the * head state branch. Therefore no cross arena metadata access * possible. * * 2) w/o opt_retain, the arena id needs to be read from the edata_t, * meaning read only cross-arena metadata access is possible. The * coalesce attempt will stop at the arena_id mismatch, and is always * under one of the ecache locks. To allow safe passthrough of such * metadata accesses, the loop below will iterate through all manual * arenas' ecache locks. As all the metadata from this base allocator * have been unlinked from the rtree, after going through all the * relevant ecache locks, it's safe to say that a) pending accesses are * all finished, and b) no new access will be generated. */ if (opt_retain) { return; } unsigned destroy_ind = base_ind_get(base_to_destroy); assert(destroy_ind >= manual_arena_base); tsdn_t *tsdn = tsd_tsdn(tsd); malloc_mutex_t *delayed_mtx[ARENA_DESTROY_MAX_DELAYED_MTX]; unsigned n_delayed = 0, total = narenas_total_get(); for (unsigned i = 0; i < total; i++) { if (i == destroy_ind) { continue; } arena_t *arena = arena_get(tsdn, i, false); if (arena == NULL) { continue; } pac_t *pac = &arena->pa_shard.pac; arena_prepare_base_deletion_sync(tsd, &pac->ecache_dirty.mtx, delayed_mtx, &n_delayed); arena_prepare_base_deletion_sync(tsd, &pac->ecache_muzzy.mtx, delayed_mtx, &n_delayed); arena_prepare_base_deletion_sync(tsd, &pac->ecache_retained.mtx, delayed_mtx, &n_delayed); } arena_prepare_base_deletion_sync_finish(tsd, delayed_mtx, n_delayed); } #undef ARENA_DESTROY_MAX_DELAYED_MTX void arena_destroy(tsd_t *tsd, arena_t *arena) { assert(base_ind_get(arena->base) >= narenas_auto); assert(arena_nthreads_get(arena, false) == 0); assert(arena_nthreads_get(arena, true) == 0); /* * No allocations have occurred since arena_reset() was called. * Furthermore, the caller (arena_i_destroy_ctl()) purged all cached * extents, so only retained extents may remain and it's safe to call * pa_shard_destroy_retained. */ pa_shard_destroy(tsd_tsdn(tsd), &arena->pa_shard); /* * Remove the arena pointer from the arenas array. We rely on the fact * that there is no way for the application to get a dirty read from the * arenas array unless there is an inherent race in the application * involving access of an arena being concurrently destroyed. The * application must synchronize knowledge of the arena's validity, so as * long as we use an atomic write to update the arenas array, the * application will get a clean read any time after it synchronizes * knowledge that the arena is no longer valid. */ arena_set(base_ind_get(arena->base), NULL); /* * Destroy the base allocator, which manages all metadata ever mapped by * this arena. The prepare function will make sure no pending access to * the metadata in this base anymore. */ arena_prepare_base_deletion(tsd, arena->base); base_delete(tsd_tsdn(tsd), arena->base); } static edata_t * arena_slab_alloc(tsdn_t *tsdn, arena_t *arena, szind_t binind, unsigned binshard, const bin_info_t *bin_info) { bool deferred_work_generated = false; witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); bool guarded = san_slab_extent_decide_guard(tsdn, arena_get_ehooks(arena)); edata_t *slab = pa_alloc(tsdn, &arena->pa_shard, bin_info->slab_size, /* alignment */ PAGE, /* slab */ true, /* szind */ binind, /* zero */ false, guarded, &deferred_work_generated); if (deferred_work_generated) { arena_handle_deferred_work(tsdn, arena); } if (slab == NULL) { return NULL; } assert(edata_slab_get(slab)); /* Initialize slab internals. */ slab_data_t *slab_data = edata_slab_data_get(slab); edata_nfree_binshard_set(slab, bin_info->nregs, binshard); bitmap_init(slab_data->bitmap, &bin_info->bitmap_info, false); return slab; } /* * Before attempting the _with_fresh_slab approaches below, the _no_fresh_slab * variants (i.e. through slabcur and nonfull) must be tried first. */ static void arena_bin_refill_slabcur_with_fresh_slab(tsdn_t *tsdn, arena_t *arena, bin_t *bin, szind_t binind, edata_t *fresh_slab) { malloc_mutex_assert_owner(tsdn, &bin->lock); /* Only called after slabcur and nonfull both failed. */ assert(bin->slabcur == NULL); assert(edata_heap_first(&bin->slabs_nonfull) == NULL); assert(fresh_slab != NULL); /* A new slab from arena_slab_alloc() */ assert(edata_nfree_get(fresh_slab) == bin_infos[binind].nregs); if (config_stats) { bin->stats.nslabs++; bin->stats.curslabs++; } bin->slabcur = fresh_slab; } /* Refill slabcur and then alloc using the fresh slab */ static void * arena_bin_malloc_with_fresh_slab(tsdn_t *tsdn, arena_t *arena, bin_t *bin, szind_t binind, edata_t *fresh_slab) { malloc_mutex_assert_owner(tsdn, &bin->lock); arena_bin_refill_slabcur_with_fresh_slab(tsdn, arena, bin, binind, fresh_slab); return arena_slab_reg_alloc(bin->slabcur, &bin_infos[binind]); } static bool arena_bin_refill_slabcur_no_fresh_slab(tsdn_t *tsdn, arena_t *arena, bin_t *bin) { malloc_mutex_assert_owner(tsdn, &bin->lock); /* Only called after arena_slab_reg_alloc[_batch] failed. */ assert(bin->slabcur == NULL || edata_nfree_get(bin->slabcur) == 0); if (bin->slabcur != NULL) { arena_bin_slabs_full_insert(arena, bin, bin->slabcur); } /* Look for a usable slab. */ bin->slabcur = arena_bin_slabs_nonfull_tryget(bin); assert(bin->slabcur == NULL || edata_nfree_get(bin->slabcur) > 0); return (bin->slabcur == NULL); } bin_t * arena_bin_choose(tsdn_t *tsdn, arena_t *arena, szind_t binind, unsigned *binshard_p) { unsigned binshard; if (tsdn_null(tsdn) || tsd_arena_get(tsdn_tsd(tsdn)) == NULL) { binshard = 0; } else { binshard = tsd_binshardsp_get(tsdn_tsd(tsdn))->binshard[binind]; } assert(binshard < bin_infos[binind].n_shards); if (binshard_p != NULL) { *binshard_p = binshard; } return arena_get_bin(arena, binind, binshard); } void arena_cache_bin_fill_small(tsdn_t *tsdn, arena_t *arena, cache_bin_t *cache_bin, cache_bin_info_t *cache_bin_info, szind_t binind, const unsigned nfill) { assert(cache_bin_ncached_get_local(cache_bin, cache_bin_info) == 0); const bin_info_t *bin_info = &bin_infos[binind]; CACHE_BIN_PTR_ARRAY_DECLARE(ptrs, nfill); cache_bin_init_ptr_array_for_fill(cache_bin, cache_bin_info, &ptrs, nfill); /* * Bin-local resources are used first: 1) bin->slabcur, and 2) nonfull * slabs. After both are exhausted, new slabs will be allocated through * arena_slab_alloc(). * * Bin lock is only taken / released right before / after the while(...) * refill loop, with new slab allocation (which has its own locking) * kept outside of the loop. This setup facilitates flat combining, at * the cost of the nested loop (through goto label_refill). * * To optimize for cases with contention and limited resources * (e.g. hugepage-backed or non-overcommit arenas), each fill-iteration * gets one chance of slab_alloc, and a retry of bin local resources * after the slab allocation (regardless if slab_alloc failed, because * the bin lock is dropped during the slab allocation). * * In other words, new slab allocation is allowed, as long as there was * progress since the previous slab_alloc. This is tracked with * made_progress below, initialized to true to jump start the first * iteration. * * In other words (again), the loop will only terminate early (i.e. stop * with filled < nfill) after going through the three steps: a) bin * local exhausted, b) unlock and slab_alloc returns null, c) re-lock * and bin local fails again. */ bool made_progress = true; edata_t *fresh_slab = NULL; bool alloc_and_retry = false; unsigned filled = 0; unsigned binshard; bin_t *bin = arena_bin_choose(tsdn, arena, binind, &binshard); label_refill: malloc_mutex_lock(tsdn, &bin->lock); while (filled < nfill) { /* Try batch-fill from slabcur first. */ edata_t *slabcur = bin->slabcur; if (slabcur != NULL && edata_nfree_get(slabcur) > 0) { unsigned tofill = nfill - filled; unsigned nfree = edata_nfree_get(slabcur); unsigned cnt = tofill < nfree ? tofill : nfree; arena_slab_reg_alloc_batch(slabcur, bin_info, cnt, &ptrs.ptr[filled]); made_progress = true; filled += cnt; continue; } /* Next try refilling slabcur from nonfull slabs. */ if (!arena_bin_refill_slabcur_no_fresh_slab(tsdn, arena, bin)) { assert(bin->slabcur != NULL); continue; } /* Then see if a new slab was reserved already. */ if (fresh_slab != NULL) { arena_bin_refill_slabcur_with_fresh_slab(tsdn, arena, bin, binind, fresh_slab); assert(bin->slabcur != NULL); fresh_slab = NULL; continue; } /* Try slab_alloc if made progress (or never did slab_alloc). */ if (made_progress) { assert(bin->slabcur == NULL); assert(fresh_slab == NULL); alloc_and_retry = true; /* Alloc a new slab then come back. */ break; } /* OOM. */ assert(fresh_slab == NULL); assert(!alloc_and_retry); break; } /* while (filled < nfill) loop. */ if (config_stats && !alloc_and_retry) { bin->stats.nmalloc += filled; bin->stats.nrequests += cache_bin->tstats.nrequests; bin->stats.curregs += filled; bin->stats.nfills++; cache_bin->tstats.nrequests = 0; } malloc_mutex_unlock(tsdn, &bin->lock); if (alloc_and_retry) { assert(fresh_slab == NULL); assert(filled < nfill); assert(made_progress); fresh_slab = arena_slab_alloc(tsdn, arena, binind, binshard, bin_info); /* fresh_slab NULL case handled in the for loop. */ alloc_and_retry = false; made_progress = false; goto label_refill; } assert(filled == nfill || (fresh_slab == NULL && !made_progress)); /* Release if allocated but not used. */ if (fresh_slab != NULL) { assert(edata_nfree_get(fresh_slab) == bin_info->nregs); arena_slab_dalloc(tsdn, arena, fresh_slab); fresh_slab = NULL; } cache_bin_finish_fill(cache_bin, cache_bin_info, &ptrs, filled); arena_decay_tick(tsdn, arena); } size_t arena_fill_small_fresh(tsdn_t *tsdn, arena_t *arena, szind_t binind, void **ptrs, size_t nfill, bool zero) { assert(binind < SC_NBINS); const bin_info_t *bin_info = &bin_infos[binind]; const size_t nregs = bin_info->nregs; assert(nregs > 0); const size_t usize = bin_info->reg_size; const bool manual_arena = !arena_is_auto(arena); unsigned binshard; bin_t *bin = arena_bin_choose(tsdn, arena, binind, &binshard); size_t nslab = 0; size_t filled = 0; edata_t *slab = NULL; edata_list_active_t fulls; edata_list_active_init(&fulls); while (filled < nfill && (slab = arena_slab_alloc(tsdn, arena, binind, binshard, bin_info)) != NULL) { assert((size_t)edata_nfree_get(slab) == nregs); ++nslab; size_t batch = nfill - filled; if (batch > nregs) { batch = nregs; } assert(batch > 0); arena_slab_reg_alloc_batch(slab, bin_info, (unsigned)batch, &ptrs[filled]); assert(edata_addr_get(slab) == ptrs[filled]); if (zero) { memset(ptrs[filled], 0, batch * usize); } filled += batch; if (batch == nregs) { if (manual_arena) { edata_list_active_append(&fulls, slab); } slab = NULL; } } malloc_mutex_lock(tsdn, &bin->lock); /* * Only the last slab can be non-empty, and the last slab is non-empty * iff slab != NULL. */ if (slab != NULL) { arena_bin_lower_slab(tsdn, arena, slab, bin); } if (manual_arena) { edata_list_active_concat(&bin->slabs_full, &fulls); } assert(edata_list_active_empty(&fulls)); if (config_stats) { bin->stats.nslabs += nslab; bin->stats.curslabs += nslab; bin->stats.nmalloc += filled; bin->stats.nrequests += filled; bin->stats.curregs += filled; } malloc_mutex_unlock(tsdn, &bin->lock); arena_decay_tick(tsdn, arena); return filled; } /* * Without allocating a new slab, try arena_slab_reg_alloc() and re-fill * bin->slabcur if necessary. */ static void * arena_bin_malloc_no_fresh_slab(tsdn_t *tsdn, arena_t *arena, bin_t *bin, szind_t binind) { malloc_mutex_assert_owner(tsdn, &bin->lock); if (bin->slabcur == NULL || edata_nfree_get(bin->slabcur) == 0) { if (arena_bin_refill_slabcur_no_fresh_slab(tsdn, arena, bin)) { return NULL; } } assert(bin->slabcur != NULL && edata_nfree_get(bin->slabcur) > 0); return arena_slab_reg_alloc(bin->slabcur, &bin_infos[binind]); } static void * arena_malloc_small(tsdn_t *tsdn, arena_t *arena, szind_t binind, bool zero) { assert(binind < SC_NBINS); const bin_info_t *bin_info = &bin_infos[binind]; size_t usize = sz_index2size(binind); unsigned binshard; bin_t *bin = arena_bin_choose(tsdn, arena, binind, &binshard); malloc_mutex_lock(tsdn, &bin->lock); edata_t *fresh_slab = NULL; void *ret = arena_bin_malloc_no_fresh_slab(tsdn, arena, bin, binind); if (ret == NULL) { malloc_mutex_unlock(tsdn, &bin->lock); /******************************/ fresh_slab = arena_slab_alloc(tsdn, arena, binind, binshard, bin_info); /********************************/ malloc_mutex_lock(tsdn, &bin->lock); /* Retry since the lock was dropped. */ ret = arena_bin_malloc_no_fresh_slab(tsdn, arena, bin, binind); if (ret == NULL) { if (fresh_slab == NULL) { /* OOM */ malloc_mutex_unlock(tsdn, &bin->lock); return NULL; } ret = arena_bin_malloc_with_fresh_slab(tsdn, arena, bin, binind, fresh_slab); fresh_slab = NULL; } } if (config_stats) { bin->stats.nmalloc++; bin->stats.nrequests++; bin->stats.curregs++; } malloc_mutex_unlock(tsdn, &bin->lock); if (fresh_slab != NULL) { arena_slab_dalloc(tsdn, arena, fresh_slab); } if (zero) { memset(ret, 0, usize); } arena_decay_tick(tsdn, arena); return ret; } void * arena_malloc_hard(tsdn_t *tsdn, arena_t *arena, size_t size, szind_t ind, bool zero) { assert(!tsdn_null(tsdn) || arena != NULL); if (likely(!tsdn_null(tsdn))) { arena = arena_choose_maybe_huge(tsdn_tsd(tsdn), arena, size); } if (unlikely(arena == NULL)) { return NULL; } if (likely(size <= SC_SMALL_MAXCLASS)) { return arena_malloc_small(tsdn, arena, ind, zero); } return large_malloc(tsdn, arena, sz_index2size(ind), zero); } void * arena_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero, tcache_t *tcache) { void *ret; if (usize <= SC_SMALL_MAXCLASS) { /* Small; alignment doesn't require special slab placement. */ /* usize should be a result of sz_sa2u() */ assert((usize & (alignment - 1)) == 0); /* * Small usize can't come from an alignment larger than a page. */ assert(alignment <= PAGE); ret = arena_malloc(tsdn, arena, usize, sz_size2index(usize), zero, tcache, true); } else { if (likely(alignment <= CACHELINE)) { ret = large_malloc(tsdn, arena, usize, zero); } else { ret = large_palloc(tsdn, arena, usize, alignment, zero); } } return ret; } void arena_prof_promote(tsdn_t *tsdn, void *ptr, size_t usize) { cassert(config_prof); assert(ptr != NULL); assert(isalloc(tsdn, ptr) == SC_LARGE_MINCLASS); assert(usize <= SC_SMALL_MAXCLASS); if (config_opt_safety_checks) { safety_check_set_redzone(ptr, usize, SC_LARGE_MINCLASS); } edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); szind_t szind = sz_size2index(usize); edata_szind_set(edata, szind); emap_remap(tsdn, &arena_emap_global, edata, szind, /* slab */ false); assert(isalloc(tsdn, ptr) == usize); } static size_t arena_prof_demote(tsdn_t *tsdn, edata_t *edata, const void *ptr) { cassert(config_prof); assert(ptr != NULL); edata_szind_set(edata, SC_NBINS); emap_remap(tsdn, &arena_emap_global, edata, SC_NBINS, /* slab */ false); assert(isalloc(tsdn, ptr) == SC_LARGE_MINCLASS); return SC_LARGE_MINCLASS; } void arena_dalloc_promoted(tsdn_t *tsdn, void *ptr, tcache_t *tcache, bool slow_path) { cassert(config_prof); assert(opt_prof); edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); size_t usize = edata_usize_get(edata); size_t bumped_usize = arena_prof_demote(tsdn, edata, ptr); if (config_opt_safety_checks && usize < SC_LARGE_MINCLASS) { /* * Currently, we only do redzoning for small sampled * allocations. */ assert(bumped_usize == SC_LARGE_MINCLASS); safety_check_verify_redzone(ptr, usize, bumped_usize); } if (bumped_usize <= tcache_maxclass && tcache != NULL) { tcache_dalloc_large(tsdn_tsd(tsdn), tcache, ptr, sz_size2index(bumped_usize), slow_path); } else { large_dalloc(tsdn, edata); } } static void arena_dissociate_bin_slab(arena_t *arena, edata_t *slab, bin_t *bin) { /* Dissociate slab from bin. */ if (slab == bin->slabcur) { bin->slabcur = NULL; } else { szind_t binind = edata_szind_get(slab); const bin_info_t *bin_info = &bin_infos[binind]; /* * The following block's conditional is necessary because if the * slab only contains one region, then it never gets inserted * into the non-full slabs heap. */ if (bin_info->nregs == 1) { arena_bin_slabs_full_remove(arena, bin, slab); } else { arena_bin_slabs_nonfull_remove(bin, slab); } } } static void arena_bin_lower_slab(tsdn_t *tsdn, arena_t *arena, edata_t *slab, bin_t *bin) { assert(edata_nfree_get(slab) > 0); /* * Make sure that if bin->slabcur is non-NULL, it refers to the * oldest/lowest non-full slab. It is okay to NULL slabcur out rather * than proactively keeping it pointing at the oldest/lowest non-full * slab. */ if (bin->slabcur != NULL && edata_snad_comp(bin->slabcur, slab) > 0) { /* Switch slabcur. */ if (edata_nfree_get(bin->slabcur) > 0) { arena_bin_slabs_nonfull_insert(bin, bin->slabcur); } else { arena_bin_slabs_full_insert(arena, bin, bin->slabcur); } bin->slabcur = slab; if (config_stats) { bin->stats.reslabs++; } } else { arena_bin_slabs_nonfull_insert(bin, slab); } } static void arena_dalloc_bin_slab_prepare(tsdn_t *tsdn, edata_t *slab, bin_t *bin) { malloc_mutex_assert_owner(tsdn, &bin->lock); assert(slab != bin->slabcur); if (config_stats) { bin->stats.curslabs--; } } void arena_dalloc_bin_locked_handle_newly_empty(tsdn_t *tsdn, arena_t *arena, edata_t *slab, bin_t *bin) { arena_dissociate_bin_slab(arena, slab, bin); arena_dalloc_bin_slab_prepare(tsdn, slab, bin); } void arena_dalloc_bin_locked_handle_newly_nonempty(tsdn_t *tsdn, arena_t *arena, edata_t *slab, bin_t *bin) { arena_bin_slabs_full_remove(arena, bin, slab); arena_bin_lower_slab(tsdn, arena, slab, bin); } static void arena_dalloc_bin(tsdn_t *tsdn, arena_t *arena, edata_t *edata, void *ptr) { szind_t binind = edata_szind_get(edata); unsigned binshard = edata_binshard_get(edata); bin_t *bin = arena_get_bin(arena, binind, binshard); malloc_mutex_lock(tsdn, &bin->lock); arena_dalloc_bin_locked_info_t info; arena_dalloc_bin_locked_begin(&info, binind); bool ret = arena_dalloc_bin_locked_step(tsdn, arena, bin, &info, binind, edata, ptr); arena_dalloc_bin_locked_finish(tsdn, arena, bin, &info); malloc_mutex_unlock(tsdn, &bin->lock); if (ret) { arena_slab_dalloc(tsdn, arena, edata); } } void arena_dalloc_small(tsdn_t *tsdn, void *ptr) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); arena_t *arena = arena_get_from_edata(edata); arena_dalloc_bin(tsdn, arena, edata, ptr); arena_decay_tick(tsdn, arena); } bool arena_ralloc_no_move(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra, bool zero, size_t *newsize) { bool ret; /* Calls with non-zero extra had to clamp extra. */ assert(extra == 0 || size + extra <= SC_LARGE_MAXCLASS); edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); if (unlikely(size > SC_LARGE_MAXCLASS)) { ret = true; goto done; } size_t usize_min = sz_s2u(size); size_t usize_max = sz_s2u(size + extra); if (likely(oldsize <= SC_SMALL_MAXCLASS && usize_min <= SC_SMALL_MAXCLASS)) { /* * Avoid moving the allocation if the size class can be left the * same. */ assert(bin_infos[sz_size2index(oldsize)].reg_size == oldsize); if ((usize_max > SC_SMALL_MAXCLASS || sz_size2index(usize_max) != sz_size2index(oldsize)) && (size > oldsize || usize_max < oldsize)) { ret = true; goto done; } arena_t *arena = arena_get_from_edata(edata); arena_decay_tick(tsdn, arena); ret = false; } else if (oldsize >= SC_LARGE_MINCLASS && usize_max >= SC_LARGE_MINCLASS) { ret = large_ralloc_no_move(tsdn, edata, usize_min, usize_max, zero); } else { ret = true; } done: assert(edata == emap_edata_lookup(tsdn, &arena_emap_global, ptr)); *newsize = edata_usize_get(edata); return ret; } static void * arena_ralloc_move_helper(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero, tcache_t *tcache) { if (alignment == 0) { return arena_malloc(tsdn, arena, usize, sz_size2index(usize), zero, tcache, true); } usize = sz_sa2u(usize, alignment); if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { return NULL; } return ipalloct(tsdn, usize, alignment, zero, tcache, arena); } void * arena_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t oldsize, size_t size, size_t alignment, bool zero, tcache_t *tcache, hook_ralloc_args_t *hook_args) { size_t usize = alignment == 0 ? sz_s2u(size) : sz_sa2u(size, alignment); if (unlikely(usize == 0 || size > SC_LARGE_MAXCLASS)) { return NULL; } if (likely(usize <= SC_SMALL_MAXCLASS)) { /* Try to avoid moving the allocation. */ UNUSED size_t newsize; if (!arena_ralloc_no_move(tsdn, ptr, oldsize, usize, 0, zero, &newsize)) { hook_invoke_expand(hook_args->is_realloc ? hook_expand_realloc : hook_expand_rallocx, ptr, oldsize, usize, (uintptr_t)ptr, hook_args->args); return ptr; } } if (oldsize >= SC_LARGE_MINCLASS && usize >= SC_LARGE_MINCLASS) { return large_ralloc(tsdn, arena, ptr, usize, alignment, zero, tcache, hook_args); } /* * size and oldsize are different enough that we need to move the * object. In that case, fall back to allocating new space and copying. */ void *ret = arena_ralloc_move_helper(tsdn, arena, usize, alignment, zero, tcache); if (ret == NULL) { return NULL; } hook_invoke_alloc(hook_args->is_realloc ? hook_alloc_realloc : hook_alloc_rallocx, ret, (uintptr_t)ret, hook_args->args); hook_invoke_dalloc(hook_args->is_realloc ? hook_dalloc_realloc : hook_dalloc_rallocx, ptr, hook_args->args); /* * Junk/zero-filling were already done by * ipalloc()/arena_malloc(). */ size_t copysize = (usize < oldsize) ? usize : oldsize; memcpy(ret, ptr, copysize); isdalloct(tsdn, ptr, oldsize, tcache, NULL, true); return ret; } ehooks_t * arena_get_ehooks(arena_t *arena) { return base_ehooks_get(arena->base); } extent_hooks_t * arena_set_extent_hooks(tsd_t *tsd, arena_t *arena, extent_hooks_t *extent_hooks) { background_thread_info_t *info; if (have_background_thread) { info = arena_background_thread_info_get(arena); malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); } /* No using the HPA now that we have the custom hooks. */ pa_shard_disable_hpa(tsd_tsdn(tsd), &arena->pa_shard); extent_hooks_t *ret = base_extent_hooks_set(arena->base, extent_hooks); if (have_background_thread) { malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); } return ret; } dss_prec_t arena_dss_prec_get(arena_t *arena) { return (dss_prec_t)atomic_load_u(&arena->dss_prec, ATOMIC_ACQUIRE); } bool arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec) { if (!have_dss) { return (dss_prec != dss_prec_disabled); } atomic_store_u(&arena->dss_prec, (unsigned)dss_prec, ATOMIC_RELEASE); return false; } ssize_t arena_dirty_decay_ms_default_get(void) { return atomic_load_zd(&dirty_decay_ms_default, ATOMIC_RELAXED); } bool arena_dirty_decay_ms_default_set(ssize_t decay_ms) { if (!decay_ms_valid(decay_ms)) { return true; } atomic_store_zd(&dirty_decay_ms_default, decay_ms, ATOMIC_RELAXED); return false; } ssize_t arena_muzzy_decay_ms_default_get(void) { return atomic_load_zd(&muzzy_decay_ms_default, ATOMIC_RELAXED); } bool arena_muzzy_decay_ms_default_set(ssize_t decay_ms) { if (!decay_ms_valid(decay_ms)) { return true; } atomic_store_zd(&muzzy_decay_ms_default, decay_ms, ATOMIC_RELAXED); return false; } bool arena_retain_grow_limit_get_set(tsd_t *tsd, arena_t *arena, size_t *old_limit, size_t *new_limit) { assert(opt_retain); return pac_retain_grow_limit_get_set(tsd_tsdn(tsd), &arena->pa_shard.pac, old_limit, new_limit); } unsigned arena_nthreads_get(arena_t *arena, bool internal) { return atomic_load_u(&arena->nthreads[internal], ATOMIC_RELAXED); } void arena_nthreads_inc(arena_t *arena, bool internal) { atomic_fetch_add_u(&arena->nthreads[internal], 1, ATOMIC_RELAXED); } void arena_nthreads_dec(arena_t *arena, bool internal) { atomic_fetch_sub_u(&arena->nthreads[internal], 1, ATOMIC_RELAXED); } arena_t * arena_new(tsdn_t *tsdn, unsigned ind, const arena_config_t *config) { arena_t *arena; base_t *base; unsigned i; if (ind == 0) { base = b0get(); } else { base = base_new(tsdn, ind, config->extent_hooks, config->metadata_use_hooks); if (base == NULL) { return NULL; } } size_t arena_size = sizeof(arena_t) + sizeof(bin_t) * nbins_total; arena = (arena_t *)base_alloc(tsdn, base, arena_size, CACHELINE); if (arena == NULL) { goto label_error; } atomic_store_u(&arena->nthreads[0], 0, ATOMIC_RELAXED); atomic_store_u(&arena->nthreads[1], 0, ATOMIC_RELAXED); arena->last_thd = NULL; if (config_stats) { if (arena_stats_init(tsdn, &arena->stats)) { goto label_error; } ql_new(&arena->tcache_ql); ql_new(&arena->cache_bin_array_descriptor_ql); if (malloc_mutex_init(&arena->tcache_ql_mtx, "tcache_ql", WITNESS_RANK_TCACHE_QL, malloc_mutex_rank_exclusive)) { goto label_error; } } atomic_store_u(&arena->dss_prec, (unsigned)extent_dss_prec_get(), ATOMIC_RELAXED); edata_list_active_init(&arena->large); if (malloc_mutex_init(&arena->large_mtx, "arena_large", WITNESS_RANK_ARENA_LARGE, malloc_mutex_rank_exclusive)) { goto label_error; } nstime_t cur_time; nstime_init_update(&cur_time); if (pa_shard_init(tsdn, &arena->pa_shard, &arena_pa_central_global, &arena_emap_global, base, ind, &arena->stats.pa_shard_stats, LOCKEDINT_MTX(arena->stats.mtx), &cur_time, oversize_threshold, arena_dirty_decay_ms_default_get(), arena_muzzy_decay_ms_default_get())) { goto label_error; } /* Initialize bins. */ atomic_store_u(&arena->binshard_next, 0, ATOMIC_RELEASE); for (i = 0; i < nbins_total; i++) { bool err = bin_init(&arena->bins[i]); if (err) { goto label_error; } } arena->base = base; /* Set arena before creating background threads. */ arena_set(ind, arena); arena->ind = ind; nstime_init_update(&arena->create_time); /* * We turn on the HPA if set to. There are two exceptions: * - Custom extent hooks (we should only return memory allocated from * them in that case). * - Arena 0 initialization. In this case, we're mid-bootstrapping, and * so arena_hpa_global is not yet initialized. */ if (opt_hpa && ehooks_are_default(base_ehooks_get(base)) && ind != 0) { hpa_shard_opts_t hpa_shard_opts = opt_hpa_opts; hpa_shard_opts.deferral_allowed = background_thread_enabled(); if (pa_shard_enable_hpa(tsdn, &arena->pa_shard, &hpa_shard_opts, &opt_hpa_sec_opts)) { goto label_error; } } /* We don't support reentrancy for arena 0 bootstrapping. */ if (ind != 0) { /* * If we're here, then arena 0 already exists, so bootstrapping * is done enough that we should have tsd. */ assert(!tsdn_null(tsdn)); pre_reentrancy(tsdn_tsd(tsdn), arena); if (test_hooks_arena_new_hook) { test_hooks_arena_new_hook(); } post_reentrancy(tsdn_tsd(tsdn)); } return arena; label_error: if (ind != 0) { base_delete(tsdn, base); } return NULL; } arena_t * arena_choose_huge(tsd_t *tsd) { /* huge_arena_ind can be 0 during init (will use a0). */ if (huge_arena_ind == 0) { assert(!malloc_initialized()); } arena_t *huge_arena = arena_get(tsd_tsdn(tsd), huge_arena_ind, false); if (huge_arena == NULL) { /* Create the huge arena on demand. */ assert(huge_arena_ind != 0); huge_arena = arena_get(tsd_tsdn(tsd), huge_arena_ind, true); if (huge_arena == NULL) { return NULL; } /* * Purge eagerly for huge allocations, because: 1) number of * huge allocations is usually small, which means ticker based * decay is not reliable; and 2) less immediate reuse is * expected for huge allocations. */ if (arena_dirty_decay_ms_default_get() > 0) { arena_decay_ms_set(tsd_tsdn(tsd), huge_arena, extent_state_dirty, 0); } if (arena_muzzy_decay_ms_default_get() > 0) { arena_decay_ms_set(tsd_tsdn(tsd), huge_arena, extent_state_muzzy, 0); } } return huge_arena; } bool arena_init_huge(void) { bool huge_enabled; /* The threshold should be large size class. */ if (opt_oversize_threshold > SC_LARGE_MAXCLASS || opt_oversize_threshold < SC_LARGE_MINCLASS) { opt_oversize_threshold = 0; oversize_threshold = SC_LARGE_MAXCLASS + PAGE; huge_enabled = false; } else { /* Reserve the index for the huge arena. */ huge_arena_ind = narenas_total_get(); oversize_threshold = opt_oversize_threshold; huge_enabled = true; } return huge_enabled; } bool arena_is_huge(unsigned arena_ind) { if (huge_arena_ind == 0) { return false; } return (arena_ind == huge_arena_ind); } bool arena_boot(sc_data_t *sc_data, base_t *base, bool hpa) { arena_dirty_decay_ms_default_set(opt_dirty_decay_ms); arena_muzzy_decay_ms_default_set(opt_muzzy_decay_ms); for (unsigned i = 0; i < SC_NBINS; i++) { sc_t *sc = &sc_data->sc[i]; div_init(&arena_binind_div_info[i], (1U << sc->lg_base) + (sc->ndelta << sc->lg_delta)); } uint32_t cur_offset = (uint32_t)offsetof(arena_t, bins); for (szind_t i = 0; i < SC_NBINS; i++) { arena_bin_offsets[i] = cur_offset; nbins_total += bin_infos[i].n_shards; cur_offset += (uint32_t)(bin_infos[i].n_shards * sizeof(bin_t)); } return pa_central_init(&arena_pa_central_global, base, hpa, &hpa_hooks_default); } void arena_prefork0(tsdn_t *tsdn, arena_t *arena) { pa_shard_prefork0(tsdn, &arena->pa_shard); } void arena_prefork1(tsdn_t *tsdn, arena_t *arena) { if (config_stats) { malloc_mutex_prefork(tsdn, &arena->tcache_ql_mtx); } } void arena_prefork2(tsdn_t *tsdn, arena_t *arena) { pa_shard_prefork2(tsdn, &arena->pa_shard); } void arena_prefork3(tsdn_t *tsdn, arena_t *arena) { pa_shard_prefork3(tsdn, &arena->pa_shard); } void arena_prefork4(tsdn_t *tsdn, arena_t *arena) { pa_shard_prefork4(tsdn, &arena->pa_shard); } void arena_prefork5(tsdn_t *tsdn, arena_t *arena) { pa_shard_prefork5(tsdn, &arena->pa_shard); } void arena_prefork6(tsdn_t *tsdn, arena_t *arena) { base_prefork(tsdn, arena->base); } void arena_prefork7(tsdn_t *tsdn, arena_t *arena) { malloc_mutex_prefork(tsdn, &arena->large_mtx); } void arena_prefork8(tsdn_t *tsdn, arena_t *arena) { for (unsigned i = 0; i < nbins_total; i++) { bin_prefork(tsdn, &arena->bins[i]); } } void arena_postfork_parent(tsdn_t *tsdn, arena_t *arena) { for (unsigned i = 0; i < nbins_total; i++) { bin_postfork_parent(tsdn, &arena->bins[i]); } malloc_mutex_postfork_parent(tsdn, &arena->large_mtx); base_postfork_parent(tsdn, arena->base); pa_shard_postfork_parent(tsdn, &arena->pa_shard); if (config_stats) { malloc_mutex_postfork_parent(tsdn, &arena->tcache_ql_mtx); } } void arena_postfork_child(tsdn_t *tsdn, arena_t *arena) { atomic_store_u(&arena->nthreads[0], 0, ATOMIC_RELAXED); atomic_store_u(&arena->nthreads[1], 0, ATOMIC_RELAXED); if (tsd_arena_get(tsdn_tsd(tsdn)) == arena) { arena_nthreads_inc(arena, false); } if (tsd_iarena_get(tsdn_tsd(tsdn)) == arena) { arena_nthreads_inc(arena, true); } if (config_stats) { ql_new(&arena->tcache_ql); ql_new(&arena->cache_bin_array_descriptor_ql); tcache_slow_t *tcache_slow = tcache_slow_get(tsdn_tsd(tsdn)); if (tcache_slow != NULL && tcache_slow->arena == arena) { tcache_t *tcache = tcache_slow->tcache; ql_elm_new(tcache_slow, link); ql_tail_insert(&arena->tcache_ql, tcache_slow, link); cache_bin_array_descriptor_init( &tcache_slow->cache_bin_array_descriptor, tcache->bins); ql_tail_insert(&arena->cache_bin_array_descriptor_ql, &tcache_slow->cache_bin_array_descriptor, link); } } for (unsigned i = 0; i < nbins_total; i++) { bin_postfork_child(tsdn, &arena->bins[i]); } malloc_mutex_postfork_child(tsdn, &arena->large_mtx); base_postfork_child(tsdn, arena->base); pa_shard_postfork_child(tsdn, &arena->pa_shard); if (config_stats) { malloc_mutex_postfork_child(tsdn, &arena->tcache_ql_mtx); } } redis-8.0.2/deps/jemalloc/src/background_thread.c000066400000000000000000000570431501533116600217630ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS /******************************************************************************/ /* Data. */ /* This option should be opt-in only. */ #define BACKGROUND_THREAD_DEFAULT false /* Read-only after initialization. */ bool opt_background_thread = BACKGROUND_THREAD_DEFAULT; size_t opt_max_background_threads = MAX_BACKGROUND_THREAD_LIMIT + 1; /* Used for thread creation, termination and stats. */ malloc_mutex_t background_thread_lock; /* Indicates global state. Atomic because decay reads this w/o locking. */ atomic_b_t background_thread_enabled_state; size_t n_background_threads; size_t max_background_threads; /* Thread info per-index. */ background_thread_info_t *background_thread_info; /******************************************************************************/ #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER static int (*pthread_create_fptr)(pthread_t *__restrict, const pthread_attr_t *, void *(*)(void *), void *__restrict); static void pthread_create_wrapper_init(void) { #ifdef JEMALLOC_LAZY_LOCK if (!isthreaded) { isthreaded = true; } #endif } int pthread_create_wrapper(pthread_t *__restrict thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *__restrict arg) { pthread_create_wrapper_init(); return pthread_create_fptr(thread, attr, start_routine, arg); } #endif /* JEMALLOC_PTHREAD_CREATE_WRAPPER */ #ifndef JEMALLOC_BACKGROUND_THREAD #define NOT_REACHED { not_reached(); } bool background_thread_create(tsd_t *tsd, unsigned arena_ind) NOT_REACHED bool background_threads_enable(tsd_t *tsd) NOT_REACHED bool background_threads_disable(tsd_t *tsd) NOT_REACHED bool background_thread_is_started(background_thread_info_t *info) NOT_REACHED void background_thread_wakeup_early(background_thread_info_t *info, nstime_t *remaining_sleep) NOT_REACHED void background_thread_prefork0(tsdn_t *tsdn) NOT_REACHED void background_thread_prefork1(tsdn_t *tsdn) NOT_REACHED void background_thread_postfork_parent(tsdn_t *tsdn) NOT_REACHED void background_thread_postfork_child(tsdn_t *tsdn) NOT_REACHED bool background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) NOT_REACHED void background_thread_ctl_init(tsdn_t *tsdn) NOT_REACHED #undef NOT_REACHED #else static bool background_thread_enabled_at_fork; static void background_thread_info_init(tsdn_t *tsdn, background_thread_info_t *info) { background_thread_wakeup_time_set(tsdn, info, 0); info->npages_to_purge_new = 0; if (config_stats) { info->tot_n_runs = 0; nstime_init_zero(&info->tot_sleep_time); } } static inline bool set_current_thread_affinity(int cpu) { #if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) cpu_set_t cpuset; #else # ifndef __NetBSD__ cpuset_t cpuset; # else cpuset_t *cpuset; # endif #endif #ifndef __NetBSD__ CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); #else cpuset = cpuset_create(); #endif #if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) return (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) != 0); #else # ifndef __NetBSD__ int ret = pthread_setaffinity_np(pthread_self(), sizeof(cpuset_t), &cpuset); # else int ret = pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset); cpuset_destroy(cpuset); # endif return ret != 0; #endif } #define BILLION UINT64_C(1000000000) /* Minimal sleep interval 100 ms. */ #define BACKGROUND_THREAD_MIN_INTERVAL_NS (BILLION / 10) static void background_thread_sleep(tsdn_t *tsdn, background_thread_info_t *info, uint64_t interval) { if (config_stats) { info->tot_n_runs++; } info->npages_to_purge_new = 0; struct timeval tv; /* Specific clock required by timedwait. */ gettimeofday(&tv, NULL); nstime_t before_sleep; nstime_init2(&before_sleep, tv.tv_sec, tv.tv_usec * 1000); int ret; if (interval == BACKGROUND_THREAD_INDEFINITE_SLEEP) { background_thread_wakeup_time_set(tsdn, info, BACKGROUND_THREAD_INDEFINITE_SLEEP); ret = pthread_cond_wait(&info->cond, &info->mtx.lock); assert(ret == 0); } else { assert(interval >= BACKGROUND_THREAD_MIN_INTERVAL_NS && interval <= BACKGROUND_THREAD_INDEFINITE_SLEEP); /* We need malloc clock (can be different from tv). */ nstime_t next_wakeup; nstime_init_update(&next_wakeup); nstime_iadd(&next_wakeup, interval); assert(nstime_ns(&next_wakeup) < BACKGROUND_THREAD_INDEFINITE_SLEEP); background_thread_wakeup_time_set(tsdn, info, nstime_ns(&next_wakeup)); nstime_t ts_wakeup; nstime_copy(&ts_wakeup, &before_sleep); nstime_iadd(&ts_wakeup, interval); struct timespec ts; ts.tv_sec = (size_t)nstime_sec(&ts_wakeup); ts.tv_nsec = (size_t)nstime_nsec(&ts_wakeup); assert(!background_thread_indefinite_sleep(info)); ret = pthread_cond_timedwait(&info->cond, &info->mtx.lock, &ts); assert(ret == ETIMEDOUT || ret == 0); } if (config_stats) { gettimeofday(&tv, NULL); nstime_t after_sleep; nstime_init2(&after_sleep, tv.tv_sec, tv.tv_usec * 1000); if (nstime_compare(&after_sleep, &before_sleep) > 0) { nstime_subtract(&after_sleep, &before_sleep); nstime_add(&info->tot_sleep_time, &after_sleep); } } } static bool background_thread_pause_check(tsdn_t *tsdn, background_thread_info_t *info) { if (unlikely(info->state == background_thread_paused)) { malloc_mutex_unlock(tsdn, &info->mtx); /* Wait on global lock to update status. */ malloc_mutex_lock(tsdn, &background_thread_lock); malloc_mutex_unlock(tsdn, &background_thread_lock); malloc_mutex_lock(tsdn, &info->mtx); return true; } return false; } static inline void background_work_sleep_once(tsdn_t *tsdn, background_thread_info_t *info, unsigned ind) { uint64_t ns_until_deferred = BACKGROUND_THREAD_DEFERRED_MAX; unsigned narenas = narenas_total_get(); bool slept_indefinitely = background_thread_indefinite_sleep(info); for (unsigned i = ind; i < narenas; i += max_background_threads) { arena_t *arena = arena_get(tsdn, i, false); if (!arena) { continue; } /* * If thread was woken up from the indefinite sleep, don't * do the work instantly, but rather check when the deferred * work that caused this thread to wake up is scheduled for. */ if (!slept_indefinitely) { arena_do_deferred_work(tsdn, arena); } if (ns_until_deferred <= BACKGROUND_THREAD_MIN_INTERVAL_NS) { /* Min interval will be used. */ continue; } uint64_t ns_arena_deferred = pa_shard_time_until_deferred_work( tsdn, &arena->pa_shard); if (ns_arena_deferred < ns_until_deferred) { ns_until_deferred = ns_arena_deferred; } } uint64_t sleep_ns; if (ns_until_deferred == BACKGROUND_THREAD_DEFERRED_MAX) { sleep_ns = BACKGROUND_THREAD_INDEFINITE_SLEEP; } else { sleep_ns = (ns_until_deferred < BACKGROUND_THREAD_MIN_INTERVAL_NS) ? BACKGROUND_THREAD_MIN_INTERVAL_NS : ns_until_deferred; } background_thread_sleep(tsdn, info, sleep_ns); } static bool background_threads_disable_single(tsd_t *tsd, background_thread_info_t *info) { if (info == &background_thread_info[0]) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); } else { malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &background_thread_lock); } pre_reentrancy(tsd, NULL); malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); bool has_thread; assert(info->state != background_thread_paused); if (info->state == background_thread_started) { has_thread = true; info->state = background_thread_stopped; pthread_cond_signal(&info->cond); } else { has_thread = false; } malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); if (!has_thread) { post_reentrancy(tsd); return false; } void *ret; if (pthread_join(info->thread, &ret)) { post_reentrancy(tsd); return true; } assert(ret == NULL); n_background_threads--; post_reentrancy(tsd); return false; } static void *background_thread_entry(void *ind_arg); static int background_thread_create_signals_masked(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { /* * Mask signals during thread creation so that the thread inherits * an empty signal set. */ sigset_t set; sigfillset(&set); sigset_t oldset; int mask_err = pthread_sigmask(SIG_SETMASK, &set, &oldset); if (mask_err != 0) { return mask_err; } int create_err = pthread_create_wrapper(thread, attr, start_routine, arg); /* * Restore the signal mask. Failure to restore the signal mask here * changes program behavior. */ int restore_err = pthread_sigmask(SIG_SETMASK, &oldset, NULL); if (restore_err != 0) { malloc_printf(": background thread creation " "failed (%d), and signal mask restoration failed " "(%d)\n", create_err, restore_err); if (opt_abort) { abort(); } } return create_err; } static bool check_background_thread_creation(tsd_t *tsd, unsigned *n_created, bool *created_threads) { bool ret = false; if (likely(*n_created == n_background_threads)) { return ret; } tsdn_t *tsdn = tsd_tsdn(tsd); malloc_mutex_unlock(tsdn, &background_thread_info[0].mtx); for (unsigned i = 1; i < max_background_threads; i++) { if (created_threads[i]) { continue; } background_thread_info_t *info = &background_thread_info[i]; malloc_mutex_lock(tsdn, &info->mtx); /* * In case of the background_thread_paused state because of * arena reset, delay the creation. */ bool create = (info->state == background_thread_started); malloc_mutex_unlock(tsdn, &info->mtx); if (!create) { continue; } pre_reentrancy(tsd, NULL); int err = background_thread_create_signals_masked(&info->thread, NULL, background_thread_entry, (void *)(uintptr_t)i); post_reentrancy(tsd); if (err == 0) { (*n_created)++; created_threads[i] = true; } else { malloc_printf(": background thread " "creation failed (%d)\n", err); if (opt_abort) { abort(); } } /* Return to restart the loop since we unlocked. */ ret = true; break; } malloc_mutex_lock(tsdn, &background_thread_info[0].mtx); return ret; } static void background_thread0_work(tsd_t *tsd) { /* Thread0 is also responsible for launching / terminating threads. */ VARIABLE_ARRAY(bool, created_threads, max_background_threads); unsigned i; for (i = 1; i < max_background_threads; i++) { created_threads[i] = false; } /* Start working, and create more threads when asked. */ unsigned n_created = 1; while (background_thread_info[0].state != background_thread_stopped) { if (background_thread_pause_check(tsd_tsdn(tsd), &background_thread_info[0])) { continue; } if (check_background_thread_creation(tsd, &n_created, (bool *)&created_threads)) { continue; } background_work_sleep_once(tsd_tsdn(tsd), &background_thread_info[0], 0); } /* * Shut down other threads at exit. Note that the ctl thread is holding * the global background_thread mutex (and is waiting) for us. */ assert(!background_thread_enabled()); for (i = 1; i < max_background_threads; i++) { background_thread_info_t *info = &background_thread_info[i]; assert(info->state != background_thread_paused); if (created_threads[i]) { background_threads_disable_single(tsd, info); } else { malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); if (info->state != background_thread_stopped) { /* The thread was not created. */ assert(info->state == background_thread_started); n_background_threads--; info->state = background_thread_stopped; } malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); } } background_thread_info[0].state = background_thread_stopped; assert(n_background_threads == 1); } static void background_work(tsd_t *tsd, unsigned ind) { background_thread_info_t *info = &background_thread_info[ind]; malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); background_thread_wakeup_time_set(tsd_tsdn(tsd), info, BACKGROUND_THREAD_INDEFINITE_SLEEP); if (ind == 0) { background_thread0_work(tsd); } else { while (info->state != background_thread_stopped) { if (background_thread_pause_check(tsd_tsdn(tsd), info)) { continue; } background_work_sleep_once(tsd_tsdn(tsd), info, ind); } } assert(info->state == background_thread_stopped); background_thread_wakeup_time_set(tsd_tsdn(tsd), info, 0); malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); } static void * background_thread_entry(void *ind_arg) { unsigned thread_ind = (unsigned)(uintptr_t)ind_arg; assert(thread_ind < max_background_threads); #ifdef JEMALLOC_HAVE_PTHREAD_SETNAME_NP pthread_setname_np(pthread_self(), "jemalloc_bg_thd"); #elif defined(__FreeBSD__) || defined(__DragonFly__) pthread_set_name_np(pthread_self(), "jemalloc_bg_thd"); #endif if (opt_percpu_arena != percpu_arena_disabled) { set_current_thread_affinity((int)thread_ind); } /* * Start periodic background work. We use internal tsd which avoids * side effects, for example triggering new arena creation (which in * turn triggers another background thread creation). */ background_work(tsd_internal_fetch(), thread_ind); assert(pthread_equal(pthread_self(), background_thread_info[thread_ind].thread)); return NULL; } static void background_thread_init(tsd_t *tsd, background_thread_info_t *info) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); info->state = background_thread_started; background_thread_info_init(tsd_tsdn(tsd), info); n_background_threads++; } static bool background_thread_create_locked(tsd_t *tsd, unsigned arena_ind) { assert(have_background_thread); malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); /* We create at most NCPUs threads. */ size_t thread_ind = arena_ind % max_background_threads; background_thread_info_t *info = &background_thread_info[thread_ind]; bool need_new_thread; malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); need_new_thread = background_thread_enabled() && (info->state == background_thread_stopped); if (need_new_thread) { background_thread_init(tsd, info); } malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); if (!need_new_thread) { return false; } if (arena_ind != 0) { /* Threads are created asynchronously by Thread 0. */ background_thread_info_t *t0 = &background_thread_info[0]; malloc_mutex_lock(tsd_tsdn(tsd), &t0->mtx); assert(t0->state == background_thread_started); pthread_cond_signal(&t0->cond); malloc_mutex_unlock(tsd_tsdn(tsd), &t0->mtx); return false; } pre_reentrancy(tsd, NULL); /* * To avoid complications (besides reentrancy), create internal * background threads with the underlying pthread_create. */ int err = background_thread_create_signals_masked(&info->thread, NULL, background_thread_entry, (void *)thread_ind); post_reentrancy(tsd); if (err != 0) { malloc_printf(": arena 0 background thread creation " "failed (%d)\n", err); malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); info->state = background_thread_stopped; n_background_threads--; malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); return true; } return false; } /* Create a new background thread if needed. */ bool background_thread_create(tsd_t *tsd, unsigned arena_ind) { assert(have_background_thread); bool ret; malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); ret = background_thread_create_locked(tsd, arena_ind); malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); return ret; } bool background_threads_enable(tsd_t *tsd) { assert(n_background_threads == 0); assert(background_thread_enabled()); malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); VARIABLE_ARRAY(bool, marked, max_background_threads); unsigned nmarked; for (unsigned i = 0; i < max_background_threads; i++) { marked[i] = false; } nmarked = 0; /* Thread 0 is required and created at the end. */ marked[0] = true; /* Mark the threads we need to create for thread 0. */ unsigned narenas = narenas_total_get(); for (unsigned i = 1; i < narenas; i++) { if (marked[i % max_background_threads] || arena_get(tsd_tsdn(tsd), i, false) == NULL) { continue; } background_thread_info_t *info = &background_thread_info[ i % max_background_threads]; malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); assert(info->state == background_thread_stopped); background_thread_init(tsd, info); malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); marked[i % max_background_threads] = true; if (++nmarked == max_background_threads) { break; } } bool err = background_thread_create_locked(tsd, 0); if (err) { return true; } for (unsigned i = 0; i < narenas; i++) { arena_t *arena = arena_get(tsd_tsdn(tsd), i, false); if (arena != NULL) { pa_shard_set_deferral_allowed(tsd_tsdn(tsd), &arena->pa_shard, true); } } return false; } bool background_threads_disable(tsd_t *tsd) { assert(!background_thread_enabled()); malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); /* Thread 0 will be responsible for terminating other threads. */ if (background_threads_disable_single(tsd, &background_thread_info[0])) { return true; } assert(n_background_threads == 0); unsigned narenas = narenas_total_get(); for (unsigned i = 0; i < narenas; i++) { arena_t *arena = arena_get(tsd_tsdn(tsd), i, false); if (arena != NULL) { pa_shard_set_deferral_allowed(tsd_tsdn(tsd), &arena->pa_shard, false); } } return false; } bool background_thread_is_started(background_thread_info_t *info) { return info->state == background_thread_started; } void background_thread_wakeup_early(background_thread_info_t *info, nstime_t *remaining_sleep) { /* * This is an optimization to increase batching. At this point * we know that background thread wakes up soon, so the time to cache * the just freed memory is bounded and low. */ if (remaining_sleep != NULL && nstime_ns(remaining_sleep) < BACKGROUND_THREAD_MIN_INTERVAL_NS) { return; } pthread_cond_signal(&info->cond); } void background_thread_prefork0(tsdn_t *tsdn) { malloc_mutex_prefork(tsdn, &background_thread_lock); background_thread_enabled_at_fork = background_thread_enabled(); } void background_thread_prefork1(tsdn_t *tsdn) { for (unsigned i = 0; i < max_background_threads; i++) { malloc_mutex_prefork(tsdn, &background_thread_info[i].mtx); } } void background_thread_postfork_parent(tsdn_t *tsdn) { for (unsigned i = 0; i < max_background_threads; i++) { malloc_mutex_postfork_parent(tsdn, &background_thread_info[i].mtx); } malloc_mutex_postfork_parent(tsdn, &background_thread_lock); } void background_thread_postfork_child(tsdn_t *tsdn) { for (unsigned i = 0; i < max_background_threads; i++) { malloc_mutex_postfork_child(tsdn, &background_thread_info[i].mtx); } malloc_mutex_postfork_child(tsdn, &background_thread_lock); if (!background_thread_enabled_at_fork) { return; } /* Clear background_thread state (reset to disabled for child). */ malloc_mutex_lock(tsdn, &background_thread_lock); n_background_threads = 0; background_thread_enabled_set(tsdn, false); for (unsigned i = 0; i < max_background_threads; i++) { background_thread_info_t *info = &background_thread_info[i]; malloc_mutex_lock(tsdn, &info->mtx); info->state = background_thread_stopped; int ret = pthread_cond_init(&info->cond, NULL); assert(ret == 0); background_thread_info_init(tsdn, info); malloc_mutex_unlock(tsdn, &info->mtx); } malloc_mutex_unlock(tsdn, &background_thread_lock); } bool background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) { assert(config_stats); malloc_mutex_lock(tsdn, &background_thread_lock); if (!background_thread_enabled()) { malloc_mutex_unlock(tsdn, &background_thread_lock); return true; } nstime_init_zero(&stats->run_interval); memset(&stats->max_counter_per_bg_thd, 0, sizeof(mutex_prof_data_t)); uint64_t num_runs = 0; stats->num_threads = n_background_threads; for (unsigned i = 0; i < max_background_threads; i++) { background_thread_info_t *info = &background_thread_info[i]; if (malloc_mutex_trylock(tsdn, &info->mtx)) { /* * Each background thread run may take a long time; * avoid waiting on the stats if the thread is active. */ continue; } if (info->state != background_thread_stopped) { num_runs += info->tot_n_runs; nstime_add(&stats->run_interval, &info->tot_sleep_time); malloc_mutex_prof_max_update(tsdn, &stats->max_counter_per_bg_thd, &info->mtx); } malloc_mutex_unlock(tsdn, &info->mtx); } stats->num_runs = num_runs; if (num_runs > 0) { nstime_idivide(&stats->run_interval, num_runs); } malloc_mutex_unlock(tsdn, &background_thread_lock); return false; } #undef BACKGROUND_THREAD_NPAGES_THRESHOLD #undef BILLION #undef BACKGROUND_THREAD_MIN_INTERVAL_NS #ifdef JEMALLOC_HAVE_DLSYM #include #endif static bool pthread_create_fptr_init(void) { if (pthread_create_fptr != NULL) { return false; } /* * Try the next symbol first, because 1) when use lazy_lock we have a * wrapper for pthread_create; and 2) application may define its own * wrapper as well (and can call malloc within the wrapper). */ #ifdef JEMALLOC_HAVE_DLSYM pthread_create_fptr = dlsym(RTLD_NEXT, "pthread_create"); #else pthread_create_fptr = NULL; #endif if (pthread_create_fptr == NULL) { if (config_lazy_lock) { malloc_write(": Error in dlsym(RTLD_NEXT, " "\"pthread_create\")\n"); abort(); } else { /* Fall back to the default symbol. */ pthread_create_fptr = pthread_create; } } return false; } /* * When lazy lock is enabled, we need to make sure setting isthreaded before * taking any background_thread locks. This is called early in ctl (instead of * wait for the pthread_create calls to trigger) because the mutex is required * before creating background threads. */ void background_thread_ctl_init(tsdn_t *tsdn) { malloc_mutex_assert_not_owner(tsdn, &background_thread_lock); #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER pthread_create_fptr_init(); pthread_create_wrapper_init(); #endif } #endif /* defined(JEMALLOC_BACKGROUND_THREAD) */ bool background_thread_boot0(void) { if (!have_background_thread && opt_background_thread) { malloc_printf(": option background_thread currently " "supports pthread only\n"); return true; } #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER if ((config_lazy_lock || opt_background_thread) && pthread_create_fptr_init()) { return true; } #endif return false; } bool background_thread_boot1(tsdn_t *tsdn, base_t *base) { #ifdef JEMALLOC_BACKGROUND_THREAD assert(have_background_thread); assert(narenas_total_get() > 0); if (opt_max_background_threads > MAX_BACKGROUND_THREAD_LIMIT) { opt_max_background_threads = DEFAULT_NUM_BACKGROUND_THREAD; } max_background_threads = opt_max_background_threads; background_thread_enabled_set(tsdn, opt_background_thread); if (malloc_mutex_init(&background_thread_lock, "background_thread_global", WITNESS_RANK_BACKGROUND_THREAD_GLOBAL, malloc_mutex_rank_exclusive)) { return true; } background_thread_info = (background_thread_info_t *)base_alloc(tsdn, base, opt_max_background_threads * sizeof(background_thread_info_t), CACHELINE); if (background_thread_info == NULL) { return true; } for (unsigned i = 0; i < max_background_threads; i++) { background_thread_info_t *info = &background_thread_info[i]; /* Thread mutex is rank_inclusive because of thread0. */ if (malloc_mutex_init(&info->mtx, "background_thread", WITNESS_RANK_BACKGROUND_THREAD, malloc_mutex_address_ordered)) { return true; } if (pthread_cond_init(&info->cond, NULL)) { return true; } malloc_mutex_lock(tsdn, &info->mtx); info->state = background_thread_stopped; background_thread_info_init(tsdn, info); malloc_mutex_unlock(tsdn, &info->mtx); } #endif return false; } redis-8.0.2/deps/jemalloc/src/base.c000066400000000000000000000366461501533116600172350ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/sz.h" /* * In auto mode, arenas switch to huge pages for the base allocator on the * second base block. a0 switches to thp on the 5th block (after 20 megabytes * of metadata), since more metadata (e.g. rtree nodes) come from a0's base. */ #define BASE_AUTO_THP_THRESHOLD 2 #define BASE_AUTO_THP_THRESHOLD_A0 5 /******************************************************************************/ /* Data. */ static base_t *b0; metadata_thp_mode_t opt_metadata_thp = METADATA_THP_DEFAULT; const char *metadata_thp_mode_names[] = { "disabled", "auto", "always" }; /******************************************************************************/ static inline bool metadata_thp_madvise(void) { return (metadata_thp_enabled() && (init_system_thp_mode == thp_mode_default)); } static void * base_map(tsdn_t *tsdn, ehooks_t *ehooks, unsigned ind, size_t size) { void *addr; bool zero = true; bool commit = true; /* Use huge page sizes and alignment regardless of opt_metadata_thp. */ assert(size == HUGEPAGE_CEILING(size)); size_t alignment = HUGEPAGE; if (ehooks_are_default(ehooks)) { addr = extent_alloc_mmap(NULL, size, alignment, &zero, &commit); if (have_madvise_huge && addr) { pages_set_thp_state(addr, size); } } else { addr = ehooks_alloc(tsdn, ehooks, NULL, size, alignment, &zero, &commit); } return addr; } static void base_unmap(tsdn_t *tsdn, ehooks_t *ehooks, unsigned ind, void *addr, size_t size) { /* * Cascade through dalloc, decommit, purge_forced, and purge_lazy, * stopping at first success. This cascade is performed for consistency * with the cascade in extent_dalloc_wrapper() because an application's * custom hooks may not support e.g. dalloc. This function is only ever * called as a side effect of arena destruction, so although it might * seem pointless to do anything besides dalloc here, the application * may in fact want the end state of all associated virtual memory to be * in some consistent-but-allocated state. */ if (ehooks_are_default(ehooks)) { if (!extent_dalloc_mmap(addr, size)) { goto label_done; } if (!pages_decommit(addr, size)) { goto label_done; } if (!pages_purge_forced(addr, size)) { goto label_done; } if (!pages_purge_lazy(addr, size)) { goto label_done; } /* Nothing worked. This should never happen. */ not_reached(); } else { if (!ehooks_dalloc(tsdn, ehooks, addr, size, true)) { goto label_done; } if (!ehooks_decommit(tsdn, ehooks, addr, size, 0, size)) { goto label_done; } if (!ehooks_purge_forced(tsdn, ehooks, addr, size, 0, size)) { goto label_done; } if (!ehooks_purge_lazy(tsdn, ehooks, addr, size, 0, size)) { goto label_done; } /* Nothing worked. That's the application's problem. */ } label_done: if (metadata_thp_madvise()) { /* Set NOHUGEPAGE after unmap to avoid kernel defrag. */ assert(((uintptr_t)addr & HUGEPAGE_MASK) == 0 && (size & HUGEPAGE_MASK) == 0); pages_nohuge(addr, size); } } static void base_edata_init(size_t *extent_sn_next, edata_t *edata, void *addr, size_t size) { size_t sn; sn = *extent_sn_next; (*extent_sn_next)++; edata_binit(edata, addr, size, sn); } static size_t base_get_num_blocks(base_t *base, bool with_new_block) { base_block_t *b = base->blocks; assert(b != NULL); size_t n_blocks = with_new_block ? 2 : 1; while (b->next != NULL) { n_blocks++; b = b->next; } return n_blocks; } static void base_auto_thp_switch(tsdn_t *tsdn, base_t *base) { assert(opt_metadata_thp == metadata_thp_auto); malloc_mutex_assert_owner(tsdn, &base->mtx); if (base->auto_thp_switched) { return; } /* Called when adding a new block. */ bool should_switch; if (base_ind_get(base) != 0) { should_switch = (base_get_num_blocks(base, true) == BASE_AUTO_THP_THRESHOLD); } else { should_switch = (base_get_num_blocks(base, true) == BASE_AUTO_THP_THRESHOLD_A0); } if (!should_switch) { return; } base->auto_thp_switched = true; assert(!config_stats || base->n_thp == 0); /* Make the initial blocks THP lazily. */ base_block_t *block = base->blocks; while (block != NULL) { assert((block->size & HUGEPAGE_MASK) == 0); pages_huge(block, block->size); if (config_stats) { base->n_thp += HUGEPAGE_CEILING(block->size - edata_bsize_get(&block->edata)) >> LG_HUGEPAGE; } block = block->next; assert(block == NULL || (base_ind_get(base) == 0)); } } static void * base_extent_bump_alloc_helper(edata_t *edata, size_t *gap_size, size_t size, size_t alignment) { void *ret; assert(alignment == ALIGNMENT_CEILING(alignment, QUANTUM)); assert(size == ALIGNMENT_CEILING(size, alignment)); *gap_size = ALIGNMENT_CEILING((uintptr_t)edata_addr_get(edata), alignment) - (uintptr_t)edata_addr_get(edata); ret = (void *)((uintptr_t)edata_addr_get(edata) + *gap_size); assert(edata_bsize_get(edata) >= *gap_size + size); edata_binit(edata, (void *)((uintptr_t)edata_addr_get(edata) + *gap_size + size), edata_bsize_get(edata) - *gap_size - size, edata_sn_get(edata)); return ret; } static void base_extent_bump_alloc_post(base_t *base, edata_t *edata, size_t gap_size, void *addr, size_t size) { if (edata_bsize_get(edata) > 0) { /* * Compute the index for the largest size class that does not * exceed extent's size. */ szind_t index_floor = sz_size2index(edata_bsize_get(edata) + 1) - 1; edata_heap_insert(&base->avail[index_floor], edata); } if (config_stats) { base->allocated += size; /* * Add one PAGE to base_resident for every page boundary that is * crossed by the new allocation. Adjust n_thp similarly when * metadata_thp is enabled. */ base->resident += PAGE_CEILING((uintptr_t)addr + size) - PAGE_CEILING((uintptr_t)addr - gap_size); assert(base->allocated <= base->resident); assert(base->resident <= base->mapped); if (metadata_thp_madvise() && (opt_metadata_thp == metadata_thp_always || base->auto_thp_switched)) { base->n_thp += (HUGEPAGE_CEILING((uintptr_t)addr + size) - HUGEPAGE_CEILING((uintptr_t)addr - gap_size)) >> LG_HUGEPAGE; assert(base->mapped >= base->n_thp << LG_HUGEPAGE); } } } static void * base_extent_bump_alloc(base_t *base, edata_t *edata, size_t size, size_t alignment) { void *ret; size_t gap_size; ret = base_extent_bump_alloc_helper(edata, &gap_size, size, alignment); base_extent_bump_alloc_post(base, edata, gap_size, ret, size); return ret; } /* * Allocate a block of virtual memory that is large enough to start with a * base_block_t header, followed by an object of specified size and alignment. * On success a pointer to the initialized base_block_t header is returned. */ static base_block_t * base_block_alloc(tsdn_t *tsdn, base_t *base, ehooks_t *ehooks, unsigned ind, pszind_t *pind_last, size_t *extent_sn_next, size_t size, size_t alignment) { alignment = ALIGNMENT_CEILING(alignment, QUANTUM); size_t usize = ALIGNMENT_CEILING(size, alignment); size_t header_size = sizeof(base_block_t); size_t gap_size = ALIGNMENT_CEILING(header_size, alignment) - header_size; /* * Create increasingly larger blocks in order to limit the total number * of disjoint virtual memory ranges. Choose the next size in the page * size class series (skipping size classes that are not a multiple of * HUGEPAGE), or a size large enough to satisfy the requested size and * alignment, whichever is larger. */ size_t min_block_size = HUGEPAGE_CEILING(sz_psz2u(header_size + gap_size + usize)); pszind_t pind_next = (*pind_last + 1 < sz_psz2ind(SC_LARGE_MAXCLASS)) ? *pind_last + 1 : *pind_last; size_t next_block_size = HUGEPAGE_CEILING(sz_pind2sz(pind_next)); size_t block_size = (min_block_size > next_block_size) ? min_block_size : next_block_size; base_block_t *block = (base_block_t *)base_map(tsdn, ehooks, ind, block_size); if (block == NULL) { return NULL; } if (metadata_thp_madvise()) { void *addr = (void *)block; assert(((uintptr_t)addr & HUGEPAGE_MASK) == 0 && (block_size & HUGEPAGE_MASK) == 0); if (opt_metadata_thp == metadata_thp_always) { pages_huge(addr, block_size); } else if (opt_metadata_thp == metadata_thp_auto && base != NULL) { /* base != NULL indicates this is not a new base. */ malloc_mutex_lock(tsdn, &base->mtx); base_auto_thp_switch(tsdn, base); if (base->auto_thp_switched) { pages_huge(addr, block_size); } malloc_mutex_unlock(tsdn, &base->mtx); } } *pind_last = sz_psz2ind(block_size); block->size = block_size; block->next = NULL; assert(block_size >= header_size); base_edata_init(extent_sn_next, &block->edata, (void *)((uintptr_t)block + header_size), block_size - header_size); return block; } /* * Allocate an extent that is at least as large as specified size, with * specified alignment. */ static edata_t * base_extent_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment) { malloc_mutex_assert_owner(tsdn, &base->mtx); ehooks_t *ehooks = base_ehooks_get_for_metadata(base); /* * Drop mutex during base_block_alloc(), because an extent hook will be * called. */ malloc_mutex_unlock(tsdn, &base->mtx); base_block_t *block = base_block_alloc(tsdn, base, ehooks, base_ind_get(base), &base->pind_last, &base->extent_sn_next, size, alignment); malloc_mutex_lock(tsdn, &base->mtx); if (block == NULL) { return NULL; } block->next = base->blocks; base->blocks = block; if (config_stats) { base->allocated += sizeof(base_block_t); base->resident += PAGE_CEILING(sizeof(base_block_t)); base->mapped += block->size; if (metadata_thp_madvise() && !(opt_metadata_thp == metadata_thp_auto && !base->auto_thp_switched)) { assert(base->n_thp > 0); base->n_thp += HUGEPAGE_CEILING(sizeof(base_block_t)) >> LG_HUGEPAGE; } assert(base->allocated <= base->resident); assert(base->resident <= base->mapped); assert(base->n_thp << LG_HUGEPAGE <= base->mapped); } return &block->edata; } base_t * b0get(void) { return b0; } base_t * base_new(tsdn_t *tsdn, unsigned ind, const extent_hooks_t *extent_hooks, bool metadata_use_hooks) { pszind_t pind_last = 0; size_t extent_sn_next = 0; /* * The base will contain the ehooks eventually, but it itself is * allocated using them. So we use some stack ehooks to bootstrap its * memory, and then initialize the ehooks within the base_t. */ ehooks_t fake_ehooks; ehooks_init(&fake_ehooks, metadata_use_hooks ? (extent_hooks_t *)extent_hooks : (extent_hooks_t *)&ehooks_default_extent_hooks, ind); base_block_t *block = base_block_alloc(tsdn, NULL, &fake_ehooks, ind, &pind_last, &extent_sn_next, sizeof(base_t), QUANTUM); if (block == NULL) { return NULL; } size_t gap_size; size_t base_alignment = CACHELINE; size_t base_size = ALIGNMENT_CEILING(sizeof(base_t), base_alignment); base_t *base = (base_t *)base_extent_bump_alloc_helper(&block->edata, &gap_size, base_size, base_alignment); ehooks_init(&base->ehooks, (extent_hooks_t *)extent_hooks, ind); ehooks_init(&base->ehooks_base, metadata_use_hooks ? (extent_hooks_t *)extent_hooks : (extent_hooks_t *)&ehooks_default_extent_hooks, ind); if (malloc_mutex_init(&base->mtx, "base", WITNESS_RANK_BASE, malloc_mutex_rank_exclusive)) { base_unmap(tsdn, &fake_ehooks, ind, block, block->size); return NULL; } base->pind_last = pind_last; base->extent_sn_next = extent_sn_next; base->blocks = block; base->auto_thp_switched = false; for (szind_t i = 0; i < SC_NSIZES; i++) { edata_heap_new(&base->avail[i]); } if (config_stats) { base->allocated = sizeof(base_block_t); base->resident = PAGE_CEILING(sizeof(base_block_t)); base->mapped = block->size; base->n_thp = (opt_metadata_thp == metadata_thp_always) && metadata_thp_madvise() ? HUGEPAGE_CEILING(sizeof(base_block_t)) >> LG_HUGEPAGE : 0; assert(base->allocated <= base->resident); assert(base->resident <= base->mapped); assert(base->n_thp << LG_HUGEPAGE <= base->mapped); } base_extent_bump_alloc_post(base, &block->edata, gap_size, base, base_size); return base; } void base_delete(tsdn_t *tsdn, base_t *base) { ehooks_t *ehooks = base_ehooks_get_for_metadata(base); base_block_t *next = base->blocks; do { base_block_t *block = next; next = block->next; base_unmap(tsdn, ehooks, base_ind_get(base), block, block->size); } while (next != NULL); } ehooks_t * base_ehooks_get(base_t *base) { return &base->ehooks; } ehooks_t * base_ehooks_get_for_metadata(base_t *base) { return &base->ehooks_base; } extent_hooks_t * base_extent_hooks_set(base_t *base, extent_hooks_t *extent_hooks) { extent_hooks_t *old_extent_hooks = ehooks_get_extent_hooks_ptr(&base->ehooks); ehooks_init(&base->ehooks, extent_hooks, ehooks_ind_get(&base->ehooks)); return old_extent_hooks; } static void * base_alloc_impl(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment, size_t *esn) { alignment = QUANTUM_CEILING(alignment); size_t usize = ALIGNMENT_CEILING(size, alignment); size_t asize = usize + alignment - QUANTUM; edata_t *edata = NULL; malloc_mutex_lock(tsdn, &base->mtx); for (szind_t i = sz_size2index(asize); i < SC_NSIZES; i++) { edata = edata_heap_remove_first(&base->avail[i]); if (edata != NULL) { /* Use existing space. */ break; } } if (edata == NULL) { /* Try to allocate more space. */ edata = base_extent_alloc(tsdn, base, usize, alignment); } void *ret; if (edata == NULL) { ret = NULL; goto label_return; } ret = base_extent_bump_alloc(base, edata, usize, alignment); if (esn != NULL) { *esn = (size_t)edata_sn_get(edata); } label_return: malloc_mutex_unlock(tsdn, &base->mtx); return ret; } /* * base_alloc() returns zeroed memory, which is always demand-zeroed for the * auto arenas, in order to make multi-page sparse data structures such as radix * tree nodes efficient with respect to physical memory usage. Upon success a * pointer to at least size bytes with specified alignment is returned. Note * that size is rounded up to the nearest multiple of alignment to avoid false * sharing. */ void * base_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment) { return base_alloc_impl(tsdn, base, size, alignment, NULL); } edata_t * base_alloc_edata(tsdn_t *tsdn, base_t *base) { size_t esn; edata_t *edata = base_alloc_impl(tsdn, base, sizeof(edata_t), EDATA_ALIGNMENT, &esn); if (edata == NULL) { return NULL; } edata_esn_set(edata, esn); return edata; } void base_stats_get(tsdn_t *tsdn, base_t *base, size_t *allocated, size_t *resident, size_t *mapped, size_t *n_thp) { cassert(config_stats); malloc_mutex_lock(tsdn, &base->mtx); assert(base->allocated <= base->resident); assert(base->resident <= base->mapped); *allocated = base->allocated; *resident = base->resident; *mapped = base->mapped; *n_thp = base->n_thp; malloc_mutex_unlock(tsdn, &base->mtx); } void base_prefork(tsdn_t *tsdn, base_t *base) { malloc_mutex_prefork(tsdn, &base->mtx); } void base_postfork_parent(tsdn_t *tsdn, base_t *base) { malloc_mutex_postfork_parent(tsdn, &base->mtx); } void base_postfork_child(tsdn_t *tsdn, base_t *base) { malloc_mutex_postfork_child(tsdn, &base->mtx); } bool base_boot(tsdn_t *tsdn) { b0 = base_new(tsdn, 0, (extent_hooks_t *)&ehooks_default_extent_hooks, /* metadata_use_hooks */ true); return (b0 == NULL); } redis-8.0.2/deps/jemalloc/src/bin.c000066400000000000000000000032141501533116600170540ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/bin.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/witness.h" bool bin_update_shard_size(unsigned bin_shard_sizes[SC_NBINS], size_t start_size, size_t end_size, size_t nshards) { if (nshards > BIN_SHARDS_MAX || nshards == 0) { return true; } if (start_size > SC_SMALL_MAXCLASS) { return false; } if (end_size > SC_SMALL_MAXCLASS) { end_size = SC_SMALL_MAXCLASS; } /* Compute the index since this may happen before sz init. */ szind_t ind1 = sz_size2index_compute(start_size); szind_t ind2 = sz_size2index_compute(end_size); for (unsigned i = ind1; i <= ind2; i++) { bin_shard_sizes[i] = (unsigned)nshards; } return false; } void bin_shard_sizes_boot(unsigned bin_shard_sizes[SC_NBINS]) { /* Load the default number of shards. */ for (unsigned i = 0; i < SC_NBINS; i++) { bin_shard_sizes[i] = N_BIN_SHARDS_DEFAULT; } } bool bin_init(bin_t *bin) { if (malloc_mutex_init(&bin->lock, "bin", WITNESS_RANK_BIN, malloc_mutex_rank_exclusive)) { return true; } bin->slabcur = NULL; edata_heap_new(&bin->slabs_nonfull); edata_list_active_init(&bin->slabs_full); if (config_stats) { memset(&bin->stats, 0, sizeof(bin_stats_t)); } return false; } void bin_prefork(tsdn_t *tsdn, bin_t *bin) { malloc_mutex_prefork(tsdn, &bin->lock); } void bin_postfork_parent(tsdn_t *tsdn, bin_t *bin) { malloc_mutex_postfork_parent(tsdn, &bin->lock); } void bin_postfork_child(tsdn_t *tsdn, bin_t *bin) { malloc_mutex_postfork_child(tsdn, &bin->lock); } redis-8.0.2/deps/jemalloc/src/bin_info.c000066400000000000000000000017021501533116600200670ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/bin_info.h" bin_info_t bin_infos[SC_NBINS]; static void bin_infos_init(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS], bin_info_t infos[SC_NBINS]) { for (unsigned i = 0; i < SC_NBINS; i++) { bin_info_t *bin_info = &infos[i]; sc_t *sc = &sc_data->sc[i]; bin_info->reg_size = ((size_t)1U << sc->lg_base) + ((size_t)sc->ndelta << sc->lg_delta); bin_info->slab_size = (sc->pgs << LG_PAGE); bin_info->nregs = (uint32_t)(bin_info->slab_size / bin_info->reg_size); bin_info->n_shards = bin_shard_sizes[i]; bitmap_info_t bitmap_info = BITMAP_INFO_INITIALIZER( bin_info->nregs); bin_info->bitmap_info = bitmap_info; } } void bin_info_boot(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS]) { assert(sc_data->initialized); bin_infos_init(sc_data, bin_shard_sizes, bin_infos); } redis-8.0.2/deps/jemalloc/src/bitmap.c000066400000000000000000000061411501533116600175620ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" /******************************************************************************/ #ifdef BITMAP_USE_TREE void bitmap_info_init(bitmap_info_t *binfo, size_t nbits) { unsigned i; size_t group_count; assert(nbits > 0); assert(nbits <= (ZU(1) << LG_BITMAP_MAXBITS)); /* * Compute the number of groups necessary to store nbits bits, and * progressively work upward through the levels until reaching a level * that requires only one group. */ binfo->levels[0].group_offset = 0; group_count = BITMAP_BITS2GROUPS(nbits); for (i = 1; group_count > 1; i++) { assert(i < BITMAP_MAX_LEVELS); binfo->levels[i].group_offset = binfo->levels[i-1].group_offset + group_count; group_count = BITMAP_BITS2GROUPS(group_count); } binfo->levels[i].group_offset = binfo->levels[i-1].group_offset + group_count; assert(binfo->levels[i].group_offset <= BITMAP_GROUPS_MAX); binfo->nlevels = i; binfo->nbits = nbits; } static size_t bitmap_info_ngroups(const bitmap_info_t *binfo) { return binfo->levels[binfo->nlevels].group_offset; } void bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill) { size_t extra; unsigned i; /* * Bits are actually inverted with regard to the external bitmap * interface. */ if (fill) { /* The "filled" bitmap starts out with all 0 bits. */ memset(bitmap, 0, bitmap_size(binfo)); return; } /* * The "empty" bitmap starts out with all 1 bits, except for trailing * unused bits (if any). Note that each group uses bit 0 to correspond * to the first logical bit in the group, so extra bits are the most * significant bits of the last group. */ memset(bitmap, 0xffU, bitmap_size(binfo)); extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK; if (extra != 0) { bitmap[binfo->levels[1].group_offset - 1] >>= extra; } for (i = 1; i < binfo->nlevels; i++) { size_t group_count = binfo->levels[i].group_offset - binfo->levels[i-1].group_offset; extra = (BITMAP_GROUP_NBITS - (group_count & BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK; if (extra != 0) { bitmap[binfo->levels[i+1].group_offset - 1] >>= extra; } } } #else /* BITMAP_USE_TREE */ void bitmap_info_init(bitmap_info_t *binfo, size_t nbits) { assert(nbits > 0); assert(nbits <= (ZU(1) << LG_BITMAP_MAXBITS)); binfo->ngroups = BITMAP_BITS2GROUPS(nbits); binfo->nbits = nbits; } static size_t bitmap_info_ngroups(const bitmap_info_t *binfo) { return binfo->ngroups; } void bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill) { size_t extra; if (fill) { memset(bitmap, 0, bitmap_size(binfo)); return; } memset(bitmap, 0xffU, bitmap_size(binfo)); extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK; if (extra != 0) { bitmap[binfo->ngroups - 1] >>= extra; } } #endif /* BITMAP_USE_TREE */ size_t bitmap_size(const bitmap_info_t *binfo) { return (bitmap_info_ngroups(binfo) << LG_SIZEOF_BITMAP); } redis-8.0.2/deps/jemalloc/src/buf_writer.c000066400000000000000000000075241501533116600204640ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/buf_writer.h" #include "jemalloc/internal/malloc_io.h" static void * buf_writer_allocate_internal_buf(tsdn_t *tsdn, size_t buf_len) { #ifdef JEMALLOC_JET if (buf_len > SC_LARGE_MAXCLASS) { return NULL; } #else assert(buf_len <= SC_LARGE_MAXCLASS); #endif return iallocztm(tsdn, buf_len, sz_size2index(buf_len), false, NULL, true, arena_get(tsdn, 0, false), true); } static void buf_writer_free_internal_buf(tsdn_t *tsdn, void *buf) { if (buf != NULL) { idalloctm(tsdn, buf, NULL, NULL, true, true); } } static void buf_writer_assert(buf_writer_t *buf_writer) { assert(buf_writer != NULL); assert(buf_writer->write_cb != NULL); if (buf_writer->buf != NULL) { assert(buf_writer->buf_size > 0); } else { assert(buf_writer->buf_size == 0); assert(buf_writer->internal_buf); } assert(buf_writer->buf_end <= buf_writer->buf_size); } bool buf_writer_init(tsdn_t *tsdn, buf_writer_t *buf_writer, write_cb_t *write_cb, void *cbopaque, char *buf, size_t buf_len) { if (write_cb != NULL) { buf_writer->write_cb = write_cb; } else { buf_writer->write_cb = je_malloc_message != NULL ? je_malloc_message : wrtmessage; } buf_writer->cbopaque = cbopaque; assert(buf_len >= 2); if (buf != NULL) { buf_writer->buf = buf; buf_writer->internal_buf = false; } else { buf_writer->buf = buf_writer_allocate_internal_buf(tsdn, buf_len); buf_writer->internal_buf = true; } if (buf_writer->buf != NULL) { buf_writer->buf_size = buf_len - 1; /* Allowing for '\0'. */ } else { buf_writer->buf_size = 0; } buf_writer->buf_end = 0; buf_writer_assert(buf_writer); return buf_writer->buf == NULL; } void buf_writer_flush(buf_writer_t *buf_writer) { buf_writer_assert(buf_writer); if (buf_writer->buf == NULL) { return; } buf_writer->buf[buf_writer->buf_end] = '\0'; buf_writer->write_cb(buf_writer->cbopaque, buf_writer->buf); buf_writer->buf_end = 0; buf_writer_assert(buf_writer); } void buf_writer_cb(void *buf_writer_arg, const char *s) { buf_writer_t *buf_writer = (buf_writer_t *)buf_writer_arg; buf_writer_assert(buf_writer); if (buf_writer->buf == NULL) { buf_writer->write_cb(buf_writer->cbopaque, s); return; } size_t i, slen, n; for (i = 0, slen = strlen(s); i < slen; i += n) { if (buf_writer->buf_end == buf_writer->buf_size) { buf_writer_flush(buf_writer); } size_t s_remain = slen - i; size_t buf_remain = buf_writer->buf_size - buf_writer->buf_end; n = s_remain < buf_remain ? s_remain : buf_remain; memcpy(buf_writer->buf + buf_writer->buf_end, s + i, n); buf_writer->buf_end += n; buf_writer_assert(buf_writer); } assert(i == slen); } void buf_writer_terminate(tsdn_t *tsdn, buf_writer_t *buf_writer) { buf_writer_assert(buf_writer); buf_writer_flush(buf_writer); if (buf_writer->internal_buf) { buf_writer_free_internal_buf(tsdn, buf_writer->buf); } } void buf_writer_pipe(buf_writer_t *buf_writer, read_cb_t *read_cb, void *read_cbopaque) { /* * A tiny local buffer in case the buffered writer failed to allocate * at init. */ static char backup_buf[16]; static buf_writer_t backup_buf_writer; buf_writer_assert(buf_writer); assert(read_cb != NULL); if (buf_writer->buf == NULL) { buf_writer_init(TSDN_NULL, &backup_buf_writer, buf_writer->write_cb, buf_writer->cbopaque, backup_buf, sizeof(backup_buf)); buf_writer = &backup_buf_writer; } assert(buf_writer->buf != NULL); ssize_t nread = 0; do { buf_writer->buf_end += nread; buf_writer_assert(buf_writer); if (buf_writer->buf_end == buf_writer->buf_size) { buf_writer_flush(buf_writer); } nread = read_cb(read_cbopaque, buf_writer->buf + buf_writer->buf_end, buf_writer->buf_size - buf_writer->buf_end); } while (nread > 0); buf_writer_flush(buf_writer); } redis-8.0.2/deps/jemalloc/src/cache_bin.c000066400000000000000000000063051501533116600202030ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/bit_util.h" #include "jemalloc/internal/cache_bin.h" #include "jemalloc/internal/safety_check.h" void cache_bin_info_init(cache_bin_info_t *info, cache_bin_sz_t ncached_max) { assert(ncached_max <= CACHE_BIN_NCACHED_MAX); size_t stack_size = (size_t)ncached_max * sizeof(void *); assert(stack_size < ((size_t)1 << (sizeof(cache_bin_sz_t) * 8))); info->ncached_max = (cache_bin_sz_t)ncached_max; } void cache_bin_info_compute_alloc(cache_bin_info_t *infos, szind_t ninfos, size_t *size, size_t *alignment) { /* For the total bin stack region (per tcache), reserve 2 more slots so * that * 1) the empty position can be safely read on the fast path before * checking "is_empty"; and * 2) the cur_ptr can go beyond the empty position by 1 step safely on * the fast path (i.e. no overflow). */ *size = sizeof(void *) * 2; for (szind_t i = 0; i < ninfos; i++) { assert(infos[i].ncached_max > 0); *size += infos[i].ncached_max * sizeof(void *); } /* * Align to at least PAGE, to minimize the # of TLBs needed by the * smaller sizes; also helps if the larger sizes don't get used at all. */ *alignment = PAGE; } void cache_bin_preincrement(cache_bin_info_t *infos, szind_t ninfos, void *alloc, size_t *cur_offset) { if (config_debug) { size_t computed_size; size_t computed_alignment; /* Pointer should be as aligned as we asked for. */ cache_bin_info_compute_alloc(infos, ninfos, &computed_size, &computed_alignment); assert(((uintptr_t)alloc & (computed_alignment - 1)) == 0); } *(uintptr_t *)((uintptr_t)alloc + *cur_offset) = cache_bin_preceding_junk; *cur_offset += sizeof(void *); } void cache_bin_postincrement(cache_bin_info_t *infos, szind_t ninfos, void *alloc, size_t *cur_offset) { *(uintptr_t *)((uintptr_t)alloc + *cur_offset) = cache_bin_trailing_junk; *cur_offset += sizeof(void *); } void cache_bin_init(cache_bin_t *bin, cache_bin_info_t *info, void *alloc, size_t *cur_offset) { /* * The full_position points to the lowest available space. Allocations * will access the slots toward higher addresses (for the benefit of * adjacent prefetch). */ void *stack_cur = (void *)((uintptr_t)alloc + *cur_offset); void *full_position = stack_cur; uint16_t bin_stack_size = info->ncached_max * sizeof(void *); *cur_offset += bin_stack_size; void *empty_position = (void *)((uintptr_t)alloc + *cur_offset); /* Init to the empty position. */ bin->stack_head = (void **)empty_position; bin->low_bits_low_water = (uint16_t)(uintptr_t)bin->stack_head; bin->low_bits_full = (uint16_t)(uintptr_t)full_position; bin->low_bits_empty = (uint16_t)(uintptr_t)empty_position; cache_bin_sz_t free_spots = cache_bin_diff(bin, bin->low_bits_full, (uint16_t)(uintptr_t)bin->stack_head, /* racy */ false); assert(free_spots == bin_stack_size); assert(cache_bin_ncached_get_local(bin, info) == 0); assert(cache_bin_empty_position_get(bin) == empty_position); assert(bin_stack_size > 0 || empty_position == full_position); } bool cache_bin_still_zero_initialized(cache_bin_t *bin) { return bin->stack_head == NULL; } redis-8.0.2/deps/jemalloc/src/ckh.c000066400000000000000000000345631501533116600170640ustar00rootroot00000000000000/* ******************************************************************************* * Implementation of (2^1+,2) cuckoo hashing, where 2^1+ indicates that each * hash bucket contains 2^n cells, for n >= 1, and 2 indicates that two hash * functions are employed. The original cuckoo hashing algorithm was described * in: * * Pagh, R., F.F. Rodler (2004) Cuckoo Hashing. Journal of Algorithms * 51(2):122-144. * * Generalization of cuckoo hashing was discussed in: * * Erlingsson, U., M. Manasse, F. McSherry (2006) A cool and practical * alternative to traditional hash tables. In Proceedings of the 7th * Workshop on Distributed Data and Structures (WDAS'06), Santa Clara, CA, * January 2006. * * This implementation uses precisely two hash functions because that is the * fewest that can work, and supporting multiple hashes is an implementation * burden. Here is a reproduction of Figure 1 from Erlingsson et al. (2006) * that shows approximate expected maximum load factors for various * configurations: * * | #cells/bucket | * #hashes | 1 | 2 | 4 | 8 | * --------+-------+-------+-------+-------+ * 1 | 0.006 | 0.006 | 0.03 | 0.12 | * 2 | 0.49 | 0.86 |>0.93< |>0.96< | * 3 | 0.91 | 0.97 | 0.98 | 0.999 | * 4 | 0.97 | 0.99 | 0.999 | | * * The number of cells per bucket is chosen such that a bucket fits in one cache * line. So, on 32- and 64-bit systems, we use (8,2) and (4,2) cuckoo hashing, * respectively. * ******************************************************************************/ #include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/ckh.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/hash.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/prng.h" #include "jemalloc/internal/util.h" /******************************************************************************/ /* Function prototypes for non-inline static functions. */ static bool ckh_grow(tsd_t *tsd, ckh_t *ckh); static void ckh_shrink(tsd_t *tsd, ckh_t *ckh); /******************************************************************************/ /* * Search bucket for key and return the cell number if found; SIZE_T_MAX * otherwise. */ static size_t ckh_bucket_search(ckh_t *ckh, size_t bucket, const void *key) { ckhc_t *cell; unsigned i; for (i = 0; i < (ZU(1) << LG_CKH_BUCKET_CELLS); i++) { cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + i]; if (cell->key != NULL && ckh->keycomp(key, cell->key)) { return (bucket << LG_CKH_BUCKET_CELLS) + i; } } return SIZE_T_MAX; } /* * Search table for key and return cell number if found; SIZE_T_MAX otherwise. */ static size_t ckh_isearch(ckh_t *ckh, const void *key) { size_t hashes[2], bucket, cell; assert(ckh != NULL); ckh->hash(key, hashes); /* Search primary bucket. */ bucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1); cell = ckh_bucket_search(ckh, bucket, key); if (cell != SIZE_T_MAX) { return cell; } /* Search secondary bucket. */ bucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1); cell = ckh_bucket_search(ckh, bucket, key); return cell; } static bool ckh_try_bucket_insert(ckh_t *ckh, size_t bucket, const void *key, const void *data) { ckhc_t *cell; unsigned offset, i; /* * Cycle through the cells in the bucket, starting at a random position. * The randomness avoids worst-case search overhead as buckets fill up. */ offset = (unsigned)prng_lg_range_u64(&ckh->prng_state, LG_CKH_BUCKET_CELLS); for (i = 0; i < (ZU(1) << LG_CKH_BUCKET_CELLS); i++) { cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + ((i + offset) & ((ZU(1) << LG_CKH_BUCKET_CELLS) - 1))]; if (cell->key == NULL) { cell->key = key; cell->data = data; ckh->count++; return false; } } return true; } /* * No space is available in bucket. Randomly evict an item, then try to find an * alternate location for that item. Iteratively repeat this * eviction/relocation procedure until either success or detection of an * eviction/relocation bucket cycle. */ static bool ckh_evict_reloc_insert(ckh_t *ckh, size_t argbucket, void const **argkey, void const **argdata) { const void *key, *data, *tkey, *tdata; ckhc_t *cell; size_t hashes[2], bucket, tbucket; unsigned i; bucket = argbucket; key = *argkey; data = *argdata; while (true) { /* * Choose a random item within the bucket to evict. This is * critical to correct function, because without (eventually) * evicting all items within a bucket during iteration, it * would be possible to get stuck in an infinite loop if there * were an item for which both hashes indicated the same * bucket. */ i = (unsigned)prng_lg_range_u64(&ckh->prng_state, LG_CKH_BUCKET_CELLS); cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + i]; assert(cell->key != NULL); /* Swap cell->{key,data} and {key,data} (evict). */ tkey = cell->key; tdata = cell->data; cell->key = key; cell->data = data; key = tkey; data = tdata; #ifdef CKH_COUNT ckh->nrelocs++; #endif /* Find the alternate bucket for the evicted item. */ ckh->hash(key, hashes); tbucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1); if (tbucket == bucket) { tbucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1); /* * It may be that (tbucket == bucket) still, if the * item's hashes both indicate this bucket. However, * we are guaranteed to eventually escape this bucket * during iteration, assuming pseudo-random item * selection (true randomness would make infinite * looping a remote possibility). The reason we can * never get trapped forever is that there are two * cases: * * 1) This bucket == argbucket, so we will quickly * detect an eviction cycle and terminate. * 2) An item was evicted to this bucket from another, * which means that at least one item in this bucket * has hashes that indicate distinct buckets. */ } /* Check for a cycle. */ if (tbucket == argbucket) { *argkey = key; *argdata = data; return true; } bucket = tbucket; if (!ckh_try_bucket_insert(ckh, bucket, key, data)) { return false; } } } static bool ckh_try_insert(ckh_t *ckh, void const**argkey, void const**argdata) { size_t hashes[2], bucket; const void *key = *argkey; const void *data = *argdata; ckh->hash(key, hashes); /* Try to insert in primary bucket. */ bucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1); if (!ckh_try_bucket_insert(ckh, bucket, key, data)) { return false; } /* Try to insert in secondary bucket. */ bucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1); if (!ckh_try_bucket_insert(ckh, bucket, key, data)) { return false; } /* * Try to find a place for this item via iterative eviction/relocation. */ return ckh_evict_reloc_insert(ckh, bucket, argkey, argdata); } /* * Try to rebuild the hash table from scratch by inserting all items from the * old table into the new. */ static bool ckh_rebuild(ckh_t *ckh, ckhc_t *aTab) { size_t count, i, nins; const void *key, *data; count = ckh->count; ckh->count = 0; for (i = nins = 0; nins < count; i++) { if (aTab[i].key != NULL) { key = aTab[i].key; data = aTab[i].data; if (ckh_try_insert(ckh, &key, &data)) { ckh->count = count; return true; } nins++; } } return false; } static bool ckh_grow(tsd_t *tsd, ckh_t *ckh) { bool ret; ckhc_t *tab, *ttab; unsigned lg_prevbuckets, lg_curcells; #ifdef CKH_COUNT ckh->ngrows++; #endif /* * It is possible (though unlikely, given well behaved hashes) that the * table will have to be doubled more than once in order to create a * usable table. */ lg_prevbuckets = ckh->lg_curbuckets; lg_curcells = ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS; while (true) { size_t usize; lg_curcells++; usize = sz_sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE); if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { ret = true; goto label_return; } tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true, NULL, true, arena_ichoose(tsd, NULL)); if (tab == NULL) { ret = true; goto label_return; } /* Swap in new table. */ ttab = ckh->tab; ckh->tab = tab; tab = ttab; ckh->lg_curbuckets = lg_curcells - LG_CKH_BUCKET_CELLS; if (!ckh_rebuild(ckh, tab)) { idalloctm(tsd_tsdn(tsd), tab, NULL, NULL, true, true); break; } /* Rebuilding failed, so back out partially rebuilt table. */ idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true); ckh->tab = tab; ckh->lg_curbuckets = lg_prevbuckets; } ret = false; label_return: return ret; } static void ckh_shrink(tsd_t *tsd, ckh_t *ckh) { ckhc_t *tab, *ttab; size_t usize; unsigned lg_prevbuckets, lg_curcells; /* * It is possible (though unlikely, given well behaved hashes) that the * table rebuild will fail. */ lg_prevbuckets = ckh->lg_curbuckets; lg_curcells = ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS - 1; usize = sz_sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE); if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { return; } tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true, NULL, true, arena_ichoose(tsd, NULL)); if (tab == NULL) { /* * An OOM error isn't worth propagating, since it doesn't * prevent this or future operations from proceeding. */ return; } /* Swap in new table. */ ttab = ckh->tab; ckh->tab = tab; tab = ttab; ckh->lg_curbuckets = lg_curcells - LG_CKH_BUCKET_CELLS; if (!ckh_rebuild(ckh, tab)) { idalloctm(tsd_tsdn(tsd), tab, NULL, NULL, true, true); #ifdef CKH_COUNT ckh->nshrinks++; #endif return; } /* Rebuilding failed, so back out partially rebuilt table. */ idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true); ckh->tab = tab; ckh->lg_curbuckets = lg_prevbuckets; #ifdef CKH_COUNT ckh->nshrinkfails++; #endif } bool ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *ckh_hash, ckh_keycomp_t *keycomp) { bool ret; size_t mincells, usize; unsigned lg_mincells; assert(minitems > 0); assert(ckh_hash != NULL); assert(keycomp != NULL); #ifdef CKH_COUNT ckh->ngrows = 0; ckh->nshrinks = 0; ckh->nshrinkfails = 0; ckh->ninserts = 0; ckh->nrelocs = 0; #endif ckh->prng_state = 42; /* Value doesn't really matter. */ ckh->count = 0; /* * Find the minimum power of 2 that is large enough to fit minitems * entries. We are using (2+,2) cuckoo hashing, which has an expected * maximum load factor of at least ~0.86, so 0.75 is a conservative load * factor that will typically allow mincells items to fit without ever * growing the table. */ assert(LG_CKH_BUCKET_CELLS > 0); mincells = ((minitems + (3 - (minitems % 3))) / 3) << 2; for (lg_mincells = LG_CKH_BUCKET_CELLS; (ZU(1) << lg_mincells) < mincells; lg_mincells++) { /* Do nothing. */ } ckh->lg_minbuckets = lg_mincells - LG_CKH_BUCKET_CELLS; ckh->lg_curbuckets = lg_mincells - LG_CKH_BUCKET_CELLS; ckh->hash = ckh_hash; ckh->keycomp = keycomp; usize = sz_sa2u(sizeof(ckhc_t) << lg_mincells, CACHELINE); if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { ret = true; goto label_return; } ckh->tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true, NULL, true, arena_ichoose(tsd, NULL)); if (ckh->tab == NULL) { ret = true; goto label_return; } ret = false; label_return: return ret; } void ckh_delete(tsd_t *tsd, ckh_t *ckh) { assert(ckh != NULL); #ifdef CKH_VERBOSE malloc_printf( "%s(%p): ngrows: %"FMTu64", nshrinks: %"FMTu64"," " nshrinkfails: %"FMTu64", ninserts: %"FMTu64"," " nrelocs: %"FMTu64"\n", __func__, ckh, (unsigned long long)ckh->ngrows, (unsigned long long)ckh->nshrinks, (unsigned long long)ckh->nshrinkfails, (unsigned long long)ckh->ninserts, (unsigned long long)ckh->nrelocs); #endif idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true); if (config_debug) { memset(ckh, JEMALLOC_FREE_JUNK, sizeof(ckh_t)); } } size_t ckh_count(ckh_t *ckh) { assert(ckh != NULL); return ckh->count; } bool ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data) { size_t i, ncells; for (i = *tabind, ncells = (ZU(1) << (ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS)); i < ncells; i++) { if (ckh->tab[i].key != NULL) { if (key != NULL) { *key = (void *)ckh->tab[i].key; } if (data != NULL) { *data = (void *)ckh->tab[i].data; } *tabind = i + 1; return false; } } return true; } bool ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data) { bool ret; assert(ckh != NULL); assert(ckh_search(ckh, key, NULL, NULL)); #ifdef CKH_COUNT ckh->ninserts++; #endif while (ckh_try_insert(ckh, &key, &data)) { if (ckh_grow(tsd, ckh)) { ret = true; goto label_return; } } ret = false; label_return: return ret; } bool ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key, void **data) { size_t cell; assert(ckh != NULL); cell = ckh_isearch(ckh, searchkey); if (cell != SIZE_T_MAX) { if (key != NULL) { *key = (void *)ckh->tab[cell].key; } if (data != NULL) { *data = (void *)ckh->tab[cell].data; } ckh->tab[cell].key = NULL; ckh->tab[cell].data = NULL; /* Not necessary. */ ckh->count--; /* Try to halve the table if it is less than 1/4 full. */ if (ckh->count < (ZU(1) << (ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS - 2)) && ckh->lg_curbuckets > ckh->lg_minbuckets) { /* Ignore error due to OOM. */ ckh_shrink(tsd, ckh); } return false; } return true; } bool ckh_search(ckh_t *ckh, const void *searchkey, void **key, void **data) { size_t cell; assert(ckh != NULL); cell = ckh_isearch(ckh, searchkey); if (cell != SIZE_T_MAX) { if (key != NULL) { *key = (void *)ckh->tab[cell].key; } if (data != NULL) { *data = (void *)ckh->tab[cell].data; } return false; } return true; } void ckh_string_hash(const void *key, size_t r_hash[2]) { hash(key, strlen((const char *)key), 0x94122f33U, r_hash); } bool ckh_string_keycomp(const void *k1, const void *k2) { assert(k1 != NULL); assert(k2 != NULL); return !strcmp((char *)k1, (char *)k2); } void ckh_pointer_hash(const void *key, size_t r_hash[2]) { union { const void *v; size_t i; } u; assert(sizeof(u.v) == sizeof(u.i)); u.v = key; hash(&u.i, sizeof(u.i), 0xd983396eU, r_hash); } bool ckh_pointer_keycomp(const void *k1, const void *k2) { return (k1 == k2); } redis-8.0.2/deps/jemalloc/src/counter.c000066400000000000000000000014671501533116600177730ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/counter.h" bool counter_accum_init(counter_accum_t *counter, uint64_t interval) { if (LOCKEDINT_MTX_INIT(counter->mtx, "counter_accum", WITNESS_RANK_COUNTER_ACCUM, malloc_mutex_rank_exclusive)) { return true; } locked_init_u64_unsynchronized(&counter->accumbytes, 0); counter->interval = interval; return false; } void counter_prefork(tsdn_t *tsdn, counter_accum_t *counter) { LOCKEDINT_MTX_PREFORK(tsdn, counter->mtx); } void counter_postfork_parent(tsdn_t *tsdn, counter_accum_t *counter) { LOCKEDINT_MTX_POSTFORK_PARENT(tsdn, counter->mtx); } void counter_postfork_child(tsdn_t *tsdn, counter_accum_t *counter) { LOCKEDINT_MTX_POSTFORK_CHILD(tsdn, counter->mtx); } redis-8.0.2/deps/jemalloc/src/ctl.c000066400000000000000000004030551501533116600170750ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/inspect.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/nstime.h" #include "jemalloc/internal/peak_event.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_log.h" #include "jemalloc/internal/prof_recent.h" #include "jemalloc/internal/prof_stats.h" #include "jemalloc/internal/prof_sys.h" #include "jemalloc/internal/safety_check.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/util.h" /******************************************************************************/ /* Data. */ /* * ctl_mtx protects the following: * - ctl_stats->* */ static malloc_mutex_t ctl_mtx; static bool ctl_initialized; static ctl_stats_t *ctl_stats; static ctl_arenas_t *ctl_arenas; /******************************************************************************/ /* Helpers for named and indexed nodes. */ static const ctl_named_node_t * ctl_named_node(const ctl_node_t *node) { return ((node->named) ? (const ctl_named_node_t *)node : NULL); } static const ctl_named_node_t * ctl_named_children(const ctl_named_node_t *node, size_t index) { const ctl_named_node_t *children = ctl_named_node(node->children); return (children ? &children[index] : NULL); } static const ctl_indexed_node_t * ctl_indexed_node(const ctl_node_t *node) { return (!node->named ? (const ctl_indexed_node_t *)node : NULL); } /******************************************************************************/ /* Function prototypes for non-inline static functions. */ #define CTL_PROTO(n) \ static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen); #define INDEX_PROTO(n) \ static const ctl_named_node_t *n##_index(tsdn_t *tsdn, \ const size_t *mib, size_t miblen, size_t i); CTL_PROTO(version) CTL_PROTO(epoch) CTL_PROTO(background_thread) CTL_PROTO(max_background_threads) CTL_PROTO(thread_tcache_enabled) CTL_PROTO(thread_tcache_flush) CTL_PROTO(thread_peak_read) CTL_PROTO(thread_peak_reset) CTL_PROTO(thread_prof_name) CTL_PROTO(thread_prof_active) CTL_PROTO(thread_arena) CTL_PROTO(thread_allocated) CTL_PROTO(thread_allocatedp) CTL_PROTO(thread_deallocated) CTL_PROTO(thread_deallocatedp) CTL_PROTO(thread_idle) CTL_PROTO(config_cache_oblivious) CTL_PROTO(config_debug) CTL_PROTO(config_fill) CTL_PROTO(config_lazy_lock) CTL_PROTO(config_malloc_conf) CTL_PROTO(config_opt_safety_checks) CTL_PROTO(config_prof) CTL_PROTO(config_prof_libgcc) CTL_PROTO(config_prof_libunwind) CTL_PROTO(config_stats) CTL_PROTO(config_utrace) CTL_PROTO(config_xmalloc) CTL_PROTO(opt_abort) CTL_PROTO(opt_abort_conf) CTL_PROTO(opt_cache_oblivious) CTL_PROTO(opt_trust_madvise) CTL_PROTO(opt_confirm_conf) CTL_PROTO(opt_hpa) CTL_PROTO(opt_hpa_slab_max_alloc) CTL_PROTO(opt_hpa_hugification_threshold) CTL_PROTO(opt_hpa_hugify_delay_ms) CTL_PROTO(opt_hpa_min_purge_interval_ms) CTL_PROTO(opt_hpa_dirty_mult) CTL_PROTO(opt_hpa_sec_nshards) CTL_PROTO(opt_hpa_sec_max_alloc) CTL_PROTO(opt_hpa_sec_max_bytes) CTL_PROTO(opt_hpa_sec_bytes_after_flush) CTL_PROTO(opt_hpa_sec_batch_fill_extra) CTL_PROTO(opt_metadata_thp) CTL_PROTO(opt_retain) CTL_PROTO(opt_dss) CTL_PROTO(opt_narenas) CTL_PROTO(opt_percpu_arena) CTL_PROTO(opt_oversize_threshold) CTL_PROTO(opt_background_thread) CTL_PROTO(opt_mutex_max_spin) CTL_PROTO(opt_max_background_threads) CTL_PROTO(opt_dirty_decay_ms) CTL_PROTO(opt_muzzy_decay_ms) CTL_PROTO(opt_stats_print) CTL_PROTO(opt_stats_print_opts) CTL_PROTO(opt_stats_interval) CTL_PROTO(opt_stats_interval_opts) CTL_PROTO(opt_junk) CTL_PROTO(opt_zero) CTL_PROTO(opt_utrace) CTL_PROTO(opt_xmalloc) CTL_PROTO(opt_experimental_infallible_new) CTL_PROTO(opt_tcache) CTL_PROTO(opt_tcache_max) CTL_PROTO(opt_tcache_nslots_small_min) CTL_PROTO(opt_tcache_nslots_small_max) CTL_PROTO(opt_tcache_nslots_large) CTL_PROTO(opt_lg_tcache_nslots_mul) CTL_PROTO(opt_tcache_gc_incr_bytes) CTL_PROTO(opt_tcache_gc_delay_bytes) CTL_PROTO(opt_lg_tcache_flush_small_div) CTL_PROTO(opt_lg_tcache_flush_large_div) CTL_PROTO(opt_thp) CTL_PROTO(opt_lg_extent_max_active_fit) CTL_PROTO(opt_prof) CTL_PROTO(opt_prof_prefix) CTL_PROTO(opt_prof_active) CTL_PROTO(opt_prof_thread_active_init) CTL_PROTO(opt_lg_prof_sample) CTL_PROTO(opt_lg_prof_interval) CTL_PROTO(opt_prof_gdump) CTL_PROTO(opt_prof_final) CTL_PROTO(opt_prof_leak) CTL_PROTO(opt_prof_leak_error) CTL_PROTO(opt_prof_accum) CTL_PROTO(opt_prof_recent_alloc_max) CTL_PROTO(opt_prof_stats) CTL_PROTO(opt_prof_sys_thread_name) CTL_PROTO(opt_prof_time_res) CTL_PROTO(opt_lg_san_uaf_align) CTL_PROTO(opt_zero_realloc) CTL_PROTO(tcache_create) CTL_PROTO(tcache_flush) CTL_PROTO(tcache_destroy) CTL_PROTO(arena_i_initialized) CTL_PROTO(arena_i_decay) CTL_PROTO(arena_i_purge) CTL_PROTO(arena_i_reset) CTL_PROTO(arena_i_destroy) CTL_PROTO(arena_i_dss) CTL_PROTO(arena_i_oversize_threshold) CTL_PROTO(arena_i_dirty_decay_ms) CTL_PROTO(arena_i_muzzy_decay_ms) CTL_PROTO(arena_i_extent_hooks) CTL_PROTO(arena_i_retain_grow_limit) INDEX_PROTO(arena_i) CTL_PROTO(arenas_bin_i_size) CTL_PROTO(arenas_bin_i_nregs) CTL_PROTO(arenas_bin_i_slab_size) CTL_PROTO(arenas_bin_i_nshards) INDEX_PROTO(arenas_bin_i) CTL_PROTO(arenas_lextent_i_size) INDEX_PROTO(arenas_lextent_i) CTL_PROTO(arenas_narenas) CTL_PROTO(arenas_dirty_decay_ms) CTL_PROTO(arenas_muzzy_decay_ms) CTL_PROTO(arenas_quantum) CTL_PROTO(arenas_page) CTL_PROTO(arenas_tcache_max) CTL_PROTO(arenas_nbins) CTL_PROTO(arenas_nhbins) CTL_PROTO(arenas_nlextents) CTL_PROTO(arenas_create) CTL_PROTO(arenas_lookup) CTL_PROTO(prof_thread_active_init) CTL_PROTO(prof_active) CTL_PROTO(prof_dump) CTL_PROTO(prof_gdump) CTL_PROTO(prof_prefix) CTL_PROTO(prof_reset) CTL_PROTO(prof_interval) CTL_PROTO(lg_prof_sample) CTL_PROTO(prof_log_start) CTL_PROTO(prof_log_stop) CTL_PROTO(prof_stats_bins_i_live) CTL_PROTO(prof_stats_bins_i_accum) INDEX_PROTO(prof_stats_bins_i) CTL_PROTO(prof_stats_lextents_i_live) CTL_PROTO(prof_stats_lextents_i_accum) INDEX_PROTO(prof_stats_lextents_i) CTL_PROTO(stats_arenas_i_small_allocated) CTL_PROTO(stats_arenas_i_small_nmalloc) CTL_PROTO(stats_arenas_i_small_ndalloc) CTL_PROTO(stats_arenas_i_small_nrequests) CTL_PROTO(stats_arenas_i_small_nfills) CTL_PROTO(stats_arenas_i_small_nflushes) CTL_PROTO(stats_arenas_i_large_allocated) CTL_PROTO(stats_arenas_i_large_nmalloc) CTL_PROTO(stats_arenas_i_large_ndalloc) CTL_PROTO(stats_arenas_i_large_nrequests) CTL_PROTO(stats_arenas_i_large_nfills) CTL_PROTO(stats_arenas_i_large_nflushes) CTL_PROTO(stats_arenas_i_bins_j_nmalloc) CTL_PROTO(stats_arenas_i_bins_j_ndalloc) CTL_PROTO(stats_arenas_i_bins_j_nrequests) CTL_PROTO(stats_arenas_i_bins_j_curregs) CTL_PROTO(stats_arenas_i_bins_j_nfills) CTL_PROTO(stats_arenas_i_bins_j_nflushes) CTL_PROTO(stats_arenas_i_bins_j_nslabs) CTL_PROTO(stats_arenas_i_bins_j_nreslabs) CTL_PROTO(stats_arenas_i_bins_j_curslabs) CTL_PROTO(stats_arenas_i_bins_j_nonfull_slabs) INDEX_PROTO(stats_arenas_i_bins_j) CTL_PROTO(stats_arenas_i_lextents_j_nmalloc) CTL_PROTO(stats_arenas_i_lextents_j_ndalloc) CTL_PROTO(stats_arenas_i_lextents_j_nrequests) CTL_PROTO(stats_arenas_i_lextents_j_curlextents) INDEX_PROTO(stats_arenas_i_lextents_j) CTL_PROTO(stats_arenas_i_extents_j_ndirty) CTL_PROTO(stats_arenas_i_extents_j_nmuzzy) CTL_PROTO(stats_arenas_i_extents_j_nretained) CTL_PROTO(stats_arenas_i_extents_j_dirty_bytes) CTL_PROTO(stats_arenas_i_extents_j_muzzy_bytes) CTL_PROTO(stats_arenas_i_extents_j_retained_bytes) INDEX_PROTO(stats_arenas_i_extents_j) CTL_PROTO(stats_arenas_i_hpa_shard_npurge_passes) CTL_PROTO(stats_arenas_i_hpa_shard_npurges) CTL_PROTO(stats_arenas_i_hpa_shard_nhugifies) CTL_PROTO(stats_arenas_i_hpa_shard_ndehugifies) /* We have a set of stats for full slabs. */ CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_npageslabs_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_npageslabs_huge) CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_nactive_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_nactive_huge) CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_ndirty_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_ndirty_huge) /* A parallel set for the empty slabs. */ CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_huge) CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_nactive_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_nactive_huge) CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_ndirty_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_ndirty_huge) /* * And one for the slabs that are neither empty nor full, but indexed by how * full they are. */ CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_huge) CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_huge) CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_nonhuge) CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_huge) INDEX_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j) CTL_PROTO(stats_arenas_i_nthreads) CTL_PROTO(stats_arenas_i_uptime) CTL_PROTO(stats_arenas_i_dss) CTL_PROTO(stats_arenas_i_dirty_decay_ms) CTL_PROTO(stats_arenas_i_muzzy_decay_ms) CTL_PROTO(stats_arenas_i_pactive) CTL_PROTO(stats_arenas_i_pdirty) CTL_PROTO(stats_arenas_i_pmuzzy) CTL_PROTO(stats_arenas_i_mapped) CTL_PROTO(stats_arenas_i_retained) CTL_PROTO(stats_arenas_i_extent_avail) CTL_PROTO(stats_arenas_i_dirty_npurge) CTL_PROTO(stats_arenas_i_dirty_nmadvise) CTL_PROTO(stats_arenas_i_dirty_purged) CTL_PROTO(stats_arenas_i_muzzy_npurge) CTL_PROTO(stats_arenas_i_muzzy_nmadvise) CTL_PROTO(stats_arenas_i_muzzy_purged) CTL_PROTO(stats_arenas_i_base) CTL_PROTO(stats_arenas_i_internal) CTL_PROTO(stats_arenas_i_metadata_thp) CTL_PROTO(stats_arenas_i_tcache_bytes) CTL_PROTO(stats_arenas_i_tcache_stashed_bytes) CTL_PROTO(stats_arenas_i_resident) CTL_PROTO(stats_arenas_i_abandoned_vm) CTL_PROTO(stats_arenas_i_hpa_sec_bytes) INDEX_PROTO(stats_arenas_i) CTL_PROTO(stats_allocated) CTL_PROTO(stats_active) CTL_PROTO(stats_background_thread_num_threads) CTL_PROTO(stats_background_thread_num_runs) CTL_PROTO(stats_background_thread_run_interval) CTL_PROTO(stats_metadata) CTL_PROTO(stats_metadata_thp) CTL_PROTO(stats_resident) CTL_PROTO(stats_mapped) CTL_PROTO(stats_retained) CTL_PROTO(stats_zero_reallocs) CTL_PROTO(experimental_hooks_install) CTL_PROTO(experimental_hooks_remove) CTL_PROTO(experimental_hooks_prof_backtrace) CTL_PROTO(experimental_hooks_prof_dump) CTL_PROTO(experimental_hooks_safety_check_abort) CTL_PROTO(experimental_thread_activity_callback) CTL_PROTO(experimental_utilization_query) CTL_PROTO(experimental_utilization_batch_query) CTL_PROTO(experimental_arenas_i_pactivep) INDEX_PROTO(experimental_arenas_i) CTL_PROTO(experimental_prof_recent_alloc_max) CTL_PROTO(experimental_prof_recent_alloc_dump) CTL_PROTO(experimental_batch_alloc) CTL_PROTO(experimental_arenas_create_ext) #define MUTEX_STATS_CTL_PROTO_GEN(n) \ CTL_PROTO(stats_##n##_num_ops) \ CTL_PROTO(stats_##n##_num_wait) \ CTL_PROTO(stats_##n##_num_spin_acq) \ CTL_PROTO(stats_##n##_num_owner_switch) \ CTL_PROTO(stats_##n##_total_wait_time) \ CTL_PROTO(stats_##n##_max_wait_time) \ CTL_PROTO(stats_##n##_max_num_thds) /* Global mutexes. */ #define OP(mtx) MUTEX_STATS_CTL_PROTO_GEN(mutexes_##mtx) MUTEX_PROF_GLOBAL_MUTEXES #undef OP /* Per arena mutexes. */ #define OP(mtx) MUTEX_STATS_CTL_PROTO_GEN(arenas_i_mutexes_##mtx) MUTEX_PROF_ARENA_MUTEXES #undef OP /* Arena bin mutexes. */ MUTEX_STATS_CTL_PROTO_GEN(arenas_i_bins_j_mutex) #undef MUTEX_STATS_CTL_PROTO_GEN CTL_PROTO(stats_mutexes_reset) /******************************************************************************/ /* mallctl tree. */ #define NAME(n) {true}, n #define CHILD(t, c) \ sizeof(c##_node) / sizeof(ctl_##t##_node_t), \ (ctl_node_t *)c##_node, \ NULL #define CTL(c) 0, NULL, c##_ctl /* * Only handles internal indexed nodes, since there are currently no external * ones. */ #define INDEX(i) {false}, i##_index static const ctl_named_node_t thread_tcache_node[] = { {NAME("enabled"), CTL(thread_tcache_enabled)}, {NAME("flush"), CTL(thread_tcache_flush)} }; static const ctl_named_node_t thread_peak_node[] = { {NAME("read"), CTL(thread_peak_read)}, {NAME("reset"), CTL(thread_peak_reset)}, }; static const ctl_named_node_t thread_prof_node[] = { {NAME("name"), CTL(thread_prof_name)}, {NAME("active"), CTL(thread_prof_active)} }; static const ctl_named_node_t thread_node[] = { {NAME("arena"), CTL(thread_arena)}, {NAME("allocated"), CTL(thread_allocated)}, {NAME("allocatedp"), CTL(thread_allocatedp)}, {NAME("deallocated"), CTL(thread_deallocated)}, {NAME("deallocatedp"), CTL(thread_deallocatedp)}, {NAME("tcache"), CHILD(named, thread_tcache)}, {NAME("peak"), CHILD(named, thread_peak)}, {NAME("prof"), CHILD(named, thread_prof)}, {NAME("idle"), CTL(thread_idle)} }; static const ctl_named_node_t config_node[] = { {NAME("cache_oblivious"), CTL(config_cache_oblivious)}, {NAME("debug"), CTL(config_debug)}, {NAME("fill"), CTL(config_fill)}, {NAME("lazy_lock"), CTL(config_lazy_lock)}, {NAME("malloc_conf"), CTL(config_malloc_conf)}, {NAME("opt_safety_checks"), CTL(config_opt_safety_checks)}, {NAME("prof"), CTL(config_prof)}, {NAME("prof_libgcc"), CTL(config_prof_libgcc)}, {NAME("prof_libunwind"), CTL(config_prof_libunwind)}, {NAME("stats"), CTL(config_stats)}, {NAME("utrace"), CTL(config_utrace)}, {NAME("xmalloc"), CTL(config_xmalloc)} }; static const ctl_named_node_t opt_node[] = { {NAME("abort"), CTL(opt_abort)}, {NAME("abort_conf"), CTL(opt_abort_conf)}, {NAME("cache_oblivious"), CTL(opt_cache_oblivious)}, {NAME("trust_madvise"), CTL(opt_trust_madvise)}, {NAME("confirm_conf"), CTL(opt_confirm_conf)}, {NAME("hpa"), CTL(opt_hpa)}, {NAME("hpa_slab_max_alloc"), CTL(opt_hpa_slab_max_alloc)}, {NAME("hpa_hugification_threshold"), CTL(opt_hpa_hugification_threshold)}, {NAME("hpa_hugify_delay_ms"), CTL(opt_hpa_hugify_delay_ms)}, {NAME("hpa_min_purge_interval_ms"), CTL(opt_hpa_min_purge_interval_ms)}, {NAME("hpa_dirty_mult"), CTL(opt_hpa_dirty_mult)}, {NAME("hpa_sec_nshards"), CTL(opt_hpa_sec_nshards)}, {NAME("hpa_sec_max_alloc"), CTL(opt_hpa_sec_max_alloc)}, {NAME("hpa_sec_max_bytes"), CTL(opt_hpa_sec_max_bytes)}, {NAME("hpa_sec_bytes_after_flush"), CTL(opt_hpa_sec_bytes_after_flush)}, {NAME("hpa_sec_batch_fill_extra"), CTL(opt_hpa_sec_batch_fill_extra)}, {NAME("metadata_thp"), CTL(opt_metadata_thp)}, {NAME("retain"), CTL(opt_retain)}, {NAME("dss"), CTL(opt_dss)}, {NAME("narenas"), CTL(opt_narenas)}, {NAME("percpu_arena"), CTL(opt_percpu_arena)}, {NAME("oversize_threshold"), CTL(opt_oversize_threshold)}, {NAME("mutex_max_spin"), CTL(opt_mutex_max_spin)}, {NAME("background_thread"), CTL(opt_background_thread)}, {NAME("max_background_threads"), CTL(opt_max_background_threads)}, {NAME("dirty_decay_ms"), CTL(opt_dirty_decay_ms)}, {NAME("muzzy_decay_ms"), CTL(opt_muzzy_decay_ms)}, {NAME("stats_print"), CTL(opt_stats_print)}, {NAME("stats_print_opts"), CTL(opt_stats_print_opts)}, {NAME("stats_interval"), CTL(opt_stats_interval)}, {NAME("stats_interval_opts"), CTL(opt_stats_interval_opts)}, {NAME("junk"), CTL(opt_junk)}, {NAME("zero"), CTL(opt_zero)}, {NAME("utrace"), CTL(opt_utrace)}, {NAME("xmalloc"), CTL(opt_xmalloc)}, {NAME("experimental_infallible_new"), CTL(opt_experimental_infallible_new)}, {NAME("tcache"), CTL(opt_tcache)}, {NAME("tcache_max"), CTL(opt_tcache_max)}, {NAME("tcache_nslots_small_min"), CTL(opt_tcache_nslots_small_min)}, {NAME("tcache_nslots_small_max"), CTL(opt_tcache_nslots_small_max)}, {NAME("tcache_nslots_large"), CTL(opt_tcache_nslots_large)}, {NAME("lg_tcache_nslots_mul"), CTL(opt_lg_tcache_nslots_mul)}, {NAME("tcache_gc_incr_bytes"), CTL(opt_tcache_gc_incr_bytes)}, {NAME("tcache_gc_delay_bytes"), CTL(opt_tcache_gc_delay_bytes)}, {NAME("lg_tcache_flush_small_div"), CTL(opt_lg_tcache_flush_small_div)}, {NAME("lg_tcache_flush_large_div"), CTL(opt_lg_tcache_flush_large_div)}, {NAME("thp"), CTL(opt_thp)}, {NAME("lg_extent_max_active_fit"), CTL(opt_lg_extent_max_active_fit)}, {NAME("prof"), CTL(opt_prof)}, {NAME("prof_prefix"), CTL(opt_prof_prefix)}, {NAME("prof_active"), CTL(opt_prof_active)}, {NAME("prof_thread_active_init"), CTL(opt_prof_thread_active_init)}, {NAME("lg_prof_sample"), CTL(opt_lg_prof_sample)}, {NAME("lg_prof_interval"), CTL(opt_lg_prof_interval)}, {NAME("prof_gdump"), CTL(opt_prof_gdump)}, {NAME("prof_final"), CTL(opt_prof_final)}, {NAME("prof_leak"), CTL(opt_prof_leak)}, {NAME("prof_leak_error"), CTL(opt_prof_leak_error)}, {NAME("prof_accum"), CTL(opt_prof_accum)}, {NAME("prof_recent_alloc_max"), CTL(opt_prof_recent_alloc_max)}, {NAME("prof_stats"), CTL(opt_prof_stats)}, {NAME("prof_sys_thread_name"), CTL(opt_prof_sys_thread_name)}, {NAME("prof_time_resolution"), CTL(opt_prof_time_res)}, {NAME("lg_san_uaf_align"), CTL(opt_lg_san_uaf_align)}, {NAME("zero_realloc"), CTL(opt_zero_realloc)} }; static const ctl_named_node_t tcache_node[] = { {NAME("create"), CTL(tcache_create)}, {NAME("flush"), CTL(tcache_flush)}, {NAME("destroy"), CTL(tcache_destroy)} }; static const ctl_named_node_t arena_i_node[] = { {NAME("initialized"), CTL(arena_i_initialized)}, {NAME("decay"), CTL(arena_i_decay)}, {NAME("purge"), CTL(arena_i_purge)}, {NAME("reset"), CTL(arena_i_reset)}, {NAME("destroy"), CTL(arena_i_destroy)}, {NAME("dss"), CTL(arena_i_dss)}, /* * Undocumented for now, since we anticipate an arena API in flux after * we cut the last 5-series release. */ {NAME("oversize_threshold"), CTL(arena_i_oversize_threshold)}, {NAME("dirty_decay_ms"), CTL(arena_i_dirty_decay_ms)}, {NAME("muzzy_decay_ms"), CTL(arena_i_muzzy_decay_ms)}, {NAME("extent_hooks"), CTL(arena_i_extent_hooks)}, {NAME("retain_grow_limit"), CTL(arena_i_retain_grow_limit)} }; static const ctl_named_node_t super_arena_i_node[] = { {NAME(""), CHILD(named, arena_i)} }; static const ctl_indexed_node_t arena_node[] = { {INDEX(arena_i)} }; static const ctl_named_node_t arenas_bin_i_node[] = { {NAME("size"), CTL(arenas_bin_i_size)}, {NAME("nregs"), CTL(arenas_bin_i_nregs)}, {NAME("slab_size"), CTL(arenas_bin_i_slab_size)}, {NAME("nshards"), CTL(arenas_bin_i_nshards)} }; static const ctl_named_node_t super_arenas_bin_i_node[] = { {NAME(""), CHILD(named, arenas_bin_i)} }; static const ctl_indexed_node_t arenas_bin_node[] = { {INDEX(arenas_bin_i)} }; static const ctl_named_node_t arenas_lextent_i_node[] = { {NAME("size"), CTL(arenas_lextent_i_size)} }; static const ctl_named_node_t super_arenas_lextent_i_node[] = { {NAME(""), CHILD(named, arenas_lextent_i)} }; static const ctl_indexed_node_t arenas_lextent_node[] = { {INDEX(arenas_lextent_i)} }; static const ctl_named_node_t arenas_node[] = { {NAME("narenas"), CTL(arenas_narenas)}, {NAME("dirty_decay_ms"), CTL(arenas_dirty_decay_ms)}, {NAME("muzzy_decay_ms"), CTL(arenas_muzzy_decay_ms)}, {NAME("quantum"), CTL(arenas_quantum)}, {NAME("page"), CTL(arenas_page)}, {NAME("tcache_max"), CTL(arenas_tcache_max)}, {NAME("nbins"), CTL(arenas_nbins)}, {NAME("nhbins"), CTL(arenas_nhbins)}, {NAME("bin"), CHILD(indexed, arenas_bin)}, {NAME("nlextents"), CTL(arenas_nlextents)}, {NAME("lextent"), CHILD(indexed, arenas_lextent)}, {NAME("create"), CTL(arenas_create)}, {NAME("lookup"), CTL(arenas_lookup)} }; static const ctl_named_node_t prof_stats_bins_i_node[] = { {NAME("live"), CTL(prof_stats_bins_i_live)}, {NAME("accum"), CTL(prof_stats_bins_i_accum)} }; static const ctl_named_node_t super_prof_stats_bins_i_node[] = { {NAME(""), CHILD(named, prof_stats_bins_i)} }; static const ctl_indexed_node_t prof_stats_bins_node[] = { {INDEX(prof_stats_bins_i)} }; static const ctl_named_node_t prof_stats_lextents_i_node[] = { {NAME("live"), CTL(prof_stats_lextents_i_live)}, {NAME("accum"), CTL(prof_stats_lextents_i_accum)} }; static const ctl_named_node_t super_prof_stats_lextents_i_node[] = { {NAME(""), CHILD(named, prof_stats_lextents_i)} }; static const ctl_indexed_node_t prof_stats_lextents_node[] = { {INDEX(prof_stats_lextents_i)} }; static const ctl_named_node_t prof_stats_node[] = { {NAME("bins"), CHILD(indexed, prof_stats_bins)}, {NAME("lextents"), CHILD(indexed, prof_stats_lextents)}, }; static const ctl_named_node_t prof_node[] = { {NAME("thread_active_init"), CTL(prof_thread_active_init)}, {NAME("active"), CTL(prof_active)}, {NAME("dump"), CTL(prof_dump)}, {NAME("gdump"), CTL(prof_gdump)}, {NAME("prefix"), CTL(prof_prefix)}, {NAME("reset"), CTL(prof_reset)}, {NAME("interval"), CTL(prof_interval)}, {NAME("lg_sample"), CTL(lg_prof_sample)}, {NAME("log_start"), CTL(prof_log_start)}, {NAME("log_stop"), CTL(prof_log_stop)}, {NAME("stats"), CHILD(named, prof_stats)} }; static const ctl_named_node_t stats_arenas_i_small_node[] = { {NAME("allocated"), CTL(stats_arenas_i_small_allocated)}, {NAME("nmalloc"), CTL(stats_arenas_i_small_nmalloc)}, {NAME("ndalloc"), CTL(stats_arenas_i_small_ndalloc)}, {NAME("nrequests"), CTL(stats_arenas_i_small_nrequests)}, {NAME("nfills"), CTL(stats_arenas_i_small_nfills)}, {NAME("nflushes"), CTL(stats_arenas_i_small_nflushes)} }; static const ctl_named_node_t stats_arenas_i_large_node[] = { {NAME("allocated"), CTL(stats_arenas_i_large_allocated)}, {NAME("nmalloc"), CTL(stats_arenas_i_large_nmalloc)}, {NAME("ndalloc"), CTL(stats_arenas_i_large_ndalloc)}, {NAME("nrequests"), CTL(stats_arenas_i_large_nrequests)}, {NAME("nfills"), CTL(stats_arenas_i_large_nfills)}, {NAME("nflushes"), CTL(stats_arenas_i_large_nflushes)} }; #define MUTEX_PROF_DATA_NODE(prefix) \ static const ctl_named_node_t stats_##prefix##_node[] = { \ {NAME("num_ops"), \ CTL(stats_##prefix##_num_ops)}, \ {NAME("num_wait"), \ CTL(stats_##prefix##_num_wait)}, \ {NAME("num_spin_acq"), \ CTL(stats_##prefix##_num_spin_acq)}, \ {NAME("num_owner_switch"), \ CTL(stats_##prefix##_num_owner_switch)}, \ {NAME("total_wait_time"), \ CTL(stats_##prefix##_total_wait_time)}, \ {NAME("max_wait_time"), \ CTL(stats_##prefix##_max_wait_time)}, \ {NAME("max_num_thds"), \ CTL(stats_##prefix##_max_num_thds)} \ /* Note that # of current waiting thread not provided. */ \ }; MUTEX_PROF_DATA_NODE(arenas_i_bins_j_mutex) static const ctl_named_node_t stats_arenas_i_bins_j_node[] = { {NAME("nmalloc"), CTL(stats_arenas_i_bins_j_nmalloc)}, {NAME("ndalloc"), CTL(stats_arenas_i_bins_j_ndalloc)}, {NAME("nrequests"), CTL(stats_arenas_i_bins_j_nrequests)}, {NAME("curregs"), CTL(stats_arenas_i_bins_j_curregs)}, {NAME("nfills"), CTL(stats_arenas_i_bins_j_nfills)}, {NAME("nflushes"), CTL(stats_arenas_i_bins_j_nflushes)}, {NAME("nslabs"), CTL(stats_arenas_i_bins_j_nslabs)}, {NAME("nreslabs"), CTL(stats_arenas_i_bins_j_nreslabs)}, {NAME("curslabs"), CTL(stats_arenas_i_bins_j_curslabs)}, {NAME("nonfull_slabs"), CTL(stats_arenas_i_bins_j_nonfull_slabs)}, {NAME("mutex"), CHILD(named, stats_arenas_i_bins_j_mutex)} }; static const ctl_named_node_t super_stats_arenas_i_bins_j_node[] = { {NAME(""), CHILD(named, stats_arenas_i_bins_j)} }; static const ctl_indexed_node_t stats_arenas_i_bins_node[] = { {INDEX(stats_arenas_i_bins_j)} }; static const ctl_named_node_t stats_arenas_i_lextents_j_node[] = { {NAME("nmalloc"), CTL(stats_arenas_i_lextents_j_nmalloc)}, {NAME("ndalloc"), CTL(stats_arenas_i_lextents_j_ndalloc)}, {NAME("nrequests"), CTL(stats_arenas_i_lextents_j_nrequests)}, {NAME("curlextents"), CTL(stats_arenas_i_lextents_j_curlextents)} }; static const ctl_named_node_t super_stats_arenas_i_lextents_j_node[] = { {NAME(""), CHILD(named, stats_arenas_i_lextents_j)} }; static const ctl_indexed_node_t stats_arenas_i_lextents_node[] = { {INDEX(stats_arenas_i_lextents_j)} }; static const ctl_named_node_t stats_arenas_i_extents_j_node[] = { {NAME("ndirty"), CTL(stats_arenas_i_extents_j_ndirty)}, {NAME("nmuzzy"), CTL(stats_arenas_i_extents_j_nmuzzy)}, {NAME("nretained"), CTL(stats_arenas_i_extents_j_nretained)}, {NAME("dirty_bytes"), CTL(stats_arenas_i_extents_j_dirty_bytes)}, {NAME("muzzy_bytes"), CTL(stats_arenas_i_extents_j_muzzy_bytes)}, {NAME("retained_bytes"), CTL(stats_arenas_i_extents_j_retained_bytes)} }; static const ctl_named_node_t super_stats_arenas_i_extents_j_node[] = { {NAME(""), CHILD(named, stats_arenas_i_extents_j)} }; static const ctl_indexed_node_t stats_arenas_i_extents_node[] = { {INDEX(stats_arenas_i_extents_j)} }; #define OP(mtx) MUTEX_PROF_DATA_NODE(arenas_i_mutexes_##mtx) MUTEX_PROF_ARENA_MUTEXES #undef OP static const ctl_named_node_t stats_arenas_i_mutexes_node[] = { #define OP(mtx) {NAME(#mtx), CHILD(named, stats_arenas_i_mutexes_##mtx)}, MUTEX_PROF_ARENA_MUTEXES #undef OP }; static const ctl_named_node_t stats_arenas_i_hpa_shard_full_slabs_node[] = { {NAME("npageslabs_nonhuge"), CTL(stats_arenas_i_hpa_shard_full_slabs_npageslabs_nonhuge)}, {NAME("npageslabs_huge"), CTL(stats_arenas_i_hpa_shard_full_slabs_npageslabs_huge)}, {NAME("nactive_nonhuge"), CTL(stats_arenas_i_hpa_shard_full_slabs_nactive_nonhuge)}, {NAME("nactive_huge"), CTL(stats_arenas_i_hpa_shard_full_slabs_nactive_huge)}, {NAME("ndirty_nonhuge"), CTL(stats_arenas_i_hpa_shard_full_slabs_ndirty_nonhuge)}, {NAME("ndirty_huge"), CTL(stats_arenas_i_hpa_shard_full_slabs_ndirty_huge)} }; static const ctl_named_node_t stats_arenas_i_hpa_shard_empty_slabs_node[] = { {NAME("npageslabs_nonhuge"), CTL(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_nonhuge)}, {NAME("npageslabs_huge"), CTL(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_huge)}, {NAME("nactive_nonhuge"), CTL(stats_arenas_i_hpa_shard_empty_slabs_nactive_nonhuge)}, {NAME("nactive_huge"), CTL(stats_arenas_i_hpa_shard_empty_slabs_nactive_huge)}, {NAME("ndirty_nonhuge"), CTL(stats_arenas_i_hpa_shard_empty_slabs_ndirty_nonhuge)}, {NAME("ndirty_huge"), CTL(stats_arenas_i_hpa_shard_empty_slabs_ndirty_huge)} }; static const ctl_named_node_t stats_arenas_i_hpa_shard_nonfull_slabs_j_node[] = { {NAME("npageslabs_nonhuge"), CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_nonhuge)}, {NAME("npageslabs_huge"), CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_huge)}, {NAME("nactive_nonhuge"), CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_nonhuge)}, {NAME("nactive_huge"), CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_huge)}, {NAME("ndirty_nonhuge"), CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_nonhuge)}, {NAME("ndirty_huge"), CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_huge)} }; static const ctl_named_node_t super_stats_arenas_i_hpa_shard_nonfull_slabs_j_node[] = { {NAME(""), CHILD(named, stats_arenas_i_hpa_shard_nonfull_slabs_j)} }; static const ctl_indexed_node_t stats_arenas_i_hpa_shard_nonfull_slabs_node[] = { {INDEX(stats_arenas_i_hpa_shard_nonfull_slabs_j)} }; static const ctl_named_node_t stats_arenas_i_hpa_shard_node[] = { {NAME("full_slabs"), CHILD(named, stats_arenas_i_hpa_shard_full_slabs)}, {NAME("empty_slabs"), CHILD(named, stats_arenas_i_hpa_shard_empty_slabs)}, {NAME("nonfull_slabs"), CHILD(indexed, stats_arenas_i_hpa_shard_nonfull_slabs)}, {NAME("npurge_passes"), CTL(stats_arenas_i_hpa_shard_npurge_passes)}, {NAME("npurges"), CTL(stats_arenas_i_hpa_shard_npurges)}, {NAME("nhugifies"), CTL(stats_arenas_i_hpa_shard_nhugifies)}, {NAME("ndehugifies"), CTL(stats_arenas_i_hpa_shard_ndehugifies)} }; static const ctl_named_node_t stats_arenas_i_node[] = { {NAME("nthreads"), CTL(stats_arenas_i_nthreads)}, {NAME("uptime"), CTL(stats_arenas_i_uptime)}, {NAME("dss"), CTL(stats_arenas_i_dss)}, {NAME("dirty_decay_ms"), CTL(stats_arenas_i_dirty_decay_ms)}, {NAME("muzzy_decay_ms"), CTL(stats_arenas_i_muzzy_decay_ms)}, {NAME("pactive"), CTL(stats_arenas_i_pactive)}, {NAME("pdirty"), CTL(stats_arenas_i_pdirty)}, {NAME("pmuzzy"), CTL(stats_arenas_i_pmuzzy)}, {NAME("mapped"), CTL(stats_arenas_i_mapped)}, {NAME("retained"), CTL(stats_arenas_i_retained)}, {NAME("extent_avail"), CTL(stats_arenas_i_extent_avail)}, {NAME("dirty_npurge"), CTL(stats_arenas_i_dirty_npurge)}, {NAME("dirty_nmadvise"), CTL(stats_arenas_i_dirty_nmadvise)}, {NAME("dirty_purged"), CTL(stats_arenas_i_dirty_purged)}, {NAME("muzzy_npurge"), CTL(stats_arenas_i_muzzy_npurge)}, {NAME("muzzy_nmadvise"), CTL(stats_arenas_i_muzzy_nmadvise)}, {NAME("muzzy_purged"), CTL(stats_arenas_i_muzzy_purged)}, {NAME("base"), CTL(stats_arenas_i_base)}, {NAME("internal"), CTL(stats_arenas_i_internal)}, {NAME("metadata_thp"), CTL(stats_arenas_i_metadata_thp)}, {NAME("tcache_bytes"), CTL(stats_arenas_i_tcache_bytes)}, {NAME("tcache_stashed_bytes"), CTL(stats_arenas_i_tcache_stashed_bytes)}, {NAME("resident"), CTL(stats_arenas_i_resident)}, {NAME("abandoned_vm"), CTL(stats_arenas_i_abandoned_vm)}, {NAME("hpa_sec_bytes"), CTL(stats_arenas_i_hpa_sec_bytes)}, {NAME("small"), CHILD(named, stats_arenas_i_small)}, {NAME("large"), CHILD(named, stats_arenas_i_large)}, {NAME("bins"), CHILD(indexed, stats_arenas_i_bins)}, {NAME("lextents"), CHILD(indexed, stats_arenas_i_lextents)}, {NAME("extents"), CHILD(indexed, stats_arenas_i_extents)}, {NAME("mutexes"), CHILD(named, stats_arenas_i_mutexes)}, {NAME("hpa_shard"), CHILD(named, stats_arenas_i_hpa_shard)} }; static const ctl_named_node_t super_stats_arenas_i_node[] = { {NAME(""), CHILD(named, stats_arenas_i)} }; static const ctl_indexed_node_t stats_arenas_node[] = { {INDEX(stats_arenas_i)} }; static const ctl_named_node_t stats_background_thread_node[] = { {NAME("num_threads"), CTL(stats_background_thread_num_threads)}, {NAME("num_runs"), CTL(stats_background_thread_num_runs)}, {NAME("run_interval"), CTL(stats_background_thread_run_interval)} }; #define OP(mtx) MUTEX_PROF_DATA_NODE(mutexes_##mtx) MUTEX_PROF_GLOBAL_MUTEXES #undef OP static const ctl_named_node_t stats_mutexes_node[] = { #define OP(mtx) {NAME(#mtx), CHILD(named, stats_mutexes_##mtx)}, MUTEX_PROF_GLOBAL_MUTEXES #undef OP {NAME("reset"), CTL(stats_mutexes_reset)} }; #undef MUTEX_PROF_DATA_NODE static const ctl_named_node_t stats_node[] = { {NAME("allocated"), CTL(stats_allocated)}, {NAME("active"), CTL(stats_active)}, {NAME("metadata"), CTL(stats_metadata)}, {NAME("metadata_thp"), CTL(stats_metadata_thp)}, {NAME("resident"), CTL(stats_resident)}, {NAME("mapped"), CTL(stats_mapped)}, {NAME("retained"), CTL(stats_retained)}, {NAME("background_thread"), CHILD(named, stats_background_thread)}, {NAME("mutexes"), CHILD(named, stats_mutexes)}, {NAME("arenas"), CHILD(indexed, stats_arenas)}, {NAME("zero_reallocs"), CTL(stats_zero_reallocs)}, }; static const ctl_named_node_t experimental_hooks_node[] = { {NAME("install"), CTL(experimental_hooks_install)}, {NAME("remove"), CTL(experimental_hooks_remove)}, {NAME("prof_backtrace"), CTL(experimental_hooks_prof_backtrace)}, {NAME("prof_dump"), CTL(experimental_hooks_prof_dump)}, {NAME("safety_check_abort"), CTL(experimental_hooks_safety_check_abort)}, }; static const ctl_named_node_t experimental_thread_node[] = { {NAME("activity_callback"), CTL(experimental_thread_activity_callback)} }; static const ctl_named_node_t experimental_utilization_node[] = { {NAME("query"), CTL(experimental_utilization_query)}, {NAME("batch_query"), CTL(experimental_utilization_batch_query)} }; static const ctl_named_node_t experimental_arenas_i_node[] = { {NAME("pactivep"), CTL(experimental_arenas_i_pactivep)} }; static const ctl_named_node_t super_experimental_arenas_i_node[] = { {NAME(""), CHILD(named, experimental_arenas_i)} }; static const ctl_indexed_node_t experimental_arenas_node[] = { {INDEX(experimental_arenas_i)} }; static const ctl_named_node_t experimental_prof_recent_node[] = { {NAME("alloc_max"), CTL(experimental_prof_recent_alloc_max)}, {NAME("alloc_dump"), CTL(experimental_prof_recent_alloc_dump)}, }; static const ctl_named_node_t experimental_node[] = { {NAME("hooks"), CHILD(named, experimental_hooks)}, {NAME("utilization"), CHILD(named, experimental_utilization)}, {NAME("arenas"), CHILD(indexed, experimental_arenas)}, {NAME("arenas_create_ext"), CTL(experimental_arenas_create_ext)}, {NAME("prof_recent"), CHILD(named, experimental_prof_recent)}, {NAME("batch_alloc"), CTL(experimental_batch_alloc)}, {NAME("thread"), CHILD(named, experimental_thread)} }; static const ctl_named_node_t root_node[] = { {NAME("version"), CTL(version)}, {NAME("epoch"), CTL(epoch)}, {NAME("background_thread"), CTL(background_thread)}, {NAME("max_background_threads"), CTL(max_background_threads)}, {NAME("thread"), CHILD(named, thread)}, {NAME("config"), CHILD(named, config)}, {NAME("opt"), CHILD(named, opt)}, {NAME("tcache"), CHILD(named, tcache)}, {NAME("arena"), CHILD(indexed, arena)}, {NAME("arenas"), CHILD(named, arenas)}, {NAME("prof"), CHILD(named, prof)}, {NAME("stats"), CHILD(named, stats)}, {NAME("experimental"), CHILD(named, experimental)} }; static const ctl_named_node_t super_root_node[] = { {NAME(""), CHILD(named, root)} }; #undef NAME #undef CHILD #undef CTL #undef INDEX /******************************************************************************/ /* * Sets *dst + *src non-atomically. This is safe, since everything is * synchronized by the ctl mutex. */ static void ctl_accum_locked_u64(locked_u64_t *dst, locked_u64_t *src) { locked_inc_u64_unsynchronized(dst, locked_read_u64_unsynchronized(src)); } static void ctl_accum_atomic_zu(atomic_zu_t *dst, atomic_zu_t *src) { size_t cur_dst = atomic_load_zu(dst, ATOMIC_RELAXED); size_t cur_src = atomic_load_zu(src, ATOMIC_RELAXED); atomic_store_zu(dst, cur_dst + cur_src, ATOMIC_RELAXED); } /******************************************************************************/ static unsigned arenas_i2a_impl(size_t i, bool compat, bool validate) { unsigned a; switch (i) { case MALLCTL_ARENAS_ALL: a = 0; break; case MALLCTL_ARENAS_DESTROYED: a = 1; break; default: if (compat && i == ctl_arenas->narenas) { /* * Provide deprecated backward compatibility for * accessing the merged stats at index narenas rather * than via MALLCTL_ARENAS_ALL. This is scheduled for * removal in 6.0.0. */ a = 0; } else if (validate && i >= ctl_arenas->narenas) { a = UINT_MAX; } else { /* * This function should never be called for an index * more than one past the range of indices that have * initialized ctl data. */ assert(i < ctl_arenas->narenas || (!validate && i == ctl_arenas->narenas)); a = (unsigned)i + 2; } break; } return a; } static unsigned arenas_i2a(size_t i) { return arenas_i2a_impl(i, true, false); } static ctl_arena_t * arenas_i_impl(tsd_t *tsd, size_t i, bool compat, bool init) { ctl_arena_t *ret; assert(!compat || !init); ret = ctl_arenas->arenas[arenas_i2a_impl(i, compat, false)]; if (init && ret == NULL) { if (config_stats) { struct container_s { ctl_arena_t ctl_arena; ctl_arena_stats_t astats; }; struct container_s *cont = (struct container_s *)base_alloc(tsd_tsdn(tsd), b0get(), sizeof(struct container_s), QUANTUM); if (cont == NULL) { return NULL; } ret = &cont->ctl_arena; ret->astats = &cont->astats; } else { ret = (ctl_arena_t *)base_alloc(tsd_tsdn(tsd), b0get(), sizeof(ctl_arena_t), QUANTUM); if (ret == NULL) { return NULL; } } ret->arena_ind = (unsigned)i; ctl_arenas->arenas[arenas_i2a_impl(i, compat, false)] = ret; } assert(ret == NULL || arenas_i2a(ret->arena_ind) == arenas_i2a(i)); return ret; } static ctl_arena_t * arenas_i(size_t i) { ctl_arena_t *ret = arenas_i_impl(tsd_fetch(), i, true, false); assert(ret != NULL); return ret; } static void ctl_arena_clear(ctl_arena_t *ctl_arena) { ctl_arena->nthreads = 0; ctl_arena->dss = dss_prec_names[dss_prec_limit]; ctl_arena->dirty_decay_ms = -1; ctl_arena->muzzy_decay_ms = -1; ctl_arena->pactive = 0; ctl_arena->pdirty = 0; ctl_arena->pmuzzy = 0; if (config_stats) { memset(&ctl_arena->astats->astats, 0, sizeof(arena_stats_t)); ctl_arena->astats->allocated_small = 0; ctl_arena->astats->nmalloc_small = 0; ctl_arena->astats->ndalloc_small = 0; ctl_arena->astats->nrequests_small = 0; ctl_arena->astats->nfills_small = 0; ctl_arena->astats->nflushes_small = 0; memset(ctl_arena->astats->bstats, 0, SC_NBINS * sizeof(bin_stats_data_t)); memset(ctl_arena->astats->lstats, 0, (SC_NSIZES - SC_NBINS) * sizeof(arena_stats_large_t)); memset(ctl_arena->astats->estats, 0, SC_NPSIZES * sizeof(pac_estats_t)); memset(&ctl_arena->astats->hpastats, 0, sizeof(hpa_shard_stats_t)); memset(&ctl_arena->astats->secstats, 0, sizeof(sec_stats_t)); } } static void ctl_arena_stats_amerge(tsdn_t *tsdn, ctl_arena_t *ctl_arena, arena_t *arena) { unsigned i; if (config_stats) { arena_stats_merge(tsdn, arena, &ctl_arena->nthreads, &ctl_arena->dss, &ctl_arena->dirty_decay_ms, &ctl_arena->muzzy_decay_ms, &ctl_arena->pactive, &ctl_arena->pdirty, &ctl_arena->pmuzzy, &ctl_arena->astats->astats, ctl_arena->astats->bstats, ctl_arena->astats->lstats, ctl_arena->astats->estats, &ctl_arena->astats->hpastats, &ctl_arena->astats->secstats); for (i = 0; i < SC_NBINS; i++) { bin_stats_t *bstats = &ctl_arena->astats->bstats[i].stats_data; ctl_arena->astats->allocated_small += bstats->curregs * sz_index2size(i); ctl_arena->astats->nmalloc_small += bstats->nmalloc; ctl_arena->astats->ndalloc_small += bstats->ndalloc; ctl_arena->astats->nrequests_small += bstats->nrequests; ctl_arena->astats->nfills_small += bstats->nfills; ctl_arena->astats->nflushes_small += bstats->nflushes; } } else { arena_basic_stats_merge(tsdn, arena, &ctl_arena->nthreads, &ctl_arena->dss, &ctl_arena->dirty_decay_ms, &ctl_arena->muzzy_decay_ms, &ctl_arena->pactive, &ctl_arena->pdirty, &ctl_arena->pmuzzy); } } static void ctl_arena_stats_sdmerge(ctl_arena_t *ctl_sdarena, ctl_arena_t *ctl_arena, bool destroyed) { unsigned i; if (!destroyed) { ctl_sdarena->nthreads += ctl_arena->nthreads; ctl_sdarena->pactive += ctl_arena->pactive; ctl_sdarena->pdirty += ctl_arena->pdirty; ctl_sdarena->pmuzzy += ctl_arena->pmuzzy; } else { assert(ctl_arena->nthreads == 0); assert(ctl_arena->pactive == 0); assert(ctl_arena->pdirty == 0); assert(ctl_arena->pmuzzy == 0); } if (config_stats) { ctl_arena_stats_t *sdstats = ctl_sdarena->astats; ctl_arena_stats_t *astats = ctl_arena->astats; if (!destroyed) { sdstats->astats.mapped += astats->astats.mapped; sdstats->astats.pa_shard_stats.pac_stats.retained += astats->astats.pa_shard_stats.pac_stats.retained; sdstats->astats.pa_shard_stats.edata_avail += astats->astats.pa_shard_stats.edata_avail; } ctl_accum_locked_u64( &sdstats->astats.pa_shard_stats.pac_stats.decay_dirty.npurge, &astats->astats.pa_shard_stats.pac_stats.decay_dirty.npurge); ctl_accum_locked_u64( &sdstats->astats.pa_shard_stats.pac_stats.decay_dirty.nmadvise, &astats->astats.pa_shard_stats.pac_stats.decay_dirty.nmadvise); ctl_accum_locked_u64( &sdstats->astats.pa_shard_stats.pac_stats.decay_dirty.purged, &astats->astats.pa_shard_stats.pac_stats.decay_dirty.purged); ctl_accum_locked_u64( &sdstats->astats.pa_shard_stats.pac_stats.decay_muzzy.npurge, &astats->astats.pa_shard_stats.pac_stats.decay_muzzy.npurge); ctl_accum_locked_u64( &sdstats->astats.pa_shard_stats.pac_stats.decay_muzzy.nmadvise, &astats->astats.pa_shard_stats.pac_stats.decay_muzzy.nmadvise); ctl_accum_locked_u64( &sdstats->astats.pa_shard_stats.pac_stats.decay_muzzy.purged, &astats->astats.pa_shard_stats.pac_stats.decay_muzzy.purged); #define OP(mtx) malloc_mutex_prof_merge( \ &(sdstats->astats.mutex_prof_data[ \ arena_prof_mutex_##mtx]), \ &(astats->astats.mutex_prof_data[ \ arena_prof_mutex_##mtx])); MUTEX_PROF_ARENA_MUTEXES #undef OP if (!destroyed) { sdstats->astats.base += astats->astats.base; sdstats->astats.resident += astats->astats.resident; sdstats->astats.metadata_thp += astats->astats.metadata_thp; ctl_accum_atomic_zu(&sdstats->astats.internal, &astats->astats.internal); } else { assert(atomic_load_zu( &astats->astats.internal, ATOMIC_RELAXED) == 0); } if (!destroyed) { sdstats->allocated_small += astats->allocated_small; } else { assert(astats->allocated_small == 0); } sdstats->nmalloc_small += astats->nmalloc_small; sdstats->ndalloc_small += astats->ndalloc_small; sdstats->nrequests_small += astats->nrequests_small; sdstats->nfills_small += astats->nfills_small; sdstats->nflushes_small += astats->nflushes_small; if (!destroyed) { sdstats->astats.allocated_large += astats->astats.allocated_large; } else { assert(astats->astats.allocated_large == 0); } sdstats->astats.nmalloc_large += astats->astats.nmalloc_large; sdstats->astats.ndalloc_large += astats->astats.ndalloc_large; sdstats->astats.nrequests_large += astats->astats.nrequests_large; sdstats->astats.nflushes_large += astats->astats.nflushes_large; ctl_accum_atomic_zu( &sdstats->astats.pa_shard_stats.pac_stats.abandoned_vm, &astats->astats.pa_shard_stats.pac_stats.abandoned_vm); sdstats->astats.tcache_bytes += astats->astats.tcache_bytes; sdstats->astats.tcache_stashed_bytes += astats->astats.tcache_stashed_bytes; if (ctl_arena->arena_ind == 0) { sdstats->astats.uptime = astats->astats.uptime; } /* Merge bin stats. */ for (i = 0; i < SC_NBINS; i++) { bin_stats_t *bstats = &astats->bstats[i].stats_data; bin_stats_t *merged = &sdstats->bstats[i].stats_data; merged->nmalloc += bstats->nmalloc; merged->ndalloc += bstats->ndalloc; merged->nrequests += bstats->nrequests; if (!destroyed) { merged->curregs += bstats->curregs; } else { assert(bstats->curregs == 0); } merged->nfills += bstats->nfills; merged->nflushes += bstats->nflushes; merged->nslabs += bstats->nslabs; merged->reslabs += bstats->reslabs; if (!destroyed) { merged->curslabs += bstats->curslabs; merged->nonfull_slabs += bstats->nonfull_slabs; } else { assert(bstats->curslabs == 0); assert(bstats->nonfull_slabs == 0); } malloc_mutex_prof_merge(&sdstats->bstats[i].mutex_data, &astats->bstats[i].mutex_data); } /* Merge stats for large allocations. */ for (i = 0; i < SC_NSIZES - SC_NBINS; i++) { ctl_accum_locked_u64(&sdstats->lstats[i].nmalloc, &astats->lstats[i].nmalloc); ctl_accum_locked_u64(&sdstats->lstats[i].ndalloc, &astats->lstats[i].ndalloc); ctl_accum_locked_u64(&sdstats->lstats[i].nrequests, &astats->lstats[i].nrequests); if (!destroyed) { sdstats->lstats[i].curlextents += astats->lstats[i].curlextents; } else { assert(astats->lstats[i].curlextents == 0); } } /* Merge extents stats. */ for (i = 0; i < SC_NPSIZES; i++) { sdstats->estats[i].ndirty += astats->estats[i].ndirty; sdstats->estats[i].nmuzzy += astats->estats[i].nmuzzy; sdstats->estats[i].nretained += astats->estats[i].nretained; sdstats->estats[i].dirty_bytes += astats->estats[i].dirty_bytes; sdstats->estats[i].muzzy_bytes += astats->estats[i].muzzy_bytes; sdstats->estats[i].retained_bytes += astats->estats[i].retained_bytes; } /* Merge HPA stats. */ hpa_shard_stats_accum(&sdstats->hpastats, &astats->hpastats); sec_stats_accum(&sdstats->secstats, &astats->secstats); } } static void ctl_arena_refresh(tsdn_t *tsdn, arena_t *arena, ctl_arena_t *ctl_sdarena, unsigned i, bool destroyed) { ctl_arena_t *ctl_arena = arenas_i(i); ctl_arena_clear(ctl_arena); ctl_arena_stats_amerge(tsdn, ctl_arena, arena); /* Merge into sum stats as well. */ ctl_arena_stats_sdmerge(ctl_sdarena, ctl_arena, destroyed); } static unsigned ctl_arena_init(tsd_t *tsd, const arena_config_t *config) { unsigned arena_ind; ctl_arena_t *ctl_arena; if ((ctl_arena = ql_last(&ctl_arenas->destroyed, destroyed_link)) != NULL) { ql_remove(&ctl_arenas->destroyed, ctl_arena, destroyed_link); arena_ind = ctl_arena->arena_ind; } else { arena_ind = ctl_arenas->narenas; } /* Trigger stats allocation. */ if (arenas_i_impl(tsd, arena_ind, false, true) == NULL) { return UINT_MAX; } /* Initialize new arena. */ if (arena_init(tsd_tsdn(tsd), arena_ind, config) == NULL) { return UINT_MAX; } if (arena_ind == ctl_arenas->narenas) { ctl_arenas->narenas++; } return arena_ind; } static void ctl_background_thread_stats_read(tsdn_t *tsdn) { background_thread_stats_t *stats = &ctl_stats->background_thread; if (!have_background_thread || background_thread_stats_read(tsdn, stats)) { memset(stats, 0, sizeof(background_thread_stats_t)); nstime_init_zero(&stats->run_interval); } malloc_mutex_prof_copy( &ctl_stats->mutex_prof_data[global_prof_mutex_max_per_bg_thd], &stats->max_counter_per_bg_thd); } static void ctl_refresh(tsdn_t *tsdn) { unsigned i; ctl_arena_t *ctl_sarena = arenas_i(MALLCTL_ARENAS_ALL); VARIABLE_ARRAY(arena_t *, tarenas, ctl_arenas->narenas); /* * Clear sum stats, since they will be merged into by * ctl_arena_refresh(). */ ctl_arena_clear(ctl_sarena); for (i = 0; i < ctl_arenas->narenas; i++) { tarenas[i] = arena_get(tsdn, i, false); } for (i = 0; i < ctl_arenas->narenas; i++) { ctl_arena_t *ctl_arena = arenas_i(i); bool initialized = (tarenas[i] != NULL); ctl_arena->initialized = initialized; if (initialized) { ctl_arena_refresh(tsdn, tarenas[i], ctl_sarena, i, false); } } if (config_stats) { ctl_stats->allocated = ctl_sarena->astats->allocated_small + ctl_sarena->astats->astats.allocated_large; ctl_stats->active = (ctl_sarena->pactive << LG_PAGE); ctl_stats->metadata = ctl_sarena->astats->astats.base + atomic_load_zu(&ctl_sarena->astats->astats.internal, ATOMIC_RELAXED); ctl_stats->resident = ctl_sarena->astats->astats.resident; ctl_stats->metadata_thp = ctl_sarena->astats->astats.metadata_thp; ctl_stats->mapped = ctl_sarena->astats->astats.mapped; ctl_stats->retained = ctl_sarena->astats->astats .pa_shard_stats.pac_stats.retained; ctl_background_thread_stats_read(tsdn); #define READ_GLOBAL_MUTEX_PROF_DATA(i, mtx) \ malloc_mutex_lock(tsdn, &mtx); \ malloc_mutex_prof_read(tsdn, &ctl_stats->mutex_prof_data[i], &mtx); \ malloc_mutex_unlock(tsdn, &mtx); if (config_prof && opt_prof) { READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_prof, bt2gctx_mtx); READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_prof_thds_data, tdatas_mtx); READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_prof_dump, prof_dump_mtx); READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_prof_recent_alloc, prof_recent_alloc_mtx); READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_prof_recent_dump, prof_recent_dump_mtx); READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_prof_stats, prof_stats_mtx); } if (have_background_thread) { READ_GLOBAL_MUTEX_PROF_DATA( global_prof_mutex_background_thread, background_thread_lock); } else { memset(&ctl_stats->mutex_prof_data[ global_prof_mutex_background_thread], 0, sizeof(mutex_prof_data_t)); } /* We own ctl mutex already. */ malloc_mutex_prof_read(tsdn, &ctl_stats->mutex_prof_data[global_prof_mutex_ctl], &ctl_mtx); #undef READ_GLOBAL_MUTEX_PROF_DATA } ctl_arenas->epoch++; } static bool ctl_init(tsd_t *tsd) { bool ret; tsdn_t *tsdn = tsd_tsdn(tsd); malloc_mutex_lock(tsdn, &ctl_mtx); if (!ctl_initialized) { ctl_arena_t *ctl_sarena, *ctl_darena; unsigned i; /* * Allocate demand-zeroed space for pointers to the full * range of supported arena indices. */ if (ctl_arenas == NULL) { ctl_arenas = (ctl_arenas_t *)base_alloc(tsdn, b0get(), sizeof(ctl_arenas_t), QUANTUM); if (ctl_arenas == NULL) { ret = true; goto label_return; } } if (config_stats && ctl_stats == NULL) { ctl_stats = (ctl_stats_t *)base_alloc(tsdn, b0get(), sizeof(ctl_stats_t), QUANTUM); if (ctl_stats == NULL) { ret = true; goto label_return; } } /* * Allocate space for the current full range of arenas * here rather than doing it lazily elsewhere, in order * to limit when OOM-caused errors can occur. */ if ((ctl_sarena = arenas_i_impl(tsd, MALLCTL_ARENAS_ALL, false, true)) == NULL) { ret = true; goto label_return; } ctl_sarena->initialized = true; if ((ctl_darena = arenas_i_impl(tsd, MALLCTL_ARENAS_DESTROYED, false, true)) == NULL) { ret = true; goto label_return; } ctl_arena_clear(ctl_darena); /* * Don't toggle ctl_darena to initialized until an arena is * actually destroyed, so that arena..initialized can be used * to query whether the stats are relevant. */ ctl_arenas->narenas = narenas_total_get(); for (i = 0; i < ctl_arenas->narenas; i++) { if (arenas_i_impl(tsd, i, false, true) == NULL) { ret = true; goto label_return; } } ql_new(&ctl_arenas->destroyed); ctl_refresh(tsdn); ctl_initialized = true; } ret = false; label_return: malloc_mutex_unlock(tsdn, &ctl_mtx); return ret; } static int ctl_lookup(tsdn_t *tsdn, const ctl_named_node_t *starting_node, const char *name, const ctl_named_node_t **ending_nodep, size_t *mibp, size_t *depthp) { int ret; const char *elm, *tdot, *dot; size_t elen, i, j; const ctl_named_node_t *node; elm = name; /* Equivalent to strchrnul(). */ dot = ((tdot = strchr(elm, '.')) != NULL) ? tdot : strchr(elm, '\0'); elen = (size_t)((uintptr_t)dot - (uintptr_t)elm); if (elen == 0) { ret = ENOENT; goto label_return; } node = starting_node; for (i = 0; i < *depthp; i++) { assert(node); assert(node->nchildren > 0); if (ctl_named_node(node->children) != NULL) { const ctl_named_node_t *pnode = node; /* Children are named. */ for (j = 0; j < node->nchildren; j++) { const ctl_named_node_t *child = ctl_named_children(node, j); if (strlen(child->name) == elen && strncmp(elm, child->name, elen) == 0) { node = child; mibp[i] = j; break; } } if (node == pnode) { ret = ENOENT; goto label_return; } } else { uintmax_t index; const ctl_indexed_node_t *inode; /* Children are indexed. */ index = malloc_strtoumax(elm, NULL, 10); if (index == UINTMAX_MAX || index > SIZE_T_MAX) { ret = ENOENT; goto label_return; } inode = ctl_indexed_node(node->children); node = inode->index(tsdn, mibp, *depthp, (size_t)index); if (node == NULL) { ret = ENOENT; goto label_return; } mibp[i] = (size_t)index; } /* Reached the end? */ if (node->ctl != NULL || *dot == '\0') { /* Terminal node. */ if (*dot != '\0') { /* * The name contains more elements than are * in this path through the tree. */ ret = ENOENT; goto label_return; } /* Complete lookup successful. */ *depthp = i + 1; break; } /* Update elm. */ elm = &dot[1]; dot = ((tdot = strchr(elm, '.')) != NULL) ? tdot : strchr(elm, '\0'); elen = (size_t)((uintptr_t)dot - (uintptr_t)elm); } if (ending_nodep != NULL) { *ending_nodep = node; } ret = 0; label_return: return ret; } int ctl_byname(tsd_t *tsd, const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; size_t depth; size_t mib[CTL_MAX_DEPTH]; const ctl_named_node_t *node; if (!ctl_initialized && ctl_init(tsd)) { ret = EAGAIN; goto label_return; } depth = CTL_MAX_DEPTH; ret = ctl_lookup(tsd_tsdn(tsd), super_root_node, name, &node, mib, &depth); if (ret != 0) { goto label_return; } if (node != NULL && node->ctl) { ret = node->ctl(tsd, mib, depth, oldp, oldlenp, newp, newlen); } else { /* The name refers to a partial path through the ctl tree. */ ret = ENOENT; } label_return: return(ret); } int ctl_nametomib(tsd_t *tsd, const char *name, size_t *mibp, size_t *miblenp) { int ret; if (!ctl_initialized && ctl_init(tsd)) { ret = EAGAIN; goto label_return; } ret = ctl_lookup(tsd_tsdn(tsd), super_root_node, name, NULL, mibp, miblenp); label_return: return(ret); } static int ctl_lookupbymib(tsdn_t *tsdn, const ctl_named_node_t **ending_nodep, const size_t *mib, size_t miblen) { int ret; const ctl_named_node_t *node = super_root_node; for (size_t i = 0; i < miblen; i++) { assert(node); assert(node->nchildren > 0); if (ctl_named_node(node->children) != NULL) { /* Children are named. */ if (node->nchildren <= mib[i]) { ret = ENOENT; goto label_return; } node = ctl_named_children(node, mib[i]); } else { const ctl_indexed_node_t *inode; /* Indexed element. */ inode = ctl_indexed_node(node->children); node = inode->index(tsdn, mib, miblen, mib[i]); if (node == NULL) { ret = ENOENT; goto label_return; } } } assert(ending_nodep != NULL); *ending_nodep = node; ret = 0; label_return: return(ret); } int ctl_bymib(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; const ctl_named_node_t *node; if (!ctl_initialized && ctl_init(tsd)) { ret = EAGAIN; goto label_return; } ret = ctl_lookupbymib(tsd_tsdn(tsd), &node, mib, miblen); if (ret != 0) { goto label_return; } /* Call the ctl function. */ if (node && node->ctl) { ret = node->ctl(tsd, mib, miblen, oldp, oldlenp, newp, newlen); } else { /* Partial MIB. */ ret = ENOENT; } label_return: return(ret); } int ctl_mibnametomib(tsd_t *tsd, size_t *mib, size_t miblen, const char *name, size_t *miblenp) { int ret; const ctl_named_node_t *node; if (!ctl_initialized && ctl_init(tsd)) { ret = EAGAIN; goto label_return; } ret = ctl_lookupbymib(tsd_tsdn(tsd), &node, mib, miblen); if (ret != 0) { goto label_return; } if (node == NULL || node->ctl != NULL) { ret = ENOENT; goto label_return; } assert(miblenp != NULL); assert(*miblenp >= miblen); *miblenp -= miblen; ret = ctl_lookup(tsd_tsdn(tsd), node, name, NULL, mib + miblen, miblenp); *miblenp += miblen; label_return: return(ret); } int ctl_bymibname(tsd_t *tsd, size_t *mib, size_t miblen, const char *name, size_t *miblenp, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; const ctl_named_node_t *node; if (!ctl_initialized && ctl_init(tsd)) { ret = EAGAIN; goto label_return; } ret = ctl_lookupbymib(tsd_tsdn(tsd), &node, mib, miblen); if (ret != 0) { goto label_return; } if (node == NULL || node->ctl != NULL) { ret = ENOENT; goto label_return; } assert(miblenp != NULL); assert(*miblenp >= miblen); *miblenp -= miblen; /* * The same node supplies the starting node and stores the ending node. */ ret = ctl_lookup(tsd_tsdn(tsd), node, name, &node, mib + miblen, miblenp); *miblenp += miblen; if (ret != 0) { goto label_return; } if (node != NULL && node->ctl) { ret = node->ctl(tsd, mib, *miblenp, oldp, oldlenp, newp, newlen); } else { /* The name refers to a partial path through the ctl tree. */ ret = ENOENT; } label_return: return(ret); } bool ctl_boot(void) { if (malloc_mutex_init(&ctl_mtx, "ctl", WITNESS_RANK_CTL, malloc_mutex_rank_exclusive)) { return true; } ctl_initialized = false; return false; } void ctl_prefork(tsdn_t *tsdn) { malloc_mutex_prefork(tsdn, &ctl_mtx); } void ctl_postfork_parent(tsdn_t *tsdn) { malloc_mutex_postfork_parent(tsdn, &ctl_mtx); } void ctl_postfork_child(tsdn_t *tsdn) { malloc_mutex_postfork_child(tsdn, &ctl_mtx); } void ctl_mtx_assert_held(tsdn_t *tsdn) { malloc_mutex_assert_owner(tsdn, &ctl_mtx); } /******************************************************************************/ /* *_ctl() functions. */ #define READONLY() do { \ if (newp != NULL || newlen != 0) { \ ret = EPERM; \ goto label_return; \ } \ } while (0) #define WRITEONLY() do { \ if (oldp != NULL || oldlenp != NULL) { \ ret = EPERM; \ goto label_return; \ } \ } while (0) /* Can read or write, but not both. */ #define READ_XOR_WRITE() do { \ if ((oldp != NULL && oldlenp != NULL) && (newp != NULL || \ newlen != 0)) { \ ret = EPERM; \ goto label_return; \ } \ } while (0) /* Can neither read nor write. */ #define NEITHER_READ_NOR_WRITE() do { \ if (oldp != NULL || oldlenp != NULL || newp != NULL || \ newlen != 0) { \ ret = EPERM; \ goto label_return; \ } \ } while (0) /* Verify that the space provided is enough. */ #define VERIFY_READ(t) do { \ if (oldp == NULL || oldlenp == NULL || *oldlenp != sizeof(t)) { \ *oldlenp = 0; \ ret = EINVAL; \ goto label_return; \ } \ } while (0) #define READ(v, t) do { \ if (oldp != NULL && oldlenp != NULL) { \ if (*oldlenp != sizeof(t)) { \ size_t copylen = (sizeof(t) <= *oldlenp) \ ? sizeof(t) : *oldlenp; \ memcpy(oldp, (void *)&(v), copylen); \ *oldlenp = copylen; \ ret = EINVAL; \ goto label_return; \ } \ *(t *)oldp = (v); \ } \ } while (0) #define WRITE(v, t) do { \ if (newp != NULL) { \ if (newlen != sizeof(t)) { \ ret = EINVAL; \ goto label_return; \ } \ (v) = *(t *)newp; \ } \ } while (0) #define ASSURED_WRITE(v, t) do { \ if (newp == NULL || newlen != sizeof(t)) { \ ret = EINVAL; \ goto label_return; \ } \ (v) = *(t *)newp; \ } while (0) #define MIB_UNSIGNED(v, i) do { \ if (mib[i] > UINT_MAX) { \ ret = EFAULT; \ goto label_return; \ } \ v = (unsigned)mib[i]; \ } while (0) /* * There's a lot of code duplication in the following macros due to limitations * in how nested cpp macros are expanded. */ #define CTL_RO_CLGEN(c, l, n, v, t) \ static int \ n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \ size_t *oldlenp, void *newp, size_t newlen) { \ int ret; \ t oldval; \ \ if (!(c)) { \ return ENOENT; \ } \ if (l) { \ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ } \ READONLY(); \ oldval = (v); \ READ(oldval, t); \ \ ret = 0; \ label_return: \ if (l) { \ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ } \ return ret; \ } #define CTL_RO_CGEN(c, n, v, t) \ static int \ n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ int ret; \ t oldval; \ \ if (!(c)) { \ return ENOENT; \ } \ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ READONLY(); \ oldval = (v); \ READ(oldval, t); \ \ ret = 0; \ label_return: \ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ return ret; \ } #define CTL_RO_GEN(n, v, t) \ static int \ n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \ size_t *oldlenp, void *newp, size_t newlen) { \ int ret; \ t oldval; \ \ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ READONLY(); \ oldval = (v); \ READ(oldval, t); \ \ ret = 0; \ label_return: \ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ return ret; \ } /* * ctl_mtx is not acquired, under the assumption that no pertinent data will * mutate during the call. */ #define CTL_RO_NL_CGEN(c, n, v, t) \ static int \ n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ int ret; \ t oldval; \ \ if (!(c)) { \ return ENOENT; \ } \ READONLY(); \ oldval = (v); \ READ(oldval, t); \ \ ret = 0; \ label_return: \ return ret; \ } #define CTL_RO_NL_GEN(n, v, t) \ static int \ n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ int ret; \ t oldval; \ \ READONLY(); \ oldval = (v); \ READ(oldval, t); \ \ ret = 0; \ label_return: \ return ret; \ } #define CTL_RO_CONFIG_GEN(n, t) \ static int \ n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ int ret; \ t oldval; \ \ READONLY(); \ oldval = n; \ READ(oldval, t); \ \ ret = 0; \ label_return: \ return ret; \ } /******************************************************************************/ CTL_RO_NL_GEN(version, JEMALLOC_VERSION, const char *) static int epoch_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; UNUSED uint64_t newval; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); WRITE(newval, uint64_t); if (newp != NULL) { ctl_refresh(tsd_tsdn(tsd)); } READ(ctl_arenas->epoch, uint64_t); ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int background_thread_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; bool oldval; if (!have_background_thread) { return ENOENT; } background_thread_ctl_init(tsd_tsdn(tsd)); malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); if (newp == NULL) { oldval = background_thread_enabled(); READ(oldval, bool); } else { if (newlen != sizeof(bool)) { ret = EINVAL; goto label_return; } oldval = background_thread_enabled(); READ(oldval, bool); bool newval = *(bool *)newp; if (newval == oldval) { ret = 0; goto label_return; } background_thread_enabled_set(tsd_tsdn(tsd), newval); if (newval) { if (background_threads_enable(tsd)) { ret = EFAULT; goto label_return; } } else { if (background_threads_disable(tsd)) { ret = EFAULT; goto label_return; } } } ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int max_background_threads_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; size_t oldval; if (!have_background_thread) { return ENOENT; } background_thread_ctl_init(tsd_tsdn(tsd)); malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); if (newp == NULL) { oldval = max_background_threads; READ(oldval, size_t); } else { if (newlen != sizeof(size_t)) { ret = EINVAL; goto label_return; } oldval = max_background_threads; READ(oldval, size_t); size_t newval = *(size_t *)newp; if (newval == oldval) { ret = 0; goto label_return; } if (newval > opt_max_background_threads) { ret = EINVAL; goto label_return; } if (background_thread_enabled()) { background_thread_enabled_set(tsd_tsdn(tsd), false); if (background_threads_disable(tsd)) { ret = EFAULT; goto label_return; } max_background_threads = newval; background_thread_enabled_set(tsd_tsdn(tsd), true); if (background_threads_enable(tsd)) { ret = EFAULT; goto label_return; } } else { max_background_threads = newval; } } ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } /******************************************************************************/ CTL_RO_CONFIG_GEN(config_cache_oblivious, bool) CTL_RO_CONFIG_GEN(config_debug, bool) CTL_RO_CONFIG_GEN(config_fill, bool) CTL_RO_CONFIG_GEN(config_lazy_lock, bool) CTL_RO_CONFIG_GEN(config_malloc_conf, const char *) CTL_RO_CONFIG_GEN(config_opt_safety_checks, bool) CTL_RO_CONFIG_GEN(config_prof, bool) CTL_RO_CONFIG_GEN(config_prof_libgcc, bool) CTL_RO_CONFIG_GEN(config_prof_libunwind, bool) CTL_RO_CONFIG_GEN(config_stats, bool) CTL_RO_CONFIG_GEN(config_utrace, bool) CTL_RO_CONFIG_GEN(config_xmalloc, bool) /******************************************************************************/ CTL_RO_NL_GEN(opt_abort, opt_abort, bool) CTL_RO_NL_GEN(opt_abort_conf, opt_abort_conf, bool) CTL_RO_NL_GEN(opt_cache_oblivious, opt_cache_oblivious, bool) CTL_RO_NL_GEN(opt_trust_madvise, opt_trust_madvise, bool) CTL_RO_NL_GEN(opt_confirm_conf, opt_confirm_conf, bool) /* HPA options. */ CTL_RO_NL_GEN(opt_hpa, opt_hpa, bool) CTL_RO_NL_GEN(opt_hpa_hugification_threshold, opt_hpa_opts.hugification_threshold, size_t) CTL_RO_NL_GEN(opt_hpa_hugify_delay_ms, opt_hpa_opts.hugify_delay_ms, uint64_t) CTL_RO_NL_GEN(opt_hpa_min_purge_interval_ms, opt_hpa_opts.min_purge_interval_ms, uint64_t) /* * This will have to change before we publicly document this option; fxp_t and * its representation are internal implementation details. */ CTL_RO_NL_GEN(opt_hpa_dirty_mult, opt_hpa_opts.dirty_mult, fxp_t) CTL_RO_NL_GEN(opt_hpa_slab_max_alloc, opt_hpa_opts.slab_max_alloc, size_t) /* HPA SEC options */ CTL_RO_NL_GEN(opt_hpa_sec_nshards, opt_hpa_sec_opts.nshards, size_t) CTL_RO_NL_GEN(opt_hpa_sec_max_alloc, opt_hpa_sec_opts.max_alloc, size_t) CTL_RO_NL_GEN(opt_hpa_sec_max_bytes, opt_hpa_sec_opts.max_bytes, size_t) CTL_RO_NL_GEN(opt_hpa_sec_bytes_after_flush, opt_hpa_sec_opts.bytes_after_flush, size_t) CTL_RO_NL_GEN(opt_hpa_sec_batch_fill_extra, opt_hpa_sec_opts.batch_fill_extra, size_t) CTL_RO_NL_GEN(opt_metadata_thp, metadata_thp_mode_names[opt_metadata_thp], const char *) CTL_RO_NL_GEN(opt_retain, opt_retain, bool) CTL_RO_NL_GEN(opt_dss, opt_dss, const char *) CTL_RO_NL_GEN(opt_narenas, opt_narenas, unsigned) CTL_RO_NL_GEN(opt_percpu_arena, percpu_arena_mode_names[opt_percpu_arena], const char *) CTL_RO_NL_GEN(opt_mutex_max_spin, opt_mutex_max_spin, int64_t) CTL_RO_NL_GEN(opt_oversize_threshold, opt_oversize_threshold, size_t) CTL_RO_NL_GEN(opt_background_thread, opt_background_thread, bool) CTL_RO_NL_GEN(opt_max_background_threads, opt_max_background_threads, size_t) CTL_RO_NL_GEN(opt_dirty_decay_ms, opt_dirty_decay_ms, ssize_t) CTL_RO_NL_GEN(opt_muzzy_decay_ms, opt_muzzy_decay_ms, ssize_t) CTL_RO_NL_GEN(opt_stats_print, opt_stats_print, bool) CTL_RO_NL_GEN(opt_stats_print_opts, opt_stats_print_opts, const char *) CTL_RO_NL_GEN(opt_stats_interval, opt_stats_interval, int64_t) CTL_RO_NL_GEN(opt_stats_interval_opts, opt_stats_interval_opts, const char *) CTL_RO_NL_CGEN(config_fill, opt_junk, opt_junk, const char *) CTL_RO_NL_CGEN(config_fill, opt_zero, opt_zero, bool) CTL_RO_NL_CGEN(config_utrace, opt_utrace, opt_utrace, bool) CTL_RO_NL_CGEN(config_xmalloc, opt_xmalloc, opt_xmalloc, bool) CTL_RO_NL_CGEN(config_enable_cxx, opt_experimental_infallible_new, opt_experimental_infallible_new, bool) CTL_RO_NL_GEN(opt_tcache, opt_tcache, bool) CTL_RO_NL_GEN(opt_tcache_max, opt_tcache_max, size_t) CTL_RO_NL_GEN(opt_tcache_nslots_small_min, opt_tcache_nslots_small_min, unsigned) CTL_RO_NL_GEN(opt_tcache_nslots_small_max, opt_tcache_nslots_small_max, unsigned) CTL_RO_NL_GEN(opt_tcache_nslots_large, opt_tcache_nslots_large, unsigned) CTL_RO_NL_GEN(opt_lg_tcache_nslots_mul, opt_lg_tcache_nslots_mul, ssize_t) CTL_RO_NL_GEN(opt_tcache_gc_incr_bytes, opt_tcache_gc_incr_bytes, size_t) CTL_RO_NL_GEN(opt_tcache_gc_delay_bytes, opt_tcache_gc_delay_bytes, size_t) CTL_RO_NL_GEN(opt_lg_tcache_flush_small_div, opt_lg_tcache_flush_small_div, unsigned) CTL_RO_NL_GEN(opt_lg_tcache_flush_large_div, opt_lg_tcache_flush_large_div, unsigned) CTL_RO_NL_GEN(opt_thp, thp_mode_names[opt_thp], const char *) CTL_RO_NL_GEN(opt_lg_extent_max_active_fit, opt_lg_extent_max_active_fit, size_t) CTL_RO_NL_CGEN(config_prof, opt_prof, opt_prof, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_prefix, opt_prof_prefix, const char *) CTL_RO_NL_CGEN(config_prof, opt_prof_active, opt_prof_active, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_thread_active_init, opt_prof_thread_active_init, bool) CTL_RO_NL_CGEN(config_prof, opt_lg_prof_sample, opt_lg_prof_sample, size_t) CTL_RO_NL_CGEN(config_prof, opt_prof_accum, opt_prof_accum, bool) CTL_RO_NL_CGEN(config_prof, opt_lg_prof_interval, opt_lg_prof_interval, ssize_t) CTL_RO_NL_CGEN(config_prof, opt_prof_gdump, opt_prof_gdump, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_final, opt_prof_final, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_leak, opt_prof_leak, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_leak_error, opt_prof_leak_error, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_recent_alloc_max, opt_prof_recent_alloc_max, ssize_t) CTL_RO_NL_CGEN(config_prof, opt_prof_stats, opt_prof_stats, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_sys_thread_name, opt_prof_sys_thread_name, bool) CTL_RO_NL_CGEN(config_prof, opt_prof_time_res, prof_time_res_mode_names[opt_prof_time_res], const char *) CTL_RO_NL_CGEN(config_uaf_detection, opt_lg_san_uaf_align, opt_lg_san_uaf_align, ssize_t) CTL_RO_NL_GEN(opt_zero_realloc, zero_realloc_mode_names[opt_zero_realloc_action], const char *) /******************************************************************************/ static int thread_arena_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; arena_t *oldarena; unsigned newind, oldind; oldarena = arena_choose(tsd, NULL); if (oldarena == NULL) { return EAGAIN; } newind = oldind = arena_ind_get(oldarena); WRITE(newind, unsigned); READ(oldind, unsigned); if (newind != oldind) { arena_t *newarena; if (newind >= narenas_total_get()) { /* New arena index is out of range. */ ret = EFAULT; goto label_return; } if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)) { if (newind < percpu_arena_ind_limit(opt_percpu_arena)) { /* * If perCPU arena is enabled, thread_arena * control is not allowed for the auto arena * range. */ ret = EPERM; goto label_return; } } /* Initialize arena if necessary. */ newarena = arena_get(tsd_tsdn(tsd), newind, true); if (newarena == NULL) { ret = EAGAIN; goto label_return; } /* Set new arena/tcache associations. */ arena_migrate(tsd, oldarena, newarena); if (tcache_available(tsd)) { tcache_arena_reassociate(tsd_tsdn(tsd), tsd_tcache_slowp_get(tsd), tsd_tcachep_get(tsd), newarena); } } ret = 0; label_return: return ret; } CTL_RO_NL_GEN(thread_allocated, tsd_thread_allocated_get(tsd), uint64_t) CTL_RO_NL_GEN(thread_allocatedp, tsd_thread_allocatedp_get(tsd), uint64_t *) CTL_RO_NL_GEN(thread_deallocated, tsd_thread_deallocated_get(tsd), uint64_t) CTL_RO_NL_GEN(thread_deallocatedp, tsd_thread_deallocatedp_get(tsd), uint64_t *) static int thread_tcache_enabled_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; bool oldval; oldval = tcache_enabled_get(tsd); if (newp != NULL) { if (newlen != sizeof(bool)) { ret = EINVAL; goto label_return; } tcache_enabled_set(tsd, *(bool *)newp); } READ(oldval, bool); ret = 0; label_return: return ret; } static int thread_tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!tcache_available(tsd)) { ret = EFAULT; goto label_return; } NEITHER_READ_NOR_WRITE(); tcache_flush(tsd); ret = 0; label_return: return ret; } static int thread_peak_read_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!config_stats) { return ENOENT; } READONLY(); peak_event_update(tsd); uint64_t result = peak_event_max(tsd); READ(result, uint64_t); ret = 0; label_return: return ret; } static int thread_peak_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!config_stats) { return ENOENT; } NEITHER_READ_NOR_WRITE(); peak_event_zero(tsd); ret = 0; label_return: return ret; } static int thread_prof_name_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!config_prof || !opt_prof) { return ENOENT; } READ_XOR_WRITE(); if (newp != NULL) { if (newlen != sizeof(const char *)) { ret = EINVAL; goto label_return; } if ((ret = prof_thread_name_set(tsd, *(const char **)newp)) != 0) { goto label_return; } } else { const char *oldname = prof_thread_name_get(tsd); READ(oldname, const char *); } ret = 0; label_return: return ret; } static int thread_prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; bool oldval; if (!config_prof) { return ENOENT; } oldval = opt_prof ? prof_thread_active_get(tsd) : false; if (newp != NULL) { if (!opt_prof) { ret = ENOENT; goto label_return; } if (newlen != sizeof(bool)) { ret = EINVAL; goto label_return; } if (prof_thread_active_set(tsd, *(bool *)newp)) { ret = EAGAIN; goto label_return; } } READ(oldval, bool); ret = 0; label_return: return ret; } static int thread_idle_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; NEITHER_READ_NOR_WRITE(); if (tcache_available(tsd)) { tcache_flush(tsd); } /* * This heuristic is perhaps not the most well-considered. But it * matches the only idling policy we have experience with in the status * quo. Over time we should investigate more principled approaches. */ if (opt_narenas > ncpus * 2) { arena_t *arena = arena_choose(tsd, NULL); if (arena != NULL) { arena_decay(tsd_tsdn(tsd), arena, false, true); } /* * The missing arena case is not actually an error; a thread * might be idle before it associates itself to one. This is * unusual, but not wrong. */ } ret = 0; label_return: return ret; } /******************************************************************************/ static int tcache_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned tcache_ind; READONLY(); VERIFY_READ(unsigned); if (tcaches_create(tsd, b0get(), &tcache_ind)) { ret = EFAULT; goto label_return; } READ(tcache_ind, unsigned); ret = 0; label_return: return ret; } static int tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned tcache_ind; WRITEONLY(); ASSURED_WRITE(tcache_ind, unsigned); tcaches_flush(tsd, tcache_ind); ret = 0; label_return: return ret; } static int tcache_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned tcache_ind; WRITEONLY(); ASSURED_WRITE(tcache_ind, unsigned); tcaches_destroy(tsd, tcache_ind); ret = 0; label_return: return ret; } /******************************************************************************/ static int arena_i_initialized_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; tsdn_t *tsdn = tsd_tsdn(tsd); unsigned arena_ind; bool initialized; READONLY(); MIB_UNSIGNED(arena_ind, 1); malloc_mutex_lock(tsdn, &ctl_mtx); initialized = arenas_i(arena_ind)->initialized; malloc_mutex_unlock(tsdn, &ctl_mtx); READ(initialized, bool); ret = 0; label_return: return ret; } static void arena_i_decay(tsdn_t *tsdn, unsigned arena_ind, bool all) { malloc_mutex_lock(tsdn, &ctl_mtx); { unsigned narenas = ctl_arenas->narenas; /* * Access via index narenas is deprecated, and scheduled for * removal in 6.0.0. */ if (arena_ind == MALLCTL_ARENAS_ALL || arena_ind == narenas) { unsigned i; VARIABLE_ARRAY(arena_t *, tarenas, narenas); for (i = 0; i < narenas; i++) { tarenas[i] = arena_get(tsdn, i, false); } /* * No further need to hold ctl_mtx, since narenas and * tarenas contain everything needed below. */ malloc_mutex_unlock(tsdn, &ctl_mtx); for (i = 0; i < narenas; i++) { if (tarenas[i] != NULL) { arena_decay(tsdn, tarenas[i], false, all); } } } else { arena_t *tarena; assert(arena_ind < narenas); tarena = arena_get(tsdn, arena_ind, false); /* No further need to hold ctl_mtx. */ malloc_mutex_unlock(tsdn, &ctl_mtx); if (tarena != NULL) { arena_decay(tsdn, tarena, false, all); } } } } static int arena_i_decay_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; NEITHER_READ_NOR_WRITE(); MIB_UNSIGNED(arena_ind, 1); arena_i_decay(tsd_tsdn(tsd), arena_ind, false); ret = 0; label_return: return ret; } static int arena_i_purge_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; NEITHER_READ_NOR_WRITE(); MIB_UNSIGNED(arena_ind, 1); arena_i_decay(tsd_tsdn(tsd), arena_ind, true); ret = 0; label_return: return ret; } static int arena_i_reset_destroy_helper(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, unsigned *arena_ind, arena_t **arena) { int ret; NEITHER_READ_NOR_WRITE(); MIB_UNSIGNED(*arena_ind, 1); *arena = arena_get(tsd_tsdn(tsd), *arena_ind, false); if (*arena == NULL || arena_is_auto(*arena)) { ret = EFAULT; goto label_return; } ret = 0; label_return: return ret; } static void arena_reset_prepare_background_thread(tsd_t *tsd, unsigned arena_ind) { /* Temporarily disable the background thread during arena reset. */ if (have_background_thread) { malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); if (background_thread_enabled()) { background_thread_info_t *info = background_thread_info_get(arena_ind); assert(info->state == background_thread_started); malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); info->state = background_thread_paused; malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); } } } static void arena_reset_finish_background_thread(tsd_t *tsd, unsigned arena_ind) { if (have_background_thread) { if (background_thread_enabled()) { background_thread_info_t *info = background_thread_info_get(arena_ind); assert(info->state == background_thread_paused); malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); info->state = background_thread_started; malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); } malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); } } static int arena_i_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; arena_t *arena; ret = arena_i_reset_destroy_helper(tsd, mib, miblen, oldp, oldlenp, newp, newlen, &arena_ind, &arena); if (ret != 0) { return ret; } arena_reset_prepare_background_thread(tsd, arena_ind); arena_reset(tsd, arena); arena_reset_finish_background_thread(tsd, arena_ind); return ret; } static int arena_i_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; arena_t *arena; ctl_arena_t *ctl_darena, *ctl_arena; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); ret = arena_i_reset_destroy_helper(tsd, mib, miblen, oldp, oldlenp, newp, newlen, &arena_ind, &arena); if (ret != 0) { goto label_return; } if (arena_nthreads_get(arena, false) != 0 || arena_nthreads_get(arena, true) != 0) { ret = EFAULT; goto label_return; } arena_reset_prepare_background_thread(tsd, arena_ind); /* Merge stats after resetting and purging arena. */ arena_reset(tsd, arena); arena_decay(tsd_tsdn(tsd), arena, false, true); ctl_darena = arenas_i(MALLCTL_ARENAS_DESTROYED); ctl_darena->initialized = true; ctl_arena_refresh(tsd_tsdn(tsd), arena, ctl_darena, arena_ind, true); /* Destroy arena. */ arena_destroy(tsd, arena); ctl_arena = arenas_i(arena_ind); ctl_arena->initialized = false; /* Record arena index for later recycling via arenas.create. */ ql_elm_new(ctl_arena, destroyed_link); ql_tail_insert(&ctl_arenas->destroyed, ctl_arena, destroyed_link); arena_reset_finish_background_thread(tsd, arena_ind); assert(ret == 0); label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int arena_i_dss_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; const char *dss = NULL; unsigned arena_ind; dss_prec_t dss_prec_old = dss_prec_limit; dss_prec_t dss_prec = dss_prec_limit; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); WRITE(dss, const char *); MIB_UNSIGNED(arena_ind, 1); if (dss != NULL) { int i; bool match = false; for (i = 0; i < dss_prec_limit; i++) { if (strcmp(dss_prec_names[i], dss) == 0) { dss_prec = i; match = true; break; } } if (!match) { ret = EINVAL; goto label_return; } } /* * Access via index narenas is deprecated, and scheduled for removal in * 6.0.0. */ if (arena_ind == MALLCTL_ARENAS_ALL || arena_ind == ctl_arenas->narenas) { if (dss_prec != dss_prec_limit && extent_dss_prec_set(dss_prec)) { ret = EFAULT; goto label_return; } dss_prec_old = extent_dss_prec_get(); } else { arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL || (dss_prec != dss_prec_limit && arena_dss_prec_set(arena, dss_prec))) { ret = EFAULT; goto label_return; } dss_prec_old = arena_dss_prec_get(arena); } dss = dss_prec_names[dss_prec_old]; READ(dss, const char *); ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int arena_i_oversize_threshold_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; MIB_UNSIGNED(arena_ind, 1); arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { ret = EFAULT; goto label_return; } if (oldp != NULL && oldlenp != NULL) { size_t oldval = atomic_load_zu( &arena->pa_shard.pac.oversize_threshold, ATOMIC_RELAXED); READ(oldval, size_t); } if (newp != NULL) { if (newlen != sizeof(size_t)) { ret = EINVAL; goto label_return; } atomic_store_zu(&arena->pa_shard.pac.oversize_threshold, *(size_t *)newp, ATOMIC_RELAXED); } ret = 0; label_return: return ret; } static int arena_i_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) { int ret; unsigned arena_ind; arena_t *arena; MIB_UNSIGNED(arena_ind, 1); arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { ret = EFAULT; goto label_return; } extent_state_t state = dirty ? extent_state_dirty : extent_state_muzzy; if (oldp != NULL && oldlenp != NULL) { size_t oldval = arena_decay_ms_get(arena, state); READ(oldval, ssize_t); } if (newp != NULL) { if (newlen != sizeof(ssize_t)) { ret = EINVAL; goto label_return; } if (arena_is_huge(arena_ind) && *(ssize_t *)newp > 0) { /* * By default the huge arena purges eagerly. If it is * set to non-zero decay time afterwards, background * thread might be needed. */ if (background_thread_create(tsd, arena_ind)) { ret = EFAULT; goto label_return; } } if (arena_decay_ms_set(tsd_tsdn(tsd), arena, state, *(ssize_t *)newp)) { ret = EFAULT; goto label_return; } } ret = 0; label_return: return ret; } static int arena_i_dirty_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { return arena_i_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, newlen, true); } static int arena_i_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { return arena_i_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, newlen, false); } static int arena_i_extent_hooks_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; arena_t *arena; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); MIB_UNSIGNED(arena_ind, 1); if (arena_ind < narenas_total_get()) { extent_hooks_t *old_extent_hooks; arena = arena_get(tsd_tsdn(tsd), arena_ind, false); if (arena == NULL) { if (arena_ind >= narenas_auto) { ret = EFAULT; goto label_return; } old_extent_hooks = (extent_hooks_t *)&ehooks_default_extent_hooks; READ(old_extent_hooks, extent_hooks_t *); if (newp != NULL) { /* Initialize a new arena as a side effect. */ extent_hooks_t *new_extent_hooks JEMALLOC_CC_SILENCE_INIT(NULL); WRITE(new_extent_hooks, extent_hooks_t *); arena_config_t config = arena_config_default; config.extent_hooks = new_extent_hooks; arena = arena_init(tsd_tsdn(tsd), arena_ind, &config); if (arena == NULL) { ret = EFAULT; goto label_return; } } } else { if (newp != NULL) { extent_hooks_t *new_extent_hooks JEMALLOC_CC_SILENCE_INIT(NULL); WRITE(new_extent_hooks, extent_hooks_t *); old_extent_hooks = arena_set_extent_hooks(tsd, arena, new_extent_hooks); READ(old_extent_hooks, extent_hooks_t *); } else { old_extent_hooks = ehooks_get_extent_hooks_ptr( arena_get_ehooks(arena)); READ(old_extent_hooks, extent_hooks_t *); } } } else { ret = EFAULT; goto label_return; } ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int arena_i_retain_grow_limit_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; arena_t *arena; if (!opt_retain) { /* Only relevant when retain is enabled. */ return ENOENT; } malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); MIB_UNSIGNED(arena_ind, 1); if (arena_ind < narenas_total_get() && (arena = arena_get(tsd_tsdn(tsd), arena_ind, false)) != NULL) { size_t old_limit, new_limit; if (newp != NULL) { WRITE(new_limit, size_t); } bool err = arena_retain_grow_limit_get_set(tsd, arena, &old_limit, newp != NULL ? &new_limit : NULL); if (!err) { READ(old_limit, size_t); ret = 0; } else { ret = EFAULT; } } else { ret = EFAULT; } label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static const ctl_named_node_t * arena_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { const ctl_named_node_t *ret; malloc_mutex_lock(tsdn, &ctl_mtx); switch (i) { case MALLCTL_ARENAS_ALL: case MALLCTL_ARENAS_DESTROYED: break; default: if (i > ctl_arenas->narenas) { ret = NULL; goto label_return; } break; } ret = super_arena_i_node; label_return: malloc_mutex_unlock(tsdn, &ctl_mtx); return ret; } /******************************************************************************/ static int arenas_narenas_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned narenas; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); READONLY(); narenas = ctl_arenas->narenas; READ(narenas, unsigned); ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int arenas_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) { int ret; if (oldp != NULL && oldlenp != NULL) { size_t oldval = (dirty ? arena_dirty_decay_ms_default_get() : arena_muzzy_decay_ms_default_get()); READ(oldval, ssize_t); } if (newp != NULL) { if (newlen != sizeof(ssize_t)) { ret = EINVAL; goto label_return; } if (dirty ? arena_dirty_decay_ms_default_set(*(ssize_t *)newp) : arena_muzzy_decay_ms_default_set(*(ssize_t *)newp)) { ret = EFAULT; goto label_return; } } ret = 0; label_return: return ret; } static int arenas_dirty_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { return arenas_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, newlen, true); } static int arenas_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { return arenas_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, newlen, false); } CTL_RO_NL_GEN(arenas_quantum, QUANTUM, size_t) CTL_RO_NL_GEN(arenas_page, PAGE, size_t) CTL_RO_NL_GEN(arenas_tcache_max, tcache_maxclass, size_t) CTL_RO_NL_GEN(arenas_nbins, SC_NBINS, unsigned) CTL_RO_NL_GEN(arenas_nhbins, nhbins, unsigned) CTL_RO_NL_GEN(arenas_bin_i_size, bin_infos[mib[2]].reg_size, size_t) CTL_RO_NL_GEN(arenas_bin_i_nregs, bin_infos[mib[2]].nregs, uint32_t) CTL_RO_NL_GEN(arenas_bin_i_slab_size, bin_infos[mib[2]].slab_size, size_t) CTL_RO_NL_GEN(arenas_bin_i_nshards, bin_infos[mib[2]].n_shards, uint32_t) static const ctl_named_node_t * arenas_bin_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { if (i > SC_NBINS) { return NULL; } return super_arenas_bin_i_node; } CTL_RO_NL_GEN(arenas_nlextents, SC_NSIZES - SC_NBINS, unsigned) CTL_RO_NL_GEN(arenas_lextent_i_size, sz_index2size(SC_NBINS+(szind_t)mib[2]), size_t) static const ctl_named_node_t * arenas_lextent_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { if (i > SC_NSIZES - SC_NBINS) { return NULL; } return super_arenas_lextent_i_node; } static int arenas_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); VERIFY_READ(unsigned); arena_config_t config = arena_config_default; WRITE(config.extent_hooks, extent_hooks_t *); if ((arena_ind = ctl_arena_init(tsd, &config)) == UINT_MAX) { ret = EAGAIN; goto label_return; } READ(arena_ind, unsigned); ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int experimental_arenas_create_ext_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); arena_config_t config = arena_config_default; VERIFY_READ(unsigned); WRITE(config, arena_config_t); if ((arena_ind = ctl_arena_init(tsd, &config)) == UINT_MAX) { ret = EAGAIN; goto label_return; } READ(arena_ind, unsigned); ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int arenas_lookup_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned arena_ind; void *ptr; edata_t *edata; arena_t *arena; ptr = NULL; ret = EINVAL; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); WRITE(ptr, void *); edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); if (edata == NULL) { goto label_return; } arena = arena_get_from_edata(edata); if (arena == NULL) { goto label_return; } arena_ind = arena_ind_get(arena); READ(arena_ind, unsigned); ret = 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } /******************************************************************************/ static int prof_thread_active_init_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; bool oldval; if (!config_prof) { return ENOENT; } if (newp != NULL) { if (!opt_prof) { ret = ENOENT; goto label_return; } if (newlen != sizeof(bool)) { ret = EINVAL; goto label_return; } oldval = prof_thread_active_init_set(tsd_tsdn(tsd), *(bool *)newp); } else { oldval = opt_prof ? prof_thread_active_init_get(tsd_tsdn(tsd)) : false; } READ(oldval, bool); ret = 0; label_return: return ret; } static int prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; bool oldval; if (!config_prof) { ret = ENOENT; goto label_return; } if (newp != NULL) { if (newlen != sizeof(bool)) { ret = EINVAL; goto label_return; } bool val = *(bool *)newp; if (!opt_prof) { if (val) { ret = ENOENT; goto label_return; } else { /* No change needed (already off). */ oldval = false; } } else { oldval = prof_active_set(tsd_tsdn(tsd), val); } } else { oldval = opt_prof ? prof_active_get(tsd_tsdn(tsd)) : false; } READ(oldval, bool); ret = 0; label_return: return ret; } static int prof_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; const char *filename = NULL; if (!config_prof || !opt_prof) { return ENOENT; } WRITEONLY(); WRITE(filename, const char *); if (prof_mdump(tsd, filename)) { ret = EFAULT; goto label_return; } ret = 0; label_return: return ret; } static int prof_gdump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; bool oldval; if (!config_prof) { return ENOENT; } if (newp != NULL) { if (!opt_prof) { ret = ENOENT; goto label_return; } if (newlen != sizeof(bool)) { ret = EINVAL; goto label_return; } oldval = prof_gdump_set(tsd_tsdn(tsd), *(bool *)newp); } else { oldval = opt_prof ? prof_gdump_get(tsd_tsdn(tsd)) : false; } READ(oldval, bool); ret = 0; label_return: return ret; } static int prof_prefix_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; const char *prefix = NULL; if (!config_prof || !opt_prof) { return ENOENT; } malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); WRITEONLY(); WRITE(prefix, const char *); ret = prof_prefix_set(tsd_tsdn(tsd), prefix) ? EFAULT : 0; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int prof_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; size_t lg_sample = lg_prof_sample; if (!config_prof || !opt_prof) { return ENOENT; } WRITEONLY(); WRITE(lg_sample, size_t); if (lg_sample >= (sizeof(uint64_t) << 3)) { lg_sample = (sizeof(uint64_t) << 3) - 1; } prof_reset(tsd, lg_sample); ret = 0; label_return: return ret; } CTL_RO_NL_CGEN(config_prof, prof_interval, prof_interval, uint64_t) CTL_RO_NL_CGEN(config_prof, lg_prof_sample, lg_prof_sample, size_t) static int prof_log_start_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; const char *filename = NULL; if (!config_prof || !opt_prof) { return ENOENT; } WRITEONLY(); WRITE(filename, const char *); if (prof_log_start(tsd_tsdn(tsd), filename)) { ret = EFAULT; goto label_return; } ret = 0; label_return: return ret; } static int prof_log_stop_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { if (!config_prof || !opt_prof) { return ENOENT; } if (prof_log_stop(tsd_tsdn(tsd))) { return EFAULT; } return 0; } static int experimental_hooks_prof_backtrace_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (oldp == NULL && newp == NULL) { ret = EINVAL; goto label_return; } if (oldp != NULL) { prof_backtrace_hook_t old_hook = prof_backtrace_hook_get(); READ(old_hook, prof_backtrace_hook_t); } if (newp != NULL) { if (!opt_prof) { ret = ENOENT; goto label_return; } prof_backtrace_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); WRITE(new_hook, prof_backtrace_hook_t); if (new_hook == NULL) { ret = EINVAL; goto label_return; } prof_backtrace_hook_set(new_hook); } ret = 0; label_return: return ret; } static int experimental_hooks_prof_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (oldp == NULL && newp == NULL) { ret = EINVAL; goto label_return; } if (oldp != NULL) { prof_dump_hook_t old_hook = prof_dump_hook_get(); READ(old_hook, prof_dump_hook_t); } if (newp != NULL) { if (!opt_prof) { ret = ENOENT; goto label_return; } prof_dump_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); WRITE(new_hook, prof_dump_hook_t); prof_dump_hook_set(new_hook); } ret = 0; label_return: return ret; } /* For integration test purpose only. No plan to move out of experimental. */ static int experimental_hooks_safety_check_abort_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; WRITEONLY(); if (newp != NULL) { if (newlen != sizeof(safety_check_abort_hook_t)) { ret = EINVAL; goto label_return; } safety_check_abort_hook_t hook JEMALLOC_CC_SILENCE_INIT(NULL); WRITE(hook, safety_check_abort_hook_t); safety_check_set_abort(hook); } ret = 0; label_return: return ret; } /******************************************************************************/ CTL_RO_CGEN(config_stats, stats_allocated, ctl_stats->allocated, size_t) CTL_RO_CGEN(config_stats, stats_active, ctl_stats->active, size_t) CTL_RO_CGEN(config_stats, stats_metadata, ctl_stats->metadata, size_t) CTL_RO_CGEN(config_stats, stats_metadata_thp, ctl_stats->metadata_thp, size_t) CTL_RO_CGEN(config_stats, stats_resident, ctl_stats->resident, size_t) CTL_RO_CGEN(config_stats, stats_mapped, ctl_stats->mapped, size_t) CTL_RO_CGEN(config_stats, stats_retained, ctl_stats->retained, size_t) CTL_RO_CGEN(config_stats, stats_background_thread_num_threads, ctl_stats->background_thread.num_threads, size_t) CTL_RO_CGEN(config_stats, stats_background_thread_num_runs, ctl_stats->background_thread.num_runs, uint64_t) CTL_RO_CGEN(config_stats, stats_background_thread_run_interval, nstime_ns(&ctl_stats->background_thread.run_interval), uint64_t) CTL_RO_CGEN(config_stats, stats_zero_reallocs, atomic_load_zu(&zero_realloc_count, ATOMIC_RELAXED), size_t) CTL_RO_GEN(stats_arenas_i_dss, arenas_i(mib[2])->dss, const char *) CTL_RO_GEN(stats_arenas_i_dirty_decay_ms, arenas_i(mib[2])->dirty_decay_ms, ssize_t) CTL_RO_GEN(stats_arenas_i_muzzy_decay_ms, arenas_i(mib[2])->muzzy_decay_ms, ssize_t) CTL_RO_GEN(stats_arenas_i_nthreads, arenas_i(mib[2])->nthreads, unsigned) CTL_RO_GEN(stats_arenas_i_uptime, nstime_ns(&arenas_i(mib[2])->astats->astats.uptime), uint64_t) CTL_RO_GEN(stats_arenas_i_pactive, arenas_i(mib[2])->pactive, size_t) CTL_RO_GEN(stats_arenas_i_pdirty, arenas_i(mib[2])->pdirty, size_t) CTL_RO_GEN(stats_arenas_i_pmuzzy, arenas_i(mib[2])->pmuzzy, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_mapped, arenas_i(mib[2])->astats->astats.mapped, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_retained, arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.retained, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_extent_avail, arenas_i(mib[2])->astats->astats.pa_shard_stats.edata_avail, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_npurge, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_dirty.npurge), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_nmadvise, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_dirty.nmadvise), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_purged, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_dirty.purged), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_npurge, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_muzzy.npurge), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_nmadvise, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_muzzy.nmadvise), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_purged, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_muzzy.purged), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_base, arenas_i(mib[2])->astats->astats.base, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_internal, atomic_load_zu(&arenas_i(mib[2])->astats->astats.internal, ATOMIC_RELAXED), size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_metadata_thp, arenas_i(mib[2])->astats->astats.metadata_thp, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_tcache_bytes, arenas_i(mib[2])->astats->astats.tcache_bytes, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_tcache_stashed_bytes, arenas_i(mib[2])->astats->astats.tcache_stashed_bytes, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_resident, arenas_i(mib[2])->astats->astats.resident, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_abandoned_vm, atomic_load_zu( &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.abandoned_vm, ATOMIC_RELAXED), size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_sec_bytes, arenas_i(mib[2])->astats->secstats.bytes, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_small_allocated, arenas_i(mib[2])->astats->allocated_small, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_small_nmalloc, arenas_i(mib[2])->astats->nmalloc_small, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_small_ndalloc, arenas_i(mib[2])->astats->ndalloc_small, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_small_nrequests, arenas_i(mib[2])->astats->nrequests_small, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_small_nfills, arenas_i(mib[2])->astats->nfills_small, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_small_nflushes, arenas_i(mib[2])->astats->nflushes_small, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_large_allocated, arenas_i(mib[2])->astats->astats.allocated_large, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_large_nmalloc, arenas_i(mib[2])->astats->astats.nmalloc_large, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_large_ndalloc, arenas_i(mib[2])->astats->astats.ndalloc_large, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_large_nrequests, arenas_i(mib[2])->astats->astats.nrequests_large, uint64_t) /* * Note: "nmalloc_large" here instead of "nfills" in the read. This is * intentional (large has no batch fill). */ CTL_RO_CGEN(config_stats, stats_arenas_i_large_nfills, arenas_i(mib[2])->astats->astats.nmalloc_large, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_large_nflushes, arenas_i(mib[2])->astats->astats.nflushes_large, uint64_t) /* Lock profiling related APIs below. */ #define RO_MUTEX_CTL_GEN(n, l) \ CTL_RO_CGEN(config_stats, stats_##n##_num_ops, \ l.n_lock_ops, uint64_t) \ CTL_RO_CGEN(config_stats, stats_##n##_num_wait, \ l.n_wait_times, uint64_t) \ CTL_RO_CGEN(config_stats, stats_##n##_num_spin_acq, \ l.n_spin_acquired, uint64_t) \ CTL_RO_CGEN(config_stats, stats_##n##_num_owner_switch, \ l.n_owner_switches, uint64_t) \ CTL_RO_CGEN(config_stats, stats_##n##_total_wait_time, \ nstime_ns(&l.tot_wait_time), uint64_t) \ CTL_RO_CGEN(config_stats, stats_##n##_max_wait_time, \ nstime_ns(&l.max_wait_time), uint64_t) \ CTL_RO_CGEN(config_stats, stats_##n##_max_num_thds, \ l.max_n_thds, uint32_t) /* Global mutexes. */ #define OP(mtx) \ RO_MUTEX_CTL_GEN(mutexes_##mtx, \ ctl_stats->mutex_prof_data[global_prof_mutex_##mtx]) MUTEX_PROF_GLOBAL_MUTEXES #undef OP /* Per arena mutexes */ #define OP(mtx) RO_MUTEX_CTL_GEN(arenas_i_mutexes_##mtx, \ arenas_i(mib[2])->astats->astats.mutex_prof_data[arena_prof_mutex_##mtx]) MUTEX_PROF_ARENA_MUTEXES #undef OP /* tcache bin mutex */ RO_MUTEX_CTL_GEN(arenas_i_bins_j_mutex, arenas_i(mib[2])->astats->bstats[mib[4]].mutex_data) #undef RO_MUTEX_CTL_GEN /* Resets all mutex stats, including global, arena and bin mutexes. */ static int stats_mutexes_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { if (!config_stats) { return ENOENT; } tsdn_t *tsdn = tsd_tsdn(tsd); #define MUTEX_PROF_RESET(mtx) \ malloc_mutex_lock(tsdn, &mtx); \ malloc_mutex_prof_data_reset(tsdn, &mtx); \ malloc_mutex_unlock(tsdn, &mtx); /* Global mutexes: ctl and prof. */ MUTEX_PROF_RESET(ctl_mtx); if (have_background_thread) { MUTEX_PROF_RESET(background_thread_lock); } if (config_prof && opt_prof) { MUTEX_PROF_RESET(bt2gctx_mtx); MUTEX_PROF_RESET(tdatas_mtx); MUTEX_PROF_RESET(prof_dump_mtx); MUTEX_PROF_RESET(prof_recent_alloc_mtx); MUTEX_PROF_RESET(prof_recent_dump_mtx); MUTEX_PROF_RESET(prof_stats_mtx); } /* Per arena mutexes. */ unsigned n = narenas_total_get(); for (unsigned i = 0; i < n; i++) { arena_t *arena = arena_get(tsdn, i, false); if (!arena) { continue; } MUTEX_PROF_RESET(arena->large_mtx); MUTEX_PROF_RESET(arena->pa_shard.edata_cache.mtx); MUTEX_PROF_RESET(arena->pa_shard.pac.ecache_dirty.mtx); MUTEX_PROF_RESET(arena->pa_shard.pac.ecache_muzzy.mtx); MUTEX_PROF_RESET(arena->pa_shard.pac.ecache_retained.mtx); MUTEX_PROF_RESET(arena->pa_shard.pac.decay_dirty.mtx); MUTEX_PROF_RESET(arena->pa_shard.pac.decay_muzzy.mtx); MUTEX_PROF_RESET(arena->tcache_ql_mtx); MUTEX_PROF_RESET(arena->base->mtx); for (szind_t j = 0; j < SC_NBINS; j++) { for (unsigned k = 0; k < bin_infos[j].n_shards; k++) { bin_t *bin = arena_get_bin(arena, j, k); MUTEX_PROF_RESET(bin->lock); } } } #undef MUTEX_PROF_RESET return 0; } CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nmalloc, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nmalloc, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_ndalloc, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.ndalloc, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nrequests, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nrequests, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curregs, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.curregs, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nfills, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nfills, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nflushes, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nflushes, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nslabs, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nslabs, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nreslabs, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.reslabs, uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curslabs, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.curslabs, size_t) CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nonfull_slabs, arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nonfull_slabs, size_t) static const ctl_named_node_t * stats_arenas_i_bins_j_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t j) { if (j > SC_NBINS) { return NULL; } return super_stats_arenas_i_bins_j_node; } CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_nmalloc, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->lstats[mib[4]].nmalloc), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_ndalloc, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->lstats[mib[4]].ndalloc), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_nrequests, locked_read_u64_unsynchronized( &arenas_i(mib[2])->astats->lstats[mib[4]].nrequests), uint64_t) CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_curlextents, arenas_i(mib[2])->astats->lstats[mib[4]].curlextents, size_t) static const ctl_named_node_t * stats_arenas_i_lextents_j_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t j) { if (j > SC_NSIZES - SC_NBINS) { return NULL; } return super_stats_arenas_i_lextents_j_node; } CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_ndirty, arenas_i(mib[2])->astats->estats[mib[4]].ndirty, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_nmuzzy, arenas_i(mib[2])->astats->estats[mib[4]].nmuzzy, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_nretained, arenas_i(mib[2])->astats->estats[mib[4]].nretained, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_dirty_bytes, arenas_i(mib[2])->astats->estats[mib[4]].dirty_bytes, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_muzzy_bytes, arenas_i(mib[2])->astats->estats[mib[4]].muzzy_bytes, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_retained_bytes, arenas_i(mib[2])->astats->estats[mib[4]].retained_bytes, size_t); static const ctl_named_node_t * stats_arenas_i_extents_j_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t j) { if (j >= SC_NPSIZES) { return NULL; } return super_stats_arenas_i_extents_j_node; } CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_npurge_passes, arenas_i(mib[2])->astats->hpastats.nonderived_stats.npurge_passes, uint64_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_npurges, arenas_i(mib[2])->astats->hpastats.nonderived_stats.npurges, uint64_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nhugifies, arenas_i(mib[2])->astats->hpastats.nonderived_stats.nhugifies, uint64_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_ndehugifies, arenas_i(mib[2])->astats->hpastats.nonderived_stats.ndehugifies, uint64_t); /* Full, nonhuge */ CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_npageslabs_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[0].npageslabs, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_nactive_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[0].nactive, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_ndirty_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[0].ndirty, size_t); /* Full, huge */ CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_npageslabs_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[1].npageslabs, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_nactive_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[1].nactive, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_ndirty_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[1].ndirty, size_t); /* Empty, nonhuge */ CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_npageslabs_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[0].npageslabs, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_nactive_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[0].nactive, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_ndirty_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[0].ndirty, size_t); /* Empty, huge */ CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_npageslabs_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[1].npageslabs, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_nactive_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[1].nactive, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_ndirty_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[1].ndirty, size_t); /* Nonfull, nonhuge */ CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][0].npageslabs, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][0].nactive, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_nonhuge, arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][0].ndirty, size_t); /* Nonfull, huge */ CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][1].npageslabs, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][1].nactive, size_t); CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_huge, arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][1].ndirty, size_t); static const ctl_named_node_t * stats_arenas_i_hpa_shard_nonfull_slabs_j_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t j) { if (j >= PSSET_NPSIZES) { return NULL; } return super_stats_arenas_i_hpa_shard_nonfull_slabs_j_node; } static bool ctl_arenas_i_verify(size_t i) { size_t a = arenas_i2a_impl(i, true, true); if (a == UINT_MAX || !ctl_arenas->arenas[a]->initialized) { return true; } return false; } static const ctl_named_node_t * stats_arenas_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { const ctl_named_node_t *ret; malloc_mutex_lock(tsdn, &ctl_mtx); if (ctl_arenas_i_verify(i)) { ret = NULL; goto label_return; } ret = super_stats_arenas_i_node; label_return: malloc_mutex_unlock(tsdn, &ctl_mtx); return ret; } static int experimental_hooks_install_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (oldp == NULL || oldlenp == NULL|| newp == NULL) { ret = EINVAL; goto label_return; } /* * Note: this is a *private* struct. This is an experimental interface; * forcing the user to know the jemalloc internals well enough to * extract the ABI hopefully ensures nobody gets too comfortable with * this API, which can change at a moment's notice. */ hooks_t hooks; WRITE(hooks, hooks_t); void *handle = hook_install(tsd_tsdn(tsd), &hooks); if (handle == NULL) { ret = EAGAIN; goto label_return; } READ(handle, void *); ret = 0; label_return: return ret; } static int experimental_hooks_remove_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; WRITEONLY(); void *handle = NULL; WRITE(handle, void *); if (handle == NULL) { ret = EINVAL; goto label_return; } hook_remove(tsd_tsdn(tsd), handle); ret = 0; label_return: return ret; } static int experimental_thread_activity_callback_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!config_stats) { return ENOENT; } activity_callback_thunk_t t_old = tsd_activity_callback_thunk_get(tsd); READ(t_old, activity_callback_thunk_t); if (newp != NULL) { /* * This initialization is unnecessary. If it's omitted, though, * clang gets confused and warns on the subsequent use of t_new. */ activity_callback_thunk_t t_new = {NULL, NULL}; WRITE(t_new, activity_callback_thunk_t); tsd_activity_callback_thunk_set(tsd, t_new); } ret = 0; label_return: return ret; } /* * Output six memory utilization entries for an input pointer, the first one of * type (void *) and the remaining five of type size_t, describing the following * (in the same order): * * (a) memory address of the extent a potential reallocation would go into, * == the five fields below describe about the extent the pointer resides in == * (b) number of free regions in the extent, * (c) number of regions in the extent, * (d) size of the extent in terms of bytes, * (e) total number of free regions in the bin the extent belongs to, and * (f) total number of regions in the bin the extent belongs to. * * Note that "(e)" and "(f)" are only available when stats are enabled; * otherwise their values are undefined. * * This API is mainly intended for small class allocations, where extents are * used as slab. Note that if the bin the extent belongs to is completely * full, "(a)" will be NULL. * * In case of large class allocations, "(a)" will be NULL, and "(e)" and "(f)" * will be zero (if stats are enabled; otherwise undefined). The other three * fields will be properly set though the values are trivial: "(b)" will be 0, * "(c)" will be 1, and "(d)" will be the usable size. * * The input pointer and size are respectively passed in by newp and newlen, * and the output fields and size are respectively oldp and *oldlenp. * * It can be beneficial to define the following macros to make it easier to * access the output: * * #define SLABCUR_READ(out) (*(void **)out) * #define COUNTS(out) ((size_t *)((void **)out + 1)) * #define NFREE_READ(out) COUNTS(out)[0] * #define NREGS_READ(out) COUNTS(out)[1] * #define SIZE_READ(out) COUNTS(out)[2] * #define BIN_NFREE_READ(out) COUNTS(out)[3] * #define BIN_NREGS_READ(out) COUNTS(out)[4] * * and then write e.g. NFREE_READ(oldp) to fetch the output. See the unit test * test_query in test/unit/extent_util.c for an example. * * For a typical defragmentation workflow making use of this API for * understanding the fragmentation level, please refer to the comment for * experimental_utilization_batch_query_ctl. * * It's up to the application how to determine the significance of * fragmentation relying on the outputs returned. Possible choices are: * * (a) if extent utilization ratio is below certain threshold, * (b) if extent memory consumption is above certain threshold, * (c) if extent utilization ratio is significantly below bin utilization ratio, * (d) if input pointer deviates a lot from potential reallocation address, or * (e) some selection/combination of the above. * * The caller needs to make sure that the input/output arguments are valid, * in particular, that the size of the output is correct, i.e.: * * *oldlenp = sizeof(void *) + sizeof(size_t) * 5 * * Otherwise, the function immediately returns EINVAL without touching anything. * * In the rare case where there's no associated extent found for the input * pointer, the function zeros out all output fields and return. Please refer * to the comment for experimental_utilization_batch_query_ctl to understand the * motivation from C++. */ static int experimental_utilization_query_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; assert(sizeof(inspect_extent_util_stats_verbose_t) == sizeof(void *) + sizeof(size_t) * 5); if (oldp == NULL || oldlenp == NULL || *oldlenp != sizeof(inspect_extent_util_stats_verbose_t) || newp == NULL) { ret = EINVAL; goto label_return; } void *ptr = NULL; WRITE(ptr, void *); inspect_extent_util_stats_verbose_t *util_stats = (inspect_extent_util_stats_verbose_t *)oldp; inspect_extent_util_stats_verbose_get(tsd_tsdn(tsd), ptr, &util_stats->nfree, &util_stats->nregs, &util_stats->size, &util_stats->bin_nfree, &util_stats->bin_nregs, &util_stats->slabcur_addr); ret = 0; label_return: return ret; } /* * Given an input array of pointers, output three memory utilization entries of * type size_t for each input pointer about the extent it resides in: * * (a) number of free regions in the extent, * (b) number of regions in the extent, and * (c) size of the extent in terms of bytes. * * This API is mainly intended for small class allocations, where extents are * used as slab. In case of large class allocations, the outputs are trivial: * "(a)" will be 0, "(b)" will be 1, and "(c)" will be the usable size. * * Note that multiple input pointers may reside on a same extent so the output * fields may contain duplicates. * * The format of the input/output looks like: * * input[0]: 1st_pointer_to_query | output[0]: 1st_extent_n_free_regions * | output[1]: 1st_extent_n_regions * | output[2]: 1st_extent_size * input[1]: 2nd_pointer_to_query | output[3]: 2nd_extent_n_free_regions * | output[4]: 2nd_extent_n_regions * | output[5]: 2nd_extent_size * ... | ... * * The input array and size are respectively passed in by newp and newlen, and * the output array and size are respectively oldp and *oldlenp. * * It can be beneficial to define the following macros to make it easier to * access the output: * * #define NFREE_READ(out, i) out[(i) * 3] * #define NREGS_READ(out, i) out[(i) * 3 + 1] * #define SIZE_READ(out, i) out[(i) * 3 + 2] * * and then write e.g. NFREE_READ(oldp, i) to fetch the output. See the unit * test test_batch in test/unit/extent_util.c for a concrete example. * * A typical workflow would be composed of the following steps: * * (1) flush tcache: mallctl("thread.tcache.flush", ...) * (2) initialize input array of pointers to query fragmentation * (3) allocate output array to hold utilization statistics * (4) query utilization: mallctl("experimental.utilization.batch_query", ...) * (5) (optional) decide if it's worthwhile to defragment; otherwise stop here * (6) disable tcache: mallctl("thread.tcache.enabled", ...) * (7) defragment allocations with significant fragmentation, e.g.: * for each allocation { * if it's fragmented { * malloc(...); * memcpy(...); * free(...); * } * } * (8) enable tcache: mallctl("thread.tcache.enabled", ...) * * The application can determine the significance of fragmentation themselves * relying on the statistics returned, both at the overall level i.e. step "(5)" * and at individual allocation level i.e. within step "(7)". Possible choices * are: * * (a) whether memory utilization ratio is below certain threshold, * (b) whether memory consumption is above certain threshold, or * (c) some combination of the two. * * The caller needs to make sure that the input/output arrays are valid and * their sizes are proper as well as matched, meaning: * * (a) newlen = n_pointers * sizeof(const void *) * (b) *oldlenp = n_pointers * sizeof(size_t) * 3 * (c) n_pointers > 0 * * Otherwise, the function immediately returns EINVAL without touching anything. * * In the rare case where there's no associated extent found for some pointers, * rather than immediately terminating the computation and raising an error, * the function simply zeros out the corresponding output fields and continues * the computation until all input pointers are handled. The motivations of * such a design are as follows: * * (a) The function always either processes nothing or processes everything, and * never leaves the output half touched and half untouched. * * (b) It facilitates usage needs especially common in C++. A vast variety of * C++ objects are instantiated with multiple dynamic memory allocations. For * example, std::string and std::vector typically use at least two allocations, * one for the metadata and one for the actual content. Other types may use * even more allocations. When inquiring about utilization statistics, the * caller often wants to examine into all such allocations, especially internal * one(s), rather than just the topmost one. The issue comes when some * implementations do certain optimizations to reduce/aggregate some internal * allocations, e.g. putting short strings directly into the metadata, and such * decisions are not known to the caller. Therefore, we permit pointers to * memory usages that may not be returned by previous malloc calls, and we * provide the caller a convenient way to identify such cases. */ static int experimental_utilization_batch_query_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; assert(sizeof(inspect_extent_util_stats_t) == sizeof(size_t) * 3); const size_t len = newlen / sizeof(const void *); if (oldp == NULL || oldlenp == NULL || newp == NULL || newlen == 0 || newlen != len * sizeof(const void *) || *oldlenp != len * sizeof(inspect_extent_util_stats_t)) { ret = EINVAL; goto label_return; } void **ptrs = (void **)newp; inspect_extent_util_stats_t *util_stats = (inspect_extent_util_stats_t *)oldp; size_t i; for (i = 0; i < len; ++i) { inspect_extent_util_stats_get(tsd_tsdn(tsd), ptrs[i], &util_stats[i].nfree, &util_stats[i].nregs, &util_stats[i].size); } ret = 0; label_return: return ret; } static const ctl_named_node_t * experimental_arenas_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { const ctl_named_node_t *ret; malloc_mutex_lock(tsdn, &ctl_mtx); if (ctl_arenas_i_verify(i)) { ret = NULL; goto label_return; } ret = super_experimental_arenas_i_node; label_return: malloc_mutex_unlock(tsdn, &ctl_mtx); return ret; } static int experimental_arenas_i_pactivep_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { if (!config_stats) { return ENOENT; } if (oldp == NULL || oldlenp == NULL || *oldlenp != sizeof(size_t *)) { return EINVAL; } unsigned arena_ind; arena_t *arena; int ret; size_t *pactivep; malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); READONLY(); MIB_UNSIGNED(arena_ind, 2); if (arena_ind < narenas_total_get() && (arena = arena_get(tsd_tsdn(tsd), arena_ind, false)) != NULL) { #if defined(JEMALLOC_GCC_ATOMIC_ATOMICS) || \ defined(JEMALLOC_GCC_SYNC_ATOMICS) || defined(_MSC_VER) /* Expose the underlying counter for fast read. */ pactivep = (size_t *)&(arena->pa_shard.nactive.repr); READ(pactivep, size_t *); ret = 0; #else ret = EFAULT; #endif } else { ret = EFAULT; } label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); return ret; } static int experimental_prof_recent_alloc_max_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!(config_prof && opt_prof)) { ret = ENOENT; goto label_return; } ssize_t old_max; if (newp != NULL) { if (newlen != sizeof(ssize_t)) { ret = EINVAL; goto label_return; } ssize_t max = *(ssize_t *)newp; if (max < -1) { ret = EINVAL; goto label_return; } old_max = prof_recent_alloc_max_ctl_write(tsd, max); } else { old_max = prof_recent_alloc_max_ctl_read(); } READ(old_max, ssize_t); ret = 0; label_return: return ret; } typedef struct write_cb_packet_s write_cb_packet_t; struct write_cb_packet_s { write_cb_t *write_cb; void *cbopaque; }; static int experimental_prof_recent_alloc_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; if (!(config_prof && opt_prof)) { ret = ENOENT; goto label_return; } assert(sizeof(write_cb_packet_t) == sizeof(void *) * 2); WRITEONLY(); write_cb_packet_t write_cb_packet; ASSURED_WRITE(write_cb_packet, write_cb_packet_t); prof_recent_alloc_dump(tsd, write_cb_packet.write_cb, write_cb_packet.cbopaque); ret = 0; label_return: return ret; } typedef struct batch_alloc_packet_s batch_alloc_packet_t; struct batch_alloc_packet_s { void **ptrs; size_t num; size_t size; int flags; }; static int experimental_batch_alloc_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; VERIFY_READ(size_t); batch_alloc_packet_t batch_alloc_packet; ASSURED_WRITE(batch_alloc_packet, batch_alloc_packet_t); size_t filled = batch_alloc(batch_alloc_packet.ptrs, batch_alloc_packet.num, batch_alloc_packet.size, batch_alloc_packet.flags); READ(filled, size_t); ret = 0; label_return: return ret; } static int prof_stats_bins_i_live_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned binind; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { ret = ENOENT; goto label_return; } READONLY(); MIB_UNSIGNED(binind, 3); if (binind >= SC_NBINS) { ret = EINVAL; goto label_return; } prof_stats_get_live(tsd, (szind_t)binind, &stats); READ(stats, prof_stats_t); ret = 0; label_return: return ret; } static int prof_stats_bins_i_accum_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned binind; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { ret = ENOENT; goto label_return; } READONLY(); MIB_UNSIGNED(binind, 3); if (binind >= SC_NBINS) { ret = EINVAL; goto label_return; } prof_stats_get_accum(tsd, (szind_t)binind, &stats); READ(stats, prof_stats_t); ret = 0; label_return: return ret; } static const ctl_named_node_t * prof_stats_bins_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { if (!(config_prof && opt_prof && opt_prof_stats)) { return NULL; } if (i >= SC_NBINS) { return NULL; } return super_prof_stats_bins_i_node; } static int prof_stats_lextents_i_live_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned lextent_ind; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { ret = ENOENT; goto label_return; } READONLY(); MIB_UNSIGNED(lextent_ind, 3); if (lextent_ind >= SC_NSIZES - SC_NBINS) { ret = EINVAL; goto label_return; } prof_stats_get_live(tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); READ(stats, prof_stats_t); ret = 0; label_return: return ret; } static int prof_stats_lextents_i_accum_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; unsigned lextent_ind; prof_stats_t stats; if (!(config_prof && opt_prof && opt_prof_stats)) { ret = ENOENT; goto label_return; } READONLY(); MIB_UNSIGNED(lextent_ind, 3); if (lextent_ind >= SC_NSIZES - SC_NBINS) { ret = EINVAL; goto label_return; } prof_stats_get_accum(tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); READ(stats, prof_stats_t); ret = 0; label_return: return ret; } static const ctl_named_node_t * prof_stats_lextents_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) { if (!(config_prof && opt_prof && opt_prof_stats)) { return NULL; } if (i >= SC_NSIZES - SC_NBINS) { return NULL; } return super_prof_stats_lextents_i_node; } redis-8.0.2/deps/jemalloc/src/decay.c000066400000000000000000000206511501533116600173750ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/decay.h" static const uint64_t h_steps[SMOOTHSTEP_NSTEPS] = { #define STEP(step, h, x, y) \ h, SMOOTHSTEP #undef STEP }; /* * Generate a new deadline that is uniformly random within the next epoch after * the current one. */ void decay_deadline_init(decay_t *decay) { nstime_copy(&decay->deadline, &decay->epoch); nstime_add(&decay->deadline, &decay->interval); if (decay_ms_read(decay) > 0) { nstime_t jitter; nstime_init(&jitter, prng_range_u64(&decay->jitter_state, nstime_ns(&decay->interval))); nstime_add(&decay->deadline, &jitter); } } void decay_reinit(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms) { atomic_store_zd(&decay->time_ms, decay_ms, ATOMIC_RELAXED); if (decay_ms > 0) { nstime_init(&decay->interval, (uint64_t)decay_ms * KQU(1000000)); nstime_idivide(&decay->interval, SMOOTHSTEP_NSTEPS); } nstime_copy(&decay->epoch, cur_time); decay->jitter_state = (uint64_t)(uintptr_t)decay; decay_deadline_init(decay); decay->nunpurged = 0; memset(decay->backlog, 0, SMOOTHSTEP_NSTEPS * sizeof(size_t)); } bool decay_init(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms) { if (config_debug) { for (size_t i = 0; i < sizeof(decay_t); i++) { assert(((char *)decay)[i] == 0); } decay->ceil_npages = 0; } if (malloc_mutex_init(&decay->mtx, "decay", WITNESS_RANK_DECAY, malloc_mutex_rank_exclusive)) { return true; } decay->purging = false; decay_reinit(decay, cur_time, decay_ms); return false; } bool decay_ms_valid(ssize_t decay_ms) { if (decay_ms < -1) { return false; } if (decay_ms == -1 || (uint64_t)decay_ms <= NSTIME_SEC_MAX * KQU(1000)) { return true; } return false; } static void decay_maybe_update_time(decay_t *decay, nstime_t *new_time) { if (unlikely(!nstime_monotonic() && nstime_compare(&decay->epoch, new_time) > 0)) { /* * Time went backwards. Move the epoch back in time and * generate a new deadline, with the expectation that time * typically flows forward for long enough periods of time that * epochs complete. Unfortunately, this strategy is susceptible * to clock jitter triggering premature epoch advances, but * clock jitter estimation and compensation isn't feasible here * because calls into this code are event-driven. */ nstime_copy(&decay->epoch, new_time); decay_deadline_init(decay); } else { /* Verify that time does not go backwards. */ assert(nstime_compare(&decay->epoch, new_time) <= 0); } } static size_t decay_backlog_npages_limit(const decay_t *decay) { /* * For each element of decay_backlog, multiply by the corresponding * fixed-point smoothstep decay factor. Sum the products, then divide * to round down to the nearest whole number of pages. */ uint64_t sum = 0; for (unsigned i = 0; i < SMOOTHSTEP_NSTEPS; i++) { sum += decay->backlog[i] * h_steps[i]; } size_t npages_limit_backlog = (size_t)(sum >> SMOOTHSTEP_BFP); return npages_limit_backlog; } /* * Update backlog, assuming that 'nadvance_u64' time intervals have passed. * Trailing 'nadvance_u64' records should be erased and 'current_npages' is * placed as the newest record. */ static void decay_backlog_update(decay_t *decay, uint64_t nadvance_u64, size_t current_npages) { if (nadvance_u64 >= SMOOTHSTEP_NSTEPS) { memset(decay->backlog, 0, (SMOOTHSTEP_NSTEPS-1) * sizeof(size_t)); } else { size_t nadvance_z = (size_t)nadvance_u64; assert((uint64_t)nadvance_z == nadvance_u64); memmove(decay->backlog, &decay->backlog[nadvance_z], (SMOOTHSTEP_NSTEPS - nadvance_z) * sizeof(size_t)); if (nadvance_z > 1) { memset(&decay->backlog[SMOOTHSTEP_NSTEPS - nadvance_z], 0, (nadvance_z-1) * sizeof(size_t)); } } size_t npages_delta = (current_npages > decay->nunpurged) ? current_npages - decay->nunpurged : 0; decay->backlog[SMOOTHSTEP_NSTEPS-1] = npages_delta; if (config_debug) { if (current_npages > decay->ceil_npages) { decay->ceil_npages = current_npages; } size_t npages_limit = decay_backlog_npages_limit(decay); assert(decay->ceil_npages >= npages_limit); if (decay->ceil_npages > npages_limit) { decay->ceil_npages = npages_limit; } } } static inline bool decay_deadline_reached(const decay_t *decay, const nstime_t *time) { return (nstime_compare(&decay->deadline, time) <= 0); } uint64_t decay_npages_purge_in(decay_t *decay, nstime_t *time, size_t npages_new) { uint64_t decay_interval_ns = decay_epoch_duration_ns(decay); size_t n_epoch = (size_t)(nstime_ns(time) / decay_interval_ns); uint64_t npages_purge; if (n_epoch >= SMOOTHSTEP_NSTEPS) { npages_purge = npages_new; } else { uint64_t h_steps_max = h_steps[SMOOTHSTEP_NSTEPS - 1]; assert(h_steps_max >= h_steps[SMOOTHSTEP_NSTEPS - 1 - n_epoch]); npages_purge = npages_new * (h_steps_max - h_steps[SMOOTHSTEP_NSTEPS - 1 - n_epoch]); npages_purge >>= SMOOTHSTEP_BFP; } return npages_purge; } bool decay_maybe_advance_epoch(decay_t *decay, nstime_t *new_time, size_t npages_current) { /* Handle possible non-monotonicity of time. */ decay_maybe_update_time(decay, new_time); if (!decay_deadline_reached(decay, new_time)) { return false; } nstime_t delta; nstime_copy(&delta, new_time); nstime_subtract(&delta, &decay->epoch); uint64_t nadvance_u64 = nstime_divide(&delta, &decay->interval); assert(nadvance_u64 > 0); /* Add nadvance_u64 decay intervals to epoch. */ nstime_copy(&delta, &decay->interval); nstime_imultiply(&delta, nadvance_u64); nstime_add(&decay->epoch, &delta); /* Set a new deadline. */ decay_deadline_init(decay); /* Update the backlog. */ decay_backlog_update(decay, nadvance_u64, npages_current); decay->npages_limit = decay_backlog_npages_limit(decay); decay->nunpurged = (decay->npages_limit > npages_current) ? decay->npages_limit : npages_current; return true; } /* * Calculate how many pages should be purged after 'interval'. * * First, calculate how many pages should remain at the moment, then subtract * the number of pages that should remain after 'interval'. The difference is * how many pages should be purged until then. * * The number of pages that should remain at a specific moment is calculated * like this: pages(now) = sum(backlog[i] * h_steps[i]). After 'interval' * passes, backlog would shift 'interval' positions to the left and sigmoid * curve would be applied starting with backlog[interval]. * * The implementation doesn't directly map to the description, but it's * essentially the same calculation, optimized to avoid iterating over * [interval..SMOOTHSTEP_NSTEPS) twice. */ static inline size_t decay_npurge_after_interval(decay_t *decay, size_t interval) { size_t i; uint64_t sum = 0; for (i = 0; i < interval; i++) { sum += decay->backlog[i] * h_steps[i]; } for (; i < SMOOTHSTEP_NSTEPS; i++) { sum += decay->backlog[i] * (h_steps[i] - h_steps[i - interval]); } return (size_t)(sum >> SMOOTHSTEP_BFP); } uint64_t decay_ns_until_purge(decay_t *decay, size_t npages_current, uint64_t npages_threshold) { if (!decay_gradually(decay)) { return DECAY_UNBOUNDED_TIME_TO_PURGE; } uint64_t decay_interval_ns = decay_epoch_duration_ns(decay); assert(decay_interval_ns > 0); if (npages_current == 0) { unsigned i; for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) { if (decay->backlog[i] > 0) { break; } } if (i == SMOOTHSTEP_NSTEPS) { /* No dirty pages recorded. Sleep indefinitely. */ return DECAY_UNBOUNDED_TIME_TO_PURGE; } } if (npages_current <= npages_threshold) { /* Use max interval. */ return decay_interval_ns * SMOOTHSTEP_NSTEPS; } /* Minimal 2 intervals to ensure reaching next epoch deadline. */ size_t lb = 2; size_t ub = SMOOTHSTEP_NSTEPS; size_t npurge_lb, npurge_ub; npurge_lb = decay_npurge_after_interval(decay, lb); if (npurge_lb > npages_threshold) { return decay_interval_ns * lb; } npurge_ub = decay_npurge_after_interval(decay, ub); if (npurge_ub < npages_threshold) { return decay_interval_ns * ub; } unsigned n_search = 0; size_t target, npurge; while ((npurge_lb + npages_threshold < npurge_ub) && (lb + 2 < ub)) { target = (lb + ub) / 2; npurge = decay_npurge_after_interval(decay, target); if (npurge > npages_threshold) { ub = target; npurge_ub = npurge; } else { lb = target; npurge_lb = npurge; } assert(n_search < lg_floor(SMOOTHSTEP_NSTEPS) + 1); ++n_search; } return decay_interval_ns * (ub + lb) / 2; } redis-8.0.2/deps/jemalloc/src/div.c000066400000000000000000000030361501533116600170700ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/div.h" #include "jemalloc/internal/assert.h" /* * Suppose we have n = q * d, all integers. We know n and d, and want q = n / d. * * For any k, we have (here, all division is exact; not C-style rounding): * floor(ceil(2^k / d) * n / 2^k) = floor((2^k + r) / d * n / 2^k), where * r = (-2^k) mod d. * * Expanding this out: * ... = floor(2^k / d * n / 2^k + r / d * n / 2^k) * = floor(n / d + (r / d) * (n / 2^k)). * * The fractional part of n / d is 0 (because of the assumption that d divides n * exactly), so we have: * ... = n / d + floor((r / d) * (n / 2^k)) * * So that our initial expression is equal to the quantity we seek, so long as * (r / d) * (n / 2^k) < 1. * * r is a remainder mod d, so r < d and r / d < 1 always. We can make * n / 2 ^ k < 1 by setting k = 32. This gets us a value of magic that works. */ void div_init(div_info_t *div_info, size_t d) { /* Nonsensical. */ assert(d != 0); /* * This would make the value of magic too high to fit into a uint32_t * (we would want magic = 2^32 exactly). This would mess with code gen * on 32-bit machines. */ assert(d != 1); uint64_t two_to_k = ((uint64_t)1 << 32); uint32_t magic = (uint32_t)(two_to_k / d); /* * We want magic = ceil(2^k / d), but C gives us floor. We have to * increment it unless the result was exact (i.e. unless d is a power of * two). */ if (two_to_k % d != 0) { magic++; } div_info->magic = magic; #ifdef JEMALLOC_DEBUG div_info->d = d; #endif } redis-8.0.2/deps/jemalloc/src/ecache.c000066400000000000000000000015731501533116600175220ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/san.h" bool ecache_init(tsdn_t *tsdn, ecache_t *ecache, extent_state_t state, unsigned ind, bool delay_coalesce) { if (malloc_mutex_init(&ecache->mtx, "extents", WITNESS_RANK_EXTENTS, malloc_mutex_rank_exclusive)) { return true; } ecache->state = state; ecache->ind = ind; ecache->delay_coalesce = delay_coalesce; eset_init(&ecache->eset, state); eset_init(&ecache->guarded_eset, state); return false; } void ecache_prefork(tsdn_t *tsdn, ecache_t *ecache) { malloc_mutex_prefork(tsdn, &ecache->mtx); } void ecache_postfork_parent(tsdn_t *tsdn, ecache_t *ecache) { malloc_mutex_postfork_parent(tsdn, &ecache->mtx); } void ecache_postfork_child(tsdn_t *tsdn, ecache_t *ecache) { malloc_mutex_postfork_child(tsdn, &ecache->mtx); } redis-8.0.2/deps/jemalloc/src/edata.c000066400000000000000000000003501501533116600173600ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" ph_gen(, edata_avail, edata_t, avail_link, edata_esnead_comp) ph_gen(, edata_heap, edata_t, heap_link, edata_snad_comp) redis-8.0.2/deps/jemalloc/src/edata_cache.c000066400000000000000000000107731501533116600205150ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" bool edata_cache_init(edata_cache_t *edata_cache, base_t *base) { edata_avail_new(&edata_cache->avail); /* * This is not strictly necessary, since the edata_cache_t is only * created inside an arena, which is zeroed on creation. But this is * handy as a safety measure. */ atomic_store_zu(&edata_cache->count, 0, ATOMIC_RELAXED); if (malloc_mutex_init(&edata_cache->mtx, "edata_cache", WITNESS_RANK_EDATA_CACHE, malloc_mutex_rank_exclusive)) { return true; } edata_cache->base = base; return false; } edata_t * edata_cache_get(tsdn_t *tsdn, edata_cache_t *edata_cache) { malloc_mutex_lock(tsdn, &edata_cache->mtx); edata_t *edata = edata_avail_first(&edata_cache->avail); if (edata == NULL) { malloc_mutex_unlock(tsdn, &edata_cache->mtx); return base_alloc_edata(tsdn, edata_cache->base); } edata_avail_remove(&edata_cache->avail, edata); atomic_load_sub_store_zu(&edata_cache->count, 1); malloc_mutex_unlock(tsdn, &edata_cache->mtx); return edata; } void edata_cache_put(tsdn_t *tsdn, edata_cache_t *edata_cache, edata_t *edata) { malloc_mutex_lock(tsdn, &edata_cache->mtx); edata_avail_insert(&edata_cache->avail, edata); atomic_load_add_store_zu(&edata_cache->count, 1); malloc_mutex_unlock(tsdn, &edata_cache->mtx); } void edata_cache_prefork(tsdn_t *tsdn, edata_cache_t *edata_cache) { malloc_mutex_prefork(tsdn, &edata_cache->mtx); } void edata_cache_postfork_parent(tsdn_t *tsdn, edata_cache_t *edata_cache) { malloc_mutex_postfork_parent(tsdn, &edata_cache->mtx); } void edata_cache_postfork_child(tsdn_t *tsdn, edata_cache_t *edata_cache) { malloc_mutex_postfork_child(tsdn, &edata_cache->mtx); } void edata_cache_fast_init(edata_cache_fast_t *ecs, edata_cache_t *fallback) { edata_list_inactive_init(&ecs->list); ecs->fallback = fallback; ecs->disabled = false; } static void edata_cache_fast_try_fill_from_fallback(tsdn_t *tsdn, edata_cache_fast_t *ecs) { edata_t *edata; malloc_mutex_lock(tsdn, &ecs->fallback->mtx); for (int i = 0; i < EDATA_CACHE_FAST_FILL; i++) { edata = edata_avail_remove_first(&ecs->fallback->avail); if (edata == NULL) { break; } edata_list_inactive_append(&ecs->list, edata); atomic_load_sub_store_zu(&ecs->fallback->count, 1); } malloc_mutex_unlock(tsdn, &ecs->fallback->mtx); } edata_t * edata_cache_fast_get(tsdn_t *tsdn, edata_cache_fast_t *ecs) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_EDATA_CACHE, 0); if (ecs->disabled) { assert(edata_list_inactive_first(&ecs->list) == NULL); return edata_cache_get(tsdn, ecs->fallback); } edata_t *edata = edata_list_inactive_first(&ecs->list); if (edata != NULL) { edata_list_inactive_remove(&ecs->list, edata); return edata; } /* Slow path; requires synchronization. */ edata_cache_fast_try_fill_from_fallback(tsdn, ecs); edata = edata_list_inactive_first(&ecs->list); if (edata != NULL) { edata_list_inactive_remove(&ecs->list, edata); } else { /* * Slowest path (fallback was also empty); allocate something * new. */ edata = base_alloc_edata(tsdn, ecs->fallback->base); } return edata; } static void edata_cache_fast_flush_all(tsdn_t *tsdn, edata_cache_fast_t *ecs) { /* * You could imagine smarter cache management policies (like * only flushing down to some threshold in anticipation of * future get requests). But just flushing everything provides * a good opportunity to defrag too, and lets us share code between the * flush and disable pathways. */ edata_t *edata; size_t nflushed = 0; malloc_mutex_lock(tsdn, &ecs->fallback->mtx); while ((edata = edata_list_inactive_first(&ecs->list)) != NULL) { edata_list_inactive_remove(&ecs->list, edata); edata_avail_insert(&ecs->fallback->avail, edata); nflushed++; } atomic_load_add_store_zu(&ecs->fallback->count, nflushed); malloc_mutex_unlock(tsdn, &ecs->fallback->mtx); } void edata_cache_fast_put(tsdn_t *tsdn, edata_cache_fast_t *ecs, edata_t *edata) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_EDATA_CACHE, 0); if (ecs->disabled) { assert(edata_list_inactive_first(&ecs->list) == NULL); edata_cache_put(tsdn, ecs->fallback, edata); return; } /* * Prepend rather than append, to do LIFO ordering in the hopes of some * cache locality. */ edata_list_inactive_prepend(&ecs->list, edata); } void edata_cache_fast_disable(tsdn_t *tsdn, edata_cache_fast_t *ecs) { edata_cache_fast_flush_all(tsdn, ecs); ecs->disabled = true; } redis-8.0.2/deps/jemalloc/src/ehooks.c000066400000000000000000000176301501533116600176030ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/extent_mmap.h" void ehooks_init(ehooks_t *ehooks, extent_hooks_t *extent_hooks, unsigned ind) { /* All other hooks are optional; this one is not. */ assert(extent_hooks->alloc != NULL); ehooks->ind = ind; ehooks_set_extent_hooks_ptr(ehooks, extent_hooks); } /* * If the caller specifies (!*zero), it is still possible to receive zeroed * memory, in which case *zero is toggled to true. arena_extent_alloc() takes * advantage of this to avoid demanding zeroed extents, but taking advantage of * them if they are returned. */ static void * extent_alloc_core(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, dss_prec_t dss_prec) { void *ret; assert(size != 0); assert(alignment != 0); /* "primary" dss. */ if (have_dss && dss_prec == dss_prec_primary && (ret = extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero, commit)) != NULL) { return ret; } /* mmap. */ if ((ret = extent_alloc_mmap(new_addr, size, alignment, zero, commit)) != NULL) { return ret; } /* "secondary" dss. */ if (have_dss && dss_prec == dss_prec_secondary && (ret = extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero, commit)) != NULL) { return ret; } /* All strategies for allocation failed. */ return NULL; } void * ehooks_default_alloc_impl(tsdn_t *tsdn, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { arena_t *arena = arena_get(tsdn, arena_ind, false); /* NULL arena indicates arena_create. */ assert(arena != NULL || alignment == HUGEPAGE); dss_prec_t dss = (arena == NULL) ? dss_prec_disabled : (dss_prec_t)atomic_load_u(&arena->dss_prec, ATOMIC_RELAXED); void *ret = extent_alloc_core(tsdn, arena, new_addr, size, alignment, zero, commit, dss); if (have_madvise_huge && ret) { pages_set_thp_state(ret, size); } return ret; } static void * ehooks_default_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { return ehooks_default_alloc_impl(tsdn_fetch(), new_addr, size, ALIGNMENT_CEILING(alignment, PAGE), zero, commit, arena_ind); } bool ehooks_default_dalloc_impl(void *addr, size_t size) { if (!have_dss || !extent_in_dss(addr)) { return extent_dalloc_mmap(addr, size); } return true; } static bool ehooks_default_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind) { return ehooks_default_dalloc_impl(addr, size); } void ehooks_default_destroy_impl(void *addr, size_t size) { if (!have_dss || !extent_in_dss(addr)) { pages_unmap(addr, size); } } static void ehooks_default_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind) { ehooks_default_destroy_impl(addr, size); } bool ehooks_default_commit_impl(void *addr, size_t offset, size_t length) { return pages_commit((void *)((uintptr_t)addr + (uintptr_t)offset), length); } static bool ehooks_default_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { return ehooks_default_commit_impl(addr, offset, length); } bool ehooks_default_decommit_impl(void *addr, size_t offset, size_t length) { return pages_decommit((void *)((uintptr_t)addr + (uintptr_t)offset), length); } static bool ehooks_default_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { return ehooks_default_decommit_impl(addr, offset, length); } #ifdef PAGES_CAN_PURGE_LAZY bool ehooks_default_purge_lazy_impl(void *addr, size_t offset, size_t length) { return pages_purge_lazy((void *)((uintptr_t)addr + (uintptr_t)offset), length); } static bool ehooks_default_purge_lazy(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { assert(addr != NULL); assert((offset & PAGE_MASK) == 0); assert(length != 0); assert((length & PAGE_MASK) == 0); return ehooks_default_purge_lazy_impl(addr, offset, length); } #endif #ifdef PAGES_CAN_PURGE_FORCED bool ehooks_default_purge_forced_impl(void *addr, size_t offset, size_t length) { return pages_purge_forced((void *)((uintptr_t)addr + (uintptr_t)offset), length); } static bool ehooks_default_purge_forced(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { assert(addr != NULL); assert((offset & PAGE_MASK) == 0); assert(length != 0); assert((length & PAGE_MASK) == 0); return ehooks_default_purge_forced_impl(addr, offset, length); } #endif bool ehooks_default_split_impl() { if (!maps_coalesce) { /* * Without retain, only whole regions can be purged (required by * MEM_RELEASE on Windows) -- therefore disallow splitting. See * comments in extent_head_no_merge(). */ return !opt_retain; } return false; } static bool ehooks_default_split(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t size_a, size_t size_b, bool committed, unsigned arena_ind) { return ehooks_default_split_impl(); } bool ehooks_default_merge_impl(tsdn_t *tsdn, void *addr_a, void *addr_b) { assert(addr_a < addr_b); /* * For non-DSS cases -- * a) W/o maps_coalesce, merge is not always allowed (Windows): * 1) w/o retain, never merge (first branch below). * 2) with retain, only merge extents from the same VirtualAlloc * region (in which case MEM_DECOMMIT is utilized for purging). * * b) With maps_coalesce, it's always possible to merge. * 1) w/o retain, always allow merge (only about dirty / muzzy). * 2) with retain, to preserve the SN / first-fit, merge is still * disallowed if b is a head extent, i.e. no merging across * different mmap regions. * * a2) and b2) are implemented in emap_try_acquire_edata_neighbor, and * sanity checked in the second branch below. */ if (!maps_coalesce && !opt_retain) { return true; } if (config_debug) { edata_t *a = emap_edata_lookup(tsdn, &arena_emap_global, addr_a); bool head_a = edata_is_head_get(a); edata_t *b = emap_edata_lookup(tsdn, &arena_emap_global, addr_b); bool head_b = edata_is_head_get(b); emap_assert_mapped(tsdn, &arena_emap_global, a); emap_assert_mapped(tsdn, &arena_emap_global, b); assert(extent_neighbor_head_state_mergeable(head_a, head_b, /* forward */ true)); } if (have_dss && !extent_dss_mergeable(addr_a, addr_b)) { return true; } return false; } bool ehooks_default_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, void *addr_b, size_t size_b, bool committed, unsigned arena_ind) { tsdn_t *tsdn = tsdn_fetch(); return ehooks_default_merge_impl(tsdn, addr_a, addr_b); } void ehooks_default_zero_impl(void *addr, size_t size) { /* * By default, we try to zero out memory using OS-provided demand-zeroed * pages. If the user has specifically requested hugepages, though, we * don't want to purge in the middle of a hugepage (which would break it * up), so we act conservatively and use memset. */ bool needs_memset = true; if (opt_thp != thp_mode_always) { needs_memset = pages_purge_forced(addr, size); } if (needs_memset) { memset(addr, 0, size); } } void ehooks_default_guard_impl(void *guard1, void *guard2) { pages_mark_guards(guard1, guard2); } void ehooks_default_unguard_impl(void *guard1, void *guard2) { pages_unmark_guards(guard1, guard2); } const extent_hooks_t ehooks_default_extent_hooks = { ehooks_default_alloc, ehooks_default_dalloc, ehooks_default_destroy, ehooks_default_commit, ehooks_default_decommit, #ifdef PAGES_CAN_PURGE_LAZY ehooks_default_purge_lazy, #else NULL, #endif #ifdef PAGES_CAN_PURGE_FORCED ehooks_default_purge_forced, #else NULL, #endif ehooks_default_split, ehooks_default_merge }; redis-8.0.2/deps/jemalloc/src/emap.c000066400000000000000000000313621501533116600172330ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/emap.h" enum emap_lock_result_e { emap_lock_result_success, emap_lock_result_failure, emap_lock_result_no_extent }; typedef enum emap_lock_result_e emap_lock_result_t; bool emap_init(emap_t *emap, base_t *base, bool zeroed) { return rtree_new(&emap->rtree, base, zeroed); } void emap_update_edata_state(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_state_t state) { witness_assert_positive_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); edata_state_set(edata, state); EMAP_DECLARE_RTREE_CTX; rtree_leaf_elm_t *elm1 = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata), /* dependent */ true, /* init_missing */ false); assert(elm1 != NULL); rtree_leaf_elm_t *elm2 = edata_size_get(edata) == PAGE ? NULL : rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_last_get(edata), /* dependent */ true, /* init_missing */ false); rtree_leaf_elm_state_update(tsdn, &emap->rtree, elm1, elm2, state); emap_assert_mapped(tsdn, emap, edata); } static inline edata_t * emap_try_acquire_edata_neighbor_impl(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_pai_t pai, extent_state_t expected_state, bool forward, bool expanding) { witness_assert_positive_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); assert(!edata_guarded_get(edata)); assert(!expanding || forward); assert(!edata_state_in_transition(expected_state)); assert(expected_state == extent_state_dirty || expected_state == extent_state_muzzy || expected_state == extent_state_retained); void *neighbor_addr = forward ? edata_past_get(edata) : edata_before_get(edata); /* * This is subtle; the rtree code asserts that its input pointer is * non-NULL, and this is a useful thing to check. But it's possible * that edata corresponds to an address of (void *)PAGE (in practice, * this has only been observed on FreeBSD when address-space * randomization is on, but it could in principle happen anywhere). In * this case, edata_before_get(edata) is NULL, triggering the assert. */ if (neighbor_addr == NULL) { return NULL; } EMAP_DECLARE_RTREE_CTX; rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)neighbor_addr, /* dependent*/ false, /* init_missing */ false); if (elm == NULL) { return NULL; } rtree_contents_t neighbor_contents = rtree_leaf_elm_read(tsdn, &emap->rtree, elm, /* dependent */ true); if (!extent_can_acquire_neighbor(edata, neighbor_contents, pai, expected_state, forward, expanding)) { return NULL; } /* From this point, the neighbor edata can be safely acquired. */ edata_t *neighbor = neighbor_contents.edata; assert(edata_state_get(neighbor) == expected_state); emap_update_edata_state(tsdn, emap, neighbor, extent_state_merging); if (expanding) { extent_assert_can_expand(edata, neighbor); } else { extent_assert_can_coalesce(edata, neighbor); } return neighbor; } edata_t * emap_try_acquire_edata_neighbor(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_pai_t pai, extent_state_t expected_state, bool forward) { return emap_try_acquire_edata_neighbor_impl(tsdn, emap, edata, pai, expected_state, forward, /* expand */ false); } edata_t * emap_try_acquire_edata_neighbor_expand(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_pai_t pai, extent_state_t expected_state) { /* Try expanding forward. */ return emap_try_acquire_edata_neighbor_impl(tsdn, emap, edata, pai, expected_state, /* forward */ true, /* expand */ true); } void emap_release_edata(tsdn_t *tsdn, emap_t *emap, edata_t *edata, extent_state_t new_state) { assert(emap_edata_in_transition(tsdn, emap, edata)); assert(emap_edata_is_acquired(tsdn, emap, edata)); emap_update_edata_state(tsdn, emap, edata, new_state); } static bool emap_rtree_leaf_elms_lookup(tsdn_t *tsdn, emap_t *emap, rtree_ctx_t *rtree_ctx, const edata_t *edata, bool dependent, bool init_missing, rtree_leaf_elm_t **r_elm_a, rtree_leaf_elm_t **r_elm_b) { *r_elm_a = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata), dependent, init_missing); if (!dependent && *r_elm_a == NULL) { return true; } assert(*r_elm_a != NULL); *r_elm_b = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_last_get(edata), dependent, init_missing); if (!dependent && *r_elm_b == NULL) { return true; } assert(*r_elm_b != NULL); return false; } static void emap_rtree_write_acquired(tsdn_t *tsdn, emap_t *emap, rtree_leaf_elm_t *elm_a, rtree_leaf_elm_t *elm_b, edata_t *edata, szind_t szind, bool slab) { rtree_contents_t contents; contents.edata = edata; contents.metadata.szind = szind; contents.metadata.slab = slab; contents.metadata.is_head = (edata == NULL) ? false : edata_is_head_get(edata); contents.metadata.state = (edata == NULL) ? 0 : edata_state_get(edata); rtree_leaf_elm_write(tsdn, &emap->rtree, elm_a, contents); if (elm_b != NULL) { rtree_leaf_elm_write(tsdn, &emap->rtree, elm_b, contents); } } bool emap_register_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind, bool slab) { assert(edata_state_get(edata) == extent_state_active); EMAP_DECLARE_RTREE_CTX; rtree_leaf_elm_t *elm_a, *elm_b; bool err = emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, edata, false, true, &elm_a, &elm_b); if (err) { return true; } assert(rtree_leaf_elm_read(tsdn, &emap->rtree, elm_a, /* dependent */ false).edata == NULL); assert(rtree_leaf_elm_read(tsdn, &emap->rtree, elm_b, /* dependent */ false).edata == NULL); emap_rtree_write_acquired(tsdn, emap, elm_a, elm_b, edata, szind, slab); return false; } /* Invoked *after* emap_register_boundary. */ void emap_register_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind) { EMAP_DECLARE_RTREE_CTX; assert(edata_slab_get(edata)); assert(edata_state_get(edata) == extent_state_active); if (config_debug) { /* Making sure the boundary is registered already. */ rtree_leaf_elm_t *elm_a, *elm_b; bool err = emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, edata, /* dependent */ true, /* init_missing */ false, &elm_a, &elm_b); assert(!err); rtree_contents_t contents_a, contents_b; contents_a = rtree_leaf_elm_read(tsdn, &emap->rtree, elm_a, /* dependent */ true); contents_b = rtree_leaf_elm_read(tsdn, &emap->rtree, elm_b, /* dependent */ true); assert(contents_a.edata == edata && contents_b.edata == edata); assert(contents_a.metadata.slab && contents_b.metadata.slab); } rtree_contents_t contents; contents.edata = edata; contents.metadata.szind = szind; contents.metadata.slab = true; contents.metadata.state = extent_state_active; contents.metadata.is_head = false; /* Not allowed to access. */ assert(edata_size_get(edata) > (2 << LG_PAGE)); rtree_write_range(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata) + PAGE, (uintptr_t)edata_last_get(edata) - PAGE, contents); } void emap_deregister_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { /* * The edata must be either in an acquired state, or protected by state * based locks. */ if (!emap_edata_is_acquired(tsdn, emap, edata)) { witness_assert_positive_depth_to_rank( tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); } EMAP_DECLARE_RTREE_CTX; rtree_leaf_elm_t *elm_a, *elm_b; emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, edata, true, false, &elm_a, &elm_b); emap_rtree_write_acquired(tsdn, emap, elm_a, elm_b, NULL, SC_NSIZES, false); } void emap_deregister_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { EMAP_DECLARE_RTREE_CTX; assert(edata_slab_get(edata)); if (edata_size_get(edata) > (2 << LG_PAGE)) { rtree_clear_range(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata) + PAGE, (uintptr_t)edata_last_get(edata) - PAGE); } } void emap_remap(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind, bool slab) { EMAP_DECLARE_RTREE_CTX; if (szind != SC_NSIZES) { rtree_contents_t contents; contents.edata = edata; contents.metadata.szind = szind; contents.metadata.slab = slab; contents.metadata.is_head = edata_is_head_get(edata); contents.metadata.state = edata_state_get(edata); rtree_write(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_addr_get(edata), contents); /* * Recall that this is called only for active->inactive and * inactive->active transitions (since only active extents have * meaningful values for szind and slab). Active, non-slab * extents only need to handle lookups at their head (on * deallocation), so we don't bother filling in the end * boundary. * * For slab extents, we do the end-mapping change. This still * leaves the interior unmodified; an emap_register_interior * call is coming in those cases, though. */ if (slab && edata_size_get(edata) > PAGE) { uintptr_t key = (uintptr_t)edata_past_get(edata) - (uintptr_t)PAGE; rtree_write(tsdn, &emap->rtree, rtree_ctx, key, contents); } } } bool emap_split_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *edata, size_t size_a, edata_t *trail, size_t size_b) { EMAP_DECLARE_RTREE_CTX; /* * We use incorrect constants for things like arena ind, zero, ranged, * and commit state, and head status. This is a fake edata_t, used to * facilitate a lookup. */ edata_t lead = {0}; edata_init(&lead, 0U, edata_addr_get(edata), size_a, false, 0, 0, extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, &lead, false, true, &prepare->lead_elm_a, &prepare->lead_elm_b); emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, trail, false, true, &prepare->trail_elm_a, &prepare->trail_elm_b); if (prepare->lead_elm_a == NULL || prepare->lead_elm_b == NULL || prepare->trail_elm_a == NULL || prepare->trail_elm_b == NULL) { return true; } return false; } void emap_split_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *lead, size_t size_a, edata_t *trail, size_t size_b) { /* * We should think about not writing to the lead leaf element. We can * get into situations where a racing realloc-like call can disagree * with a size lookup request. I think it's fine to declare that these * situations are race bugs, but there's an argument to be made that for * things like xallocx, a size lookup call should return either the old * size or the new size, but not anything else. */ emap_rtree_write_acquired(tsdn, emap, prepare->lead_elm_a, prepare->lead_elm_b, lead, SC_NSIZES, /* slab */ false); emap_rtree_write_acquired(tsdn, emap, prepare->trail_elm_a, prepare->trail_elm_b, trail, SC_NSIZES, /* slab */ false); } void emap_merge_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *lead, edata_t *trail) { EMAP_DECLARE_RTREE_CTX; emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, lead, true, false, &prepare->lead_elm_a, &prepare->lead_elm_b); emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, trail, true, false, &prepare->trail_elm_a, &prepare->trail_elm_b); } void emap_merge_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, edata_t *lead, edata_t *trail) { rtree_contents_t clear_contents; clear_contents.edata = NULL; clear_contents.metadata.szind = SC_NSIZES; clear_contents.metadata.slab = false; clear_contents.metadata.is_head = false; clear_contents.metadata.state = (extent_state_t)0; if (prepare->lead_elm_b != NULL) { rtree_leaf_elm_write(tsdn, &emap->rtree, prepare->lead_elm_b, clear_contents); } rtree_leaf_elm_t *merged_b; if (prepare->trail_elm_b != NULL) { rtree_leaf_elm_write(tsdn, &emap->rtree, prepare->trail_elm_a, clear_contents); merged_b = prepare->trail_elm_b; } else { merged_b = prepare->trail_elm_a; } emap_rtree_write_acquired(tsdn, emap, prepare->lead_elm_a, merged_b, lead, SC_NSIZES, false); } void emap_do_assert_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { EMAP_DECLARE_RTREE_CTX; rtree_contents_t contents = rtree_read(tsdn, &emap->rtree, rtree_ctx, (uintptr_t)edata_base_get(edata)); assert(contents.edata == edata); assert(contents.metadata.is_head == edata_is_head_get(edata)); assert(contents.metadata.state == edata_state_get(edata)); } void emap_do_assert_not_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { emap_full_alloc_ctx_t context1 = {0}; emap_full_alloc_ctx_try_lookup(tsdn, emap, edata_base_get(edata), &context1); assert(context1.edata == NULL); emap_full_alloc_ctx_t context2 = {0}; emap_full_alloc_ctx_try_lookup(tsdn, emap, edata_last_get(edata), &context2); assert(context2.edata == NULL); } redis-8.0.2/deps/jemalloc/src/eset.c000066400000000000000000000212051501533116600172440ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/eset.h" #define ESET_NPSIZES (SC_NPSIZES + 1) static void eset_bin_init(eset_bin_t *bin) { edata_heap_new(&bin->heap); /* * heap_min doesn't need initialization; it gets filled in when the bin * goes from non-empty to empty. */ } static void eset_bin_stats_init(eset_bin_stats_t *bin_stats) { atomic_store_zu(&bin_stats->nextents, 0, ATOMIC_RELAXED); atomic_store_zu(&bin_stats->nbytes, 0, ATOMIC_RELAXED); } void eset_init(eset_t *eset, extent_state_t state) { for (unsigned i = 0; i < ESET_NPSIZES; i++) { eset_bin_init(&eset->bins[i]); eset_bin_stats_init(&eset->bin_stats[i]); } fb_init(eset->bitmap, ESET_NPSIZES); edata_list_inactive_init(&eset->lru); eset->state = state; } size_t eset_npages_get(eset_t *eset) { return atomic_load_zu(&eset->npages, ATOMIC_RELAXED); } size_t eset_nextents_get(eset_t *eset, pszind_t pind) { return atomic_load_zu(&eset->bin_stats[pind].nextents, ATOMIC_RELAXED); } size_t eset_nbytes_get(eset_t *eset, pszind_t pind) { return atomic_load_zu(&eset->bin_stats[pind].nbytes, ATOMIC_RELAXED); } static void eset_stats_add(eset_t *eset, pszind_t pind, size_t sz) { size_t cur = atomic_load_zu(&eset->bin_stats[pind].nextents, ATOMIC_RELAXED); atomic_store_zu(&eset->bin_stats[pind].nextents, cur + 1, ATOMIC_RELAXED); cur = atomic_load_zu(&eset->bin_stats[pind].nbytes, ATOMIC_RELAXED); atomic_store_zu(&eset->bin_stats[pind].nbytes, cur + sz, ATOMIC_RELAXED); } static void eset_stats_sub(eset_t *eset, pszind_t pind, size_t sz) { size_t cur = atomic_load_zu(&eset->bin_stats[pind].nextents, ATOMIC_RELAXED); atomic_store_zu(&eset->bin_stats[pind].nextents, cur - 1, ATOMIC_RELAXED); cur = atomic_load_zu(&eset->bin_stats[pind].nbytes, ATOMIC_RELAXED); atomic_store_zu(&eset->bin_stats[pind].nbytes, cur - sz, ATOMIC_RELAXED); } void eset_insert(eset_t *eset, edata_t *edata) { assert(edata_state_get(edata) == eset->state); size_t size = edata_size_get(edata); size_t psz = sz_psz_quantize_floor(size); pszind_t pind = sz_psz2ind(psz); edata_cmp_summary_t edata_cmp_summary = edata_cmp_summary_get(edata); if (edata_heap_empty(&eset->bins[pind].heap)) { fb_set(eset->bitmap, ESET_NPSIZES, (size_t)pind); /* Only element is automatically the min element. */ eset->bins[pind].heap_min = edata_cmp_summary; } else { /* * There's already a min element; update the summary if we're * about to insert a lower one. */ if (edata_cmp_summary_comp(edata_cmp_summary, eset->bins[pind].heap_min) < 0) { eset->bins[pind].heap_min = edata_cmp_summary; } } edata_heap_insert(&eset->bins[pind].heap, edata); if (config_stats) { eset_stats_add(eset, pind, size); } edata_list_inactive_append(&eset->lru, edata); size_t npages = size >> LG_PAGE; /* * All modifications to npages hold the mutex (as asserted above), so we * don't need an atomic fetch-add; we can get by with a load followed by * a store. */ size_t cur_eset_npages = atomic_load_zu(&eset->npages, ATOMIC_RELAXED); atomic_store_zu(&eset->npages, cur_eset_npages + npages, ATOMIC_RELAXED); } void eset_remove(eset_t *eset, edata_t *edata) { assert(edata_state_get(edata) == eset->state || edata_state_in_transition(edata_state_get(edata))); size_t size = edata_size_get(edata); size_t psz = sz_psz_quantize_floor(size); pszind_t pind = sz_psz2ind(psz); if (config_stats) { eset_stats_sub(eset, pind, size); } edata_cmp_summary_t edata_cmp_summary = edata_cmp_summary_get(edata); edata_heap_remove(&eset->bins[pind].heap, edata); if (edata_heap_empty(&eset->bins[pind].heap)) { fb_unset(eset->bitmap, ESET_NPSIZES, (size_t)pind); } else { /* * This is a little weird; we compare if the summaries are * equal, rather than if the edata we removed was the heap * minimum. The reason why is that getting the heap minimum * can cause a pairing heap merge operation. We can avoid this * if we only update the min if it's changed, in which case the * summaries of the removed element and the min element should * compare equal. */ if (edata_cmp_summary_comp(edata_cmp_summary, eset->bins[pind].heap_min) == 0) { eset->bins[pind].heap_min = edata_cmp_summary_get( edata_heap_first(&eset->bins[pind].heap)); } } edata_list_inactive_remove(&eset->lru, edata); size_t npages = size >> LG_PAGE; /* * As in eset_insert, we hold eset->mtx and so don't need atomic * operations for updating eset->npages. */ size_t cur_extents_npages = atomic_load_zu(&eset->npages, ATOMIC_RELAXED); assert(cur_extents_npages >= npages); atomic_store_zu(&eset->npages, cur_extents_npages - (size >> LG_PAGE), ATOMIC_RELAXED); } /* * Find an extent with size [min_size, max_size) to satisfy the alignment * requirement. For each size, try only the first extent in the heap. */ static edata_t * eset_fit_alignment(eset_t *eset, size_t min_size, size_t max_size, size_t alignment) { pszind_t pind = sz_psz2ind(sz_psz_quantize_ceil(min_size)); pszind_t pind_max = sz_psz2ind(sz_psz_quantize_ceil(max_size)); for (pszind_t i = (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)pind); i < pind_max; i = (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)i + 1)) { assert(i < SC_NPSIZES); assert(!edata_heap_empty(&eset->bins[i].heap)); edata_t *edata = edata_heap_first(&eset->bins[i].heap); uintptr_t base = (uintptr_t)edata_base_get(edata); size_t candidate_size = edata_size_get(edata); assert(candidate_size >= min_size); uintptr_t next_align = ALIGNMENT_CEILING((uintptr_t)base, PAGE_CEILING(alignment)); if (base > next_align || base + candidate_size <= next_align) { /* Overflow or not crossing the next alignment. */ continue; } size_t leadsize = next_align - base; if (candidate_size - leadsize >= min_size) { return edata; } } return NULL; } /* * Do first-fit extent selection, i.e. select the oldest/lowest extent that is * large enough. * * lg_max_fit is the (log of the) maximum ratio between the requested size and * the returned size that we'll allow. This can reduce fragmentation by * avoiding reusing and splitting large extents for smaller sizes. In practice, * it's set to opt_lg_extent_max_active_fit for the dirty eset and SC_PTR_BITS * for others. */ static edata_t * eset_first_fit(eset_t *eset, size_t size, bool exact_only, unsigned lg_max_fit) { edata_t *ret = NULL; edata_cmp_summary_t ret_summ JEMALLOC_CC_SILENCE_INIT({0}); pszind_t pind = sz_psz2ind(sz_psz_quantize_ceil(size)); if (exact_only) { return edata_heap_empty(&eset->bins[pind].heap) ? NULL : edata_heap_first(&eset->bins[pind].heap); } for (pszind_t i = (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)pind); i < ESET_NPSIZES; i = (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)i + 1)) { assert(!edata_heap_empty(&eset->bins[i].heap)); if (lg_max_fit == SC_PTR_BITS) { /* * We'll shift by this below, and shifting out all the * bits is undefined. Decreasing is safe, since the * page size is larger than 1 byte. */ lg_max_fit = SC_PTR_BITS - 1; } if ((sz_pind2sz(i) >> lg_max_fit) > size) { break; } if (ret == NULL || edata_cmp_summary_comp( eset->bins[i].heap_min, ret_summ) < 0) { /* * We grab the edata as early as possible, even though * we might change it later. Practically, a large * portion of eset_fit calls succeed at the first valid * index, so this doesn't cost much, and we get the * effect of prefetching the edata as early as possible. */ edata_t *edata = edata_heap_first(&eset->bins[i].heap); assert(edata_size_get(edata) >= size); assert(ret == NULL || edata_snad_comp(edata, ret) < 0); assert(ret == NULL || edata_cmp_summary_comp( eset->bins[i].heap_min, edata_cmp_summary_get(edata)) == 0); ret = edata; ret_summ = eset->bins[i].heap_min; } if (i == SC_NPSIZES) { break; } assert(i < SC_NPSIZES); } return ret; } edata_t * eset_fit(eset_t *eset, size_t esize, size_t alignment, bool exact_only, unsigned lg_max_fit) { size_t max_size = esize + PAGE_CEILING(alignment) - PAGE; /* Beware size_t wrap-around. */ if (max_size < esize) { return NULL; } edata_t *edata = eset_first_fit(eset, max_size, exact_only, lg_max_fit); if (alignment > PAGE && edata == NULL) { /* * max_size guarantees the alignment requirement but is rather * pessimistic. Next we try to satisfy the aligned allocation * with sizes in [esize, max_size). */ edata = eset_fit_alignment(eset, esize, max_size, alignment); } return edata; } redis-8.0.2/deps/jemalloc/src/exp_grow.c000066400000000000000000000003631501533116600201400ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" void exp_grow_init(exp_grow_t *exp_grow) { exp_grow->next = sz_psz2ind(HUGEPAGE); exp_grow->limit = sz_psz2ind(SC_LARGE_MAXCLASS); } redis-8.0.2/deps/jemalloc/src/extent.c000066400000000000000000001201641501533116600176170ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/emap.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/ph.h" #include "jemalloc/internal/mutex.h" /******************************************************************************/ /* Data. */ size_t opt_lg_extent_max_active_fit = LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT; static bool extent_commit_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length, bool growing_retained); static bool extent_purge_lazy_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length, bool growing_retained); static bool extent_purge_forced_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length, bool growing_retained); static edata_t *extent_split_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks); static bool extent_merge_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *a, edata_t *b, bool holding_core_locks); /* Used exclusively for gdump triggering. */ static atomic_zu_t curpages; static atomic_zu_t highpages; /******************************************************************************/ /* * Function prototypes for static functions that are referenced prior to * definition. */ static void extent_deregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata); static edata_t *extent_recycle(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t usize, size_t alignment, bool zero, bool *commit, bool growing_retained, bool guarded); static edata_t *extent_try_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata, bool *coalesced); static edata_t *extent_alloc_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool *commit, bool guarded); /******************************************************************************/ size_t extent_sn_next(pac_t *pac) { return atomic_fetch_add_zu(&pac->extent_sn_next, 1, ATOMIC_RELAXED); } static inline bool extent_may_force_decay(pac_t *pac) { return !(pac_decay_ms_get(pac, extent_state_dirty) == -1 || pac_decay_ms_get(pac, extent_state_muzzy) == -1); } static bool extent_try_delayed_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata) { emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active); bool coalesced; edata = extent_try_coalesce(tsdn, pac, ehooks, ecache, edata, &coalesced); emap_update_edata_state(tsdn, pac->emap, edata, ecache->state); if (!coalesced) { return true; } eset_insert(&ecache->eset, edata); return false; } edata_t * ecache_alloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool guarded) { assert(size != 0); assert(alignment != 0); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); bool commit = true; edata_t *edata = extent_recycle(tsdn, pac, ehooks, ecache, expand_edata, size, alignment, zero, &commit, false, guarded); assert(edata == NULL || edata_pai_get(edata) == EXTENT_PAI_PAC); assert(edata == NULL || edata_guarded_get(edata) == guarded); return edata; } edata_t * ecache_alloc_grow(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool guarded) { assert(size != 0); assert(alignment != 0); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); bool commit = true; edata_t *edata = extent_alloc_retained(tsdn, pac, ehooks, expand_edata, size, alignment, zero, &commit, guarded); if (edata == NULL) { if (opt_retain && expand_edata != NULL) { /* * When retain is enabled and trying to expand, we do * not attempt extent_alloc_wrapper which does mmap that * is very unlikely to succeed (unless it happens to be * at the end). */ return NULL; } if (guarded) { /* * Means no cached guarded extents available (and no * grow_retained was attempted). The pac_alloc flow * will alloc regular extents to make new guarded ones. */ return NULL; } void *new_addr = (expand_edata == NULL) ? NULL : edata_past_get(expand_edata); edata = extent_alloc_wrapper(tsdn, pac, ehooks, new_addr, size, alignment, zero, &commit, /* growing_retained */ false); } assert(edata == NULL || edata_pai_get(edata) == EXTENT_PAI_PAC); return edata; } void ecache_dalloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata) { assert(edata_base_get(edata) != NULL); assert(edata_size_get(edata) != 0); assert(edata_pai_get(edata) == EXTENT_PAI_PAC); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); edata_addr_set(edata, edata_base_get(edata)); edata_zeroed_set(edata, false); extent_record(tsdn, pac, ehooks, ecache, edata); } edata_t * ecache_evict(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, size_t npages_min) { malloc_mutex_lock(tsdn, &ecache->mtx); /* * Get the LRU coalesced extent, if any. If coalescing was delayed, * the loop will iterate until the LRU extent is fully coalesced. */ edata_t *edata; while (true) { /* Get the LRU extent, if any. */ eset_t *eset = &ecache->eset; edata = edata_list_inactive_first(&eset->lru); if (edata == NULL) { /* * Next check if there are guarded extents. They are * more expensive to purge (since they are not * mergeable), thus in favor of caching them longer. */ eset = &ecache->guarded_eset; edata = edata_list_inactive_first(&eset->lru); if (edata == NULL) { goto label_return; } } /* Check the eviction limit. */ size_t extents_npages = ecache_npages_get(ecache); if (extents_npages <= npages_min) { edata = NULL; goto label_return; } eset_remove(eset, edata); if (!ecache->delay_coalesce || edata_guarded_get(edata)) { break; } /* Try to coalesce. */ if (extent_try_delayed_coalesce(tsdn, pac, ehooks, ecache, edata)) { break; } /* * The LRU extent was just coalesced and the result placed in * the LRU at its neighbor's position. Start over. */ } /* * Either mark the extent active or deregister it to protect against * concurrent operations. */ switch (ecache->state) { case extent_state_active: not_reached(); case extent_state_dirty: case extent_state_muzzy: emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active); break; case extent_state_retained: extent_deregister(tsdn, pac, edata); break; default: not_reached(); } label_return: malloc_mutex_unlock(tsdn, &ecache->mtx); return edata; } /* * This can only happen when we fail to allocate a new extent struct (which * indicates OOM), e.g. when trying to split an existing extent. */ static void extents_abandon_vm(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata, bool growing_retained) { size_t sz = edata_size_get(edata); if (config_stats) { atomic_fetch_add_zu(&pac->stats->abandoned_vm, sz, ATOMIC_RELAXED); } /* * Leak extent after making sure its pages have already been purged, so * that this is only a virtual memory leak. */ if (ecache->state == extent_state_dirty) { if (extent_purge_lazy_impl(tsdn, ehooks, edata, 0, sz, growing_retained)) { extent_purge_forced_impl(tsdn, ehooks, edata, 0, edata_size_get(edata), growing_retained); } } edata_cache_put(tsdn, pac->edata_cache, edata); } static void extent_deactivate_locked_impl(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, edata_t *edata) { malloc_mutex_assert_owner(tsdn, &ecache->mtx); assert(edata_arena_ind_get(edata) == ecache_ind_get(ecache)); emap_update_edata_state(tsdn, pac->emap, edata, ecache->state); eset_t *eset = edata_guarded_get(edata) ? &ecache->guarded_eset : &ecache->eset; eset_insert(eset, edata); } static void extent_deactivate_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, edata_t *edata) { assert(edata_state_get(edata) == extent_state_active); extent_deactivate_locked_impl(tsdn, pac, ecache, edata); } static void extent_deactivate_check_state_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, edata_t *edata, extent_state_t expected_state) { assert(edata_state_get(edata) == expected_state); extent_deactivate_locked_impl(tsdn, pac, ecache, edata); } static void extent_activate_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, eset_t *eset, edata_t *edata) { assert(edata_arena_ind_get(edata) == ecache_ind_get(ecache)); assert(edata_state_get(edata) == ecache->state || edata_state_get(edata) == extent_state_merging); eset_remove(eset, edata); emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active); } void extent_gdump_add(tsdn_t *tsdn, const edata_t *edata) { cassert(config_prof); /* prof_gdump() requirement. */ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (opt_prof && edata_state_get(edata) == extent_state_active) { size_t nadd = edata_size_get(edata) >> LG_PAGE; size_t cur = atomic_fetch_add_zu(&curpages, nadd, ATOMIC_RELAXED) + nadd; size_t high = atomic_load_zu(&highpages, ATOMIC_RELAXED); while (cur > high && !atomic_compare_exchange_weak_zu( &highpages, &high, cur, ATOMIC_RELAXED, ATOMIC_RELAXED)) { /* * Don't refresh cur, because it may have decreased * since this thread lost the highpages update race. * Note that high is updated in case of CAS failure. */ } if (cur > high && prof_gdump_get_unlocked()) { prof_gdump(tsdn); } } } static void extent_gdump_sub(tsdn_t *tsdn, const edata_t *edata) { cassert(config_prof); if (opt_prof && edata_state_get(edata) == extent_state_active) { size_t nsub = edata_size_get(edata) >> LG_PAGE; assert(atomic_load_zu(&curpages, ATOMIC_RELAXED) >= nsub); atomic_fetch_sub_zu(&curpages, nsub, ATOMIC_RELAXED); } } static bool extent_register_impl(tsdn_t *tsdn, pac_t *pac, edata_t *edata, bool gdump_add) { assert(edata_state_get(edata) == extent_state_active); /* * No locking needed, as the edata must be in active state, which * prevents other threads from accessing the edata. */ if (emap_register_boundary(tsdn, pac->emap, edata, SC_NSIZES, /* slab */ false)) { return true; } if (config_prof && gdump_add) { extent_gdump_add(tsdn, edata); } return false; } static bool extent_register(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { return extent_register_impl(tsdn, pac, edata, true); } static bool extent_register_no_gdump_add(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { return extent_register_impl(tsdn, pac, edata, false); } static void extent_reregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { bool err = extent_register(tsdn, pac, edata); assert(!err); } /* * Removes all pointers to the given extent from the global rtree. */ static void extent_deregister_impl(tsdn_t *tsdn, pac_t *pac, edata_t *edata, bool gdump) { emap_deregister_boundary(tsdn, pac->emap, edata); if (config_prof && gdump) { extent_gdump_sub(tsdn, edata); } } static void extent_deregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { extent_deregister_impl(tsdn, pac, edata, true); } static void extent_deregister_no_gdump_sub(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { extent_deregister_impl(tsdn, pac, edata, false); } /* * Tries to find and remove an extent from ecache that can be used for the * given allocation request. */ static edata_t * extent_recycle_extract(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, bool guarded) { malloc_mutex_assert_owner(tsdn, &ecache->mtx); assert(alignment > 0); if (config_debug && expand_edata != NULL) { /* * Non-NULL expand_edata indicates in-place expanding realloc. * new_addr must either refer to a non-existing extent, or to * the base of an extant extent, since only active slabs support * interior lookups (which of course cannot be recycled). */ void *new_addr = edata_past_get(expand_edata); assert(PAGE_ADDR2BASE(new_addr) == new_addr); assert(alignment <= PAGE); } edata_t *edata; eset_t *eset = guarded ? &ecache->guarded_eset : &ecache->eset; if (expand_edata != NULL) { edata = emap_try_acquire_edata_neighbor_expand(tsdn, pac->emap, expand_edata, EXTENT_PAI_PAC, ecache->state); if (edata != NULL) { extent_assert_can_expand(expand_edata, edata); if (edata_size_get(edata) < size) { emap_release_edata(tsdn, pac->emap, edata, ecache->state); edata = NULL; } } } else { /* * A large extent might be broken up from its original size to * some small size to satisfy a small request. When that small * request is freed, though, it won't merge back with the larger * extent if delayed coalescing is on. The large extent can * then no longer satify a request for its original size. To * limit this effect, when delayed coalescing is enabled, we * put a cap on how big an extent we can split for a request. */ unsigned lg_max_fit = ecache->delay_coalesce ? (unsigned)opt_lg_extent_max_active_fit : SC_PTR_BITS; /* * If split and merge are not allowed (Windows w/o retain), try * exact fit only. * * For simplicity purposes, splitting guarded extents is not * supported. Hence, we do only exact fit for guarded * allocations. */ bool exact_only = (!maps_coalesce && !opt_retain) || guarded; edata = eset_fit(eset, size, alignment, exact_only, lg_max_fit); } if (edata == NULL) { return NULL; } assert(!guarded || edata_guarded_get(edata)); extent_activate_locked(tsdn, pac, ecache, eset, edata); return edata; } /* * Given an allocation request and an extent guaranteed to be able to satisfy * it, this splits off lead and trail extents, leaving edata pointing to an * extent satisfying the allocation. * This function doesn't put lead or trail into any ecache; it's the caller's * job to ensure that they can be reused. */ typedef enum { /* * Split successfully. lead, edata, and trail, are modified to extents * describing the ranges before, in, and after the given allocation. */ extent_split_interior_ok, /* * The extent can't satisfy the given allocation request. None of the * input edata_t *s are touched. */ extent_split_interior_cant_alloc, /* * In a potentially invalid state. Must leak (if *to_leak is non-NULL), * and salvage what's still salvageable (if *to_salvage is non-NULL). * None of lead, edata, or trail are valid. */ extent_split_interior_error } extent_split_interior_result_t; static extent_split_interior_result_t extent_split_interior(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, /* The result of splitting, in case of success. */ edata_t **edata, edata_t **lead, edata_t **trail, /* The mess to clean up, in case of error. */ edata_t **to_leak, edata_t **to_salvage, edata_t *expand_edata, size_t size, size_t alignment) { size_t leadsize = ALIGNMENT_CEILING((uintptr_t)edata_base_get(*edata), PAGE_CEILING(alignment)) - (uintptr_t)edata_base_get(*edata); assert(expand_edata == NULL || leadsize == 0); if (edata_size_get(*edata) < leadsize + size) { return extent_split_interior_cant_alloc; } size_t trailsize = edata_size_get(*edata) - leadsize - size; *lead = NULL; *trail = NULL; *to_leak = NULL; *to_salvage = NULL; /* Split the lead. */ if (leadsize != 0) { assert(!edata_guarded_get(*edata)); *lead = *edata; *edata = extent_split_impl(tsdn, pac, ehooks, *lead, leadsize, size + trailsize, /* holding_core_locks*/ true); if (*edata == NULL) { *to_leak = *lead; *lead = NULL; return extent_split_interior_error; } } /* Split the trail. */ if (trailsize != 0) { assert(!edata_guarded_get(*edata)); *trail = extent_split_impl(tsdn, pac, ehooks, *edata, size, trailsize, /* holding_core_locks */ true); if (*trail == NULL) { *to_leak = *edata; *to_salvage = *lead; *lead = NULL; *edata = NULL; return extent_split_interior_error; } } return extent_split_interior_ok; } /* * This fulfills the indicated allocation request out of the given extent (which * the caller should have ensured was big enough). If there's any unused space * before or after the resulting allocation, that space is given its own extent * and put back into ecache. */ static edata_t * extent_recycle_split(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, edata_t *edata, bool growing_retained) { assert(!edata_guarded_get(edata) || size == edata_size_get(edata)); malloc_mutex_assert_owner(tsdn, &ecache->mtx); edata_t *lead; edata_t *trail; edata_t *to_leak JEMALLOC_CC_SILENCE_INIT(NULL); edata_t *to_salvage JEMALLOC_CC_SILENCE_INIT(NULL); extent_split_interior_result_t result = extent_split_interior( tsdn, pac, ehooks, &edata, &lead, &trail, &to_leak, &to_salvage, expand_edata, size, alignment); if (!maps_coalesce && result != extent_split_interior_ok && !opt_retain) { /* * Split isn't supported (implies Windows w/o retain). Avoid * leaking the extent. */ assert(to_leak != NULL && lead == NULL && trail == NULL); extent_deactivate_locked(tsdn, pac, ecache, to_leak); return NULL; } if (result == extent_split_interior_ok) { if (lead != NULL) { extent_deactivate_locked(tsdn, pac, ecache, lead); } if (trail != NULL) { extent_deactivate_locked(tsdn, pac, ecache, trail); } return edata; } else { /* * We should have picked an extent that was large enough to * fulfill our allocation request. */ assert(result == extent_split_interior_error); if (to_salvage != NULL) { extent_deregister(tsdn, pac, to_salvage); } if (to_leak != NULL) { extent_deregister_no_gdump_sub(tsdn, pac, to_leak); /* * May go down the purge path (which assume no ecache * locks). Only happens with OOM caused split failures. */ malloc_mutex_unlock(tsdn, &ecache->mtx); extents_abandon_vm(tsdn, pac, ehooks, ecache, to_leak, growing_retained); malloc_mutex_lock(tsdn, &ecache->mtx); } return NULL; } unreachable(); } /* * Tries to satisfy the given allocation request by reusing one of the extents * in the given ecache_t. */ static edata_t * extent_recycle(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool *commit, bool growing_retained, bool guarded) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, growing_retained ? 1 : 0); assert(!guarded || expand_edata == NULL); assert(!guarded || alignment <= PAGE); malloc_mutex_lock(tsdn, &ecache->mtx); edata_t *edata = extent_recycle_extract(tsdn, pac, ehooks, ecache, expand_edata, size, alignment, guarded); if (edata == NULL) { malloc_mutex_unlock(tsdn, &ecache->mtx); return NULL; } edata = extent_recycle_split(tsdn, pac, ehooks, ecache, expand_edata, size, alignment, edata, growing_retained); malloc_mutex_unlock(tsdn, &ecache->mtx); if (edata == NULL) { return NULL; } assert(edata_state_get(edata) == extent_state_active); if (extent_commit_zero(tsdn, ehooks, edata, *commit, zero, growing_retained)) { extent_record(tsdn, pac, ehooks, ecache, edata); return NULL; } if (edata_committed_get(edata)) { /* * This reverses the purpose of this variable - previously it * was treated as an input parameter, now it turns into an * output parameter, reporting if the edata has actually been * committed. */ *commit = true; } return edata; } /* * If virtual memory is retained, create increasingly larger extents from which * to split requested extents in order to limit the total number of disjoint * virtual memory ranges retained by each shard. */ static edata_t * extent_grow_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, size_t size, size_t alignment, bool zero, bool *commit) { malloc_mutex_assert_owner(tsdn, &pac->grow_mtx); size_t alloc_size_min = size + PAGE_CEILING(alignment) - PAGE; /* Beware size_t wrap-around. */ if (alloc_size_min < size) { goto label_err; } /* * Find the next extent size in the series that would be large enough to * satisfy this request. */ size_t alloc_size; pszind_t exp_grow_skip; bool err = exp_grow_size_prepare(&pac->exp_grow, alloc_size_min, &alloc_size, &exp_grow_skip); if (err) { goto label_err; } edata_t *edata = edata_cache_get(tsdn, pac->edata_cache); if (edata == NULL) { goto label_err; } bool zeroed = false; bool committed = false; void *ptr = ehooks_alloc(tsdn, ehooks, NULL, alloc_size, PAGE, &zeroed, &committed); if (ptr == NULL) { edata_cache_put(tsdn, pac->edata_cache, edata); goto label_err; } edata_init(edata, ecache_ind_get(&pac->ecache_retained), ptr, alloc_size, false, SC_NSIZES, extent_sn_next(pac), extent_state_active, zeroed, committed, EXTENT_PAI_PAC, EXTENT_IS_HEAD); if (extent_register_no_gdump_add(tsdn, pac, edata)) { edata_cache_put(tsdn, pac->edata_cache, edata); goto label_err; } if (edata_committed_get(edata)) { *commit = true; } edata_t *lead; edata_t *trail; edata_t *to_leak JEMALLOC_CC_SILENCE_INIT(NULL); edata_t *to_salvage JEMALLOC_CC_SILENCE_INIT(NULL); extent_split_interior_result_t result = extent_split_interior(tsdn, pac, ehooks, &edata, &lead, &trail, &to_leak, &to_salvage, NULL, size, alignment); if (result == extent_split_interior_ok) { if (lead != NULL) { extent_record(tsdn, pac, ehooks, &pac->ecache_retained, lead); } if (trail != NULL) { extent_record(tsdn, pac, ehooks, &pac->ecache_retained, trail); } } else { /* * We should have allocated a sufficiently large extent; the * cant_alloc case should not occur. */ assert(result == extent_split_interior_error); if (to_salvage != NULL) { if (config_prof) { extent_gdump_add(tsdn, to_salvage); } extent_record(tsdn, pac, ehooks, &pac->ecache_retained, to_salvage); } if (to_leak != NULL) { extent_deregister_no_gdump_sub(tsdn, pac, to_leak); extents_abandon_vm(tsdn, pac, ehooks, &pac->ecache_retained, to_leak, true); } goto label_err; } if (*commit && !edata_committed_get(edata)) { if (extent_commit_impl(tsdn, ehooks, edata, 0, edata_size_get(edata), true)) { extent_record(tsdn, pac, ehooks, &pac->ecache_retained, edata); goto label_err; } /* A successful commit should return zeroed memory. */ if (config_debug) { void *addr = edata_addr_get(edata); size_t *p = (size_t *)(uintptr_t)addr; /* Check the first page only. */ for (size_t i = 0; i < PAGE / sizeof(size_t); i++) { assert(p[i] == 0); } } } /* * Increment extent_grow_next if doing so wouldn't exceed the allowed * range. */ /* All opportunities for failure are past. */ exp_grow_size_commit(&pac->exp_grow, exp_grow_skip); malloc_mutex_unlock(tsdn, &pac->grow_mtx); if (config_prof) { /* Adjust gdump stats now that extent is final size. */ extent_gdump_add(tsdn, edata); } if (zero && !edata_zeroed_get(edata)) { ehooks_zero(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata)); } return edata; label_err: malloc_mutex_unlock(tsdn, &pac->grow_mtx); return NULL; } static edata_t * extent_alloc_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *expand_edata, size_t size, size_t alignment, bool zero, bool *commit, bool guarded) { assert(size != 0); assert(alignment != 0); malloc_mutex_lock(tsdn, &pac->grow_mtx); edata_t *edata = extent_recycle(tsdn, pac, ehooks, &pac->ecache_retained, expand_edata, size, alignment, zero, commit, /* growing_retained */ true, guarded); if (edata != NULL) { malloc_mutex_unlock(tsdn, &pac->grow_mtx); if (config_prof) { extent_gdump_add(tsdn, edata); } } else if (opt_retain && expand_edata == NULL && !guarded) { edata = extent_grow_retained(tsdn, pac, ehooks, size, alignment, zero, commit); /* extent_grow_retained() always releases pac->grow_mtx. */ } else { malloc_mutex_unlock(tsdn, &pac->grow_mtx); } malloc_mutex_assert_not_owner(tsdn, &pac->grow_mtx); return edata; } static bool extent_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *inner, edata_t *outer, bool forward) { extent_assert_can_coalesce(inner, outer); eset_remove(&ecache->eset, outer); bool err = extent_merge_impl(tsdn, pac, ehooks, forward ? inner : outer, forward ? outer : inner, /* holding_core_locks */ true); if (err) { extent_deactivate_check_state_locked(tsdn, pac, ecache, outer, extent_state_merging); } return err; } static edata_t * extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata, bool *coalesced) { assert(!edata_guarded_get(edata)); /* * We avoid checking / locking inactive neighbors for large size * classes, since they are eagerly coalesced on deallocation which can * cause lock contention. */ /* * Continue attempting to coalesce until failure, to protect against * races with other threads that are thwarted by this one. */ bool again; do { again = false; /* Try to coalesce forward. */ edata_t *next = emap_try_acquire_edata_neighbor(tsdn, pac->emap, edata, EXTENT_PAI_PAC, ecache->state, /* forward */ true); if (next != NULL) { if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata, next, true)) { if (ecache->delay_coalesce) { /* Do minimal coalescing. */ *coalesced = true; return edata; } again = true; } } /* Try to coalesce backward. */ edata_t *prev = emap_try_acquire_edata_neighbor(tsdn, pac->emap, edata, EXTENT_PAI_PAC, ecache->state, /* forward */ false); if (prev != NULL) { if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata, prev, false)) { edata = prev; if (ecache->delay_coalesce) { /* Do minimal coalescing. */ *coalesced = true; return edata; } again = true; } } } while (again); if (ecache->delay_coalesce) { *coalesced = false; } return edata; } static edata_t * extent_try_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata, bool *coalesced) { return extent_try_coalesce_impl(tsdn, pac, ehooks, ecache, edata, coalesced); } static edata_t * extent_try_coalesce_large(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata, bool *coalesced) { return extent_try_coalesce_impl(tsdn, pac, ehooks, ecache, edata, coalesced); } /* Purge a single extent to retained / unmapped directly. */ static void extent_maximally_purge(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata) { size_t extent_size = edata_size_get(edata); extent_dalloc_wrapper(tsdn, pac, ehooks, edata); if (config_stats) { /* Update stats accordingly. */ LOCKEDINT_MTX_LOCK(tsdn, *pac->stats_mtx); locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), &pac->stats->decay_dirty.nmadvise, 1); locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), &pac->stats->decay_dirty.purged, extent_size >> LG_PAGE); LOCKEDINT_MTX_UNLOCK(tsdn, *pac->stats_mtx); atomic_fetch_sub_zu(&pac->stats->pac_mapped, extent_size, ATOMIC_RELAXED); } } /* * Does the metadata management portions of putting an unused extent into the * given ecache_t (coalesces and inserts into the eset). */ void extent_record(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, edata_t *edata) { assert((ecache->state != extent_state_dirty && ecache->state != extent_state_muzzy) || !edata_zeroed_get(edata)); malloc_mutex_lock(tsdn, &ecache->mtx); emap_assert_mapped(tsdn, pac->emap, edata); if (edata_guarded_get(edata)) { goto label_skip_coalesce; } if (!ecache->delay_coalesce) { edata = extent_try_coalesce(tsdn, pac, ehooks, ecache, edata, NULL); } else if (edata_size_get(edata) >= SC_LARGE_MINCLASS) { assert(ecache == &pac->ecache_dirty); /* Always coalesce large extents eagerly. */ bool coalesced; do { assert(edata_state_get(edata) == extent_state_active); edata = extent_try_coalesce_large(tsdn, pac, ehooks, ecache, edata, &coalesced); } while (coalesced); if (edata_size_get(edata) >= atomic_load_zu(&pac->oversize_threshold, ATOMIC_RELAXED) && extent_may_force_decay(pac)) { /* Shortcut to purge the oversize extent eagerly. */ malloc_mutex_unlock(tsdn, &ecache->mtx); extent_maximally_purge(tsdn, pac, ehooks, edata); return; } } label_skip_coalesce: extent_deactivate_locked(tsdn, pac, ecache, edata); malloc_mutex_unlock(tsdn, &ecache->mtx); } void extent_dalloc_gap(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (extent_register(tsdn, pac, edata)) { edata_cache_put(tsdn, pac->edata_cache, edata); return; } extent_dalloc_wrapper(tsdn, pac, ehooks, edata); } static bool extent_dalloc_wrapper_try(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata) { bool err; assert(edata_base_get(edata) != NULL); assert(edata_size_get(edata) != 0); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); edata_addr_set(edata, edata_base_get(edata)); /* Try to deallocate. */ err = ehooks_dalloc(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), edata_committed_get(edata)); if (!err) { edata_cache_put(tsdn, pac->edata_cache, edata); } return err; } edata_t * extent_alloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, void *new_addr, size_t size, size_t alignment, bool zero, bool *commit, bool growing_retained) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, growing_retained ? 1 : 0); edata_t *edata = edata_cache_get(tsdn, pac->edata_cache); if (edata == NULL) { return NULL; } size_t palignment = ALIGNMENT_CEILING(alignment, PAGE); void *addr = ehooks_alloc(tsdn, ehooks, new_addr, size, palignment, &zero, commit); if (addr == NULL) { edata_cache_put(tsdn, pac->edata_cache, edata); return NULL; } edata_init(edata, ecache_ind_get(&pac->ecache_dirty), addr, size, /* slab */ false, SC_NSIZES, extent_sn_next(pac), extent_state_active, zero, *commit, EXTENT_PAI_PAC, opt_retain ? EXTENT_IS_HEAD : EXTENT_NOT_HEAD); /* * Retained memory is not counted towards gdump. Only if an extent is * allocated as a separate mapping, i.e. growing_retained is false, then * gdump should be updated. */ bool gdump_add = !growing_retained; if (extent_register_impl(tsdn, pac, edata, gdump_add)) { edata_cache_put(tsdn, pac->edata_cache, edata); return NULL; } return edata; } void extent_dalloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata) { assert(edata_pai_get(edata) == EXTENT_PAI_PAC); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); /* Avoid calling the default extent_dalloc unless have to. */ if (!ehooks_dalloc_will_fail(ehooks)) { /* Remove guard pages for dalloc / unmap. */ if (edata_guarded_get(edata)) { assert(ehooks_are_default(ehooks)); san_unguard_pages_two_sided(tsdn, ehooks, edata, pac->emap); } /* * Deregister first to avoid a race with other allocating * threads, and reregister if deallocation fails. */ extent_deregister(tsdn, pac, edata); if (!extent_dalloc_wrapper_try(tsdn, pac, ehooks, edata)) { return; } extent_reregister(tsdn, pac, edata); } /* Try to decommit; purge if that fails. */ bool zeroed; if (!edata_committed_get(edata)) { zeroed = true; } else if (!extent_decommit_wrapper(tsdn, ehooks, edata, 0, edata_size_get(edata))) { zeroed = true; } else if (!ehooks_purge_forced(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), 0, edata_size_get(edata))) { zeroed = true; } else if (edata_state_get(edata) == extent_state_muzzy || !ehooks_purge_lazy(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), 0, edata_size_get(edata))) { zeroed = false; } else { zeroed = false; } edata_zeroed_set(edata, zeroed); if (config_prof) { extent_gdump_sub(tsdn, edata); } extent_record(tsdn, pac, ehooks, &pac->ecache_retained, edata); } void extent_destroy_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata) { assert(edata_base_get(edata) != NULL); assert(edata_size_get(edata) != 0); extent_state_t state = edata_state_get(edata); assert(state == extent_state_retained || state == extent_state_active); assert(emap_edata_is_acquired(tsdn, pac->emap, edata)); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); if (edata_guarded_get(edata)) { assert(opt_retain); san_unguard_pages_pre_destroy(tsdn, ehooks, edata, pac->emap); } edata_addr_set(edata, edata_base_get(edata)); /* Try to destroy; silently fail otherwise. */ ehooks_destroy(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), edata_committed_get(edata)); edata_cache_put(tsdn, pac->edata_cache, edata); } static bool extent_commit_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length, bool growing_retained) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, growing_retained ? 1 : 0); bool err = ehooks_commit(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), offset, length); edata_committed_set(edata, edata_committed_get(edata) || !err); return err; } bool extent_commit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length) { return extent_commit_impl(tsdn, ehooks, edata, offset, length, /* growing_retained */ false); } bool extent_decommit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); bool err = ehooks_decommit(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), offset, length); edata_committed_set(edata, edata_committed_get(edata) && err); return err; } static bool extent_purge_lazy_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length, bool growing_retained) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, growing_retained ? 1 : 0); bool err = ehooks_purge_lazy(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), offset, length); return err; } bool extent_purge_lazy_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length) { return extent_purge_lazy_impl(tsdn, ehooks, edata, offset, length, false); } static bool extent_purge_forced_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length, bool growing_retained) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, growing_retained ? 1 : 0); bool err = ehooks_purge_forced(tsdn, ehooks, edata_base_get(edata), edata_size_get(edata), offset, length); return err; } bool extent_purge_forced_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, size_t offset, size_t length) { return extent_purge_forced_impl(tsdn, ehooks, edata, offset, length, false); } /* * Accepts the extent to split, and the characteristics of each side of the * split. The 'a' parameters go with the 'lead' of the resulting pair of * extents (the lower addressed portion of the split), and the 'b' parameters go * with the trail (the higher addressed portion). This makes 'extent' the lead, * and returns the trail (except in case of error). */ static edata_t * extent_split_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks) { assert(edata_size_get(edata) == size_a + size_b); /* Only the shrink path may split w/o holding core locks. */ if (holding_core_locks) { witness_assert_positive_depth_to_rank( tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); } else { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); } if (ehooks_split_will_fail(ehooks)) { return NULL; } edata_t *trail = edata_cache_get(tsdn, pac->edata_cache); if (trail == NULL) { goto label_error_a; } edata_init(trail, edata_arena_ind_get(edata), (void *)((uintptr_t)edata_base_get(edata) + size_a), size_b, /* slab */ false, SC_NSIZES, edata_sn_get(edata), edata_state_get(edata), edata_zeroed_get(edata), edata_committed_get(edata), EXTENT_PAI_PAC, EXTENT_NOT_HEAD); emap_prepare_t prepare; bool err = emap_split_prepare(tsdn, pac->emap, &prepare, edata, size_a, trail, size_b); if (err) { goto label_error_b; } /* * No need to acquire trail or edata, because: 1) trail was new (just * allocated); and 2) edata is either an active allocation (the shrink * path), or in an acquired state (extracted from the ecache on the * extent_recycle_split path). */ assert(emap_edata_is_acquired(tsdn, pac->emap, edata)); assert(emap_edata_is_acquired(tsdn, pac->emap, trail)); err = ehooks_split(tsdn, ehooks, edata_base_get(edata), size_a + size_b, size_a, size_b, edata_committed_get(edata)); if (err) { goto label_error_b; } edata_size_set(edata, size_a); emap_split_commit(tsdn, pac->emap, &prepare, edata, size_a, trail, size_b); return trail; label_error_b: edata_cache_put(tsdn, pac->edata_cache, trail); label_error_a: return NULL; } edata_t * extent_split_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks) { return extent_split_impl(tsdn, pac, ehooks, edata, size_a, size_b, holding_core_locks); } static bool extent_merge_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *a, edata_t *b, bool holding_core_locks) { /* Only the expanding path may merge w/o holding ecache locks. */ if (holding_core_locks) { witness_assert_positive_depth_to_rank( tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); } else { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); } assert(edata_base_get(a) < edata_base_get(b)); assert(edata_arena_ind_get(a) == edata_arena_ind_get(b)); assert(edata_arena_ind_get(a) == ehooks_ind_get(ehooks)); emap_assert_mapped(tsdn, pac->emap, a); emap_assert_mapped(tsdn, pac->emap, b); bool err = ehooks_merge(tsdn, ehooks, edata_base_get(a), edata_size_get(a), edata_base_get(b), edata_size_get(b), edata_committed_get(a)); if (err) { return true; } /* * The rtree writes must happen while all the relevant elements are * owned, so the following code uses decomposed helper functions rather * than extent_{,de}register() to do things in the right order. */ emap_prepare_t prepare; emap_merge_prepare(tsdn, pac->emap, &prepare, a, b); assert(edata_state_get(a) == extent_state_active || edata_state_get(a) == extent_state_merging); edata_state_set(a, extent_state_active); edata_size_set(a, edata_size_get(a) + edata_size_get(b)); edata_sn_set(a, (edata_sn_get(a) < edata_sn_get(b)) ? edata_sn_get(a) : edata_sn_get(b)); edata_zeroed_set(a, edata_zeroed_get(a) && edata_zeroed_get(b)); emap_merge_commit(tsdn, pac->emap, &prepare, a, b); edata_cache_put(tsdn, pac->edata_cache, b); return false; } bool extent_merge_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *a, edata_t *b) { return extent_merge_impl(tsdn, pac, ehooks, a, b, /* holding_core_locks */ false); } bool extent_commit_zero(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, bool commit, bool zero, bool growing_retained) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, growing_retained ? 1 : 0); if (commit && !edata_committed_get(edata)) { if (extent_commit_impl(tsdn, ehooks, edata, 0, edata_size_get(edata), growing_retained)) { return true; } } if (zero && !edata_zeroed_get(edata)) { void *addr = edata_base_get(edata); size_t size = edata_size_get(edata); ehooks_zero(tsdn, ehooks, addr, size); } return false; } bool extent_boot(void) { assert(sizeof(slab_data_t) >= sizeof(e_prof_info_t)); if (have_dss) { extent_dss_boot(); } return false; } redis-8.0.2/deps/jemalloc/src/extent_dss.c000066400000000000000000000160501501533116600204660ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/spin.h" /******************************************************************************/ /* Data. */ const char *opt_dss = DSS_DEFAULT; const char *dss_prec_names[] = { "disabled", "primary", "secondary", "N/A" }; /* * Current dss precedence default, used when creating new arenas. NB: This is * stored as unsigned rather than dss_prec_t because in principle there's no * guarantee that sizeof(dss_prec_t) is the same as sizeof(unsigned), and we use * atomic operations to synchronize the setting. */ static atomic_u_t dss_prec_default = ATOMIC_INIT( (unsigned)DSS_PREC_DEFAULT); /* Base address of the DSS. */ static void *dss_base; /* Atomic boolean indicating whether a thread is currently extending DSS. */ static atomic_b_t dss_extending; /* Atomic boolean indicating whether the DSS is exhausted. */ static atomic_b_t dss_exhausted; /* Atomic current upper limit on DSS addresses. */ static atomic_p_t dss_max; /******************************************************************************/ static void * extent_dss_sbrk(intptr_t increment) { #ifdef JEMALLOC_DSS return sbrk(increment); #else not_implemented(); return NULL; #endif } dss_prec_t extent_dss_prec_get(void) { dss_prec_t ret; if (!have_dss) { return dss_prec_disabled; } ret = (dss_prec_t)atomic_load_u(&dss_prec_default, ATOMIC_ACQUIRE); return ret; } bool extent_dss_prec_set(dss_prec_t dss_prec) { if (!have_dss) { return (dss_prec != dss_prec_disabled); } atomic_store_u(&dss_prec_default, (unsigned)dss_prec, ATOMIC_RELEASE); return false; } static void extent_dss_extending_start(void) { spin_t spinner = SPIN_INITIALIZER; while (true) { bool expected = false; if (atomic_compare_exchange_weak_b(&dss_extending, &expected, true, ATOMIC_ACQ_REL, ATOMIC_RELAXED)) { break; } spin_adaptive(&spinner); } } static void extent_dss_extending_finish(void) { assert(atomic_load_b(&dss_extending, ATOMIC_RELAXED)); atomic_store_b(&dss_extending, false, ATOMIC_RELEASE); } static void * extent_dss_max_update(void *new_addr) { /* * Get the current end of the DSS as max_cur and assure that dss_max is * up to date. */ void *max_cur = extent_dss_sbrk(0); if (max_cur == (void *)-1) { return NULL; } atomic_store_p(&dss_max, max_cur, ATOMIC_RELEASE); /* Fixed new_addr can only be supported if it is at the edge of DSS. */ if (new_addr != NULL && max_cur != new_addr) { return NULL; } return max_cur; } void * extent_alloc_dss(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit) { edata_t *gap; cassert(have_dss); assert(size > 0); assert(alignment == ALIGNMENT_CEILING(alignment, PAGE)); /* * sbrk() uses a signed increment argument, so take care not to * interpret a large allocation request as a negative increment. */ if ((intptr_t)size < 0) { return NULL; } gap = edata_cache_get(tsdn, &arena->pa_shard.edata_cache); if (gap == NULL) { return NULL; } extent_dss_extending_start(); if (!atomic_load_b(&dss_exhausted, ATOMIC_ACQUIRE)) { /* * The loop is necessary to recover from races with other * threads that are using the DSS for something other than * malloc. */ while (true) { void *max_cur = extent_dss_max_update(new_addr); if (max_cur == NULL) { goto label_oom; } bool head_state = opt_retain ? EXTENT_IS_HEAD : EXTENT_NOT_HEAD; /* * Compute how much page-aligned gap space (if any) is * necessary to satisfy alignment. This space can be * recycled for later use. */ void *gap_addr_page = (void *)(PAGE_CEILING( (uintptr_t)max_cur)); void *ret = (void *)ALIGNMENT_CEILING( (uintptr_t)gap_addr_page, alignment); size_t gap_size_page = (uintptr_t)ret - (uintptr_t)gap_addr_page; if (gap_size_page != 0) { edata_init(gap, arena_ind_get(arena), gap_addr_page, gap_size_page, false, SC_NSIZES, extent_sn_next( &arena->pa_shard.pac), extent_state_active, false, true, EXTENT_PAI_PAC, head_state); } /* * Compute the address just past the end of the desired * allocation space. */ void *dss_next = (void *)((uintptr_t)ret + size); if ((uintptr_t)ret < (uintptr_t)max_cur || (uintptr_t)dss_next < (uintptr_t)max_cur) { goto label_oom; /* Wrap-around. */ } /* Compute the increment, including subpage bytes. */ void *gap_addr_subpage = max_cur; size_t gap_size_subpage = (uintptr_t)ret - (uintptr_t)gap_addr_subpage; intptr_t incr = gap_size_subpage + size; assert((uintptr_t)max_cur + incr == (uintptr_t)ret + size); /* Try to allocate. */ void *dss_prev = extent_dss_sbrk(incr); if (dss_prev == max_cur) { /* Success. */ atomic_store_p(&dss_max, dss_next, ATOMIC_RELEASE); extent_dss_extending_finish(); if (gap_size_page != 0) { ehooks_t *ehooks = arena_get_ehooks( arena); extent_dalloc_gap(tsdn, &arena->pa_shard.pac, ehooks, gap); } else { edata_cache_put(tsdn, &arena->pa_shard.edata_cache, gap); } if (!*commit) { *commit = pages_decommit(ret, size); } if (*zero && *commit) { edata_t edata = {0}; ehooks_t *ehooks = arena_get_ehooks( arena); edata_init(&edata, arena_ind_get(arena), ret, size, size, false, SC_NSIZES, extent_state_active, false, true, EXTENT_PAI_PAC, head_state); if (extent_purge_forced_wrapper(tsdn, ehooks, &edata, 0, size)) { memset(ret, 0, size); } } return ret; } /* * Failure, whether due to OOM or a race with a raw * sbrk() call from outside the allocator. */ if (dss_prev == (void *)-1) { /* OOM. */ atomic_store_b(&dss_exhausted, true, ATOMIC_RELEASE); goto label_oom; } } } label_oom: extent_dss_extending_finish(); edata_cache_put(tsdn, &arena->pa_shard.edata_cache, gap); return NULL; } static bool extent_in_dss_helper(void *addr, void *max) { return ((uintptr_t)addr >= (uintptr_t)dss_base && (uintptr_t)addr < (uintptr_t)max); } bool extent_in_dss(void *addr) { cassert(have_dss); return extent_in_dss_helper(addr, atomic_load_p(&dss_max, ATOMIC_ACQUIRE)); } bool extent_dss_mergeable(void *addr_a, void *addr_b) { void *max; cassert(have_dss); if ((uintptr_t)addr_a < (uintptr_t)dss_base && (uintptr_t)addr_b < (uintptr_t)dss_base) { return true; } max = atomic_load_p(&dss_max, ATOMIC_ACQUIRE); return (extent_in_dss_helper(addr_a, max) == extent_in_dss_helper(addr_b, max)); } void extent_dss_boot(void) { cassert(have_dss); dss_base = extent_dss_sbrk(0); atomic_store_b(&dss_extending, false, ATOMIC_RELAXED); atomic_store_b(&dss_exhausted, dss_base == (void *)-1, ATOMIC_RELAXED); atomic_store_p(&dss_max, dss_base, ATOMIC_RELAXED); } /******************************************************************************/ redis-8.0.2/deps/jemalloc/src/extent_mmap.c000066400000000000000000000016051501533116600206270ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/extent_mmap.h" /******************************************************************************/ /* Data. */ bool opt_retain = #ifdef JEMALLOC_RETAIN true #else false #endif ; /******************************************************************************/ void * extent_alloc_mmap(void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit) { assert(alignment == ALIGNMENT_CEILING(alignment, PAGE)); void *ret = pages_map(new_addr, size, alignment, commit); if (ret == NULL) { return NULL; } assert(ret != NULL); if (*commit) { *zero = true; } return ret; } bool extent_dalloc_mmap(void *addr, size_t size) { if (!opt_retain) { pages_unmap(addr, size); } return opt_retain; } redis-8.0.2/deps/jemalloc/src/fxp.c000066400000000000000000000063101501533116600171010ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/fxp.h" static bool fxp_isdigit(char c) { return '0' <= c && c <= '9'; } bool fxp_parse(fxp_t *result, const char *str, char **end) { /* * Using malloc_strtoumax in this method isn't as handy as you might * expect (I tried). In the fractional part, significant leading zeros * mean that you still need to do your own parsing, now with trickier * math. In the integer part, the casting (uintmax_t to uint32_t) * forces more reasoning about bounds than just checking for overflow as * we parse. */ uint32_t integer_part = 0; const char *cur = str; /* The string must start with a digit or a decimal point. */ if (*cur != '.' && !fxp_isdigit(*cur)) { return true; } while ('0' <= *cur && *cur <= '9') { integer_part *= 10; integer_part += *cur - '0'; if (integer_part >= (1U << 16)) { return true; } cur++; } /* * We've parsed all digits at the beginning of the string, without * overflow. Either we're done, or there's a fractional part. */ if (*cur != '.') { *result = (integer_part << 16); if (end != NULL) { *end = (char *)cur; } return false; } /* There's a fractional part. */ cur++; if (!fxp_isdigit(*cur)) { /* Shouldn't end on the decimal point. */ return true; } /* * We use a lot of precision for the fractional part, even though we'll * discard most of it; this lets us get exact values for the important * special case where the denominator is a small power of 2 (for * instance, 1/512 == 0.001953125 is exactly representable even with * only 16 bits of fractional precision). We need to left-shift by 16 * before dividing so we pick the number of digits to be * floor(log(2**48)) = 14. */ uint64_t fractional_part = 0; uint64_t frac_div = 1; for (int i = 0; i < FXP_FRACTIONAL_PART_DIGITS; i++) { fractional_part *= 10; frac_div *= 10; if (fxp_isdigit(*cur)) { fractional_part += *cur - '0'; cur++; } } /* * We only parse the first maxdigits characters, but we can still ignore * any digits after that. */ while (fxp_isdigit(*cur)) { cur++; } assert(fractional_part < frac_div); uint32_t fractional_repr = (uint32_t)( (fractional_part << 16) / frac_div); /* Success! */ *result = (integer_part << 16) + fractional_repr; if (end != NULL) { *end = (char *)cur; } return false; } void fxp_print(fxp_t a, char buf[FXP_BUF_SIZE]) { uint32_t integer_part = fxp_round_down(a); uint32_t fractional_part = (a & ((1U << 16) - 1)); int leading_fraction_zeros = 0; uint64_t fraction_digits = fractional_part; for (int i = 0; i < FXP_FRACTIONAL_PART_DIGITS; i++) { if (fraction_digits < (1U << 16) && fraction_digits * 10 >= (1U << 16)) { leading_fraction_zeros = i; } fraction_digits *= 10; } fraction_digits >>= 16; while (fraction_digits > 0 && fraction_digits % 10 == 0) { fraction_digits /= 10; } size_t printed = malloc_snprintf(buf, FXP_BUF_SIZE, "%"FMTu32".", integer_part); for (int i = 0; i < leading_fraction_zeros; i++) { buf[printed] = '0'; printed++; } malloc_snprintf(&buf[printed], FXP_BUF_SIZE - printed, "%"FMTu64, fraction_digits); } redis-8.0.2/deps/jemalloc/src/hook.c000066400000000000000000000131431501533116600172460ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/hook.h" #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/seq.h" typedef struct hooks_internal_s hooks_internal_t; struct hooks_internal_s { hooks_t hooks; bool in_use; }; seq_define(hooks_internal_t, hooks) static atomic_u_t nhooks = ATOMIC_INIT(0); static seq_hooks_t hooks[HOOK_MAX]; static malloc_mutex_t hooks_mu; bool hook_boot() { return malloc_mutex_init(&hooks_mu, "hooks", WITNESS_RANK_HOOK, malloc_mutex_rank_exclusive); } static void * hook_install_locked(hooks_t *to_install) { hooks_internal_t hooks_internal; for (int i = 0; i < HOOK_MAX; i++) { bool success = seq_try_load_hooks(&hooks_internal, &hooks[i]); /* We hold mu; no concurrent access. */ assert(success); if (!hooks_internal.in_use) { hooks_internal.hooks = *to_install; hooks_internal.in_use = true; seq_store_hooks(&hooks[i], &hooks_internal); atomic_store_u(&nhooks, atomic_load_u(&nhooks, ATOMIC_RELAXED) + 1, ATOMIC_RELAXED); return &hooks[i]; } } return NULL; } void * hook_install(tsdn_t *tsdn, hooks_t *to_install) { malloc_mutex_lock(tsdn, &hooks_mu); void *ret = hook_install_locked(to_install); if (ret != NULL) { tsd_global_slow_inc(tsdn); } malloc_mutex_unlock(tsdn, &hooks_mu); return ret; } static void hook_remove_locked(seq_hooks_t *to_remove) { hooks_internal_t hooks_internal; bool success = seq_try_load_hooks(&hooks_internal, to_remove); /* We hold mu; no concurrent access. */ assert(success); /* Should only remove hooks that were added. */ assert(hooks_internal.in_use); hooks_internal.in_use = false; seq_store_hooks(to_remove, &hooks_internal); atomic_store_u(&nhooks, atomic_load_u(&nhooks, ATOMIC_RELAXED) - 1, ATOMIC_RELAXED); } void hook_remove(tsdn_t *tsdn, void *opaque) { if (config_debug) { char *hooks_begin = (char *)&hooks[0]; char *hooks_end = (char *)&hooks[HOOK_MAX]; char *hook = (char *)opaque; assert(hooks_begin <= hook && hook < hooks_end && (hook - hooks_begin) % sizeof(seq_hooks_t) == 0); } malloc_mutex_lock(tsdn, &hooks_mu); hook_remove_locked((seq_hooks_t *)opaque); tsd_global_slow_dec(tsdn); malloc_mutex_unlock(tsdn, &hooks_mu); } #define FOR_EACH_HOOK_BEGIN(hooks_internal_ptr) \ for (int for_each_hook_counter = 0; \ for_each_hook_counter < HOOK_MAX; \ for_each_hook_counter++) { \ bool for_each_hook_success = seq_try_load_hooks( \ (hooks_internal_ptr), &hooks[for_each_hook_counter]); \ if (!for_each_hook_success) { \ continue; \ } \ if (!(hooks_internal_ptr)->in_use) { \ continue; \ } #define FOR_EACH_HOOK_END \ } static bool * hook_reentrantp() { /* * We prevent user reentrancy within hooks. This is basically just a * thread-local bool that triggers an early-exit. * * We don't fold in_hook into reentrancy. There are two reasons for * this: * - Right now, we turn on reentrancy during things like extent hook * execution. Allocating during extent hooks is not officially * supported, but we don't want to break it for the time being. These * sorts of allocations should probably still be hooked, though. * - If a hook allocates, we may want it to be relatively fast (after * all, it executes on every allocator operation). Turning on * reentrancy is a fairly heavyweight mode (disabling tcache, * redirecting to arena 0, etc.). It's possible we may one day want * to turn on reentrant mode here, if it proves too difficult to keep * this working. But that's fairly easy for us to see; OTOH, people * not using hooks because they're too slow is easy for us to miss. * * The tricky part is * that this code might get invoked even if we don't have access to tsd. * This function mimics getting a pointer to thread-local data, except * that it might secretly return a pointer to some global data if we * know that the caller will take the early-exit path. * If we return a bool that indicates that we are reentrant, then the * caller will go down the early exit path, leaving the global * untouched. */ static bool in_hook_global = true; tsdn_t *tsdn = tsdn_fetch(); bool *in_hook = tsdn_in_hookp_get(tsdn); if (in_hook!= NULL) { return in_hook; } return &in_hook_global; } #define HOOK_PROLOGUE \ if (likely(atomic_load_u(&nhooks, ATOMIC_RELAXED) == 0)) { \ return; \ } \ bool *in_hook = hook_reentrantp(); \ if (*in_hook) { \ return; \ } \ *in_hook = true; #define HOOK_EPILOGUE \ *in_hook = false; void hook_invoke_alloc(hook_alloc_t type, void *result, uintptr_t result_raw, uintptr_t args_raw[3]) { HOOK_PROLOGUE hooks_internal_t hook; FOR_EACH_HOOK_BEGIN(&hook) hook_alloc h = hook.hooks.alloc_hook; if (h != NULL) { h(hook.hooks.extra, type, result, result_raw, args_raw); } FOR_EACH_HOOK_END HOOK_EPILOGUE } void hook_invoke_dalloc(hook_dalloc_t type, void *address, uintptr_t args_raw[3]) { HOOK_PROLOGUE hooks_internal_t hook; FOR_EACH_HOOK_BEGIN(&hook) hook_dalloc h = hook.hooks.dalloc_hook; if (h != NULL) { h(hook.hooks.extra, type, address, args_raw); } FOR_EACH_HOOK_END HOOK_EPILOGUE } void hook_invoke_expand(hook_expand_t type, void *address, size_t old_usize, size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]) { HOOK_PROLOGUE hooks_internal_t hook; FOR_EACH_HOOK_BEGIN(&hook) hook_expand h = hook.hooks.expand_hook; if (h != NULL) { h(hook.hooks.extra, type, address, old_usize, new_usize, result_raw, args_raw); } FOR_EACH_HOOK_END HOOK_EPILOGUE } redis-8.0.2/deps/jemalloc/src/hpa.c000066400000000000000000000775721501533116600170760ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/hpa.h" #include "jemalloc/internal/fb.h" #include "jemalloc/internal/witness.h" #define HPA_EDEN_SIZE (128 * HUGEPAGE) static edata_t *hpa_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated); static size_t hpa_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated); static bool hpa_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); static bool hpa_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated); static void hpa_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated); static void hpa_dalloc_batch(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated); static uint64_t hpa_time_until_deferred_work(tsdn_t *tsdn, pai_t *self); bool hpa_supported() { #ifdef _WIN32 /* * At least until the API and implementation is somewhat settled, we * don't want to try to debug the VM subsystem on the hardest-to-test * platform. */ return false; #endif if (!pages_can_hugify) { return false; } /* * We fundamentally rely on a address-space-hungry growth strategy for * hugepages. */ if (LG_SIZEOF_PTR != 3) { return false; } /* * If we couldn't detect the value of HUGEPAGE, HUGEPAGE_PAGES becomes * this sentinel value -- see the comment in pages.h. */ if (HUGEPAGE_PAGES == 1) { return false; } return true; } static void hpa_do_consistency_checks(hpa_shard_t *shard) { assert(shard->base != NULL); } bool hpa_central_init(hpa_central_t *central, base_t *base, const hpa_hooks_t *hooks) { /* malloc_conf processing should have filtered out these cases. */ assert(hpa_supported()); bool err; err = malloc_mutex_init(¢ral->grow_mtx, "hpa_central_grow", WITNESS_RANK_HPA_CENTRAL_GROW, malloc_mutex_rank_exclusive); if (err) { return true; } err = malloc_mutex_init(¢ral->mtx, "hpa_central", WITNESS_RANK_HPA_CENTRAL, malloc_mutex_rank_exclusive); if (err) { return true; } central->base = base; central->eden = NULL; central->eden_len = 0; central->age_counter = 0; central->hooks = *hooks; return false; } static hpdata_t * hpa_alloc_ps(tsdn_t *tsdn, hpa_central_t *central) { return (hpdata_t *)base_alloc(tsdn, central->base, sizeof(hpdata_t), CACHELINE); } hpdata_t * hpa_central_extract(tsdn_t *tsdn, hpa_central_t *central, size_t size, bool *oom) { /* Don't yet support big allocations; these should get filtered out. */ assert(size <= HUGEPAGE); /* * Should only try to extract from the central allocator if the local * shard is exhausted. We should hold the grow_mtx on that shard. */ witness_assert_positive_depth_to_rank( tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_HPA_SHARD_GROW); malloc_mutex_lock(tsdn, ¢ral->grow_mtx); *oom = false; hpdata_t *ps = NULL; /* Is eden a perfect fit? */ if (central->eden != NULL && central->eden_len == HUGEPAGE) { ps = hpa_alloc_ps(tsdn, central); if (ps == NULL) { *oom = true; malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); return NULL; } hpdata_init(ps, central->eden, central->age_counter++); central->eden = NULL; central->eden_len = 0; malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); return ps; } /* * We're about to try to allocate from eden by splitting. If eden is * NULL, we have to allocate it too. Otherwise, we just have to * allocate an edata_t for the new psset. */ if (central->eden == NULL) { /* * During development, we're primarily concerned with systems * with overcommit. Eventually, we should be more careful here. */ bool commit = true; /* Allocate address space, bailing if we fail. */ void *new_eden = pages_map(NULL, HPA_EDEN_SIZE, HUGEPAGE, &commit); if (new_eden == NULL) { *oom = true; malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); return NULL; } ps = hpa_alloc_ps(tsdn, central); if (ps == NULL) { pages_unmap(new_eden, HPA_EDEN_SIZE); *oom = true; malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); return NULL; } central->eden = new_eden; central->eden_len = HPA_EDEN_SIZE; } else { /* Eden is already nonempty; only need an edata for ps. */ ps = hpa_alloc_ps(tsdn, central); if (ps == NULL) { *oom = true; malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); return NULL; } } assert(ps != NULL); assert(central->eden != NULL); assert(central->eden_len > HUGEPAGE); assert(central->eden_len % HUGEPAGE == 0); assert(HUGEPAGE_ADDR2BASE(central->eden) == central->eden); hpdata_init(ps, central->eden, central->age_counter++); char *eden_char = (char *)central->eden; eden_char += HUGEPAGE; central->eden = (void *)eden_char; central->eden_len -= HUGEPAGE; malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); return ps; } bool hpa_shard_init(hpa_shard_t *shard, hpa_central_t *central, emap_t *emap, base_t *base, edata_cache_t *edata_cache, unsigned ind, const hpa_shard_opts_t *opts) { /* malloc_conf processing should have filtered out these cases. */ assert(hpa_supported()); bool err; err = malloc_mutex_init(&shard->grow_mtx, "hpa_shard_grow", WITNESS_RANK_HPA_SHARD_GROW, malloc_mutex_rank_exclusive); if (err) { return true; } err = malloc_mutex_init(&shard->mtx, "hpa_shard", WITNESS_RANK_HPA_SHARD, malloc_mutex_rank_exclusive); if (err) { return true; } assert(edata_cache != NULL); shard->central = central; shard->base = base; edata_cache_fast_init(&shard->ecf, edata_cache); psset_init(&shard->psset); shard->age_counter = 0; shard->ind = ind; shard->emap = emap; shard->opts = *opts; shard->npending_purge = 0; nstime_init_zero(&shard->last_purge); shard->stats.npurge_passes = 0; shard->stats.npurges = 0; shard->stats.nhugifies = 0; shard->stats.ndehugifies = 0; /* * Fill these in last, so that if an hpa_shard gets used despite * initialization failing, we'll at least crash instead of just * operating on corrupted data. */ shard->pai.alloc = &hpa_alloc; shard->pai.alloc_batch = &hpa_alloc_batch; shard->pai.expand = &hpa_expand; shard->pai.shrink = &hpa_shrink; shard->pai.dalloc = &hpa_dalloc; shard->pai.dalloc_batch = &hpa_dalloc_batch; shard->pai.time_until_deferred_work = &hpa_time_until_deferred_work; hpa_do_consistency_checks(shard); return false; } /* * Note that the stats functions here follow the usual stats naming conventions; * "merge" obtains the stats from some live object of instance, while "accum" * only combines the stats from one stats objet to another. Hence the lack of * locking here. */ static void hpa_shard_nonderived_stats_accum(hpa_shard_nonderived_stats_t *dst, hpa_shard_nonderived_stats_t *src) { dst->npurge_passes += src->npurge_passes; dst->npurges += src->npurges; dst->nhugifies += src->nhugifies; dst->ndehugifies += src->ndehugifies; } void hpa_shard_stats_accum(hpa_shard_stats_t *dst, hpa_shard_stats_t *src) { psset_stats_accum(&dst->psset_stats, &src->psset_stats); hpa_shard_nonderived_stats_accum(&dst->nonderived_stats, &src->nonderived_stats); } void hpa_shard_stats_merge(tsdn_t *tsdn, hpa_shard_t *shard, hpa_shard_stats_t *dst) { hpa_do_consistency_checks(shard); malloc_mutex_lock(tsdn, &shard->grow_mtx); malloc_mutex_lock(tsdn, &shard->mtx); psset_stats_accum(&dst->psset_stats, &shard->psset.stats); hpa_shard_nonderived_stats_accum(&dst->nonderived_stats, &shard->stats); malloc_mutex_unlock(tsdn, &shard->mtx); malloc_mutex_unlock(tsdn, &shard->grow_mtx); } static bool hpa_good_hugification_candidate(hpa_shard_t *shard, hpdata_t *ps) { /* * Note that this needs to be >= rather than just >, because of the * important special case in which the hugification threshold is exactly * HUGEPAGE. */ return hpdata_nactive_get(ps) * PAGE >= shard->opts.hugification_threshold; } static size_t hpa_adjusted_ndirty(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); return psset_ndirty(&shard->psset) - shard->npending_purge; } static size_t hpa_ndirty_max(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); if (shard->opts.dirty_mult == (fxp_t)-1) { return (size_t)-1; } return fxp_mul_frac(psset_nactive(&shard->psset), shard->opts.dirty_mult); } static bool hpa_hugify_blocked_by_ndirty(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); if (to_hugify == NULL) { return false; } return hpa_adjusted_ndirty(tsdn, shard) + hpdata_nretained_get(to_hugify) > hpa_ndirty_max(tsdn, shard); } static bool hpa_should_purge(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); if (hpa_adjusted_ndirty(tsdn, shard) > hpa_ndirty_max(tsdn, shard)) { return true; } if (hpa_hugify_blocked_by_ndirty(tsdn, shard)) { return true; } return false; } static void hpa_update_purge_hugify_eligibility(tsdn_t *tsdn, hpa_shard_t *shard, hpdata_t *ps) { malloc_mutex_assert_owner(tsdn, &shard->mtx); if (hpdata_changing_state_get(ps)) { hpdata_purge_allowed_set(ps, false); hpdata_disallow_hugify(ps); return; } /* * Hugepages are distinctly costly to purge, so try to avoid it unless * they're *particularly* full of dirty pages. Eventually, we should * use a smarter / more dynamic heuristic for situations where we have * to manually hugify. * * In situations where we don't manually hugify, this problem is * reduced. The "bad" situation we're trying to avoid is one's that's * common in some Linux configurations (where both enabled and defrag * are set to madvise) that can lead to long latency spikes on the first * access after a hugification. The ideal policy in such configurations * is probably time-based for both purging and hugifying; only hugify a * hugepage if it's met the criteria for some extended period of time, * and only dehugify it if it's failed to meet the criteria for an * extended period of time. When background threads are on, we should * try to take this hit on one of them, as well. * * I think the ideal setting is THP always enabled, and defrag set to * deferred; in that case we don't need any explicit calls on the * allocator's end at all; we just try to pack allocations in a * hugepage-friendly manner and let the OS hugify in the background. */ hpdata_purge_allowed_set(ps, hpdata_ndirty_get(ps) > 0); if (hpa_good_hugification_candidate(shard, ps) && !hpdata_huge_get(ps)) { nstime_t now; shard->central->hooks.curtime(&now, /* first_reading */ true); hpdata_allow_hugify(ps, now); } /* * Once a hugepage has become eligible for hugification, we don't mark * it as ineligible just because it stops meeting the criteria (this * could lead to situations where a hugepage that spends most of its * time meeting the criteria never quite getting hugified if there are * intervening deallocations). The idea is that the hugification delay * will allow them to get purged, reseting their "hugify-allowed" bit. * If they don't get purged, then the hugification isn't hurting and * might help. As an exception, we don't hugify hugepages that are now * empty; it definitely doesn't help there until the hugepage gets * reused, which is likely not for a while. */ if (hpdata_nactive_get(ps) == 0) { hpdata_disallow_hugify(ps); } } static bool hpa_shard_has_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); return to_hugify != NULL || hpa_should_purge(tsdn, shard); } /* Returns whether or not we purged anything. */ static bool hpa_try_purge(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); hpdata_t *to_purge = psset_pick_purge(&shard->psset); if (to_purge == NULL) { return false; } assert(hpdata_purge_allowed_get(to_purge)); assert(!hpdata_changing_state_get(to_purge)); /* * Don't let anyone else purge or hugify this page while * we're purging it (allocations and deallocations are * OK). */ psset_update_begin(&shard->psset, to_purge); assert(hpdata_alloc_allowed_get(to_purge)); hpdata_mid_purge_set(to_purge, true); hpdata_purge_allowed_set(to_purge, false); hpdata_disallow_hugify(to_purge); /* * Unlike with hugification (where concurrent * allocations are allowed), concurrent allocation out * of a hugepage being purged is unsafe; we might hand * out an extent for an allocation and then purge it * (clearing out user data). */ hpdata_alloc_allowed_set(to_purge, false); psset_update_end(&shard->psset, to_purge); /* Gather all the metadata we'll need during the purge. */ bool dehugify = hpdata_huge_get(to_purge); hpdata_purge_state_t purge_state; size_t num_to_purge = hpdata_purge_begin(to_purge, &purge_state); shard->npending_purge += num_to_purge; malloc_mutex_unlock(tsdn, &shard->mtx); /* Actually do the purging, now that the lock is dropped. */ if (dehugify) { shard->central->hooks.dehugify(hpdata_addr_get(to_purge), HUGEPAGE); } size_t total_purged = 0; uint64_t purges_this_pass = 0; void *purge_addr; size_t purge_size; while (hpdata_purge_next(to_purge, &purge_state, &purge_addr, &purge_size)) { total_purged += purge_size; assert(total_purged <= HUGEPAGE); purges_this_pass++; shard->central->hooks.purge(purge_addr, purge_size); } malloc_mutex_lock(tsdn, &shard->mtx); /* The shard updates */ shard->npending_purge -= num_to_purge; shard->stats.npurge_passes++; shard->stats.npurges += purges_this_pass; shard->central->hooks.curtime(&shard->last_purge, /* first_reading */ false); if (dehugify) { shard->stats.ndehugifies++; } /* The hpdata updates. */ psset_update_begin(&shard->psset, to_purge); if (dehugify) { hpdata_dehugify(to_purge); } hpdata_purge_end(to_purge, &purge_state); hpdata_mid_purge_set(to_purge, false); hpdata_alloc_allowed_set(to_purge, true); hpa_update_purge_hugify_eligibility(tsdn, shard, to_purge); psset_update_end(&shard->psset, to_purge); return true; } /* Returns whether or not we hugified anything. */ static bool hpa_try_hugify(tsdn_t *tsdn, hpa_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); if (hpa_hugify_blocked_by_ndirty(tsdn, shard)) { return false; } hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); if (to_hugify == NULL) { return false; } assert(hpdata_hugify_allowed_get(to_hugify)); assert(!hpdata_changing_state_get(to_hugify)); /* Make sure that it's been hugifiable for long enough. */ nstime_t time_hugify_allowed = hpdata_time_hugify_allowed(to_hugify); uint64_t millis = shard->central->hooks.ms_since(&time_hugify_allowed); if (millis < shard->opts.hugify_delay_ms) { return false; } /* * Don't let anyone else purge or hugify this page while * we're hugifying it (allocations and deallocations are * OK). */ psset_update_begin(&shard->psset, to_hugify); hpdata_mid_hugify_set(to_hugify, true); hpdata_purge_allowed_set(to_hugify, false); hpdata_disallow_hugify(to_hugify); assert(hpdata_alloc_allowed_get(to_hugify)); psset_update_end(&shard->psset, to_hugify); malloc_mutex_unlock(tsdn, &shard->mtx); shard->central->hooks.hugify(hpdata_addr_get(to_hugify), HUGEPAGE); malloc_mutex_lock(tsdn, &shard->mtx); shard->stats.nhugifies++; psset_update_begin(&shard->psset, to_hugify); hpdata_hugify(to_hugify); hpdata_mid_hugify_set(to_hugify, false); hpa_update_purge_hugify_eligibility(tsdn, shard, to_hugify); psset_update_end(&shard->psset, to_hugify); return true; } /* * Execution of deferred work is forced if it's triggered by an explicit * hpa_shard_do_deferred_work() call. */ static void hpa_shard_maybe_do_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard, bool forced) { malloc_mutex_assert_owner(tsdn, &shard->mtx); if (!forced && shard->opts.deferral_allowed) { return; } /* * If we're on a background thread, do work so long as there's work to * be done. Otherwise, bound latency to not be *too* bad by doing at * most a small fixed number of operations. */ bool hugified = false; bool purged = false; size_t max_ops = (forced ? (size_t)-1 : 16); size_t nops = 0; do { /* * Always purge before hugifying, to make sure we get some * ability to hit our quiescence targets. */ purged = false; while (hpa_should_purge(tsdn, shard) && nops < max_ops) { purged = hpa_try_purge(tsdn, shard); if (purged) { nops++; } } hugified = hpa_try_hugify(tsdn, shard); if (hugified) { nops++; } malloc_mutex_assert_owner(tsdn, &shard->mtx); malloc_mutex_assert_owner(tsdn, &shard->mtx); } while ((hugified || purged) && nops < max_ops); } static edata_t * hpa_try_alloc_one_no_grow(tsdn_t *tsdn, hpa_shard_t *shard, size_t size, bool *oom) { bool err; edata_t *edata = edata_cache_fast_get(tsdn, &shard->ecf); if (edata == NULL) { *oom = true; return NULL; } hpdata_t *ps = psset_pick_alloc(&shard->psset, size); if (ps == NULL) { edata_cache_fast_put(tsdn, &shard->ecf, edata); return NULL; } psset_update_begin(&shard->psset, ps); if (hpdata_empty(ps)) { /* * If the pageslab used to be empty, treat it as though it's * brand new for fragmentation-avoidance purposes; what we're * trying to approximate is the age of the allocations *in* that * pageslab, and the allocations in the new pageslab are * definitionally the youngest in this hpa shard. */ hpdata_age_set(ps, shard->age_counter++); } void *addr = hpdata_reserve_alloc(ps, size); edata_init(edata, shard->ind, addr, size, /* slab */ false, SC_NSIZES, /* sn */ hpdata_age_get(ps), extent_state_active, /* zeroed */ false, /* committed */ true, EXTENT_PAI_HPA, EXTENT_NOT_HEAD); edata_ps_set(edata, ps); /* * This could theoretically be moved outside of the critical section, * but that introduces the potential for a race. Without the lock, the * (initially nonempty, since this is the reuse pathway) pageslab we * allocated out of could become otherwise empty while the lock is * dropped. This would force us to deal with a pageslab eviction down * the error pathway, which is a pain. */ err = emap_register_boundary(tsdn, shard->emap, edata, SC_NSIZES, /* slab */ false); if (err) { hpdata_unreserve(ps, edata_addr_get(edata), edata_size_get(edata)); /* * We should arguably reset dirty state here, but this would * require some sort of prepare + commit functionality that's a * little much to deal with for now. * * We don't have a do_deferred_work down this pathway, on the * principle that we didn't *really* affect shard state (we * tweaked the stats, but our tweaks weren't really accurate). */ psset_update_end(&shard->psset, ps); edata_cache_fast_put(tsdn, &shard->ecf, edata); *oom = true; return NULL; } hpa_update_purge_hugify_eligibility(tsdn, shard, ps); psset_update_end(&shard->psset, ps); return edata; } static size_t hpa_try_alloc_batch_no_grow(tsdn_t *tsdn, hpa_shard_t *shard, size_t size, bool *oom, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated) { malloc_mutex_lock(tsdn, &shard->mtx); size_t nsuccess = 0; for (; nsuccess < nallocs; nsuccess++) { edata_t *edata = hpa_try_alloc_one_no_grow(tsdn, shard, size, oom); if (edata == NULL) { break; } edata_list_active_append(results, edata); } hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ false); *deferred_work_generated = hpa_shard_has_deferred_work(tsdn, shard); malloc_mutex_unlock(tsdn, &shard->mtx); return nsuccess; } static size_t hpa_alloc_batch_psset(tsdn_t *tsdn, hpa_shard_t *shard, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated) { assert(size <= shard->opts.slab_max_alloc); bool oom = false; size_t nsuccess = hpa_try_alloc_batch_no_grow(tsdn, shard, size, &oom, nallocs, results, deferred_work_generated); if (nsuccess == nallocs || oom) { return nsuccess; } /* * We didn't OOM, but weren't able to fill everything requested of us; * try to grow. */ malloc_mutex_lock(tsdn, &shard->grow_mtx); /* * Check for grow races; maybe some earlier thread expanded the psset * in between when we dropped the main mutex and grabbed the grow mutex. */ nsuccess += hpa_try_alloc_batch_no_grow(tsdn, shard, size, &oom, nallocs - nsuccess, results, deferred_work_generated); if (nsuccess == nallocs || oom) { malloc_mutex_unlock(tsdn, &shard->grow_mtx); return nsuccess; } /* * Note that we don't hold shard->mtx here (while growing); * deallocations (and allocations of smaller sizes) may still succeed * while we're doing this potentially expensive system call. */ hpdata_t *ps = hpa_central_extract(tsdn, shard->central, size, &oom); if (ps == NULL) { malloc_mutex_unlock(tsdn, &shard->grow_mtx); return nsuccess; } /* * We got the pageslab; allocate from it. This does an unlock followed * by a lock on the same mutex, and holds the grow mutex while doing * deferred work, but this is an uncommon path; the simplicity is worth * it. */ malloc_mutex_lock(tsdn, &shard->mtx); psset_insert(&shard->psset, ps); malloc_mutex_unlock(tsdn, &shard->mtx); nsuccess += hpa_try_alloc_batch_no_grow(tsdn, shard, size, &oom, nallocs - nsuccess, results, deferred_work_generated); /* * Drop grow_mtx before doing deferred work; other threads blocked on it * should be allowed to proceed while we're working. */ malloc_mutex_unlock(tsdn, &shard->grow_mtx); return nsuccess; } static hpa_shard_t * hpa_from_pai(pai_t *self) { assert(self->alloc = &hpa_alloc); assert(self->expand = &hpa_expand); assert(self->shrink = &hpa_shrink); assert(self->dalloc = &hpa_dalloc); return (hpa_shard_t *)self; } static size_t hpa_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated) { assert(nallocs > 0); assert((size & PAGE_MASK) == 0); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); hpa_shard_t *shard = hpa_from_pai(self); if (size > shard->opts.slab_max_alloc) { return 0; } size_t nsuccess = hpa_alloc_batch_psset(tsdn, shard, size, nallocs, results, deferred_work_generated); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); /* * Guard the sanity checks with config_debug because the loop cannot be * proven non-circular by the compiler, even if everything within the * loop is optimized away. */ if (config_debug) { edata_t *edata; ql_foreach(edata, &results->head, ql_link_active) { emap_assert_mapped(tsdn, shard->emap, edata); assert(edata_pai_get(edata) == EXTENT_PAI_HPA); assert(edata_state_get(edata) == extent_state_active); assert(edata_arena_ind_get(edata) == shard->ind); assert(edata_szind_get_maybe_invalid(edata) == SC_NSIZES); assert(!edata_slab_get(edata)); assert(edata_committed_get(edata)); assert(edata_base_get(edata) == edata_addr_get(edata)); assert(edata_base_get(edata) != NULL); } } return nsuccess; } static edata_t * hpa_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated) { assert((size & PAGE_MASK) == 0); assert(!guarded); witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); /* We don't handle alignment or zeroing for now. */ if (alignment > PAGE || zero) { return NULL; } /* * An alloc with alignment == PAGE and zero == false is equivalent to a * batch alloc of 1. Just do that, so we can share code. */ edata_list_active_t results; edata_list_active_init(&results); size_t nallocs = hpa_alloc_batch(tsdn, self, size, /* nallocs */ 1, &results, deferred_work_generated); assert(nallocs == 0 || nallocs == 1); edata_t *edata = edata_list_active_first(&results); return edata; } static bool hpa_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated) { /* Expand not yet supported. */ return true; } static bool hpa_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated) { /* Shrink not yet supported. */ return true; } static void hpa_dalloc_prepare_unlocked(tsdn_t *tsdn, hpa_shard_t *shard, edata_t *edata) { malloc_mutex_assert_not_owner(tsdn, &shard->mtx); assert(edata_pai_get(edata) == EXTENT_PAI_HPA); assert(edata_state_get(edata) == extent_state_active); assert(edata_arena_ind_get(edata) == shard->ind); assert(edata_szind_get_maybe_invalid(edata) == SC_NSIZES); assert(edata_committed_get(edata)); assert(edata_base_get(edata) != NULL); /* * Another thread shouldn't be trying to touch the metadata of an * allocation being freed. The one exception is a merge attempt from a * lower-addressed PAC extent; in this case we have a nominal race on * the edata metadata bits, but in practice the fact that the PAI bits * are different will prevent any further access. The race is bad, but * benign in practice, and the long term plan is to track enough state * in the rtree to prevent these merge attempts in the first place. */ edata_addr_set(edata, edata_base_get(edata)); edata_zeroed_set(edata, false); emap_deregister_boundary(tsdn, shard->emap, edata); } static void hpa_dalloc_locked(tsdn_t *tsdn, hpa_shard_t *shard, edata_t *edata) { malloc_mutex_assert_owner(tsdn, &shard->mtx); /* * Release the metadata early, to avoid having to remember to do it * while we're also doing tricky purging logic. First, we need to grab * a few bits of metadata from it. * * Note that the shard mutex protects ps's metadata too; it wouldn't be * correct to try to read most information out of it without the lock. */ hpdata_t *ps = edata_ps_get(edata); /* Currently, all edatas come from pageslabs. */ assert(ps != NULL); void *unreserve_addr = edata_addr_get(edata); size_t unreserve_size = edata_size_get(edata); edata_cache_fast_put(tsdn, &shard->ecf, edata); psset_update_begin(&shard->psset, ps); hpdata_unreserve(ps, unreserve_addr, unreserve_size); hpa_update_purge_hugify_eligibility(tsdn, shard, ps); psset_update_end(&shard->psset, ps); } static void hpa_dalloc_batch(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated) { hpa_shard_t *shard = hpa_from_pai(self); edata_t *edata; ql_foreach(edata, &list->head, ql_link_active) { hpa_dalloc_prepare_unlocked(tsdn, shard, edata); } malloc_mutex_lock(tsdn, &shard->mtx); /* Now, remove from the list. */ while ((edata = edata_list_active_first(list)) != NULL) { edata_list_active_remove(list, edata); hpa_dalloc_locked(tsdn, shard, edata); } hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ false); *deferred_work_generated = hpa_shard_has_deferred_work(tsdn, shard); malloc_mutex_unlock(tsdn, &shard->mtx); } static void hpa_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated) { assert(!edata_guarded_get(edata)); /* Just a dalloc_batch of size 1; this lets us share logic. */ edata_list_active_t dalloc_list; edata_list_active_init(&dalloc_list); edata_list_active_append(&dalloc_list, edata); hpa_dalloc_batch(tsdn, self, &dalloc_list, deferred_work_generated); } /* * Calculate time until either purging or hugification ought to happen. * Called by background threads. */ static uint64_t hpa_time_until_deferred_work(tsdn_t *tsdn, pai_t *self) { hpa_shard_t *shard = hpa_from_pai(self); uint64_t time_ns = BACKGROUND_THREAD_DEFERRED_MAX; malloc_mutex_lock(tsdn, &shard->mtx); hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); if (to_hugify != NULL) { nstime_t time_hugify_allowed = hpdata_time_hugify_allowed(to_hugify); uint64_t since_hugify_allowed_ms = shard->central->hooks.ms_since(&time_hugify_allowed); /* * If not enough time has passed since hugification was allowed, * sleep for the rest. */ if (since_hugify_allowed_ms < shard->opts.hugify_delay_ms) { time_ns = shard->opts.hugify_delay_ms - since_hugify_allowed_ms; time_ns *= 1000 * 1000; } else { malloc_mutex_unlock(tsdn, &shard->mtx); return BACKGROUND_THREAD_DEFERRED_MIN; } } if (hpa_should_purge(tsdn, shard)) { /* * If we haven't purged before, no need to check interval * between purges. Simply purge as soon as possible. */ if (shard->stats.npurge_passes == 0) { malloc_mutex_unlock(tsdn, &shard->mtx); return BACKGROUND_THREAD_DEFERRED_MIN; } uint64_t since_last_purge_ms = shard->central->hooks.ms_since( &shard->last_purge); if (since_last_purge_ms < shard->opts.min_purge_interval_ms) { uint64_t until_purge_ns; until_purge_ns = shard->opts.min_purge_interval_ms - since_last_purge_ms; until_purge_ns *= 1000 * 1000; if (until_purge_ns < time_ns) { time_ns = until_purge_ns; } } else { time_ns = BACKGROUND_THREAD_DEFERRED_MIN; } } malloc_mutex_unlock(tsdn, &shard->mtx); return time_ns; } void hpa_shard_disable(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); malloc_mutex_lock(tsdn, &shard->mtx); edata_cache_fast_disable(tsdn, &shard->ecf); malloc_mutex_unlock(tsdn, &shard->mtx); } static void hpa_shard_assert_stats_empty(psset_bin_stats_t *bin_stats) { assert(bin_stats->npageslabs == 0); assert(bin_stats->nactive == 0); } static void hpa_assert_empty(tsdn_t *tsdn, hpa_shard_t *shard, psset_t *psset) { malloc_mutex_assert_owner(tsdn, &shard->mtx); for (int huge = 0; huge <= 1; huge++) { hpa_shard_assert_stats_empty(&psset->stats.full_slabs[huge]); for (pszind_t i = 0; i < PSSET_NPSIZES; i++) { hpa_shard_assert_stats_empty( &psset->stats.nonfull_slabs[i][huge]); } } } void hpa_shard_destroy(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); /* * By the time we're here, the arena code should have dalloc'd all the * active extents, which means we should have eventually evicted * everything from the psset, so it shouldn't be able to serve even a * 1-page allocation. */ if (config_debug) { malloc_mutex_lock(tsdn, &shard->mtx); hpa_assert_empty(tsdn, shard, &shard->psset); malloc_mutex_unlock(tsdn, &shard->mtx); } hpdata_t *ps; while ((ps = psset_pick_alloc(&shard->psset, PAGE)) != NULL) { /* There should be no allocations anywhere. */ assert(hpdata_empty(ps)); psset_remove(&shard->psset, ps); shard->central->hooks.unmap(hpdata_addr_get(ps), HUGEPAGE); } } void hpa_shard_set_deferral_allowed(tsdn_t *tsdn, hpa_shard_t *shard, bool deferral_allowed) { hpa_do_consistency_checks(shard); malloc_mutex_lock(tsdn, &shard->mtx); bool deferral_previously_allowed = shard->opts.deferral_allowed; shard->opts.deferral_allowed = deferral_allowed; if (deferral_previously_allowed && !deferral_allowed) { hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ true); } malloc_mutex_unlock(tsdn, &shard->mtx); } void hpa_shard_do_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); malloc_mutex_lock(tsdn, &shard->mtx); hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ true); malloc_mutex_unlock(tsdn, &shard->mtx); } void hpa_shard_prefork3(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); malloc_mutex_prefork(tsdn, &shard->grow_mtx); } void hpa_shard_prefork4(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); malloc_mutex_prefork(tsdn, &shard->mtx); } void hpa_shard_postfork_parent(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); malloc_mutex_postfork_parent(tsdn, &shard->grow_mtx); malloc_mutex_postfork_parent(tsdn, &shard->mtx); } void hpa_shard_postfork_child(tsdn_t *tsdn, hpa_shard_t *shard) { hpa_do_consistency_checks(shard); malloc_mutex_postfork_child(tsdn, &shard->grow_mtx); malloc_mutex_postfork_child(tsdn, &shard->mtx); } redis-8.0.2/deps/jemalloc/src/hpa_hooks.c000066400000000000000000000027171501533116600202660ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/hpa_hooks.h" static void *hpa_hooks_map(size_t size); static void hpa_hooks_unmap(void *ptr, size_t size); static void hpa_hooks_purge(void *ptr, size_t size); static void hpa_hooks_hugify(void *ptr, size_t size); static void hpa_hooks_dehugify(void *ptr, size_t size); static void hpa_hooks_curtime(nstime_t *r_nstime, bool first_reading); static uint64_t hpa_hooks_ms_since(nstime_t *past_nstime); hpa_hooks_t hpa_hooks_default = { &hpa_hooks_map, &hpa_hooks_unmap, &hpa_hooks_purge, &hpa_hooks_hugify, &hpa_hooks_dehugify, &hpa_hooks_curtime, &hpa_hooks_ms_since }; static void * hpa_hooks_map(size_t size) { bool commit = true; return pages_map(NULL, size, HUGEPAGE, &commit); } static void hpa_hooks_unmap(void *ptr, size_t size) { pages_unmap(ptr, size); } static void hpa_hooks_purge(void *ptr, size_t size) { pages_purge_forced(ptr, size); } static void hpa_hooks_hugify(void *ptr, size_t size) { bool err = pages_huge(ptr, size); (void)err; } static void hpa_hooks_dehugify(void *ptr, size_t size) { bool err = pages_nohuge(ptr, size); (void)err; } static void hpa_hooks_curtime(nstime_t *r_nstime, bool first_reading) { if (first_reading) { nstime_init_zero(r_nstime); } nstime_update(r_nstime); } static uint64_t hpa_hooks_ms_since(nstime_t *past_nstime) { return nstime_ns_since(past_nstime) / 1000 / 1000; } redis-8.0.2/deps/jemalloc/src/hpdata.c000066400000000000000000000236501501533116600175530ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/hpdata.h" static int hpdata_age_comp(const hpdata_t *a, const hpdata_t *b) { uint64_t a_age = hpdata_age_get(a); uint64_t b_age = hpdata_age_get(b); /* * hpdata ages are operation counts in the psset; no two should be the * same. */ assert(a_age != b_age); return (a_age > b_age) - (a_age < b_age); } ph_gen(, hpdata_age_heap, hpdata_t, age_link, hpdata_age_comp) void hpdata_init(hpdata_t *hpdata, void *addr, uint64_t age) { hpdata_addr_set(hpdata, addr); hpdata_age_set(hpdata, age); hpdata->h_huge = false; hpdata->h_alloc_allowed = true; hpdata->h_in_psset_alloc_container = false; hpdata->h_purge_allowed = false; hpdata->h_hugify_allowed = false; hpdata->h_in_psset_hugify_container = false; hpdata->h_mid_purge = false; hpdata->h_mid_hugify = false; hpdata->h_updating = false; hpdata->h_in_psset = false; hpdata_longest_free_range_set(hpdata, HUGEPAGE_PAGES); hpdata->h_nactive = 0; fb_init(hpdata->active_pages, HUGEPAGE_PAGES); hpdata->h_ntouched = 0; fb_init(hpdata->touched_pages, HUGEPAGE_PAGES); hpdata_assert_consistent(hpdata); } void * hpdata_reserve_alloc(hpdata_t *hpdata, size_t sz) { hpdata_assert_consistent(hpdata); /* * This is a metadata change; the hpdata should therefore either not be * in the psset, or should have explicitly marked itself as being * mid-update. */ assert(!hpdata->h_in_psset || hpdata->h_updating); assert(hpdata->h_alloc_allowed); assert((sz & PAGE_MASK) == 0); size_t npages = sz >> LG_PAGE; assert(npages <= hpdata_longest_free_range_get(hpdata)); size_t result; size_t start = 0; /* * These are dead stores, but the compiler will issue warnings on them * since it can't tell statically that found is always true below. */ size_t begin = 0; size_t len = 0; size_t largest_unchosen_range = 0; while (true) { bool found = fb_urange_iter(hpdata->active_pages, HUGEPAGE_PAGES, start, &begin, &len); /* * A precondition to this function is that hpdata must be able * to serve the allocation. */ assert(found); assert(len <= hpdata_longest_free_range_get(hpdata)); if (len >= npages) { /* * We use first-fit within the page slabs; this gives * bounded worst-case fragmentation within a slab. It's * not necessarily right; we could experiment with * various other options. */ break; } if (len > largest_unchosen_range) { largest_unchosen_range = len; } start = begin + len; } /* We found a range; remember it. */ result = begin; fb_set_range(hpdata->active_pages, HUGEPAGE_PAGES, begin, npages); hpdata->h_nactive += npages; /* * We might be about to dirty some memory for the first time; update our * count if so. */ size_t new_dirty = fb_ucount(hpdata->touched_pages, HUGEPAGE_PAGES, result, npages); fb_set_range(hpdata->touched_pages, HUGEPAGE_PAGES, result, npages); hpdata->h_ntouched += new_dirty; /* * If we allocated out of a range that was the longest in the hpdata, it * might be the only one of that size and we'll have to adjust the * metadata. */ if (len == hpdata_longest_free_range_get(hpdata)) { start = begin + npages; while (start < HUGEPAGE_PAGES) { bool found = fb_urange_iter(hpdata->active_pages, HUGEPAGE_PAGES, start, &begin, &len); if (!found) { break; } assert(len <= hpdata_longest_free_range_get(hpdata)); if (len == hpdata_longest_free_range_get(hpdata)) { largest_unchosen_range = len; break; } if (len > largest_unchosen_range) { largest_unchosen_range = len; } start = begin + len; } hpdata_longest_free_range_set(hpdata, largest_unchosen_range); } hpdata_assert_consistent(hpdata); return (void *)( (uintptr_t)hpdata_addr_get(hpdata) + (result << LG_PAGE)); } void hpdata_unreserve(hpdata_t *hpdata, void *addr, size_t sz) { hpdata_assert_consistent(hpdata); /* See the comment in reserve. */ assert(!hpdata->h_in_psset || hpdata->h_updating); assert(((uintptr_t)addr & PAGE_MASK) == 0); assert((sz & PAGE_MASK) == 0); size_t begin = ((uintptr_t)addr - (uintptr_t)hpdata_addr_get(hpdata)) >> LG_PAGE; assert(begin < HUGEPAGE_PAGES); size_t npages = sz >> LG_PAGE; size_t old_longest_range = hpdata_longest_free_range_get(hpdata); fb_unset_range(hpdata->active_pages, HUGEPAGE_PAGES, begin, npages); /* We might have just created a new, larger range. */ size_t new_begin = (fb_fls(hpdata->active_pages, HUGEPAGE_PAGES, begin) + 1); size_t new_end = fb_ffs(hpdata->active_pages, HUGEPAGE_PAGES, begin + npages - 1); size_t new_range_len = new_end - new_begin; if (new_range_len > old_longest_range) { hpdata_longest_free_range_set(hpdata, new_range_len); } hpdata->h_nactive -= npages; hpdata_assert_consistent(hpdata); } size_t hpdata_purge_begin(hpdata_t *hpdata, hpdata_purge_state_t *purge_state) { hpdata_assert_consistent(hpdata); /* * See the comment below; we might purge any inactive extent, so it's * unsafe for any other thread to turn any inactive extent active while * we're operating on it. */ assert(!hpdata_alloc_allowed_get(hpdata)); purge_state->npurged = 0; purge_state->next_purge_search_begin = 0; /* * Initialize to_purge. * * It's possible to end up in situations where two dirty extents are * separated by a retained extent: * - 1 page allocated. * - 1 page allocated. * - 1 pages allocated. * * If the middle page is freed and purged, and then the first and third * pages are freed, and then another purge pass happens, the hpdata * looks like this: * - 1 page dirty. * - 1 page retained. * - 1 page dirty. * * But it's safe to do a single 3-page purge. * * We do this by first computing the dirty pages, and then filling in * any gaps by extending each range in the dirty bitmap to extend until * the next active page. This purges more pages, but the expensive part * of purging is the TLB shootdowns, rather than the kernel state * tracking; doing a little bit more of the latter is fine if it saves * us from doing some of the former. */ /* * The dirty pages are those that are touched but not active. Note that * in a normal-ish case, HUGEPAGE_PAGES is something like 512 and the * fb_group_t is 64 bits, so this is 64 bytes, spread across 8 * fb_group_ts. */ fb_group_t dirty_pages[FB_NGROUPS(HUGEPAGE_PAGES)]; fb_init(dirty_pages, HUGEPAGE_PAGES); fb_bit_not(dirty_pages, hpdata->active_pages, HUGEPAGE_PAGES); fb_bit_and(dirty_pages, dirty_pages, hpdata->touched_pages, HUGEPAGE_PAGES); fb_init(purge_state->to_purge, HUGEPAGE_PAGES); size_t next_bit = 0; while (next_bit < HUGEPAGE_PAGES) { size_t next_dirty = fb_ffs(dirty_pages, HUGEPAGE_PAGES, next_bit); /* Recall that fb_ffs returns nbits if no set bit is found. */ if (next_dirty == HUGEPAGE_PAGES) { break; } size_t next_active = fb_ffs(hpdata->active_pages, HUGEPAGE_PAGES, next_dirty); /* * Don't purge past the end of the dirty extent, into retained * pages. This helps the kernel a tiny bit, but honestly it's * mostly helpful for testing (where we tend to write test cases * that think in terms of the dirty ranges). */ ssize_t last_dirty = fb_fls(dirty_pages, HUGEPAGE_PAGES, next_active - 1); assert(last_dirty >= 0); assert((size_t)last_dirty >= next_dirty); assert((size_t)last_dirty - next_dirty + 1 <= HUGEPAGE_PAGES); fb_set_range(purge_state->to_purge, HUGEPAGE_PAGES, next_dirty, last_dirty - next_dirty + 1); next_bit = next_active + 1; } /* We should purge, at least, everything dirty. */ size_t ndirty = hpdata->h_ntouched - hpdata->h_nactive; purge_state->ndirty_to_purge = ndirty; assert(ndirty <= fb_scount( purge_state->to_purge, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)); assert(ndirty == fb_scount(dirty_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)); hpdata_assert_consistent(hpdata); return ndirty; } bool hpdata_purge_next(hpdata_t *hpdata, hpdata_purge_state_t *purge_state, void **r_purge_addr, size_t *r_purge_size) { /* * Note that we don't have a consistency check here; we're accessing * hpdata without synchronization, and therefore have no right to expect * a consistent state. */ assert(!hpdata_alloc_allowed_get(hpdata)); if (purge_state->next_purge_search_begin == HUGEPAGE_PAGES) { return false; } size_t purge_begin; size_t purge_len; bool found_range = fb_srange_iter(purge_state->to_purge, HUGEPAGE_PAGES, purge_state->next_purge_search_begin, &purge_begin, &purge_len); if (!found_range) { return false; } *r_purge_addr = (void *)( (uintptr_t)hpdata_addr_get(hpdata) + purge_begin * PAGE); *r_purge_size = purge_len * PAGE; purge_state->next_purge_search_begin = purge_begin + purge_len; purge_state->npurged += purge_len; assert(purge_state->npurged <= HUGEPAGE_PAGES); return true; } void hpdata_purge_end(hpdata_t *hpdata, hpdata_purge_state_t *purge_state) { assert(!hpdata_alloc_allowed_get(hpdata)); hpdata_assert_consistent(hpdata); /* See the comment in reserve. */ assert(!hpdata->h_in_psset || hpdata->h_updating); assert(purge_state->npurged == fb_scount(purge_state->to_purge, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)); assert(purge_state->npurged >= purge_state->ndirty_to_purge); fb_bit_not(purge_state->to_purge, purge_state->to_purge, HUGEPAGE_PAGES); fb_bit_and(hpdata->touched_pages, hpdata->touched_pages, purge_state->to_purge, HUGEPAGE_PAGES); assert(hpdata->h_ntouched >= purge_state->ndirty_to_purge); hpdata->h_ntouched -= purge_state->ndirty_to_purge; hpdata_assert_consistent(hpdata); } void hpdata_hugify(hpdata_t *hpdata) { hpdata_assert_consistent(hpdata); hpdata->h_huge = true; fb_set_range(hpdata->touched_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES); hpdata->h_ntouched = HUGEPAGE_PAGES; hpdata_assert_consistent(hpdata); } void hpdata_dehugify(hpdata_t *hpdata) { hpdata_assert_consistent(hpdata); hpdata->h_huge = false; hpdata_assert_consistent(hpdata); } redis-8.0.2/deps/jemalloc/src/inspect.c000066400000000000000000000044061501533116600177550ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" void inspect_extent_util_stats_get(tsdn_t *tsdn, const void *ptr, size_t *nfree, size_t *nregs, size_t *size) { assert(ptr != NULL && nfree != NULL && nregs != NULL && size != NULL); const edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); if (unlikely(edata == NULL)) { *nfree = *nregs = *size = 0; return; } *size = edata_size_get(edata); if (!edata_slab_get(edata)) { *nfree = 0; *nregs = 1; } else { *nfree = edata_nfree_get(edata); *nregs = bin_infos[edata_szind_get(edata)].nregs; assert(*nfree <= *nregs); assert(*nfree * edata_usize_get(edata) <= *size); } } void inspect_extent_util_stats_verbose_get(tsdn_t *tsdn, const void *ptr, size_t *nfree, size_t *nregs, size_t *size, size_t *bin_nfree, size_t *bin_nregs, void **slabcur_addr) { assert(ptr != NULL && nfree != NULL && nregs != NULL && size != NULL && bin_nfree != NULL && bin_nregs != NULL && slabcur_addr != NULL); const edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); if (unlikely(edata == NULL)) { *nfree = *nregs = *size = *bin_nfree = *bin_nregs = 0; *slabcur_addr = NULL; return; } *size = edata_size_get(edata); if (!edata_slab_get(edata)) { *nfree = *bin_nfree = *bin_nregs = 0; *nregs = 1; *slabcur_addr = NULL; return; } *nfree = edata_nfree_get(edata); const szind_t szind = edata_szind_get(edata); *nregs = bin_infos[szind].nregs; assert(*nfree <= *nregs); assert(*nfree * edata_usize_get(edata) <= *size); arena_t *arena = (arena_t *)atomic_load_p( &arenas[edata_arena_ind_get(edata)], ATOMIC_RELAXED); assert(arena != NULL); const unsigned binshard = edata_binshard_get(edata); bin_t *bin = arena_get_bin(arena, szind, binshard); malloc_mutex_lock(tsdn, &bin->lock); if (config_stats) { *bin_nregs = *nregs * bin->stats.curslabs; assert(*bin_nregs >= bin->stats.curregs); *bin_nfree = *bin_nregs - bin->stats.curregs; } else { *bin_nfree = *bin_nregs = 0; } edata_t *slab; if (bin->slabcur != NULL) { slab = bin->slabcur; } else { slab = edata_heap_first(&bin->slabs_nonfull); } *slabcur_addr = slab != NULL ? edata_addr_get(slab) : NULL; malloc_mutex_unlock(tsdn, &bin->lock); } redis-8.0.2/deps/jemalloc/src/jemalloc.c000066400000000000000000003571701501533116600201070ustar00rootroot00000000000000#define JEMALLOC_C_ #include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/atomic.h" #include "jemalloc/internal/buf_writer.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/emap.h" #include "jemalloc/internal/extent_dss.h" #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/fxp.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/hook.h" #include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/log.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/nstime.h" #include "jemalloc/internal/rtree.h" #include "jemalloc/internal/safety_check.h" #include "jemalloc/internal/sc.h" #include "jemalloc/internal/spin.h" #include "jemalloc/internal/sz.h" #include "jemalloc/internal/ticker.h" #include "jemalloc/internal/thread_event.h" #include "jemalloc/internal/util.h" /******************************************************************************/ /* Data. */ /* Runtime configuration options. */ const char *je_malloc_conf #ifndef _WIN32 JEMALLOC_ATTR(weak) #endif ; /* * The usual rule is that the closer to runtime you are, the higher priority * your configuration settings are (so the jemalloc config options get lower * priority than the per-binary setting, which gets lower priority than the /etc * setting, which gets lower priority than the environment settings). * * But it's a fairly common use case in some testing environments for a user to * be able to control the binary, but nothing else (e.g. a performancy canary * uses the production OS and environment variables, but can run any binary in * those circumstances). For these use cases, it's handy to have an in-binary * mechanism for overriding environment variable settings, with the idea that if * the results are positive they get promoted to the official settings, and * moved from the binary to the environment variable. * * We don't actually want this to be widespread, so we'll give it a silly name * and not mention it in headers or documentation. */ const char *je_malloc_conf_2_conf_harder #ifndef _WIN32 JEMALLOC_ATTR(weak) #endif ; bool opt_abort = #ifdef JEMALLOC_DEBUG true #else false #endif ; bool opt_abort_conf = #ifdef JEMALLOC_DEBUG true #else false #endif ; /* Intentionally default off, even with debug builds. */ bool opt_confirm_conf = false; const char *opt_junk = #if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL)) "true" #else "false" #endif ; bool opt_junk_alloc = #if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL)) true #else false #endif ; bool opt_junk_free = #if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL)) true #else false #endif ; bool opt_trust_madvise = #ifdef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS false #else true #endif ; bool opt_cache_oblivious = #ifdef JEMALLOC_CACHE_OBLIVIOUS true #else false #endif ; zero_realloc_action_t opt_zero_realloc_action = #ifdef JEMALLOC_ZERO_REALLOC_DEFAULT_FREE zero_realloc_action_free #else zero_realloc_action_alloc #endif ; atomic_zu_t zero_realloc_count = ATOMIC_INIT(0); const char *zero_realloc_mode_names[] = { "alloc", "free", "abort", }; /* * These are the documented values for junk fill debugging facilities -- see the * man page. */ static const uint8_t junk_alloc_byte = 0xa5; static const uint8_t junk_free_byte = 0x5a; static void default_junk_alloc(void *ptr, size_t usize) { memset(ptr, junk_alloc_byte, usize); } static void default_junk_free(void *ptr, size_t usize) { memset(ptr, junk_free_byte, usize); } void (*junk_alloc_callback)(void *ptr, size_t size) = &default_junk_alloc; void (*junk_free_callback)(void *ptr, size_t size) = &default_junk_free; bool opt_utrace = false; bool opt_xmalloc = false; bool opt_experimental_infallible_new = false; bool opt_zero = false; unsigned opt_narenas = 0; fxp_t opt_narenas_ratio = FXP_INIT_INT(4); unsigned ncpus; /* Protects arenas initialization. */ malloc_mutex_t arenas_lock; /* The global hpa, and whether it's on. */ bool opt_hpa = false; hpa_shard_opts_t opt_hpa_opts = HPA_SHARD_OPTS_DEFAULT; sec_opts_t opt_hpa_sec_opts = SEC_OPTS_DEFAULT; /* * Arenas that are used to service external requests. Not all elements of the * arenas array are necessarily used; arenas are created lazily as needed. * * arenas[0..narenas_auto) are used for automatic multiplexing of threads and * arenas. arenas[narenas_auto..narenas_total) are only used if the application * takes some action to create them and allocate from them. * * Points to an arena_t. */ JEMALLOC_ALIGNED(CACHELINE) atomic_p_t arenas[MALLOCX_ARENA_LIMIT]; static atomic_u_t narenas_total; /* Use narenas_total_*(). */ /* Below three are read-only after initialization. */ static arena_t *a0; /* arenas[0]. */ unsigned narenas_auto; unsigned manual_arena_base; malloc_init_t malloc_init_state = malloc_init_uninitialized; /* False should be the common case. Set to true to trigger initialization. */ bool malloc_slow = true; /* When malloc_slow is true, set the corresponding bits for sanity check. */ enum { flag_opt_junk_alloc = (1U), flag_opt_junk_free = (1U << 1), flag_opt_zero = (1U << 2), flag_opt_utrace = (1U << 3), flag_opt_xmalloc = (1U << 4) }; static uint8_t malloc_slow_flags; #ifdef JEMALLOC_THREADED_INIT /* Used to let the initializing thread recursively allocate. */ # define NO_INITIALIZER ((unsigned long)0) # define INITIALIZER pthread_self() # define IS_INITIALIZER (malloc_initializer == pthread_self()) static pthread_t malloc_initializer = NO_INITIALIZER; #else # define NO_INITIALIZER false # define INITIALIZER true # define IS_INITIALIZER malloc_initializer static bool malloc_initializer = NO_INITIALIZER; #endif /* Used to avoid initialization races. */ #ifdef _WIN32 #if _WIN32_WINNT >= 0x0600 static malloc_mutex_t init_lock = SRWLOCK_INIT; #else static malloc_mutex_t init_lock; static bool init_lock_initialized = false; JEMALLOC_ATTR(constructor) static void WINAPI _init_init_lock(void) { /* * If another constructor in the same binary is using mallctl to e.g. * set up extent hooks, it may end up running before this one, and * malloc_init_hard will crash trying to lock the uninitialized lock. So * we force an initialization of the lock in malloc_init_hard as well. * We don't try to care about atomicity of the accessed to the * init_lock_initialized boolean, since it really only matters early in * the process creation, before any separate thread normally starts * doing anything. */ if (!init_lock_initialized) { malloc_mutex_init(&init_lock, "init", WITNESS_RANK_INIT, malloc_mutex_rank_exclusive); } init_lock_initialized = true; } #ifdef _MSC_VER # pragma section(".CRT$XCU", read) JEMALLOC_SECTION(".CRT$XCU") JEMALLOC_ATTR(used) static const void (WINAPI *init_init_lock)(void) = _init_init_lock; #endif #endif #else static malloc_mutex_t init_lock = MALLOC_MUTEX_INITIALIZER; #endif typedef struct { void *p; /* Input pointer (as in realloc(p, s)). */ size_t s; /* Request size. */ void *r; /* Result pointer. */ } malloc_utrace_t; #ifdef JEMALLOC_UTRACE # define UTRACE(a, b, c) do { \ if (unlikely(opt_utrace)) { \ int utrace_serrno = errno; \ malloc_utrace_t ut; \ ut.p = (a); \ ut.s = (b); \ ut.r = (c); \ UTRACE_CALL(&ut, sizeof(ut)); \ errno = utrace_serrno; \ } \ } while (0) #else # define UTRACE(a, b, c) #endif /* Whether encountered any invalid config options. */ static bool had_conf_error = false; /******************************************************************************/ /* * Function prototypes for static functions that are referenced prior to * definition. */ static bool malloc_init_hard_a0(void); static bool malloc_init_hard(void); /******************************************************************************/ /* * Begin miscellaneous support functions. */ JEMALLOC_ALWAYS_INLINE bool malloc_init_a0(void) { if (unlikely(malloc_init_state == malloc_init_uninitialized)) { return malloc_init_hard_a0(); } return false; } JEMALLOC_ALWAYS_INLINE bool malloc_init(void) { if (unlikely(!malloc_initialized()) && malloc_init_hard()) { return true; } return false; } /* * The a0*() functions are used instead of i{d,}alloc() in situations that * cannot tolerate TLS variable access. */ static void * a0ialloc(size_t size, bool zero, bool is_internal) { if (unlikely(malloc_init_a0())) { return NULL; } return iallocztm(TSDN_NULL, size, sz_size2index(size), zero, NULL, is_internal, arena_get(TSDN_NULL, 0, true), true); } static void a0idalloc(void *ptr, bool is_internal) { idalloctm(TSDN_NULL, ptr, NULL, NULL, is_internal, true); } void * a0malloc(size_t size) { return a0ialloc(size, false, true); } void a0dalloc(void *ptr) { a0idalloc(ptr, true); } /* * FreeBSD's libc uses the bootstrap_*() functions in bootstrap-sensitive * situations that cannot tolerate TLS variable access (TLS allocation and very * early internal data structure initialization). */ void * bootstrap_malloc(size_t size) { if (unlikely(size == 0)) { size = 1; } return a0ialloc(size, false, false); } void * bootstrap_calloc(size_t num, size_t size) { size_t num_size; num_size = num * size; if (unlikely(num_size == 0)) { assert(num == 0 || size == 0); num_size = 1; } return a0ialloc(num_size, true, false); } void bootstrap_free(void *ptr) { if (unlikely(ptr == NULL)) { return; } a0idalloc(ptr, false); } void arena_set(unsigned ind, arena_t *arena) { atomic_store_p(&arenas[ind], arena, ATOMIC_RELEASE); } static void narenas_total_set(unsigned narenas) { atomic_store_u(&narenas_total, narenas, ATOMIC_RELEASE); } static void narenas_total_inc(void) { atomic_fetch_add_u(&narenas_total, 1, ATOMIC_RELEASE); } unsigned narenas_total_get(void) { return atomic_load_u(&narenas_total, ATOMIC_ACQUIRE); } /* Create a new arena and insert it into the arenas array at index ind. */ static arena_t * arena_init_locked(tsdn_t *tsdn, unsigned ind, const arena_config_t *config) { arena_t *arena; assert(ind <= narenas_total_get()); if (ind >= MALLOCX_ARENA_LIMIT) { return NULL; } if (ind == narenas_total_get()) { narenas_total_inc(); } /* * Another thread may have already initialized arenas[ind] if it's an * auto arena. */ arena = arena_get(tsdn, ind, false); if (arena != NULL) { assert(arena_is_auto(arena)); return arena; } /* Actually initialize the arena. */ arena = arena_new(tsdn, ind, config); return arena; } static void arena_new_create_background_thread(tsdn_t *tsdn, unsigned ind) { if (ind == 0) { return; } /* * Avoid creating a new background thread just for the huge arena, which * purges eagerly by default. */ if (have_background_thread && !arena_is_huge(ind)) { if (background_thread_create(tsdn_tsd(tsdn), ind)) { malloc_printf(": error in background thread " "creation for arena %u. Abort.\n", ind); abort(); } } } arena_t * arena_init(tsdn_t *tsdn, unsigned ind, const arena_config_t *config) { arena_t *arena; malloc_mutex_lock(tsdn, &arenas_lock); arena = arena_init_locked(tsdn, ind, config); malloc_mutex_unlock(tsdn, &arenas_lock); arena_new_create_background_thread(tsdn, ind); return arena; } static void arena_bind(tsd_t *tsd, unsigned ind, bool internal) { arena_t *arena = arena_get(tsd_tsdn(tsd), ind, false); arena_nthreads_inc(arena, internal); if (internal) { tsd_iarena_set(tsd, arena); } else { tsd_arena_set(tsd, arena); unsigned shard = atomic_fetch_add_u(&arena->binshard_next, 1, ATOMIC_RELAXED); tsd_binshards_t *bins = tsd_binshardsp_get(tsd); for (unsigned i = 0; i < SC_NBINS; i++) { assert(bin_infos[i].n_shards > 0 && bin_infos[i].n_shards <= BIN_SHARDS_MAX); bins->binshard[i] = shard % bin_infos[i].n_shards; } } } void arena_migrate(tsd_t *tsd, arena_t *oldarena, arena_t *newarena) { assert(oldarena != NULL); assert(newarena != NULL); arena_nthreads_dec(oldarena, false); arena_nthreads_inc(newarena, false); tsd_arena_set(tsd, newarena); if (arena_nthreads_get(oldarena, false) == 0) { /* Purge if the old arena has no associated threads anymore. */ arena_decay(tsd_tsdn(tsd), oldarena, /* is_background_thread */ false, /* all */ true); } } static void arena_unbind(tsd_t *tsd, unsigned ind, bool internal) { arena_t *arena; arena = arena_get(tsd_tsdn(tsd), ind, false); arena_nthreads_dec(arena, internal); if (internal) { tsd_iarena_set(tsd, NULL); } else { tsd_arena_set(tsd, NULL); } } /* Slow path, called only by arena_choose(). */ arena_t * arena_choose_hard(tsd_t *tsd, bool internal) { arena_t *ret JEMALLOC_CC_SILENCE_INIT(NULL); if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)) { unsigned choose = percpu_arena_choose(); ret = arena_get(tsd_tsdn(tsd), choose, true); assert(ret != NULL); arena_bind(tsd, arena_ind_get(ret), false); arena_bind(tsd, arena_ind_get(ret), true); return ret; } if (narenas_auto > 1) { unsigned i, j, choose[2], first_null; bool is_new_arena[2]; /* * Determine binding for both non-internal and internal * allocation. * * choose[0]: For application allocation. * choose[1]: For internal metadata allocation. */ for (j = 0; j < 2; j++) { choose[j] = 0; is_new_arena[j] = false; } first_null = narenas_auto; malloc_mutex_lock(tsd_tsdn(tsd), &arenas_lock); assert(arena_get(tsd_tsdn(tsd), 0, false) != NULL); for (i = 1; i < narenas_auto; i++) { if (arena_get(tsd_tsdn(tsd), i, false) != NULL) { /* * Choose the first arena that has the lowest * number of threads assigned to it. */ for (j = 0; j < 2; j++) { if (arena_nthreads_get(arena_get( tsd_tsdn(tsd), i, false), !!j) < arena_nthreads_get(arena_get( tsd_tsdn(tsd), choose[j], false), !!j)) { choose[j] = i; } } } else if (first_null == narenas_auto) { /* * Record the index of the first uninitialized * arena, in case all extant arenas are in use. * * NB: It is possible for there to be * discontinuities in terms of initialized * versus uninitialized arenas, due to the * "thread.arena" mallctl. */ first_null = i; } } for (j = 0; j < 2; j++) { if (arena_nthreads_get(arena_get(tsd_tsdn(tsd), choose[j], false), !!j) == 0 || first_null == narenas_auto) { /* * Use an unloaded arena, or the least loaded * arena if all arenas are already initialized. */ if (!!j == internal) { ret = arena_get(tsd_tsdn(tsd), choose[j], false); } } else { arena_t *arena; /* Initialize a new arena. */ choose[j] = first_null; arena = arena_init_locked(tsd_tsdn(tsd), choose[j], &arena_config_default); if (arena == NULL) { malloc_mutex_unlock(tsd_tsdn(tsd), &arenas_lock); return NULL; } is_new_arena[j] = true; if (!!j == internal) { ret = arena; } } arena_bind(tsd, choose[j], !!j); } malloc_mutex_unlock(tsd_tsdn(tsd), &arenas_lock); for (j = 0; j < 2; j++) { if (is_new_arena[j]) { assert(choose[j] > 0); arena_new_create_background_thread( tsd_tsdn(tsd), choose[j]); } } } else { ret = arena_get(tsd_tsdn(tsd), 0, false); arena_bind(tsd, 0, false); arena_bind(tsd, 0, true); } return ret; } void iarena_cleanup(tsd_t *tsd) { arena_t *iarena; iarena = tsd_iarena_get(tsd); if (iarena != NULL) { arena_unbind(tsd, arena_ind_get(iarena), true); } } void arena_cleanup(tsd_t *tsd) { arena_t *arena; arena = tsd_arena_get(tsd); if (arena != NULL) { arena_unbind(tsd, arena_ind_get(arena), false); } } static void stats_print_atexit(void) { if (config_stats) { tsdn_t *tsdn; unsigned narenas, i; tsdn = tsdn_fetch(); /* * Merge stats from extant threads. This is racy, since * individual threads do not lock when recording tcache stats * events. As a consequence, the final stats may be slightly * out of date by the time they are reported, if other threads * continue to allocate. */ for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { arena_t *arena = arena_get(tsdn, i, false); if (arena != NULL) { tcache_slow_t *tcache_slow; malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); ql_foreach(tcache_slow, &arena->tcache_ql, link) { tcache_stats_merge(tsdn, tcache_slow->tcache, arena); } malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); } } } je_malloc_stats_print(NULL, NULL, opt_stats_print_opts); } /* * Ensure that we don't hold any locks upon entry to or exit from allocator * code (in a "broad" sense that doesn't count a reentrant allocation as an * entrance or exit). */ JEMALLOC_ALWAYS_INLINE void check_entry_exit_locking(tsdn_t *tsdn) { if (!config_debug) { return; } if (tsdn_null(tsdn)) { return; } tsd_t *tsd = tsdn_tsd(tsdn); /* * It's possible we hold locks at entry/exit if we're in a nested * allocation. */ int8_t reentrancy_level = tsd_reentrancy_level_get(tsd); if (reentrancy_level != 0) { return; } witness_assert_lockless(tsdn_witness_tsdp_get(tsdn)); } /* * End miscellaneous support functions. */ /******************************************************************************/ /* * Begin initialization functions. */ static char * jemalloc_secure_getenv(const char *name) { #ifdef JEMALLOC_HAVE_SECURE_GETENV return secure_getenv(name); #else # ifdef JEMALLOC_HAVE_ISSETUGID if (issetugid() != 0) { return NULL; } # endif return getenv(name); #endif } static unsigned malloc_ncpus(void) { long result; #ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); result = si.dwNumberOfProcessors; #elif defined(CPU_COUNT) /* * glibc >= 2.6 has the CPU_COUNT macro. * * glibc's sysconf() uses isspace(). glibc allocates for the first time * *before* setting up the isspace tables. Therefore we need a * different method to get the number of CPUs. * * The getaffinity approach is also preferred when only a subset of CPUs * is available, to avoid using more arenas than necessary. */ { # if defined(__FreeBSD__) || defined(__DragonFly__) cpuset_t set; # else cpu_set_t set; # endif # if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) sched_getaffinity(0, sizeof(set), &set); # else pthread_getaffinity_np(pthread_self(), sizeof(set), &set); # endif result = CPU_COUNT(&set); } #else result = sysconf(_SC_NPROCESSORS_ONLN); #endif return ((result == -1) ? 1 : (unsigned)result); } /* * Ensure that number of CPUs is determistinc, i.e. it is the same based on: * - sched_getaffinity() * - _SC_NPROCESSORS_ONLN * - _SC_NPROCESSORS_CONF * Since otherwise tricky things is possible with percpu arenas in use. */ static bool malloc_cpu_count_is_deterministic() { #ifdef _WIN32 return true; #else long cpu_onln = sysconf(_SC_NPROCESSORS_ONLN); long cpu_conf = sysconf(_SC_NPROCESSORS_CONF); if (cpu_onln != cpu_conf) { return false; } # if defined(CPU_COUNT) # if defined(__FreeBSD__) || defined(__DragonFly__) cpuset_t set; # else cpu_set_t set; # endif /* __FreeBSD__ */ # if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) sched_getaffinity(0, sizeof(set), &set); # else /* !JEMALLOC_HAVE_SCHED_SETAFFINITY */ pthread_getaffinity_np(pthread_self(), sizeof(set), &set); # endif /* JEMALLOC_HAVE_SCHED_SETAFFINITY */ long cpu_affinity = CPU_COUNT(&set); if (cpu_affinity != cpu_conf) { return false; } # endif /* CPU_COUNT */ return true; #endif } static void init_opt_stats_opts(const char *v, size_t vlen, char *dest) { size_t opts_len = strlen(dest); assert(opts_len <= stats_print_tot_num_options); for (size_t i = 0; i < vlen; i++) { switch (v[i]) { #define OPTION(o, v, d, s) case o: break; STATS_PRINT_OPTIONS #undef OPTION default: continue; } if (strchr(dest, v[i]) != NULL) { /* Ignore repeated. */ continue; } dest[opts_len++] = v[i]; dest[opts_len] = '\0'; assert(opts_len <= stats_print_tot_num_options); } assert(opts_len == strlen(dest)); } /* Reads the next size pair in a multi-sized option. */ static bool malloc_conf_multi_sizes_next(const char **slab_size_segment_cur, size_t *vlen_left, size_t *slab_start, size_t *slab_end, size_t *new_size) { const char *cur = *slab_size_segment_cur; char *end; uintmax_t um; set_errno(0); /* First number, then '-' */ um = malloc_strtoumax(cur, &end, 0); if (get_errno() != 0 || *end != '-') { return true; } *slab_start = (size_t)um; cur = end + 1; /* Second number, then ':' */ um = malloc_strtoumax(cur, &end, 0); if (get_errno() != 0 || *end != ':') { return true; } *slab_end = (size_t)um; cur = end + 1; /* Last number */ um = malloc_strtoumax(cur, &end, 0); if (get_errno() != 0) { return true; } *new_size = (size_t)um; /* Consume the separator if there is one. */ if (*end == '|') { end++; } *vlen_left -= end - *slab_size_segment_cur; *slab_size_segment_cur = end; return false; } static bool malloc_conf_next(char const **opts_p, char const **k_p, size_t *klen_p, char const **v_p, size_t *vlen_p) { bool accept; const char *opts = *opts_p; *k_p = opts; for (accept = false; !accept;) { switch (*opts) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '_': opts++; break; case ':': opts++; *klen_p = (uintptr_t)opts - 1 - (uintptr_t)*k_p; *v_p = opts; accept = true; break; case '\0': if (opts != *opts_p) { malloc_write(": Conf string ends " "with key\n"); had_conf_error = true; } return true; default: malloc_write(": Malformed conf string\n"); had_conf_error = true; return true; } } for (accept = false; !accept;) { switch (*opts) { case ',': opts++; /* * Look ahead one character here, because the next time * this function is called, it will assume that end of * input has been cleanly reached if no input remains, * but we have optimistically already consumed the * comma if one exists. */ if (*opts == '\0') { malloc_write(": Conf string ends " "with comma\n"); had_conf_error = true; } *vlen_p = (uintptr_t)opts - 1 - (uintptr_t)*v_p; accept = true; break; case '\0': *vlen_p = (uintptr_t)opts - (uintptr_t)*v_p; accept = true; break; default: opts++; break; } } *opts_p = opts; return false; } static void malloc_abort_invalid_conf(void) { assert(opt_abort_conf); malloc_printf(": Abort (abort_conf:true) on invalid conf " "value (see above).\n"); abort(); } static void malloc_conf_error(const char *msg, const char *k, size_t klen, const char *v, size_t vlen) { malloc_printf(": %s: %.*s:%.*s\n", msg, (int)klen, k, (int)vlen, v); /* If abort_conf is set, error out after processing all options. */ const char *experimental = "experimental_"; if (strncmp(k, experimental, strlen(experimental)) == 0) { /* However, tolerate experimental features. */ return; } had_conf_error = true; } static void malloc_slow_flag_init(void) { /* * Combine the runtime options into malloc_slow for fast path. Called * after processing all the options. */ malloc_slow_flags |= (opt_junk_alloc ? flag_opt_junk_alloc : 0) | (opt_junk_free ? flag_opt_junk_free : 0) | (opt_zero ? flag_opt_zero : 0) | (opt_utrace ? flag_opt_utrace : 0) | (opt_xmalloc ? flag_opt_xmalloc : 0); malloc_slow = (malloc_slow_flags != 0); } /* Number of sources for initializing malloc_conf */ #define MALLOC_CONF_NSOURCES 5 static const char * obtain_malloc_conf(unsigned which_source, char buf[PATH_MAX + 1]) { if (config_debug) { static unsigned read_source = 0; /* * Each source should only be read once, to minimize # of * syscalls on init. */ assert(read_source++ == which_source); } assert(which_source < MALLOC_CONF_NSOURCES); const char *ret; switch (which_source) { case 0: ret = config_malloc_conf; break; case 1: if (je_malloc_conf != NULL) { /* Use options that were compiled into the program. */ ret = je_malloc_conf; } else { /* No configuration specified. */ ret = NULL; } break; case 2: { ssize_t linklen = 0; #ifndef _WIN32 int saved_errno = errno; const char *linkname = # ifdef JEMALLOC_PREFIX "/etc/"JEMALLOC_PREFIX"malloc.conf" # else "/etc/malloc.conf" # endif ; /* * Try to use the contents of the "/etc/malloc.conf" symbolic * link's name. */ #ifndef JEMALLOC_READLINKAT linklen = readlink(linkname, buf, PATH_MAX); #else linklen = readlinkat(AT_FDCWD, linkname, buf, PATH_MAX); #endif if (linklen == -1) { /* No configuration specified. */ linklen = 0; /* Restore errno. */ set_errno(saved_errno); } #endif buf[linklen] = '\0'; ret = buf; break; } case 3: { const char *envname = #ifdef JEMALLOC_PREFIX JEMALLOC_CPREFIX"MALLOC_CONF" #else "MALLOC_CONF" #endif ; if ((ret = jemalloc_secure_getenv(envname)) != NULL) { /* * Do nothing; opts is already initialized to the value * of the MALLOC_CONF environment variable. */ } else { /* No configuration specified. */ ret = NULL; } break; } case 4: { ret = je_malloc_conf_2_conf_harder; break; } default: not_reached(); ret = NULL; } return ret; } static void malloc_conf_init_helper(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS], bool initial_call, const char *opts_cache[MALLOC_CONF_NSOURCES], char buf[PATH_MAX + 1]) { static const char *opts_explain[MALLOC_CONF_NSOURCES] = { "string specified via --with-malloc-conf", "string pointed to by the global variable malloc_conf", ("\"name\" of the file referenced by the symbolic link named " "/etc/malloc.conf"), "value of the environment variable MALLOC_CONF", ("string pointed to by the global variable " "malloc_conf_2_conf_harder"), }; unsigned i; const char *opts, *k, *v; size_t klen, vlen; for (i = 0; i < MALLOC_CONF_NSOURCES; i++) { /* Get runtime configuration. */ if (initial_call) { opts_cache[i] = obtain_malloc_conf(i, buf); } opts = opts_cache[i]; if (!initial_call && opt_confirm_conf) { malloc_printf( ": malloc_conf #%u (%s): \"%s\"\n", i + 1, opts_explain[i], opts != NULL ? opts : ""); } if (opts == NULL) { continue; } while (*opts != '\0' && !malloc_conf_next(&opts, &k, &klen, &v, &vlen)) { #define CONF_ERROR(msg, k, klen, v, vlen) \ if (!initial_call) { \ malloc_conf_error( \ msg, k, klen, v, vlen); \ cur_opt_valid = false; \ } #define CONF_CONTINUE { \ if (!initial_call && opt_confirm_conf \ && cur_opt_valid) { \ malloc_printf(": -- " \ "Set conf value: %.*s:%.*s" \ "\n", (int)klen, k, \ (int)vlen, v); \ } \ continue; \ } #define CONF_MATCH(n) \ (sizeof(n)-1 == klen && strncmp(n, k, klen) == 0) #define CONF_MATCH_VALUE(n) \ (sizeof(n)-1 == vlen && strncmp(n, v, vlen) == 0) #define CONF_HANDLE_BOOL(o, n) \ if (CONF_MATCH(n)) { \ if (CONF_MATCH_VALUE("true")) { \ o = true; \ } else if (CONF_MATCH_VALUE("false")) { \ o = false; \ } else { \ CONF_ERROR("Invalid conf value",\ k, klen, v, vlen); \ } \ CONF_CONTINUE; \ } /* * One of the CONF_MIN macros below expands, in one of the use points, * to "unsigned integer < 0", which is always false, triggering the * GCC -Wtype-limits warning, which we disable here and re-enable below. */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_TYPE_LIMITS #define CONF_DONT_CHECK_MIN(um, min) false #define CONF_CHECK_MIN(um, min) ((um) < (min)) #define CONF_DONT_CHECK_MAX(um, max) false #define CONF_CHECK_MAX(um, max) ((um) > (max)) #define CONF_VALUE_READ(max_t, result) \ char *end; \ set_errno(0); \ result = (max_t)malloc_strtoumax(v, &end, 0); #define CONF_VALUE_READ_FAIL() \ (get_errno() != 0 || (uintptr_t)end - (uintptr_t)v != vlen) #define CONF_HANDLE_T(t, max_t, o, n, min, max, check_min, check_max, clip) \ if (CONF_MATCH(n)) { \ max_t mv; \ CONF_VALUE_READ(max_t, mv) \ if (CONF_VALUE_READ_FAIL()) { \ CONF_ERROR("Invalid conf value",\ k, klen, v, vlen); \ } else if (clip) { \ if (check_min(mv, (t)(min))) { \ o = (t)(min); \ } else if ( \ check_max(mv, (t)(max))) { \ o = (t)(max); \ } else { \ o = (t)mv; \ } \ } else { \ if (check_min(mv, (t)(min)) || \ check_max(mv, (t)(max))) { \ CONF_ERROR( \ "Out-of-range " \ "conf value", \ k, klen, v, vlen); \ } else { \ o = (t)mv; \ } \ } \ CONF_CONTINUE; \ } #define CONF_HANDLE_T_U(t, o, n, min, max, check_min, check_max, clip) \ CONF_HANDLE_T(t, uintmax_t, o, n, min, max, check_min, \ check_max, clip) #define CONF_HANDLE_T_SIGNED(t, o, n, min, max, check_min, check_max, clip)\ CONF_HANDLE_T(t, intmax_t, o, n, min, max, check_min, \ check_max, clip) #define CONF_HANDLE_UNSIGNED(o, n, min, max, check_min, check_max, \ clip) \ CONF_HANDLE_T_U(unsigned, o, n, min, max, \ check_min, check_max, clip) #define CONF_HANDLE_SIZE_T(o, n, min, max, check_min, check_max, clip) \ CONF_HANDLE_T_U(size_t, o, n, min, max, \ check_min, check_max, clip) #define CONF_HANDLE_INT64_T(o, n, min, max, check_min, check_max, clip) \ CONF_HANDLE_T_SIGNED(int64_t, o, n, min, max, \ check_min, check_max, clip) #define CONF_HANDLE_UINT64_T(o, n, min, max, check_min, check_max, clip)\ CONF_HANDLE_T_U(uint64_t, o, n, min, max, \ check_min, check_max, clip) #define CONF_HANDLE_SSIZE_T(o, n, min, max) \ CONF_HANDLE_T_SIGNED(ssize_t, o, n, min, max, \ CONF_CHECK_MIN, CONF_CHECK_MAX, false) #define CONF_HANDLE_CHAR_P(o, n, d) \ if (CONF_MATCH(n)) { \ size_t cpylen = (vlen <= \ sizeof(o)-1) ? vlen : \ sizeof(o)-1; \ strncpy(o, v, cpylen); \ o[cpylen] = '\0'; \ CONF_CONTINUE; \ } bool cur_opt_valid = true; CONF_HANDLE_BOOL(opt_confirm_conf, "confirm_conf") if (initial_call) { continue; } CONF_HANDLE_BOOL(opt_abort, "abort") CONF_HANDLE_BOOL(opt_abort_conf, "abort_conf") CONF_HANDLE_BOOL(opt_trust_madvise, "trust_madvise") if (strncmp("metadata_thp", k, klen) == 0) { int m; bool match = false; for (m = 0; m < metadata_thp_mode_limit; m++) { if (strncmp(metadata_thp_mode_names[m], v, vlen) == 0) { opt_metadata_thp = m; match = true; break; } } if (!match) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } CONF_HANDLE_BOOL(opt_retain, "retain") if (strncmp("dss", k, klen) == 0) { int m; bool match = false; for (m = 0; m < dss_prec_limit; m++) { if (strncmp(dss_prec_names[m], v, vlen) == 0) { if (extent_dss_prec_set(m)) { CONF_ERROR( "Error setting dss", k, klen, v, vlen); } else { opt_dss = dss_prec_names[m]; match = true; break; } } } if (!match) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } if (CONF_MATCH("narenas")) { if (CONF_MATCH_VALUE("default")) { opt_narenas = 0; CONF_CONTINUE; } else { CONF_HANDLE_UNSIGNED(opt_narenas, "narenas", 1, UINT_MAX, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, /* clip */ false) } } if (CONF_MATCH("narenas_ratio")) { char *end; bool err = fxp_parse(&opt_narenas_ratio, v, &end); if (err || (size_t)(end - v) != vlen) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } if (CONF_MATCH("bin_shards")) { const char *bin_shards_segment_cur = v; size_t vlen_left = vlen; do { size_t size_start; size_t size_end; size_t nshards; bool err = malloc_conf_multi_sizes_next( &bin_shards_segment_cur, &vlen_left, &size_start, &size_end, &nshards); if (err || bin_update_shard_size( bin_shard_sizes, size_start, size_end, nshards)) { CONF_ERROR( "Invalid settings for " "bin_shards", k, klen, v, vlen); break; } } while (vlen_left > 0); CONF_CONTINUE; } CONF_HANDLE_INT64_T(opt_mutex_max_spin, "mutex_max_spin", -1, INT64_MAX, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, false); CONF_HANDLE_SSIZE_T(opt_dirty_decay_ms, "dirty_decay_ms", -1, NSTIME_SEC_MAX * KQU(1000) < QU(SSIZE_MAX) ? NSTIME_SEC_MAX * KQU(1000) : SSIZE_MAX); CONF_HANDLE_SSIZE_T(opt_muzzy_decay_ms, "muzzy_decay_ms", -1, NSTIME_SEC_MAX * KQU(1000) < QU(SSIZE_MAX) ? NSTIME_SEC_MAX * KQU(1000) : SSIZE_MAX); CONF_HANDLE_BOOL(opt_stats_print, "stats_print") if (CONF_MATCH("stats_print_opts")) { init_opt_stats_opts(v, vlen, opt_stats_print_opts); CONF_CONTINUE; } CONF_HANDLE_INT64_T(opt_stats_interval, "stats_interval", -1, INT64_MAX, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, false) if (CONF_MATCH("stats_interval_opts")) { init_opt_stats_opts(v, vlen, opt_stats_interval_opts); CONF_CONTINUE; } if (config_fill) { if (CONF_MATCH("junk")) { if (CONF_MATCH_VALUE("true")) { opt_junk = "true"; opt_junk_alloc = opt_junk_free = true; } else if (CONF_MATCH_VALUE("false")) { opt_junk = "false"; opt_junk_alloc = opt_junk_free = false; } else if (CONF_MATCH_VALUE("alloc")) { opt_junk = "alloc"; opt_junk_alloc = true; opt_junk_free = false; } else if (CONF_MATCH_VALUE("free")) { opt_junk = "free"; opt_junk_alloc = false; opt_junk_free = true; } else { CONF_ERROR( "Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } CONF_HANDLE_BOOL(opt_zero, "zero") } if (config_utrace) { CONF_HANDLE_BOOL(opt_utrace, "utrace") } if (config_xmalloc) { CONF_HANDLE_BOOL(opt_xmalloc, "xmalloc") } if (config_enable_cxx) { CONF_HANDLE_BOOL( opt_experimental_infallible_new, "experimental_infallible_new") } CONF_HANDLE_BOOL(opt_tcache, "tcache") CONF_HANDLE_SIZE_T(opt_tcache_max, "tcache_max", 0, TCACHE_MAXCLASS_LIMIT, CONF_DONT_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) if (CONF_MATCH("lg_tcache_max")) { size_t m; CONF_VALUE_READ(size_t, m) if (CONF_VALUE_READ_FAIL()) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } else { /* clip if necessary */ if (m > TCACHE_LG_MAXCLASS_LIMIT) { m = TCACHE_LG_MAXCLASS_LIMIT; } opt_tcache_max = (size_t)1 << m; } CONF_CONTINUE; } /* * Anyone trying to set a value outside -16 to 16 is * deeply confused. */ CONF_HANDLE_SSIZE_T(opt_lg_tcache_nslots_mul, "lg_tcache_nslots_mul", -16, 16) /* Ditto with values past 2048. */ CONF_HANDLE_UNSIGNED(opt_tcache_nslots_small_min, "tcache_nslots_small_min", 1, 2048, CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) CONF_HANDLE_UNSIGNED(opt_tcache_nslots_small_max, "tcache_nslots_small_max", 1, 2048, CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) CONF_HANDLE_UNSIGNED(opt_tcache_nslots_large, "tcache_nslots_large", 1, 2048, CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) CONF_HANDLE_SIZE_T(opt_tcache_gc_incr_bytes, "tcache_gc_incr_bytes", 1024, SIZE_T_MAX, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, /* clip */ true) CONF_HANDLE_SIZE_T(opt_tcache_gc_delay_bytes, "tcache_gc_delay_bytes", 0, SIZE_T_MAX, CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, /* clip */ false) CONF_HANDLE_UNSIGNED(opt_lg_tcache_flush_small_div, "lg_tcache_flush_small_div", 1, 16, CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) CONF_HANDLE_UNSIGNED(opt_lg_tcache_flush_large_div, "lg_tcache_flush_large_div", 1, 16, CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) /* * The runtime option of oversize_threshold remains * undocumented. It may be tweaked in the next major * release (6.0). The default value 8M is rather * conservative / safe. Tuning it further down may * improve fragmentation a bit more, but may also cause * contention on the huge arena. */ CONF_HANDLE_SIZE_T(opt_oversize_threshold, "oversize_threshold", 0, SC_LARGE_MAXCLASS, CONF_DONT_CHECK_MIN, CONF_CHECK_MAX, false) CONF_HANDLE_SIZE_T(opt_lg_extent_max_active_fit, "lg_extent_max_active_fit", 0, (sizeof(size_t) << 3), CONF_DONT_CHECK_MIN, CONF_CHECK_MAX, false) if (strncmp("percpu_arena", k, klen) == 0) { bool match = false; for (int m = percpu_arena_mode_names_base; m < percpu_arena_mode_names_limit; m++) { if (strncmp(percpu_arena_mode_names[m], v, vlen) == 0) { if (!have_percpu_arena) { CONF_ERROR( "No getcpu support", k, klen, v, vlen); } opt_percpu_arena = m; match = true; break; } } if (!match) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } CONF_HANDLE_BOOL(opt_background_thread, "background_thread"); CONF_HANDLE_SIZE_T(opt_max_background_threads, "max_background_threads", 1, opt_max_background_threads, CONF_CHECK_MIN, CONF_CHECK_MAX, true); CONF_HANDLE_BOOL(opt_hpa, "hpa") CONF_HANDLE_SIZE_T(opt_hpa_opts.slab_max_alloc, "hpa_slab_max_alloc", PAGE, HUGEPAGE, CONF_CHECK_MIN, CONF_CHECK_MAX, true); /* * Accept either a ratio-based or an exact hugification * threshold. */ CONF_HANDLE_SIZE_T(opt_hpa_opts.hugification_threshold, "hpa_hugification_threshold", PAGE, HUGEPAGE, CONF_CHECK_MIN, CONF_CHECK_MAX, true); if (CONF_MATCH("hpa_hugification_threshold_ratio")) { fxp_t ratio; char *end; bool err = fxp_parse(&ratio, v, &end); if (err || (size_t)(end - v) != vlen || ratio > FXP_INIT_INT(1)) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } else { opt_hpa_opts.hugification_threshold = fxp_mul_frac(HUGEPAGE, ratio); } CONF_CONTINUE; } CONF_HANDLE_UINT64_T( opt_hpa_opts.hugify_delay_ms, "hpa_hugify_delay_ms", 0, 0, CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false); CONF_HANDLE_UINT64_T( opt_hpa_opts.min_purge_interval_ms, "hpa_min_purge_interval_ms", 0, 0, CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false); if (CONF_MATCH("hpa_dirty_mult")) { if (CONF_MATCH_VALUE("-1")) { opt_hpa_opts.dirty_mult = (fxp_t)-1; CONF_CONTINUE; } fxp_t ratio; char *end; bool err = fxp_parse(&ratio, v, &end); if (err || (size_t)(end - v) != vlen) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } else { opt_hpa_opts.dirty_mult = ratio; } CONF_CONTINUE; } CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.nshards, "hpa_sec_nshards", 0, 0, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, true); CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.max_alloc, "hpa_sec_max_alloc", PAGE, 0, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, true); CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.max_bytes, "hpa_sec_max_bytes", PAGE, 0, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, true); CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.bytes_after_flush, "hpa_sec_bytes_after_flush", PAGE, 0, CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, true); CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.batch_fill_extra, "hpa_sec_batch_fill_extra", 0, HUGEPAGE_PAGES, CONF_CHECK_MIN, CONF_CHECK_MAX, true); if (CONF_MATCH("slab_sizes")) { if (CONF_MATCH_VALUE("default")) { sc_data_init(sc_data); CONF_CONTINUE; } bool err; const char *slab_size_segment_cur = v; size_t vlen_left = vlen; do { size_t slab_start; size_t slab_end; size_t pgs; err = malloc_conf_multi_sizes_next( &slab_size_segment_cur, &vlen_left, &slab_start, &slab_end, &pgs); if (!err) { sc_data_update_slab_size( sc_data, slab_start, slab_end, (int)pgs); } else { CONF_ERROR("Invalid settings " "for slab_sizes", k, klen, v, vlen); } } while (!err && vlen_left > 0); CONF_CONTINUE; } if (config_prof) { CONF_HANDLE_BOOL(opt_prof, "prof") CONF_HANDLE_CHAR_P(opt_prof_prefix, "prof_prefix", "jeprof") CONF_HANDLE_BOOL(opt_prof_active, "prof_active") CONF_HANDLE_BOOL(opt_prof_thread_active_init, "prof_thread_active_init") CONF_HANDLE_SIZE_T(opt_lg_prof_sample, "lg_prof_sample", 0, (sizeof(uint64_t) << 3) - 1, CONF_DONT_CHECK_MIN, CONF_CHECK_MAX, true) CONF_HANDLE_BOOL(opt_prof_accum, "prof_accum") CONF_HANDLE_SSIZE_T(opt_lg_prof_interval, "lg_prof_interval", -1, (sizeof(uint64_t) << 3) - 1) CONF_HANDLE_BOOL(opt_prof_gdump, "prof_gdump") CONF_HANDLE_BOOL(opt_prof_final, "prof_final") CONF_HANDLE_BOOL(opt_prof_leak, "prof_leak") CONF_HANDLE_BOOL(opt_prof_leak_error, "prof_leak_error") CONF_HANDLE_BOOL(opt_prof_log, "prof_log") CONF_HANDLE_SSIZE_T(opt_prof_recent_alloc_max, "prof_recent_alloc_max", -1, SSIZE_MAX) CONF_HANDLE_BOOL(opt_prof_stats, "prof_stats") CONF_HANDLE_BOOL(opt_prof_sys_thread_name, "prof_sys_thread_name") if (CONF_MATCH("prof_time_resolution")) { if (CONF_MATCH_VALUE("default")) { opt_prof_time_res = prof_time_res_default; } else if (CONF_MATCH_VALUE("high")) { if (!config_high_res_timer) { CONF_ERROR( "No high resolution" " timer support", k, klen, v, vlen); } else { opt_prof_time_res = prof_time_res_high; } } else { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } /* * Undocumented. When set to false, don't * correct for an unbiasing bug in jeprof * attribution. This can be handy if you want * to get consistent numbers from your binary * across different jemalloc versions, even if * those numbers are incorrect. The default is * true. */ CONF_HANDLE_BOOL(opt_prof_unbias, "prof_unbias") } if (config_log) { if (CONF_MATCH("log")) { size_t cpylen = ( vlen <= sizeof(log_var_names) ? vlen : sizeof(log_var_names) - 1); strncpy(log_var_names, v, cpylen); log_var_names[cpylen] = '\0'; CONF_CONTINUE; } } if (CONF_MATCH("thp")) { bool match = false; for (int m = 0; m < thp_mode_names_limit; m++) { if (strncmp(thp_mode_names[m],v, vlen) == 0) { if (!have_madvise_huge && !have_memcntl) { CONF_ERROR( "No THP support", k, klen, v, vlen); } opt_thp = m; match = true; break; } } if (!match) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } if (CONF_MATCH("zero_realloc")) { if (CONF_MATCH_VALUE("alloc")) { opt_zero_realloc_action = zero_realloc_action_alloc; } else if (CONF_MATCH_VALUE("free")) { opt_zero_realloc_action = zero_realloc_action_free; } else if (CONF_MATCH_VALUE("abort")) { opt_zero_realloc_action = zero_realloc_action_abort; } else { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } CONF_CONTINUE; } if (config_uaf_detection && CONF_MATCH("lg_san_uaf_align")) { ssize_t a; CONF_VALUE_READ(ssize_t, a) if (CONF_VALUE_READ_FAIL() || a < -1) { CONF_ERROR("Invalid conf value", k, klen, v, vlen); } if (a == -1) { opt_lg_san_uaf_align = -1; CONF_CONTINUE; } /* clip if necessary */ ssize_t max_allowed = (sizeof(size_t) << 3) - 1; ssize_t min_allowed = LG_PAGE; if (a > max_allowed) { a = max_allowed; } else if (a < min_allowed) { a = min_allowed; } opt_lg_san_uaf_align = a; CONF_CONTINUE; } CONF_HANDLE_SIZE_T(opt_san_guard_small, "san_guard_small", 0, SIZE_T_MAX, CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false) CONF_HANDLE_SIZE_T(opt_san_guard_large, "san_guard_large", 0, SIZE_T_MAX, CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false) CONF_ERROR("Invalid conf pair", k, klen, v, vlen); #undef CONF_ERROR #undef CONF_CONTINUE #undef CONF_MATCH #undef CONF_MATCH_VALUE #undef CONF_HANDLE_BOOL #undef CONF_DONT_CHECK_MIN #undef CONF_CHECK_MIN #undef CONF_DONT_CHECK_MAX #undef CONF_CHECK_MAX #undef CONF_HANDLE_T #undef CONF_HANDLE_T_U #undef CONF_HANDLE_T_SIGNED #undef CONF_HANDLE_UNSIGNED #undef CONF_HANDLE_SIZE_T #undef CONF_HANDLE_SSIZE_T #undef CONF_HANDLE_CHAR_P /* Re-enable diagnostic "-Wtype-limits" */ JEMALLOC_DIAGNOSTIC_POP } if (opt_abort_conf && had_conf_error) { malloc_abort_invalid_conf(); } } atomic_store_b(&log_init_done, true, ATOMIC_RELEASE); } static bool malloc_conf_init_check_deps(void) { if (opt_prof_leak_error && !opt_prof_final) { malloc_printf(": prof_leak_error is set w/o " "prof_final.\n"); return true; } return false; } static void malloc_conf_init(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS]) { const char *opts_cache[MALLOC_CONF_NSOURCES] = {NULL, NULL, NULL, NULL, NULL}; char buf[PATH_MAX + 1]; /* The first call only set the confirm_conf option and opts_cache */ malloc_conf_init_helper(NULL, NULL, true, opts_cache, buf); malloc_conf_init_helper(sc_data, bin_shard_sizes, false, opts_cache, NULL); if (malloc_conf_init_check_deps()) { /* check_deps does warning msg only; abort below if needed. */ if (opt_abort_conf) { malloc_abort_invalid_conf(); } } } #undef MALLOC_CONF_NSOURCES static bool malloc_init_hard_needed(void) { if (malloc_initialized() || (IS_INITIALIZER && malloc_init_state == malloc_init_recursible)) { /* * Another thread initialized the allocator before this one * acquired init_lock, or this thread is the initializing * thread, and it is recursively allocating. */ return false; } #ifdef JEMALLOC_THREADED_INIT if (malloc_initializer != NO_INITIALIZER && !IS_INITIALIZER) { /* Busy-wait until the initializing thread completes. */ spin_t spinner = SPIN_INITIALIZER; do { malloc_mutex_unlock(TSDN_NULL, &init_lock); spin_adaptive(&spinner); malloc_mutex_lock(TSDN_NULL, &init_lock); } while (!malloc_initialized()); return false; } #endif return true; } static bool malloc_init_hard_a0_locked() { malloc_initializer = INITIALIZER; JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS sc_data_t sc_data = {0}; JEMALLOC_DIAGNOSTIC_POP /* * Ordering here is somewhat tricky; we need sc_boot() first, since that * determines what the size classes will be, and then * malloc_conf_init(), since any slab size tweaking will need to be done * before sz_boot and bin_info_boot, which assume that the values they * read out of sc_data_global are final. */ sc_boot(&sc_data); unsigned bin_shard_sizes[SC_NBINS]; bin_shard_sizes_boot(bin_shard_sizes); /* * prof_boot0 only initializes opt_prof_prefix. We need to do it before * we parse malloc_conf options, in case malloc_conf parsing overwrites * it. */ if (config_prof) { prof_boot0(); } malloc_conf_init(&sc_data, bin_shard_sizes); san_init(opt_lg_san_uaf_align); sz_boot(&sc_data, opt_cache_oblivious); bin_info_boot(&sc_data, bin_shard_sizes); if (opt_stats_print) { /* Print statistics at exit. */ if (atexit(stats_print_atexit) != 0) { malloc_write(": Error in atexit()\n"); if (opt_abort) { abort(); } } } if (stats_boot()) { return true; } if (pages_boot()) { return true; } if (base_boot(TSDN_NULL)) { return true; } /* emap_global is static, hence zeroed. */ if (emap_init(&arena_emap_global, b0get(), /* zeroed */ true)) { return true; } if (extent_boot()) { return true; } if (ctl_boot()) { return true; } if (config_prof) { prof_boot1(); } if (opt_hpa && !hpa_supported()) { malloc_printf(": HPA not supported in the current " "configuration; %s.", opt_abort_conf ? "aborting" : "disabling"); if (opt_abort_conf) { malloc_abort_invalid_conf(); } else { opt_hpa = false; } } if (arena_boot(&sc_data, b0get(), opt_hpa)) { return true; } if (tcache_boot(TSDN_NULL, b0get())) { return true; } if (malloc_mutex_init(&arenas_lock, "arenas", WITNESS_RANK_ARENAS, malloc_mutex_rank_exclusive)) { return true; } hook_boot(); /* * Create enough scaffolding to allow recursive allocation in * malloc_ncpus(). */ narenas_auto = 1; manual_arena_base = narenas_auto + 1; memset(arenas, 0, sizeof(arena_t *) * narenas_auto); /* * Initialize one arena here. The rest are lazily created in * arena_choose_hard(). */ if (arena_init(TSDN_NULL, 0, &arena_config_default) == NULL) { return true; } a0 = arena_get(TSDN_NULL, 0, false); if (opt_hpa && !hpa_supported()) { malloc_printf(": HPA not supported in the current " "configuration; %s.", opt_abort_conf ? "aborting" : "disabling"); if (opt_abort_conf) { malloc_abort_invalid_conf(); } else { opt_hpa = false; } } else if (opt_hpa) { hpa_shard_opts_t hpa_shard_opts = opt_hpa_opts; hpa_shard_opts.deferral_allowed = background_thread_enabled(); if (pa_shard_enable_hpa(TSDN_NULL, &a0->pa_shard, &hpa_shard_opts, &opt_hpa_sec_opts)) { return true; } } malloc_init_state = malloc_init_a0_initialized; return false; } static bool malloc_init_hard_a0(void) { bool ret; malloc_mutex_lock(TSDN_NULL, &init_lock); ret = malloc_init_hard_a0_locked(); malloc_mutex_unlock(TSDN_NULL, &init_lock); return ret; } /* Initialize data structures which may trigger recursive allocation. */ static bool malloc_init_hard_recursible(void) { malloc_init_state = malloc_init_recursible; ncpus = malloc_ncpus(); if (opt_percpu_arena != percpu_arena_disabled) { bool cpu_count_is_deterministic = malloc_cpu_count_is_deterministic(); if (!cpu_count_is_deterministic) { /* * If # of CPU is not deterministic, and narenas not * specified, disables per cpu arena since it may not * detect CPU IDs properly. */ if (opt_narenas == 0) { opt_percpu_arena = percpu_arena_disabled; malloc_write(": Number of CPUs " "detected is not deterministic. Per-CPU " "arena disabled.\n"); if (opt_abort_conf) { malloc_abort_invalid_conf(); } if (opt_abort) { abort(); } } } } #if (defined(JEMALLOC_HAVE_PTHREAD_ATFORK) && !defined(JEMALLOC_MUTEX_INIT_CB) \ && !defined(JEMALLOC_ZONE) && !defined(_WIN32) && \ !defined(__native_client__)) /* LinuxThreads' pthread_atfork() allocates. */ if (pthread_atfork(jemalloc_prefork, jemalloc_postfork_parent, jemalloc_postfork_child) != 0) { malloc_write(": Error in pthread_atfork()\n"); if (opt_abort) { abort(); } return true; } #endif if (background_thread_boot0()) { return true; } return false; } static unsigned malloc_narenas_default(void) { assert(ncpus > 0); /* * For SMP systems, create more than one arena per CPU by * default. */ if (ncpus > 1) { fxp_t fxp_ncpus = FXP_INIT_INT(ncpus); fxp_t goal = fxp_mul(fxp_ncpus, opt_narenas_ratio); uint32_t int_goal = fxp_round_nearest(goal); if (int_goal == 0) { return 1; } return int_goal; } else { return 1; } } static percpu_arena_mode_t percpu_arena_as_initialized(percpu_arena_mode_t mode) { assert(!malloc_initialized()); assert(mode <= percpu_arena_disabled); if (mode != percpu_arena_disabled) { mode += percpu_arena_mode_enabled_base; } return mode; } static bool malloc_init_narenas(void) { assert(ncpus > 0); if (opt_percpu_arena != percpu_arena_disabled) { if (!have_percpu_arena || malloc_getcpu() < 0) { opt_percpu_arena = percpu_arena_disabled; malloc_printf(": perCPU arena getcpu() not " "available. Setting narenas to %u.\n", opt_narenas ? opt_narenas : malloc_narenas_default()); if (opt_abort) { abort(); } } else { if (ncpus >= MALLOCX_ARENA_LIMIT) { malloc_printf(": narenas w/ percpu" "arena beyond limit (%d)\n", ncpus); if (opt_abort) { abort(); } return true; } /* NB: opt_percpu_arena isn't fully initialized yet. */ if (percpu_arena_as_initialized(opt_percpu_arena) == per_phycpu_arena && ncpus % 2 != 0) { malloc_printf(": invalid " "configuration -- per physical CPU arena " "with odd number (%u) of CPUs (no hyper " "threading?).\n", ncpus); if (opt_abort) abort(); } unsigned n = percpu_arena_ind_limit( percpu_arena_as_initialized(opt_percpu_arena)); if (opt_narenas < n) { /* * If narenas is specified with percpu_arena * enabled, actual narenas is set as the greater * of the two. percpu_arena_choose will be free * to use any of the arenas based on CPU * id. This is conservative (at a small cost) * but ensures correctness. * * If for some reason the ncpus determined at * boot is not the actual number (e.g. because * of affinity setting from numactl), reserving * narenas this way provides a workaround for * percpu_arena. */ opt_narenas = n; } } } if (opt_narenas == 0) { opt_narenas = malloc_narenas_default(); } assert(opt_narenas > 0); narenas_auto = opt_narenas; /* * Limit the number of arenas to the indexing range of MALLOCX_ARENA(). */ if (narenas_auto >= MALLOCX_ARENA_LIMIT) { narenas_auto = MALLOCX_ARENA_LIMIT - 1; malloc_printf(": Reducing narenas to limit (%d)\n", narenas_auto); } narenas_total_set(narenas_auto); if (arena_init_huge()) { narenas_total_inc(); } manual_arena_base = narenas_total_get(); return false; } static void malloc_init_percpu(void) { opt_percpu_arena = percpu_arena_as_initialized(opt_percpu_arena); } static bool malloc_init_hard_finish(void) { if (malloc_mutex_boot()) { return true; } malloc_init_state = malloc_init_initialized; malloc_slow_flag_init(); return false; } static void malloc_init_hard_cleanup(tsdn_t *tsdn, bool reentrancy_set) { malloc_mutex_assert_owner(tsdn, &init_lock); malloc_mutex_unlock(tsdn, &init_lock); if (reentrancy_set) { assert(!tsdn_null(tsdn)); tsd_t *tsd = tsdn_tsd(tsdn); assert(tsd_reentrancy_level_get(tsd) > 0); post_reentrancy(tsd); } } static bool malloc_init_hard(void) { tsd_t *tsd; #if defined(_WIN32) && _WIN32_WINNT < 0x0600 _init_init_lock(); #endif malloc_mutex_lock(TSDN_NULL, &init_lock); #define UNLOCK_RETURN(tsdn, ret, reentrancy) \ malloc_init_hard_cleanup(tsdn, reentrancy); \ return ret; if (!malloc_init_hard_needed()) { UNLOCK_RETURN(TSDN_NULL, false, false) } if (malloc_init_state != malloc_init_a0_initialized && malloc_init_hard_a0_locked()) { UNLOCK_RETURN(TSDN_NULL, true, false) } malloc_mutex_unlock(TSDN_NULL, &init_lock); /* Recursive allocation relies on functional tsd. */ tsd = malloc_tsd_boot0(); if (tsd == NULL) { return true; } if (malloc_init_hard_recursible()) { return true; } malloc_mutex_lock(tsd_tsdn(tsd), &init_lock); /* Set reentrancy level to 1 during init. */ pre_reentrancy(tsd, NULL); /* Initialize narenas before prof_boot2 (for allocation). */ if (malloc_init_narenas() || background_thread_boot1(tsd_tsdn(tsd), b0get())) { UNLOCK_RETURN(tsd_tsdn(tsd), true, true) } if (config_prof && prof_boot2(tsd, b0get())) { UNLOCK_RETURN(tsd_tsdn(tsd), true, true) } malloc_init_percpu(); if (malloc_init_hard_finish()) { UNLOCK_RETURN(tsd_tsdn(tsd), true, true) } post_reentrancy(tsd); malloc_mutex_unlock(tsd_tsdn(tsd), &init_lock); witness_assert_lockless(witness_tsd_tsdn( tsd_witness_tsdp_get_unsafe(tsd))); malloc_tsd_boot1(); /* Update TSD after tsd_boot1. */ tsd = tsd_fetch(); if (opt_background_thread) { assert(have_background_thread); /* * Need to finish init & unlock first before creating background * threads (pthread_create depends on malloc). ctl_init (which * sets isthreaded) needs to be called without holding any lock. */ background_thread_ctl_init(tsd_tsdn(tsd)); if (background_thread_create(tsd, 0)) { return true; } } #undef UNLOCK_RETURN return false; } /* * End initialization functions. */ /******************************************************************************/ /* * Begin allocation-path internal functions and data structures. */ /* * Settings determined by the documented behavior of the allocation functions. */ typedef struct static_opts_s static_opts_t; struct static_opts_s { /* Whether or not allocation size may overflow. */ bool may_overflow; /* * Whether or not allocations (with alignment) of size 0 should be * treated as size 1. */ bool bump_empty_aligned_alloc; /* * Whether to assert that allocations are not of size 0 (after any * bumping). */ bool assert_nonempty_alloc; /* * Whether or not to modify the 'result' argument to malloc in case of * error. */ bool null_out_result_on_error; /* Whether to set errno when we encounter an error condition. */ bool set_errno_on_error; /* * The minimum valid alignment for functions requesting aligned storage. */ size_t min_alignment; /* The error string to use if we oom. */ const char *oom_string; /* The error string to use if the passed-in alignment is invalid. */ const char *invalid_alignment_string; /* * False if we're configured to skip some time-consuming operations. * * This isn't really a malloc "behavior", but it acts as a useful * summary of several other static (or at least, static after program * initialization) options. */ bool slow; /* * Return size. */ bool usize; }; JEMALLOC_ALWAYS_INLINE void static_opts_init(static_opts_t *static_opts) { static_opts->may_overflow = false; static_opts->bump_empty_aligned_alloc = false; static_opts->assert_nonempty_alloc = false; static_opts->null_out_result_on_error = false; static_opts->set_errno_on_error = false; static_opts->min_alignment = 0; static_opts->oom_string = ""; static_opts->invalid_alignment_string = ""; static_opts->slow = false; static_opts->usize = false; } /* * These correspond to the macros in jemalloc/jemalloc_macros.h. Broadly, we * should have one constant here per magic value there. Note however that the * representations need not be related. */ #define TCACHE_IND_NONE ((unsigned)-1) #define TCACHE_IND_AUTOMATIC ((unsigned)-2) #define ARENA_IND_AUTOMATIC ((unsigned)-1) typedef struct dynamic_opts_s dynamic_opts_t; struct dynamic_opts_s { void **result; size_t usize; size_t num_items; size_t item_size; size_t alignment; bool zero; unsigned tcache_ind; unsigned arena_ind; }; JEMALLOC_ALWAYS_INLINE void dynamic_opts_init(dynamic_opts_t *dynamic_opts) { dynamic_opts->result = NULL; dynamic_opts->usize = 0; dynamic_opts->num_items = 0; dynamic_opts->item_size = 0; dynamic_opts->alignment = 0; dynamic_opts->zero = false; dynamic_opts->tcache_ind = TCACHE_IND_AUTOMATIC; dynamic_opts->arena_ind = ARENA_IND_AUTOMATIC; } /* * ind parameter is optional and is only checked and filled if alignment == 0; * return true if result is out of range. */ JEMALLOC_ALWAYS_INLINE bool aligned_usize_get(size_t size, size_t alignment, size_t *usize, szind_t *ind, bool bump_empty_aligned_alloc) { assert(usize != NULL); if (alignment == 0) { if (ind != NULL) { *ind = sz_size2index(size); if (unlikely(*ind >= SC_NSIZES)) { return true; } *usize = sz_index2size(*ind); assert(*usize > 0 && *usize <= SC_LARGE_MAXCLASS); return false; } *usize = sz_s2u(size); } else { if (bump_empty_aligned_alloc && unlikely(size == 0)) { size = 1; } *usize = sz_sa2u(size, alignment); } if (unlikely(*usize == 0 || *usize > SC_LARGE_MAXCLASS)) { return true; } return false; } JEMALLOC_ALWAYS_INLINE bool zero_get(bool guarantee, bool slow) { if (config_fill && slow && unlikely(opt_zero)) { return true; } else { return guarantee; } } JEMALLOC_ALWAYS_INLINE tcache_t * tcache_get_from_ind(tsd_t *tsd, unsigned tcache_ind, bool slow, bool is_alloc) { tcache_t *tcache; if (tcache_ind == TCACHE_IND_AUTOMATIC) { if (likely(!slow)) { /* Getting tcache ptr unconditionally. */ tcache = tsd_tcachep_get(tsd); assert(tcache == tcache_get(tsd)); } else if (is_alloc || likely(tsd_reentrancy_level_get(tsd) == 0)) { tcache = tcache_get(tsd); } else { tcache = NULL; } } else { /* * Should not specify tcache on deallocation path when being * reentrant. */ assert(is_alloc || tsd_reentrancy_level_get(tsd) == 0 || tsd_state_nocleanup(tsd)); if (tcache_ind == TCACHE_IND_NONE) { tcache = NULL; } else { tcache = tcaches_get(tsd, tcache_ind); } } return tcache; } /* Return true if a manual arena is specified and arena_get() OOMs. */ JEMALLOC_ALWAYS_INLINE bool arena_get_from_ind(tsd_t *tsd, unsigned arena_ind, arena_t **arena_p) { if (arena_ind == ARENA_IND_AUTOMATIC) { /* * In case of automatic arena management, we defer arena * computation until as late as we can, hoping to fill the * allocation out of the tcache. */ *arena_p = NULL; } else { *arena_p = arena_get(tsd_tsdn(tsd), arena_ind, true); if (unlikely(*arena_p == NULL) && arena_ind >= narenas_auto) { return true; } } return false; } /* ind is ignored if dopts->alignment > 0. */ JEMALLOC_ALWAYS_INLINE void * imalloc_no_sample(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd, size_t size, size_t usize, szind_t ind) { /* Fill in the tcache. */ tcache_t *tcache = tcache_get_from_ind(tsd, dopts->tcache_ind, sopts->slow, /* is_alloc */ true); /* Fill in the arena. */ arena_t *arena; if (arena_get_from_ind(tsd, dopts->arena_ind, &arena)) { return NULL; } if (unlikely(dopts->alignment != 0)) { return ipalloct(tsd_tsdn(tsd), usize, dopts->alignment, dopts->zero, tcache, arena); } return iallocztm(tsd_tsdn(tsd), size, ind, dopts->zero, tcache, false, arena, sopts->slow); } JEMALLOC_ALWAYS_INLINE void * imalloc_sample(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd, size_t usize, szind_t ind) { void *ret; /* * For small allocations, sampling bumps the usize. If so, we allocate * from the ind_large bucket. */ szind_t ind_large; size_t bumped_usize = usize; dopts->alignment = prof_sample_align(dopts->alignment); if (usize <= SC_SMALL_MAXCLASS) { assert(((dopts->alignment == 0) ? sz_s2u(SC_LARGE_MINCLASS) : sz_sa2u(SC_LARGE_MINCLASS, dopts->alignment)) == SC_LARGE_MINCLASS); ind_large = sz_size2index(SC_LARGE_MINCLASS); bumped_usize = sz_s2u(SC_LARGE_MINCLASS); ret = imalloc_no_sample(sopts, dopts, tsd, bumped_usize, bumped_usize, ind_large); if (unlikely(ret == NULL)) { return NULL; } arena_prof_promote(tsd_tsdn(tsd), ret, usize); } else { ret = imalloc_no_sample(sopts, dopts, tsd, usize, usize, ind); } assert(prof_sample_aligned(ret)); return ret; } /* * Returns true if the allocation will overflow, and false otherwise. Sets * *size to the product either way. */ JEMALLOC_ALWAYS_INLINE bool compute_size_with_overflow(bool may_overflow, dynamic_opts_t *dopts, size_t *size) { /* * This function is just num_items * item_size, except that we may have * to check for overflow. */ if (!may_overflow) { assert(dopts->num_items == 1); *size = dopts->item_size; return false; } /* A size_t with its high-half bits all set to 1. */ static const size_t high_bits = SIZE_T_MAX << (sizeof(size_t) * 8 / 2); *size = dopts->item_size * dopts->num_items; if (unlikely(*size == 0)) { return (dopts->num_items != 0 && dopts->item_size != 0); } /* * We got a non-zero size, but we don't know if we overflowed to get * there. To avoid having to do a divide, we'll be clever and note that * if both A and B can be represented in N/2 bits, then their product * can be represented in N bits (without the possibility of overflow). */ if (likely((high_bits & (dopts->num_items | dopts->item_size)) == 0)) { return false; } if (likely(*size / dopts->item_size == dopts->num_items)) { return false; } return true; } JEMALLOC_ALWAYS_INLINE int imalloc_body(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd) { /* Where the actual allocated memory will live. */ void *allocation = NULL; /* Filled in by compute_size_with_overflow below. */ size_t size = 0; /* * The zero initialization for ind is actually dead store, in that its * value is reset before any branch on its value is taken. Sometimes * though, it's convenient to pass it as arguments before this point. * To avoid undefined behavior then, we initialize it with dummy stores. */ szind_t ind = 0; /* usize will always be properly initialized. */ size_t usize; /* Reentrancy is only checked on slow path. */ int8_t reentrancy_level; /* Compute the amount of memory the user wants. */ if (unlikely(compute_size_with_overflow(sopts->may_overflow, dopts, &size))) { goto label_oom; } if (unlikely(dopts->alignment < sopts->min_alignment || (dopts->alignment & (dopts->alignment - 1)) != 0)) { goto label_invalid_alignment; } /* This is the beginning of the "core" algorithm. */ dopts->zero = zero_get(dopts->zero, sopts->slow); if (aligned_usize_get(size, dopts->alignment, &usize, &ind, sopts->bump_empty_aligned_alloc)) { goto label_oom; } dopts->usize = usize; /* Validate the user input. */ if (sopts->assert_nonempty_alloc) { assert (size != 0); } check_entry_exit_locking(tsd_tsdn(tsd)); /* * If we need to handle reentrancy, we can do it out of a * known-initialized arena (i.e. arena 0). */ reentrancy_level = tsd_reentrancy_level_get(tsd); if (sopts->slow && unlikely(reentrancy_level > 0)) { /* * We should never specify particular arenas or tcaches from * within our internal allocations. */ assert(dopts->tcache_ind == TCACHE_IND_AUTOMATIC || dopts->tcache_ind == TCACHE_IND_NONE); assert(dopts->arena_ind == ARENA_IND_AUTOMATIC); dopts->tcache_ind = TCACHE_IND_NONE; /* We know that arena 0 has already been initialized. */ dopts->arena_ind = 0; } /* * If dopts->alignment > 0, then ind is still 0, but usize was computed * in the previous if statement. Down the positive alignment path, * imalloc_no_sample and imalloc_sample will ignore ind. */ /* If profiling is on, get our profiling context. */ if (config_prof && opt_prof) { bool prof_active = prof_active_get_unlocked(); bool sample_event = te_prof_sample_event_lookahead(tsd, usize); prof_tctx_t *tctx = prof_alloc_prep(tsd, prof_active, sample_event); emap_alloc_ctx_t alloc_ctx; if (likely((uintptr_t)tctx == (uintptr_t)1U)) { alloc_ctx.slab = (usize <= SC_SMALL_MAXCLASS); allocation = imalloc_no_sample( sopts, dopts, tsd, usize, usize, ind); } else if ((uintptr_t)tctx > (uintptr_t)1U) { allocation = imalloc_sample( sopts, dopts, tsd, usize, ind); alloc_ctx.slab = false; } else { allocation = NULL; } if (unlikely(allocation == NULL)) { prof_alloc_rollback(tsd, tctx); goto label_oom; } prof_malloc(tsd, allocation, size, usize, &alloc_ctx, tctx); } else { assert(!opt_prof); allocation = imalloc_no_sample(sopts, dopts, tsd, size, usize, ind); if (unlikely(allocation == NULL)) { goto label_oom; } } /* * Allocation has been done at this point. We still have some * post-allocation work to do though. */ thread_alloc_event(tsd, usize); assert(dopts->alignment == 0 || ((uintptr_t)allocation & (dopts->alignment - 1)) == ZU(0)); assert(usize == isalloc(tsd_tsdn(tsd), allocation)); if (config_fill && sopts->slow && !dopts->zero && unlikely(opt_junk_alloc)) { junk_alloc_callback(allocation, usize); } if (sopts->slow) { UTRACE(0, size, allocation); } /* Success! */ check_entry_exit_locking(tsd_tsdn(tsd)); *dopts->result = allocation; return 0; label_oom: if (unlikely(sopts->slow) && config_xmalloc && unlikely(opt_xmalloc)) { malloc_write(sopts->oom_string); abort(); } if (sopts->slow) { UTRACE(NULL, size, NULL); } check_entry_exit_locking(tsd_tsdn(tsd)); if (sopts->set_errno_on_error) { set_errno(ENOMEM); } if (sopts->null_out_result_on_error) { *dopts->result = NULL; } return ENOMEM; /* * This label is only jumped to by one goto; we move it out of line * anyways to avoid obscuring the non-error paths, and for symmetry with * the oom case. */ label_invalid_alignment: if (config_xmalloc && unlikely(opt_xmalloc)) { malloc_write(sopts->invalid_alignment_string); abort(); } if (sopts->set_errno_on_error) { set_errno(EINVAL); } if (sopts->slow) { UTRACE(NULL, size, NULL); } check_entry_exit_locking(tsd_tsdn(tsd)); if (sopts->null_out_result_on_error) { *dopts->result = NULL; } return EINVAL; } JEMALLOC_ALWAYS_INLINE bool imalloc_init_check(static_opts_t *sopts, dynamic_opts_t *dopts) { if (unlikely(!malloc_initialized()) && unlikely(malloc_init())) { if (config_xmalloc && unlikely(opt_xmalloc)) { malloc_write(sopts->oom_string); abort(); } UTRACE(NULL, dopts->num_items * dopts->item_size, NULL); set_errno(ENOMEM); *dopts->result = NULL; return false; } return true; } /* Returns the errno-style error code of the allocation. */ JEMALLOC_ALWAYS_INLINE int imalloc(static_opts_t *sopts, dynamic_opts_t *dopts) { if (tsd_get_allocates() && !imalloc_init_check(sopts, dopts)) { return ENOMEM; } /* We always need the tsd. Let's grab it right away. */ tsd_t *tsd = tsd_fetch(); assert(tsd); if (likely(tsd_fast(tsd))) { /* Fast and common path. */ tsd_assert_fast(tsd); sopts->slow = false; return imalloc_body(sopts, dopts, tsd); } else { if (!tsd_get_allocates() && !imalloc_init_check(sopts, dopts)) { return ENOMEM; } sopts->slow = true; return imalloc_body(sopts, dopts, tsd); } } JEMALLOC_NOINLINE void * malloc_default(size_t size) { void *ret; static_opts_t sopts; dynamic_opts_t dopts; /* * This variant has logging hook on exit but not on entry. It's callled * only by je_malloc, below, which emits the entry one for us (and, if * it calls us, does so only via tail call). */ static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.null_out_result_on_error = true; sopts.set_errno_on_error = true; sopts.oom_string = ": Error in malloc(): out of memory\n"; dopts.result = &ret; dopts.num_items = 1; dopts.item_size = size; imalloc(&sopts, &dopts); /* * Note that this branch gets optimized away -- it immediately follows * the check on tsd_fast that sets sopts.slow. */ if (sopts.slow) { uintptr_t args[3] = {size}; hook_invoke_alloc(hook_alloc_malloc, ret, (uintptr_t)ret, args); } LOG("core.malloc.exit", "result: %p", ret); return ret; } /******************************************************************************/ /* * Begin malloc(3)-compatible functions. */ JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1) je_malloc(size_t size) { return imalloc_fastpath(size, &malloc_default); } JEMALLOC_EXPORT int JEMALLOC_NOTHROW JEMALLOC_ATTR(nonnull(1)) je_posix_memalign(void **memptr, size_t alignment, size_t size) { int ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.posix_memalign.entry", "mem ptr: %p, alignment: %zu, " "size: %zu", memptr, alignment, size); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.bump_empty_aligned_alloc = true; sopts.min_alignment = sizeof(void *); sopts.oom_string = ": Error allocating aligned memory: out of memory\n"; sopts.invalid_alignment_string = ": Error allocating aligned memory: invalid alignment\n"; dopts.result = memptr; dopts.num_items = 1; dopts.item_size = size; dopts.alignment = alignment; ret = imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {(uintptr_t)memptr, (uintptr_t)alignment, (uintptr_t)size}; hook_invoke_alloc(hook_alloc_posix_memalign, *memptr, (uintptr_t)ret, args); } LOG("core.posix_memalign.exit", "result: %d, alloc ptr: %p", ret, *memptr); return ret; } JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(2) je_aligned_alloc(size_t alignment, size_t size) { void *ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.aligned_alloc.entry", "alignment: %zu, size: %zu\n", alignment, size); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.bump_empty_aligned_alloc = true; sopts.null_out_result_on_error = true; sopts.set_errno_on_error = true; sopts.min_alignment = 1; sopts.oom_string = ": Error allocating aligned memory: out of memory\n"; sopts.invalid_alignment_string = ": Error allocating aligned memory: invalid alignment\n"; dopts.result = &ret; dopts.num_items = 1; dopts.item_size = size; dopts.alignment = alignment; imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {(uintptr_t)alignment, (uintptr_t)size}; hook_invoke_alloc(hook_alloc_aligned_alloc, ret, (uintptr_t)ret, args); } LOG("core.aligned_alloc.exit", "result: %p", ret); return ret; } JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE2(1, 2) je_calloc(size_t num, size_t size) { void *ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.calloc.entry", "num: %zu, size: %zu\n", num, size); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.may_overflow = true; sopts.null_out_result_on_error = true; sopts.set_errno_on_error = true; sopts.oom_string = ": Error in calloc(): out of memory\n"; dopts.result = &ret; dopts.num_items = num; dopts.item_size = size; dopts.zero = true; imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {(uintptr_t)num, (uintptr_t)size}; hook_invoke_alloc(hook_alloc_calloc, ret, (uintptr_t)ret, args); } LOG("core.calloc.exit", "result: %p", ret); return ret; } JEMALLOC_ALWAYS_INLINE void ifree(tsd_t *tsd, void *ptr, tcache_t *tcache, bool slow_path) { if (!slow_path) { tsd_assert_fast(tsd); } check_entry_exit_locking(tsd_tsdn(tsd)); if (tsd_reentrancy_level_get(tsd) != 0) { assert(slow_path); } assert(ptr != NULL); assert(malloc_initialized() || IS_INITIALIZER); emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind != SC_NSIZES); size_t usize = sz_index2size(alloc_ctx.szind); if (config_prof && opt_prof) { prof_free(tsd, ptr, usize, &alloc_ctx); } if (likely(!slow_path)) { idalloctm(tsd_tsdn(tsd), ptr, tcache, &alloc_ctx, false, false); } else { if (config_fill && slow_path && opt_junk_free) { junk_free_callback(ptr, usize); } idalloctm(tsd_tsdn(tsd), ptr, tcache, &alloc_ctx, false, true); } thread_dalloc_event(tsd, usize); } JEMALLOC_ALWAYS_INLINE bool maybe_check_alloc_ctx(tsd_t *tsd, void *ptr, emap_alloc_ctx_t *alloc_ctx) { if (config_opt_size_checks) { emap_alloc_ctx_t dbg_ctx; emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, &dbg_ctx); if (alloc_ctx->szind != dbg_ctx.szind) { safety_check_fail_sized_dealloc( /* current_dealloc */ true, ptr, /* true_size */ sz_size2index(dbg_ctx.szind), /* input_size */ sz_size2index(alloc_ctx->szind)); return true; } if (alloc_ctx->slab != dbg_ctx.slab) { safety_check_fail( "Internal heap corruption detected: " "mismatch in slab bit"); return true; } } return false; } JEMALLOC_ALWAYS_INLINE void isfree(tsd_t *tsd, void *ptr, size_t usize, tcache_t *tcache, bool slow_path) { if (!slow_path) { tsd_assert_fast(tsd); } check_entry_exit_locking(tsd_tsdn(tsd)); if (tsd_reentrancy_level_get(tsd) != 0) { assert(slow_path); } assert(ptr != NULL); assert(malloc_initialized() || IS_INITIALIZER); emap_alloc_ctx_t alloc_ctx; if (!config_prof) { alloc_ctx.szind = sz_size2index(usize); alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); } else { if (likely(!prof_sample_aligned(ptr))) { /* * When the ptr is not page aligned, it was not sampled. * usize can be trusted to determine szind and slab. */ alloc_ctx.szind = sz_size2index(usize); alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); } else if (opt_prof) { emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); if (config_opt_safety_checks) { /* Small alloc may have !slab (sampled). */ if (unlikely(alloc_ctx.szind != sz_size2index(usize))) { safety_check_fail_sized_dealloc( /* current_dealloc */ true, ptr, /* true_size */ sz_index2size( alloc_ctx.szind), /* input_size */ usize); } } } else { alloc_ctx.szind = sz_size2index(usize); alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); } } bool fail = maybe_check_alloc_ctx(tsd, ptr, &alloc_ctx); if (fail) { /* * This is a heap corruption bug. In real life we'll crash; for * the unit test we just want to avoid breaking anything too * badly to get a test result out. Let's leak instead of trying * to free. */ return; } if (config_prof && opt_prof) { prof_free(tsd, ptr, usize, &alloc_ctx); } if (likely(!slow_path)) { isdalloct(tsd_tsdn(tsd), ptr, usize, tcache, &alloc_ctx, false); } else { if (config_fill && slow_path && opt_junk_free) { junk_free_callback(ptr, usize); } isdalloct(tsd_tsdn(tsd), ptr, usize, tcache, &alloc_ctx, true); } thread_dalloc_event(tsd, usize); } JEMALLOC_NOINLINE void free_default(void *ptr) { UTRACE(ptr, 0, 0); if (likely(ptr != NULL)) { /* * We avoid setting up tsd fully (e.g. tcache, arena binding) * based on only free() calls -- other activities trigger the * minimal to full transition. This is because free() may * happen during thread shutdown after tls deallocation: if a * thread never had any malloc activities until then, a * fully-setup tsd won't be destructed properly. */ tsd_t *tsd = tsd_fetch_min(); check_entry_exit_locking(tsd_tsdn(tsd)); if (likely(tsd_fast(tsd))) { tcache_t *tcache = tcache_get_from_ind(tsd, TCACHE_IND_AUTOMATIC, /* slow */ false, /* is_alloc */ false); ifree(tsd, ptr, tcache, /* slow */ false); } else { tcache_t *tcache = tcache_get_from_ind(tsd, TCACHE_IND_AUTOMATIC, /* slow */ true, /* is_alloc */ false); uintptr_t args_raw[3] = {(uintptr_t)ptr}; hook_invoke_dalloc(hook_dalloc_free, ptr, args_raw); ifree(tsd, ptr, tcache, /* slow */ true); } check_entry_exit_locking(tsd_tsdn(tsd)); } } JEMALLOC_ALWAYS_INLINE bool free_fastpath_nonfast_aligned(void *ptr, bool check_prof) { /* * free_fastpath do not handle two uncommon cases: 1) sampled profiled * objects and 2) sampled junk & stash for use-after-free detection. * Both have special alignments which are used to escape the fastpath. * * prof_sample is page-aligned, which covers the UAF check when both * are enabled (the assertion below). Avoiding redundant checks since * this is on the fastpath -- at most one runtime branch from this. */ if (config_debug && cache_bin_nonfast_aligned(ptr)) { assert(prof_sample_aligned(ptr)); } if (config_prof && check_prof) { /* When prof is enabled, the prof_sample alignment is enough. */ if (prof_sample_aligned(ptr)) { return true; } else { return false; } } if (config_uaf_detection) { if (cache_bin_nonfast_aligned(ptr)) { return true; } else { return false; } } return false; } /* Returns whether or not the free attempt was successful. */ JEMALLOC_ALWAYS_INLINE bool free_fastpath(void *ptr, size_t size, bool size_hint) { tsd_t *tsd = tsd_get(false); /* The branch gets optimized away unless tsd_get_allocates(). */ if (unlikely(tsd == NULL)) { return false; } /* * The tsd_fast() / initialized checks are folded into the branch * testing (deallocated_after >= threshold) later in this function. * The threshold will be set to 0 when !tsd_fast. */ assert(tsd_fast(tsd) || *tsd_thread_deallocated_next_event_fastp_get_unsafe(tsd) == 0); emap_alloc_ctx_t alloc_ctx; if (!size_hint) { bool err = emap_alloc_ctx_try_lookup_fast(tsd, &arena_emap_global, ptr, &alloc_ctx); /* Note: profiled objects will have alloc_ctx.slab set */ if (unlikely(err || !alloc_ctx.slab || free_fastpath_nonfast_aligned(ptr, /* check_prof */ false))) { return false; } assert(alloc_ctx.szind != SC_NSIZES); } else { /* * Check for both sizes that are too large, and for sampled / * special aligned objects. The alignment check will also check * for null ptr. */ if (unlikely(size > SC_LOOKUP_MAXCLASS || free_fastpath_nonfast_aligned(ptr, /* check_prof */ true))) { return false; } alloc_ctx.szind = sz_size2index_lookup(size); /* Max lookup class must be small. */ assert(alloc_ctx.szind < SC_NBINS); /* This is a dead store, except when opt size checking is on. */ alloc_ctx.slab = true; } /* * Currently the fastpath only handles small sizes. The branch on * SC_LOOKUP_MAXCLASS makes sure of it. This lets us avoid checking * tcache szind upper limit (i.e. tcache_maxclass) as well. */ assert(alloc_ctx.slab); uint64_t deallocated, threshold; te_free_fastpath_ctx(tsd, &deallocated, &threshold); size_t usize = sz_index2size(alloc_ctx.szind); uint64_t deallocated_after = deallocated + usize; /* * Check for events and tsd non-nominal (fast_threshold will be set to * 0) in a single branch. Note that this handles the uninitialized case * as well (TSD init will be triggered on the non-fastpath). Therefore * anything depends on a functional TSD (e.g. the alloc_ctx sanity check * below) needs to be after this branch. */ if (unlikely(deallocated_after >= threshold)) { return false; } assert(tsd_fast(tsd)); bool fail = maybe_check_alloc_ctx(tsd, ptr, &alloc_ctx); if (fail) { /* See the comment in isfree. */ return true; } tcache_t *tcache = tcache_get_from_ind(tsd, TCACHE_IND_AUTOMATIC, /* slow */ false, /* is_alloc */ false); cache_bin_t *bin = &tcache->bins[alloc_ctx.szind]; /* * If junking were enabled, this is where we would do it. It's not * though, since we ensured above that we're on the fast path. Assert * that to double-check. */ assert(!opt_junk_free); if (!cache_bin_dalloc_easy(bin, ptr)) { return false; } *tsd_thread_deallocatedp_get(tsd) = deallocated_after; return true; } JEMALLOC_EXPORT void JEMALLOC_NOTHROW je_free(void *ptr) { LOG("core.free.entry", "ptr: %p", ptr); if (!free_fastpath(ptr, 0, false)) { free_default(ptr); } LOG("core.free.exit", ""); } /* * End malloc(3)-compatible functions. */ /******************************************************************************/ /* * Begin non-standard override functions. */ #ifdef JEMALLOC_OVERRIDE_MEMALIGN JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ATTR(malloc) je_memalign(size_t alignment, size_t size) { void *ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.memalign.entry", "alignment: %zu, size: %zu\n", alignment, size); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.min_alignment = 1; sopts.oom_string = ": Error allocating aligned memory: out of memory\n"; sopts.invalid_alignment_string = ": Error allocating aligned memory: invalid alignment\n"; sopts.null_out_result_on_error = true; dopts.result = &ret; dopts.num_items = 1; dopts.item_size = size; dopts.alignment = alignment; imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {alignment, size}; hook_invoke_alloc(hook_alloc_memalign, ret, (uintptr_t)ret, args); } LOG("core.memalign.exit", "result: %p", ret); return ret; } #endif #ifdef JEMALLOC_OVERRIDE_VALLOC JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ATTR(malloc) je_valloc(size_t size) { void *ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.valloc.entry", "size: %zu\n", size); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.null_out_result_on_error = true; sopts.min_alignment = PAGE; sopts.oom_string = ": Error allocating aligned memory: out of memory\n"; sopts.invalid_alignment_string = ": Error allocating aligned memory: invalid alignment\n"; dopts.result = &ret; dopts.num_items = 1; dopts.item_size = size; dopts.alignment = PAGE; imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {size}; hook_invoke_alloc(hook_alloc_valloc, ret, (uintptr_t)ret, args); } LOG("core.valloc.exit", "result: %p\n", ret); return ret; } #endif #if defined(JEMALLOC_IS_MALLOC) && defined(JEMALLOC_GLIBC_MALLOC_HOOK) /* * glibc provides the RTLD_DEEPBIND flag for dlopen which can make it possible * to inconsistently reference libc's malloc(3)-compatible functions * (https://bugzilla.mozilla.org/show_bug.cgi?id=493541). * * These definitions interpose hooks in glibc. The functions are actually * passed an extra argument for the caller return address, which will be * ignored. */ #include // defines __GLIBC__ if we are compiling against glibc JEMALLOC_EXPORT void (*__free_hook)(void *ptr) = je_free; JEMALLOC_EXPORT void *(*__malloc_hook)(size_t size) = je_malloc; JEMALLOC_EXPORT void *(*__realloc_hook)(void *ptr, size_t size) = je_realloc; # ifdef JEMALLOC_GLIBC_MEMALIGN_HOOK JEMALLOC_EXPORT void *(*__memalign_hook)(size_t alignment, size_t size) = je_memalign; # endif # ifdef __GLIBC__ /* * To enable static linking with glibc, the libc specific malloc interface must * be implemented also, so none of glibc's malloc.o functions are added to the * link. */ # define ALIAS(je_fn) __attribute__((alias (#je_fn), used)) /* To force macro expansion of je_ prefix before stringification. */ # define PREALIAS(je_fn) ALIAS(je_fn) # ifdef JEMALLOC_OVERRIDE___LIBC_CALLOC void *__libc_calloc(size_t n, size_t size) PREALIAS(je_calloc); # endif # ifdef JEMALLOC_OVERRIDE___LIBC_FREE void __libc_free(void* ptr) PREALIAS(je_free); # endif # ifdef JEMALLOC_OVERRIDE___LIBC_MALLOC void *__libc_malloc(size_t size) PREALIAS(je_malloc); # endif # ifdef JEMALLOC_OVERRIDE___LIBC_MEMALIGN void *__libc_memalign(size_t align, size_t s) PREALIAS(je_memalign); # endif # ifdef JEMALLOC_OVERRIDE___LIBC_REALLOC void *__libc_realloc(void* ptr, size_t size) PREALIAS(je_realloc); # endif # ifdef JEMALLOC_OVERRIDE___LIBC_VALLOC void *__libc_valloc(size_t size) PREALIAS(je_valloc); # endif # ifdef JEMALLOC_OVERRIDE___POSIX_MEMALIGN int __posix_memalign(void** r, size_t a, size_t s) PREALIAS(je_posix_memalign); # endif # undef PREALIAS # undef ALIAS # endif #endif /* * End non-standard override functions. */ /******************************************************************************/ /* * Begin non-standard functions. */ JEMALLOC_ALWAYS_INLINE unsigned mallocx_tcache_get(int flags) { if (likely((flags & MALLOCX_TCACHE_MASK) == 0)) { return TCACHE_IND_AUTOMATIC; } else if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE) { return TCACHE_IND_NONE; } else { return MALLOCX_TCACHE_GET(flags); } } JEMALLOC_ALWAYS_INLINE unsigned mallocx_arena_get(int flags) { if (unlikely((flags & MALLOCX_ARENA_MASK) != 0)) { return MALLOCX_ARENA_GET(flags); } else { return ARENA_IND_AUTOMATIC; } } #ifdef JEMALLOC_EXPERIMENTAL_SMALLOCX_API #define JEMALLOC_SMALLOCX_CONCAT_HELPER(x, y) x ## y #define JEMALLOC_SMALLOCX_CONCAT_HELPER2(x, y) \ JEMALLOC_SMALLOCX_CONCAT_HELPER(x, y) typedef struct { void *ptr; size_t size; } smallocx_return_t; JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN smallocx_return_t JEMALLOC_NOTHROW /* * The attribute JEMALLOC_ATTR(malloc) cannot be used due to: * - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86488 */ JEMALLOC_SMALLOCX_CONCAT_HELPER2(je_smallocx_, JEMALLOC_VERSION_GID_IDENT) (size_t size, int flags) { /* * Note: the attribute JEMALLOC_ALLOC_SIZE(1) cannot be * used here because it makes writing beyond the `size` * of the `ptr` undefined behavior, but the objective * of this function is to allow writing beyond `size` * up to `smallocx_return_t::size`. */ smallocx_return_t ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.smallocx.entry", "size: %zu, flags: %d", size, flags); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.assert_nonempty_alloc = true; sopts.null_out_result_on_error = true; sopts.oom_string = ": Error in mallocx(): out of memory\n"; sopts.usize = true; dopts.result = &ret.ptr; dopts.num_items = 1; dopts.item_size = size; if (unlikely(flags != 0)) { dopts.alignment = MALLOCX_ALIGN_GET(flags); dopts.zero = MALLOCX_ZERO_GET(flags); dopts.tcache_ind = mallocx_tcache_get(flags); dopts.arena_ind = mallocx_arena_get(flags); } imalloc(&sopts, &dopts); assert(dopts.usize == je_nallocx(size, flags)); ret.size = dopts.usize; LOG("core.smallocx.exit", "result: %p, size: %zu", ret.ptr, ret.size); return ret; } #undef JEMALLOC_SMALLOCX_CONCAT_HELPER #undef JEMALLOC_SMALLOCX_CONCAT_HELPER2 #endif JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1) je_mallocx(size_t size, int flags) { void *ret; static_opts_t sopts; dynamic_opts_t dopts; LOG("core.mallocx.entry", "size: %zu, flags: %d", size, flags); static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.assert_nonempty_alloc = true; sopts.null_out_result_on_error = true; sopts.oom_string = ": Error in mallocx(): out of memory\n"; dopts.result = &ret; dopts.num_items = 1; dopts.item_size = size; if (unlikely(flags != 0)) { dopts.alignment = MALLOCX_ALIGN_GET(flags); dopts.zero = MALLOCX_ZERO_GET(flags); dopts.tcache_ind = mallocx_tcache_get(flags); dopts.arena_ind = mallocx_arena_get(flags); } imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {size, flags}; hook_invoke_alloc(hook_alloc_mallocx, ret, (uintptr_t)ret, args); } LOG("core.mallocx.exit", "result: %p", ret); return ret; } static void * irallocx_prof_sample(tsdn_t *tsdn, void *old_ptr, size_t old_usize, size_t usize, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena, prof_tctx_t *tctx, hook_ralloc_args_t *hook_args) { void *p; if (tctx == NULL) { return NULL; } alignment = prof_sample_align(alignment); if (usize <= SC_SMALL_MAXCLASS) { p = iralloct(tsdn, old_ptr, old_usize, SC_LARGE_MINCLASS, alignment, zero, tcache, arena, hook_args); if (p == NULL) { return NULL; } arena_prof_promote(tsdn, p, usize); } else { p = iralloct(tsdn, old_ptr, old_usize, usize, alignment, zero, tcache, arena, hook_args); } assert(prof_sample_aligned(p)); return p; } JEMALLOC_ALWAYS_INLINE void * irallocx_prof(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t size, size_t alignment, size_t usize, bool zero, tcache_t *tcache, arena_t *arena, emap_alloc_ctx_t *alloc_ctx, hook_ralloc_args_t *hook_args) { prof_info_t old_prof_info; prof_info_get_and_reset_recent(tsd, old_ptr, alloc_ctx, &old_prof_info); bool prof_active = prof_active_get_unlocked(); bool sample_event = te_prof_sample_event_lookahead(tsd, usize); prof_tctx_t *tctx = prof_alloc_prep(tsd, prof_active, sample_event); void *p; if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) { p = irallocx_prof_sample(tsd_tsdn(tsd), old_ptr, old_usize, usize, alignment, zero, tcache, arena, tctx, hook_args); } else { p = iralloct(tsd_tsdn(tsd), old_ptr, old_usize, size, alignment, zero, tcache, arena, hook_args); } if (unlikely(p == NULL)) { prof_alloc_rollback(tsd, tctx); return NULL; } assert(usize == isalloc(tsd_tsdn(tsd), p)); prof_realloc(tsd, p, size, usize, tctx, prof_active, old_ptr, old_usize, &old_prof_info, sample_event); return p; } static void * do_rallocx(void *ptr, size_t size, int flags, bool is_realloc) { void *p; tsd_t *tsd; size_t usize; size_t old_usize; size_t alignment = MALLOCX_ALIGN_GET(flags); arena_t *arena; assert(ptr != NULL); assert(size != 0); assert(malloc_initialized() || IS_INITIALIZER); tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); bool zero = zero_get(MALLOCX_ZERO_GET(flags), /* slow */ true); unsigned arena_ind = mallocx_arena_get(flags); if (arena_get_from_ind(tsd, arena_ind, &arena)) { goto label_oom; } unsigned tcache_ind = mallocx_tcache_get(flags); tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, /* slow */ true, /* is_alloc */ true); emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind != SC_NSIZES); old_usize = sz_index2size(alloc_ctx.szind); assert(old_usize == isalloc(tsd_tsdn(tsd), ptr)); if (aligned_usize_get(size, alignment, &usize, NULL, false)) { goto label_oom; } hook_ralloc_args_t hook_args = {is_realloc, {(uintptr_t)ptr, size, flags, 0}}; if (config_prof && opt_prof) { p = irallocx_prof(tsd, ptr, old_usize, size, alignment, usize, zero, tcache, arena, &alloc_ctx, &hook_args); if (unlikely(p == NULL)) { goto label_oom; } } else { p = iralloct(tsd_tsdn(tsd), ptr, old_usize, size, alignment, zero, tcache, arena, &hook_args); if (unlikely(p == NULL)) { goto label_oom; } assert(usize == isalloc(tsd_tsdn(tsd), p)); } assert(alignment == 0 || ((uintptr_t)p & (alignment - 1)) == ZU(0)); thread_alloc_event(tsd, usize); thread_dalloc_event(tsd, old_usize); UTRACE(ptr, size, p); check_entry_exit_locking(tsd_tsdn(tsd)); if (config_fill && unlikely(opt_junk_alloc) && usize > old_usize && !zero) { size_t excess_len = usize - old_usize; void *excess_start = (void *)((uintptr_t)p + old_usize); junk_alloc_callback(excess_start, excess_len); } return p; label_oom: if (config_xmalloc && unlikely(opt_xmalloc)) { malloc_write(": Error in rallocx(): out of memory\n"); abort(); } UTRACE(ptr, size, 0); check_entry_exit_locking(tsd_tsdn(tsd)); return NULL; } JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ALLOC_SIZE(2) je_rallocx(void *ptr, size_t size, int flags) { LOG("core.rallocx.entry", "ptr: %p, size: %zu, flags: %d", ptr, size, flags); void *ret = do_rallocx(ptr, size, flags, false); LOG("core.rallocx.exit", "result: %p", ret); return ret; } static void * do_realloc_nonnull_zero(void *ptr) { if (config_stats) { atomic_fetch_add_zu(&zero_realloc_count, 1, ATOMIC_RELAXED); } if (opt_zero_realloc_action == zero_realloc_action_alloc) { /* * The user might have gotten an alloc setting while expecting a * free setting. If that's the case, we at least try to * reduce the harm, and turn off the tcache while allocating, so * that we'll get a true first fit. */ return do_rallocx(ptr, 1, MALLOCX_TCACHE_NONE, true); } else if (opt_zero_realloc_action == zero_realloc_action_free) { UTRACE(ptr, 0, 0); tsd_t *tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); tcache_t *tcache = tcache_get_from_ind(tsd, TCACHE_IND_AUTOMATIC, /* slow */ true, /* is_alloc */ false); uintptr_t args[3] = {(uintptr_t)ptr, 0}; hook_invoke_dalloc(hook_dalloc_realloc, ptr, args); ifree(tsd, ptr, tcache, true); check_entry_exit_locking(tsd_tsdn(tsd)); return NULL; } else { safety_check_fail("Called realloc(non-null-ptr, 0) with " "zero_realloc:abort set\n"); /* In real code, this will never run; the safety check failure * will call abort. In the unit test, we just want to bail out * without corrupting internal state that the test needs to * finish. */ return NULL; } } JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN void JEMALLOC_NOTHROW * JEMALLOC_ALLOC_SIZE(2) je_realloc(void *ptr, size_t size) { LOG("core.realloc.entry", "ptr: %p, size: %zu\n", ptr, size); if (likely(ptr != NULL && size != 0)) { void *ret = do_rallocx(ptr, size, 0, true); LOG("core.realloc.exit", "result: %p", ret); return ret; } else if (ptr != NULL && size == 0) { void *ret = do_realloc_nonnull_zero(ptr); LOG("core.realloc.exit", "result: %p", ret); return ret; } else { /* realloc(NULL, size) is equivalent to malloc(size). */ void *ret; static_opts_t sopts; dynamic_opts_t dopts; static_opts_init(&sopts); dynamic_opts_init(&dopts); sopts.null_out_result_on_error = true; sopts.set_errno_on_error = true; sopts.oom_string = ": Error in realloc(): out of memory\n"; dopts.result = &ret; dopts.num_items = 1; dopts.item_size = size; imalloc(&sopts, &dopts); if (sopts.slow) { uintptr_t args[3] = {(uintptr_t)ptr, size}; hook_invoke_alloc(hook_alloc_realloc, ret, (uintptr_t)ret, args); } LOG("core.realloc.exit", "result: %p", ret); return ret; } } JEMALLOC_ALWAYS_INLINE size_t ixallocx_helper(tsdn_t *tsdn, void *ptr, size_t old_usize, size_t size, size_t extra, size_t alignment, bool zero) { size_t newsize; if (ixalloc(tsdn, ptr, old_usize, size, extra, alignment, zero, &newsize)) { return old_usize; } return newsize; } static size_t ixallocx_prof_sample(tsdn_t *tsdn, void *ptr, size_t old_usize, size_t size, size_t extra, size_t alignment, bool zero, prof_tctx_t *tctx) { /* Sampled allocation needs to be page aligned. */ if (tctx == NULL || !prof_sample_aligned(ptr)) { return old_usize; } return ixallocx_helper(tsdn, ptr, old_usize, size, extra, alignment, zero); } JEMALLOC_ALWAYS_INLINE size_t ixallocx_prof(tsd_t *tsd, void *ptr, size_t old_usize, size_t size, size_t extra, size_t alignment, bool zero, emap_alloc_ctx_t *alloc_ctx) { /* * old_prof_info is only used for asserting that the profiling info * isn't changed by the ixalloc() call. */ prof_info_t old_prof_info; prof_info_get(tsd, ptr, alloc_ctx, &old_prof_info); /* * usize isn't knowable before ixalloc() returns when extra is non-zero. * Therefore, compute its maximum possible value and use that in * prof_alloc_prep() to decide whether to capture a backtrace. * prof_realloc() will use the actual usize to decide whether to sample. */ size_t usize_max; if (aligned_usize_get(size + extra, alignment, &usize_max, NULL, false)) { /* * usize_max is out of range, and chances are that allocation * will fail, but use the maximum possible value and carry on * with prof_alloc_prep(), just in case allocation succeeds. */ usize_max = SC_LARGE_MAXCLASS; } bool prof_active = prof_active_get_unlocked(); bool sample_event = te_prof_sample_event_lookahead(tsd, usize_max); prof_tctx_t *tctx = prof_alloc_prep(tsd, prof_active, sample_event); size_t usize; if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) { usize = ixallocx_prof_sample(tsd_tsdn(tsd), ptr, old_usize, size, extra, alignment, zero, tctx); } else { usize = ixallocx_helper(tsd_tsdn(tsd), ptr, old_usize, size, extra, alignment, zero); } /* * At this point we can still safely get the original profiling * information associated with the ptr, because (a) the edata_t object * associated with the ptr still lives and (b) the profiling info * fields are not touched. "(a)" is asserted in the outer je_xallocx() * function, and "(b)" is indirectly verified below by checking that * the alloc_tctx field is unchanged. */ prof_info_t prof_info; if (usize == old_usize) { prof_info_get(tsd, ptr, alloc_ctx, &prof_info); prof_alloc_rollback(tsd, tctx); } else { prof_info_get_and_reset_recent(tsd, ptr, alloc_ctx, &prof_info); assert(usize <= usize_max); sample_event = te_prof_sample_event_lookahead(tsd, usize); prof_realloc(tsd, ptr, size, usize, tctx, prof_active, ptr, old_usize, &prof_info, sample_event); } assert(old_prof_info.alloc_tctx == prof_info.alloc_tctx); return usize; } JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW je_xallocx(void *ptr, size_t size, size_t extra, int flags) { tsd_t *tsd; size_t usize, old_usize; size_t alignment = MALLOCX_ALIGN_GET(flags); bool zero = zero_get(MALLOCX_ZERO_GET(flags), /* slow */ true); LOG("core.xallocx.entry", "ptr: %p, size: %zu, extra: %zu, " "flags: %d", ptr, size, extra, flags); assert(ptr != NULL); assert(size != 0); assert(SIZE_T_MAX - size >= extra); assert(malloc_initialized() || IS_INITIALIZER); tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); /* * old_edata is only for verifying that xallocx() keeps the edata_t * object associated with the ptr (though the content of the edata_t * object can be changed). */ edata_t *old_edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); emap_alloc_ctx_t alloc_ctx; emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, &alloc_ctx); assert(alloc_ctx.szind != SC_NSIZES); old_usize = sz_index2size(alloc_ctx.szind); assert(old_usize == isalloc(tsd_tsdn(tsd), ptr)); /* * The API explicitly absolves itself of protecting against (size + * extra) numerical overflow, but we may need to clamp extra to avoid * exceeding SC_LARGE_MAXCLASS. * * Ordinarily, size limit checking is handled deeper down, but here we * have to check as part of (size + extra) clamping, since we need the * clamped value in the above helper functions. */ if (unlikely(size > SC_LARGE_MAXCLASS)) { usize = old_usize; goto label_not_resized; } if (unlikely(SC_LARGE_MAXCLASS - size < extra)) { extra = SC_LARGE_MAXCLASS - size; } if (config_prof && opt_prof) { usize = ixallocx_prof(tsd, ptr, old_usize, size, extra, alignment, zero, &alloc_ctx); } else { usize = ixallocx_helper(tsd_tsdn(tsd), ptr, old_usize, size, extra, alignment, zero); } /* * xallocx() should keep using the same edata_t object (though its * content can be changed). */ assert(emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr) == old_edata); if (unlikely(usize == old_usize)) { goto label_not_resized; } thread_alloc_event(tsd, usize); thread_dalloc_event(tsd, old_usize); if (config_fill && unlikely(opt_junk_alloc) && usize > old_usize && !zero) { size_t excess_len = usize - old_usize; void *excess_start = (void *)((uintptr_t)ptr + old_usize); junk_alloc_callback(excess_start, excess_len); } label_not_resized: if (unlikely(!tsd_fast(tsd))) { uintptr_t args[4] = {(uintptr_t)ptr, size, extra, flags}; hook_invoke_expand(hook_expand_xallocx, ptr, old_usize, usize, (uintptr_t)usize, args); } UTRACE(ptr, size, ptr); check_entry_exit_locking(tsd_tsdn(tsd)); LOG("core.xallocx.exit", "result: %zu", usize); return usize; } JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW JEMALLOC_ATTR(pure) je_sallocx(const void *ptr, int flags) { size_t usize; tsdn_t *tsdn; LOG("core.sallocx.entry", "ptr: %p, flags: %d", ptr, flags); assert(malloc_initialized() || IS_INITIALIZER); assert(ptr != NULL); tsdn = tsdn_fetch(); check_entry_exit_locking(tsdn); if (config_debug || force_ivsalloc) { usize = ivsalloc(tsdn, ptr); assert(force_ivsalloc || usize != 0); } else { usize = isalloc(tsdn, ptr); } check_entry_exit_locking(tsdn); LOG("core.sallocx.exit", "result: %zu", usize); return usize; } JEMALLOC_EXPORT void JEMALLOC_NOTHROW je_dallocx(void *ptr, int flags) { LOG("core.dallocx.entry", "ptr: %p, flags: %d", ptr, flags); assert(ptr != NULL); assert(malloc_initialized() || IS_INITIALIZER); tsd_t *tsd = tsd_fetch_min(); bool fast = tsd_fast(tsd); check_entry_exit_locking(tsd_tsdn(tsd)); unsigned tcache_ind = mallocx_tcache_get(flags); tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, !fast, /* is_alloc */ false); UTRACE(ptr, 0, 0); if (likely(fast)) { tsd_assert_fast(tsd); ifree(tsd, ptr, tcache, false); } else { uintptr_t args_raw[3] = {(uintptr_t)ptr, flags}; hook_invoke_dalloc(hook_dalloc_dallocx, ptr, args_raw); ifree(tsd, ptr, tcache, true); } check_entry_exit_locking(tsd_tsdn(tsd)); LOG("core.dallocx.exit", ""); } JEMALLOC_ALWAYS_INLINE size_t inallocx(tsdn_t *tsdn, size_t size, int flags) { check_entry_exit_locking(tsdn); size_t usize; /* In case of out of range, let the user see it rather than fail. */ aligned_usize_get(size, MALLOCX_ALIGN_GET(flags), &usize, NULL, false); check_entry_exit_locking(tsdn); return usize; } JEMALLOC_NOINLINE void sdallocx_default(void *ptr, size_t size, int flags) { assert(ptr != NULL); assert(malloc_initialized() || IS_INITIALIZER); tsd_t *tsd = tsd_fetch_min(); bool fast = tsd_fast(tsd); size_t usize = inallocx(tsd_tsdn(tsd), size, flags); check_entry_exit_locking(tsd_tsdn(tsd)); unsigned tcache_ind = mallocx_tcache_get(flags); tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, !fast, /* is_alloc */ false); UTRACE(ptr, 0, 0); if (likely(fast)) { tsd_assert_fast(tsd); isfree(tsd, ptr, usize, tcache, false); } else { uintptr_t args_raw[3] = {(uintptr_t)ptr, size, flags}; hook_invoke_dalloc(hook_dalloc_sdallocx, ptr, args_raw); isfree(tsd, ptr, usize, tcache, true); } check_entry_exit_locking(tsd_tsdn(tsd)); } JEMALLOC_EXPORT void JEMALLOC_NOTHROW je_sdallocx(void *ptr, size_t size, int flags) { LOG("core.sdallocx.entry", "ptr: %p, size: %zu, flags: %d", ptr, size, flags); if (flags != 0 || !free_fastpath(ptr, size, true)) { sdallocx_default(ptr, size, flags); } LOG("core.sdallocx.exit", ""); } void JEMALLOC_NOTHROW je_sdallocx_noflags(void *ptr, size_t size) { LOG("core.sdallocx.entry", "ptr: %p, size: %zu, flags: 0", ptr, size); if (!free_fastpath(ptr, size, true)) { sdallocx_default(ptr, size, 0); } LOG("core.sdallocx.exit", ""); } JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW JEMALLOC_ATTR(pure) je_nallocx(size_t size, int flags) { size_t usize; tsdn_t *tsdn; assert(size != 0); if (unlikely(malloc_init())) { LOG("core.nallocx.exit", "result: %zu", ZU(0)); return 0; } tsdn = tsdn_fetch(); check_entry_exit_locking(tsdn); usize = inallocx(tsdn, size, flags); if (unlikely(usize > SC_LARGE_MAXCLASS)) { LOG("core.nallocx.exit", "result: %zu", ZU(0)); return 0; } check_entry_exit_locking(tsdn); LOG("core.nallocx.exit", "result: %zu", usize); return usize; } JEMALLOC_EXPORT int JEMALLOC_NOTHROW je_mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; tsd_t *tsd; LOG("core.mallctl.entry", "name: %s", name); if (unlikely(malloc_init())) { LOG("core.mallctl.exit", "result: %d", EAGAIN); return EAGAIN; } tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); ret = ctl_byname(tsd, name, oldp, oldlenp, newp, newlen); check_entry_exit_locking(tsd_tsdn(tsd)); LOG("core.mallctl.exit", "result: %d", ret); return ret; } JEMALLOC_EXPORT int JEMALLOC_NOTHROW je_mallctlnametomib(const char *name, size_t *mibp, size_t *miblenp) { int ret; LOG("core.mallctlnametomib.entry", "name: %s", name); if (unlikely(malloc_init())) { LOG("core.mallctlnametomib.exit", "result: %d", EAGAIN); return EAGAIN; } tsd_t *tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); ret = ctl_nametomib(tsd, name, mibp, miblenp); check_entry_exit_locking(tsd_tsdn(tsd)); LOG("core.mallctlnametomib.exit", "result: %d", ret); return ret; } JEMALLOC_EXPORT int JEMALLOC_NOTHROW je_mallctlbymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { int ret; tsd_t *tsd; LOG("core.mallctlbymib.entry", ""); if (unlikely(malloc_init())) { LOG("core.mallctlbymib.exit", "result: %d", EAGAIN); return EAGAIN; } tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); ret = ctl_bymib(tsd, mib, miblen, oldp, oldlenp, newp, newlen); check_entry_exit_locking(tsd_tsdn(tsd)); LOG("core.mallctlbymib.exit", "result: %d", ret); return ret; } #define STATS_PRINT_BUFSIZE 65536 JEMALLOC_EXPORT void JEMALLOC_NOTHROW je_malloc_stats_print(void (*write_cb)(void *, const char *), void *cbopaque, const char *opts) { tsdn_t *tsdn; LOG("core.malloc_stats_print.entry", ""); tsdn = tsdn_fetch(); check_entry_exit_locking(tsdn); if (config_debug) { stats_print(write_cb, cbopaque, opts); } else { buf_writer_t buf_writer; buf_writer_init(tsdn, &buf_writer, write_cb, cbopaque, NULL, STATS_PRINT_BUFSIZE); stats_print(buf_writer_cb, &buf_writer, opts); buf_writer_terminate(tsdn, &buf_writer); } check_entry_exit_locking(tsdn); LOG("core.malloc_stats_print.exit", ""); } #undef STATS_PRINT_BUFSIZE JEMALLOC_ALWAYS_INLINE size_t je_malloc_usable_size_impl(JEMALLOC_USABLE_SIZE_CONST void *ptr) { assert(malloc_initialized() || IS_INITIALIZER); tsdn_t *tsdn = tsdn_fetch(); check_entry_exit_locking(tsdn); size_t ret; if (unlikely(ptr == NULL)) { ret = 0; } else { if (config_debug || force_ivsalloc) { ret = ivsalloc(tsdn, ptr); assert(force_ivsalloc || ret != 0); } else { ret = isalloc(tsdn, ptr); } } check_entry_exit_locking(tsdn); return ret; } JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr) { LOG("core.malloc_usable_size.entry", "ptr: %p", ptr); size_t ret = je_malloc_usable_size_impl(ptr); LOG("core.malloc_usable_size.exit", "result: %zu", ret); return ret; } #ifdef JEMALLOC_HAVE_MALLOC_SIZE JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW je_malloc_size(const void *ptr) { LOG("core.malloc_size.entry", "ptr: %p", ptr); size_t ret = je_malloc_usable_size_impl(ptr); LOG("core.malloc_size.exit", "result: %zu", ret); return ret; } #endif static void batch_alloc_prof_sample_assert(tsd_t *tsd, size_t batch, size_t usize) { assert(config_prof && opt_prof); bool prof_sample_event = te_prof_sample_event_lookahead(tsd, batch * usize); assert(!prof_sample_event); size_t surplus; prof_sample_event = te_prof_sample_event_lookahead_surplus(tsd, (batch + 1) * usize, &surplus); assert(prof_sample_event); assert(surplus < usize); } size_t batch_alloc(void **ptrs, size_t num, size_t size, int flags) { LOG("core.batch_alloc.entry", "ptrs: %p, num: %zu, size: %zu, flags: %d", ptrs, num, size, flags); tsd_t *tsd = tsd_fetch(); check_entry_exit_locking(tsd_tsdn(tsd)); size_t filled = 0; if (unlikely(tsd == NULL || tsd_reentrancy_level_get(tsd) > 0)) { goto label_done; } size_t alignment = MALLOCX_ALIGN_GET(flags); size_t usize; if (aligned_usize_get(size, alignment, &usize, NULL, false)) { goto label_done; } szind_t ind = sz_size2index(usize); bool zero = zero_get(MALLOCX_ZERO_GET(flags), /* slow */ true); /* * The cache bin and arena will be lazily initialized; it's hard to * know in advance whether each of them needs to be initialized. */ cache_bin_t *bin = NULL; arena_t *arena = NULL; size_t nregs = 0; if (likely(ind < SC_NBINS)) { nregs = bin_infos[ind].nregs; assert(nregs > 0); } while (filled < num) { size_t batch = num - filled; size_t surplus = SIZE_MAX; /* Dead store. */ bool prof_sample_event = config_prof && opt_prof && prof_active_get_unlocked() && te_prof_sample_event_lookahead_surplus(tsd, batch * usize, &surplus); if (prof_sample_event) { /* * Adjust so that the batch does not trigger prof * sampling. */ batch -= surplus / usize + 1; batch_alloc_prof_sample_assert(tsd, batch, usize); } size_t progress = 0; if (likely(ind < SC_NBINS) && batch >= nregs) { if (arena == NULL) { unsigned arena_ind = mallocx_arena_get(flags); if (arena_get_from_ind(tsd, arena_ind, &arena)) { goto label_done; } if (arena == NULL) { arena = arena_choose(tsd, NULL); } if (unlikely(arena == NULL)) { goto label_done; } } size_t arena_batch = batch - batch % nregs; size_t n = arena_fill_small_fresh(tsd_tsdn(tsd), arena, ind, ptrs + filled, arena_batch, zero); progress += n; filled += n; } if (likely(ind < nhbins) && progress < batch) { if (bin == NULL) { unsigned tcache_ind = mallocx_tcache_get(flags); tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, /* slow */ true, /* is_alloc */ true); if (tcache != NULL) { bin = &tcache->bins[ind]; } } /* * If we don't have a tcache bin, we don't want to * immediately give up, because there's the possibility * that the user explicitly requested to bypass the * tcache, or that the user explicitly turned off the * tcache; in such cases, we go through the slow path, * i.e. the mallocx() call at the end of the while loop. */ if (bin != NULL) { size_t bin_batch = batch - progress; /* * n can be less than bin_batch, meaning that * the cache bin does not have enough memory. * In such cases, we rely on the slow path, * i.e. the mallocx() call at the end of the * while loop, to fill in the cache, and in the * next iteration of the while loop, the tcache * will contain a lot of memory, and we can * harvest them here. Compared to the * alternative approach where we directly go to * the arena bins here, the overhead of our * current approach should usually be minimal, * since we never try to fetch more memory than * what a slab contains via the tcache. An * additional benefit is that the tcache will * not be empty for the next allocation request. */ size_t n = cache_bin_alloc_batch(bin, bin_batch, ptrs + filled); if (config_stats) { bin->tstats.nrequests += n; } if (zero) { for (size_t i = 0; i < n; ++i) { memset(ptrs[filled + i], 0, usize); } } if (config_prof && opt_prof && unlikely(ind >= SC_NBINS)) { for (size_t i = 0; i < n; ++i) { prof_tctx_reset_sampled(tsd, ptrs[filled + i]); } } progress += n; filled += n; } } /* * For thread events other than prof sampling, trigger them as * if there's a single allocation of size (n * usize). This is * fine because: * (a) these events do not alter the allocation itself, and * (b) it's possible that some event would have been triggered * multiple times, instead of only once, if the allocations * were handled individually, but it would do no harm (or * even be beneficial) to coalesce the triggerings. */ thread_alloc_event(tsd, progress * usize); if (progress < batch || prof_sample_event) { void *p = je_mallocx(size, flags); if (p == NULL) { /* OOM */ break; } if (progress == batch) { assert(prof_sampled(tsd, p)); } ptrs[filled++] = p; } } label_done: check_entry_exit_locking(tsd_tsdn(tsd)); LOG("core.batch_alloc.exit", "result: %zu", filled); return filled; } /* * End non-standard functions. */ /******************************************************************************/ /* * The following functions are used by threading libraries for protection of * malloc during fork(). */ /* * If an application creates a thread before doing any allocation in the main * thread, then calls fork(2) in the main thread followed by memory allocation * in the child process, a race can occur that results in deadlock within the * child: the main thread may have forked while the created thread had * partially initialized the allocator. Ordinarily jemalloc prevents * fork/malloc races via the following functions it registers during * initialization using pthread_atfork(), but of course that does no good if * the allocator isn't fully initialized at fork time. The following library * constructor is a partial solution to this problem. It may still be possible * to trigger the deadlock described above, but doing so would involve forking * via a library constructor that runs before jemalloc's runs. */ #ifndef JEMALLOC_JET JEMALLOC_ATTR(constructor) static void jemalloc_constructor(void) { malloc_init(); } #endif #ifndef JEMALLOC_MUTEX_INIT_CB void jemalloc_prefork(void) #else JEMALLOC_EXPORT void _malloc_prefork(void) #endif { tsd_t *tsd; unsigned i, j, narenas; arena_t *arena; #ifdef JEMALLOC_MUTEX_INIT_CB if (!malloc_initialized()) { return; } #endif assert(malloc_initialized()); tsd = tsd_fetch(); narenas = narenas_total_get(); witness_prefork(tsd_witness_tsdp_get(tsd)); /* Acquire all mutexes in a safe order. */ ctl_prefork(tsd_tsdn(tsd)); tcache_prefork(tsd_tsdn(tsd)); malloc_mutex_prefork(tsd_tsdn(tsd), &arenas_lock); if (have_background_thread) { background_thread_prefork0(tsd_tsdn(tsd)); } prof_prefork0(tsd_tsdn(tsd)); if (have_background_thread) { background_thread_prefork1(tsd_tsdn(tsd)); } /* Break arena prefork into stages to preserve lock order. */ for (i = 0; i < 9; i++) { for (j = 0; j < narenas; j++) { if ((arena = arena_get(tsd_tsdn(tsd), j, false)) != NULL) { switch (i) { case 0: arena_prefork0(tsd_tsdn(tsd), arena); break; case 1: arena_prefork1(tsd_tsdn(tsd), arena); break; case 2: arena_prefork2(tsd_tsdn(tsd), arena); break; case 3: arena_prefork3(tsd_tsdn(tsd), arena); break; case 4: arena_prefork4(tsd_tsdn(tsd), arena); break; case 5: arena_prefork5(tsd_tsdn(tsd), arena); break; case 6: arena_prefork6(tsd_tsdn(tsd), arena); break; case 7: arena_prefork7(tsd_tsdn(tsd), arena); break; case 8: arena_prefork8(tsd_tsdn(tsd), arena); break; default: not_reached(); } } } } prof_prefork1(tsd_tsdn(tsd)); stats_prefork(tsd_tsdn(tsd)); tsd_prefork(tsd); } #ifndef JEMALLOC_MUTEX_INIT_CB void jemalloc_postfork_parent(void) #else JEMALLOC_EXPORT void _malloc_postfork(void) #endif { tsd_t *tsd; unsigned i, narenas; #ifdef JEMALLOC_MUTEX_INIT_CB if (!malloc_initialized()) { return; } #endif assert(malloc_initialized()); tsd = tsd_fetch(); tsd_postfork_parent(tsd); witness_postfork_parent(tsd_witness_tsdp_get(tsd)); /* Release all mutexes, now that fork() has completed. */ stats_postfork_parent(tsd_tsdn(tsd)); for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { arena_t *arena; if ((arena = arena_get(tsd_tsdn(tsd), i, false)) != NULL) { arena_postfork_parent(tsd_tsdn(tsd), arena); } } prof_postfork_parent(tsd_tsdn(tsd)); if (have_background_thread) { background_thread_postfork_parent(tsd_tsdn(tsd)); } malloc_mutex_postfork_parent(tsd_tsdn(tsd), &arenas_lock); tcache_postfork_parent(tsd_tsdn(tsd)); ctl_postfork_parent(tsd_tsdn(tsd)); } void jemalloc_postfork_child(void) { tsd_t *tsd; unsigned i, narenas; assert(malloc_initialized()); tsd = tsd_fetch(); tsd_postfork_child(tsd); witness_postfork_child(tsd_witness_tsdp_get(tsd)); /* Release all mutexes, now that fork() has completed. */ stats_postfork_child(tsd_tsdn(tsd)); for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { arena_t *arena; if ((arena = arena_get(tsd_tsdn(tsd), i, false)) != NULL) { arena_postfork_child(tsd_tsdn(tsd), arena); } } prof_postfork_child(tsd_tsdn(tsd)); if (have_background_thread) { background_thread_postfork_child(tsd_tsdn(tsd)); } malloc_mutex_postfork_child(tsd_tsdn(tsd), &arenas_lock); tcache_postfork_child(tsd_tsdn(tsd)); ctl_postfork_child(tsd_tsdn(tsd)); } /******************************************************************************/ /* Helps the application decide if a pointer is worth re-allocating in order to reduce fragmentation. * returns 1 if the allocation should be moved, and 0 if the allocation be kept. * If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */ JEMALLOC_EXPORT int JEMALLOC_NOTHROW get_defrag_hint(void* ptr) { assert(ptr != NULL); return iget_defrag_hint(TSDN_NULL, ptr); } redis-8.0.2/deps/jemalloc/src/jemalloc_cpp.cpp000066400000000000000000000140741501533116600213020ustar00rootroot00000000000000#include #include #define JEMALLOC_CPP_CPP_ #ifdef __cplusplus extern "C" { #endif #include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #ifdef __cplusplus } #endif // All operators in this file are exported. // Possibly alias hidden versions of malloc and sdallocx to avoid an extra plt // thunk? // // extern __typeof (sdallocx) sdallocx_int // __attribute ((alias ("sdallocx"), // visibility ("hidden"))); // // ... but it needs to work with jemalloc namespaces. void *operator new(std::size_t size); void *operator new[](std::size_t size); void *operator new(std::size_t size, const std::nothrow_t &) noexcept; void *operator new[](std::size_t size, const std::nothrow_t &) noexcept; void operator delete(void *ptr) noexcept; void operator delete[](void *ptr) noexcept; void operator delete(void *ptr, const std::nothrow_t &) noexcept; void operator delete[](void *ptr, const std::nothrow_t &) noexcept; #if __cpp_sized_deallocation >= 201309 /* C++14's sized-delete operators. */ void operator delete(void *ptr, std::size_t size) noexcept; void operator delete[](void *ptr, std::size_t size) noexcept; #endif #if __cpp_aligned_new >= 201606 /* C++17's over-aligned operators. */ void *operator new(std::size_t size, std::align_val_t); void *operator new(std::size_t size, std::align_val_t, const std::nothrow_t &) noexcept; void *operator new[](std::size_t size, std::align_val_t); void *operator new[](std::size_t size, std::align_val_t, const std::nothrow_t &) noexcept; void operator delete(void* ptr, std::align_val_t) noexcept; void operator delete(void* ptr, std::align_val_t, const std::nothrow_t &) noexcept; void operator delete(void* ptr, std::size_t size, std::align_val_t al) noexcept; void operator delete[](void* ptr, std::align_val_t) noexcept; void operator delete[](void* ptr, std::align_val_t, const std::nothrow_t &) noexcept; void operator delete[](void* ptr, std::size_t size, std::align_val_t al) noexcept; #endif JEMALLOC_NOINLINE static void * handleOOM(std::size_t size, bool nothrow) { if (opt_experimental_infallible_new) { safety_check_fail(": Allocation failed and " "opt.experimental_infallible_new is true. Aborting.\n"); return nullptr; } void *ptr = nullptr; while (ptr == nullptr) { std::new_handler handler; // GCC-4.8 and clang 4.0 do not have std::get_new_handler. { static std::mutex mtx; std::lock_guard lock(mtx); handler = std::set_new_handler(nullptr); std::set_new_handler(handler); } if (handler == nullptr) break; try { handler(); } catch (const std::bad_alloc &) { break; } ptr = je_malloc(size); } if (ptr == nullptr && !nothrow) std::__throw_bad_alloc(); return ptr; } template JEMALLOC_NOINLINE static void * fallback_impl(std::size_t size) noexcept(IsNoExcept) { void *ptr = malloc_default(size); if (likely(ptr != nullptr)) { return ptr; } return handleOOM(size, IsNoExcept); } template JEMALLOC_ALWAYS_INLINE void * newImpl(std::size_t size) noexcept(IsNoExcept) { return imalloc_fastpath(size, &fallback_impl); } void * operator new(std::size_t size) { return newImpl(size); } void * operator new[](std::size_t size) { return newImpl(size); } void * operator new(std::size_t size, const std::nothrow_t &) noexcept { return newImpl(size); } void * operator new[](std::size_t size, const std::nothrow_t &) noexcept { return newImpl(size); } #if __cpp_aligned_new >= 201606 template JEMALLOC_ALWAYS_INLINE void * alignedNewImpl(std::size_t size, std::align_val_t alignment) noexcept(IsNoExcept) { void *ptr = je_aligned_alloc(static_cast(alignment), size); if (likely(ptr != nullptr)) { return ptr; } return handleOOM(size, IsNoExcept); } void * operator new(std::size_t size, std::align_val_t alignment) { return alignedNewImpl(size, alignment); } void * operator new[](std::size_t size, std::align_val_t alignment) { return alignedNewImpl(size, alignment); } void * operator new(std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept { return alignedNewImpl(size, alignment); } void * operator new[](std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept { return alignedNewImpl(size, alignment); } #endif // __cpp_aligned_new void operator delete(void *ptr) noexcept { je_free(ptr); } void operator delete[](void *ptr) noexcept { je_free(ptr); } void operator delete(void *ptr, const std::nothrow_t &) noexcept { je_free(ptr); } void operator delete[](void *ptr, const std::nothrow_t &) noexcept { je_free(ptr); } #if __cpp_sized_deallocation >= 201309 JEMALLOC_ALWAYS_INLINE void sizedDeleteImpl(void* ptr, std::size_t size) noexcept { if (unlikely(ptr == nullptr)) { return; } je_sdallocx_noflags(ptr, size); } void operator delete(void *ptr, std::size_t size) noexcept { sizedDeleteImpl(ptr, size); } void operator delete[](void *ptr, std::size_t size) noexcept { sizedDeleteImpl(ptr, size); } #endif // __cpp_sized_deallocation #if __cpp_aligned_new >= 201606 JEMALLOC_ALWAYS_INLINE void alignedSizedDeleteImpl(void* ptr, std::size_t size, std::align_val_t alignment) noexcept { if (config_debug) { assert(((size_t)alignment & ((size_t)alignment - 1)) == 0); } if (unlikely(ptr == nullptr)) { return; } je_sdallocx(ptr, size, MALLOCX_ALIGN(alignment)); } void operator delete(void* ptr, std::align_val_t) noexcept { je_free(ptr); } void operator delete[](void* ptr, std::align_val_t) noexcept { je_free(ptr); } void operator delete(void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { je_free(ptr); } void operator delete[](void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { je_free(ptr); } void operator delete(void* ptr, std::size_t size, std::align_val_t alignment) noexcept { alignedSizedDeleteImpl(ptr, size, alignment); } void operator delete[](void* ptr, std::size_t size, std::align_val_t alignment) noexcept { alignedSizedDeleteImpl(ptr, size, alignment); } #endif // __cpp_aligned_new redis-8.0.2/deps/jemalloc/src/large.c000066400000000000000000000221771501533116600174070ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/emap.h" #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/prof_recent.h" #include "jemalloc/internal/util.h" /******************************************************************************/ void * large_malloc(tsdn_t *tsdn, arena_t *arena, size_t usize, bool zero) { assert(usize == sz_s2u(usize)); return large_palloc(tsdn, arena, usize, CACHELINE, zero); } void * large_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero) { size_t ausize; edata_t *edata; UNUSED bool idump JEMALLOC_CC_SILENCE_INIT(false); assert(!tsdn_null(tsdn) || arena != NULL); ausize = sz_sa2u(usize, alignment); if (unlikely(ausize == 0 || ausize > SC_LARGE_MAXCLASS)) { return NULL; } if (likely(!tsdn_null(tsdn))) { arena = arena_choose_maybe_huge(tsdn_tsd(tsdn), arena, usize); } if (unlikely(arena == NULL) || (edata = arena_extent_alloc_large(tsdn, arena, usize, alignment, zero)) == NULL) { return NULL; } /* See comments in arena_bin_slabs_full_insert(). */ if (!arena_is_auto(arena)) { /* Insert edata into large. */ malloc_mutex_lock(tsdn, &arena->large_mtx); edata_list_active_append(&arena->large, edata); malloc_mutex_unlock(tsdn, &arena->large_mtx); } arena_decay_tick(tsdn, arena); return edata_addr_get(edata); } static bool large_ralloc_no_move_shrink(tsdn_t *tsdn, edata_t *edata, size_t usize) { arena_t *arena = arena_get_from_edata(edata); ehooks_t *ehooks = arena_get_ehooks(arena); size_t old_size = edata_size_get(edata); size_t old_usize = edata_usize_get(edata); assert(old_usize > usize); if (ehooks_split_will_fail(ehooks)) { return true; } bool deferred_work_generated = false; bool err = pa_shrink(tsdn, &arena->pa_shard, edata, old_size, usize + sz_large_pad, sz_size2index(usize), &deferred_work_generated); if (err) { return true; } if (deferred_work_generated) { arena_handle_deferred_work(tsdn, arena); } arena_extent_ralloc_large_shrink(tsdn, arena, edata, old_usize); return false; } static bool large_ralloc_no_move_expand(tsdn_t *tsdn, edata_t *edata, size_t usize, bool zero) { arena_t *arena = arena_get_from_edata(edata); size_t old_size = edata_size_get(edata); size_t old_usize = edata_usize_get(edata); size_t new_size = usize + sz_large_pad; szind_t szind = sz_size2index(usize); bool deferred_work_generated = false; bool err = pa_expand(tsdn, &arena->pa_shard, edata, old_size, new_size, szind, zero, &deferred_work_generated); if (deferred_work_generated) { arena_handle_deferred_work(tsdn, arena); } if (err) { return true; } if (zero) { if (opt_cache_oblivious) { assert(sz_large_pad == PAGE); /* * Zero the trailing bytes of the original allocation's * last page, since they are in an indeterminate state. * There will always be trailing bytes, because ptr's * offset from the beginning of the extent is a multiple * of CACHELINE in [0 .. PAGE). */ void *zbase = (void *) ((uintptr_t)edata_addr_get(edata) + old_usize); void *zpast = PAGE_ADDR2BASE((void *)((uintptr_t)zbase + PAGE)); size_t nzero = (uintptr_t)zpast - (uintptr_t)zbase; assert(nzero > 0); memset(zbase, 0, nzero); } } arena_extent_ralloc_large_expand(tsdn, arena, edata, old_usize); return false; } bool large_ralloc_no_move(tsdn_t *tsdn, edata_t *edata, size_t usize_min, size_t usize_max, bool zero) { size_t oldusize = edata_usize_get(edata); /* The following should have been caught by callers. */ assert(usize_min > 0 && usize_max <= SC_LARGE_MAXCLASS); /* Both allocation sizes must be large to avoid a move. */ assert(oldusize >= SC_LARGE_MINCLASS && usize_max >= SC_LARGE_MINCLASS); if (usize_max > oldusize) { /* Attempt to expand the allocation in-place. */ if (!large_ralloc_no_move_expand(tsdn, edata, usize_max, zero)) { arena_decay_tick(tsdn, arena_get_from_edata(edata)); return false; } /* Try again, this time with usize_min. */ if (usize_min < usize_max && usize_min > oldusize && large_ralloc_no_move_expand(tsdn, edata, usize_min, zero)) { arena_decay_tick(tsdn, arena_get_from_edata(edata)); return false; } } /* * Avoid moving the allocation if the existing extent size accommodates * the new size. */ if (oldusize >= usize_min && oldusize <= usize_max) { arena_decay_tick(tsdn, arena_get_from_edata(edata)); return false; } /* Attempt to shrink the allocation in-place. */ if (oldusize > usize_max) { if (!large_ralloc_no_move_shrink(tsdn, edata, usize_max)) { arena_decay_tick(tsdn, arena_get_from_edata(edata)); return false; } } return true; } static void * large_ralloc_move_helper(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, bool zero) { if (alignment <= CACHELINE) { return large_malloc(tsdn, arena, usize, zero); } return large_palloc(tsdn, arena, usize, alignment, zero); } void * large_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t usize, size_t alignment, bool zero, tcache_t *tcache, hook_ralloc_args_t *hook_args) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); size_t oldusize = edata_usize_get(edata); /* The following should have been caught by callers. */ assert(usize > 0 && usize <= SC_LARGE_MAXCLASS); /* Both allocation sizes must be large to avoid a move. */ assert(oldusize >= SC_LARGE_MINCLASS && usize >= SC_LARGE_MINCLASS); /* Try to avoid moving the allocation. */ if (!large_ralloc_no_move(tsdn, edata, usize, usize, zero)) { hook_invoke_expand(hook_args->is_realloc ? hook_expand_realloc : hook_expand_rallocx, ptr, oldusize, usize, (uintptr_t)ptr, hook_args->args); return edata_addr_get(edata); } /* * usize and old size are different enough that we need to use a * different size class. In that case, fall back to allocating new * space and copying. */ void *ret = large_ralloc_move_helper(tsdn, arena, usize, alignment, zero); if (ret == NULL) { return NULL; } hook_invoke_alloc(hook_args->is_realloc ? hook_alloc_realloc : hook_alloc_rallocx, ret, (uintptr_t)ret, hook_args->args); hook_invoke_dalloc(hook_args->is_realloc ? hook_dalloc_realloc : hook_dalloc_rallocx, ptr, hook_args->args); size_t copysize = (usize < oldusize) ? usize : oldusize; memcpy(ret, edata_addr_get(edata), copysize); isdalloct(tsdn, edata_addr_get(edata), oldusize, tcache, NULL, true); return ret; } /* * locked indicates whether the arena's large_mtx is currently held. */ static void large_dalloc_prep_impl(tsdn_t *tsdn, arena_t *arena, edata_t *edata, bool locked) { if (!locked) { /* See comments in arena_bin_slabs_full_insert(). */ if (!arena_is_auto(arena)) { malloc_mutex_lock(tsdn, &arena->large_mtx); edata_list_active_remove(&arena->large, edata); malloc_mutex_unlock(tsdn, &arena->large_mtx); } } else { /* Only hold the large_mtx if necessary. */ if (!arena_is_auto(arena)) { malloc_mutex_assert_owner(tsdn, &arena->large_mtx); edata_list_active_remove(&arena->large, edata); } } arena_extent_dalloc_large_prep(tsdn, arena, edata); } static void large_dalloc_finish_impl(tsdn_t *tsdn, arena_t *arena, edata_t *edata) { bool deferred_work_generated = false; pa_dalloc(tsdn, &arena->pa_shard, edata, &deferred_work_generated); if (deferred_work_generated) { arena_handle_deferred_work(tsdn, arena); } } void large_dalloc_prep_locked(tsdn_t *tsdn, edata_t *edata) { large_dalloc_prep_impl(tsdn, arena_get_from_edata(edata), edata, true); } void large_dalloc_finish(tsdn_t *tsdn, edata_t *edata) { large_dalloc_finish_impl(tsdn, arena_get_from_edata(edata), edata); } void large_dalloc(tsdn_t *tsdn, edata_t *edata) { arena_t *arena = arena_get_from_edata(edata); large_dalloc_prep_impl(tsdn, arena, edata, false); large_dalloc_finish_impl(tsdn, arena, edata); arena_decay_tick(tsdn, arena); } size_t large_salloc(tsdn_t *tsdn, const edata_t *edata) { return edata_usize_get(edata); } void large_prof_info_get(tsd_t *tsd, edata_t *edata, prof_info_t *prof_info, bool reset_recent) { assert(prof_info != NULL); prof_tctx_t *alloc_tctx = edata_prof_tctx_get(edata); prof_info->alloc_tctx = alloc_tctx; if ((uintptr_t)alloc_tctx > (uintptr_t)1U) { nstime_copy(&prof_info->alloc_time, edata_prof_alloc_time_get(edata)); prof_info->alloc_size = edata_prof_alloc_size_get(edata); if (reset_recent) { /* * Reset the pointer on the recent allocation record, * so that this allocation is recorded as released. */ prof_recent_alloc_reset(tsd, edata); } } } static void large_prof_tctx_set(edata_t *edata, prof_tctx_t *tctx) { edata_prof_tctx_set(edata, tctx); } void large_prof_tctx_reset(edata_t *edata) { large_prof_tctx_set(edata, (prof_tctx_t *)(uintptr_t)1U); } void large_prof_info_set(edata_t *edata, prof_tctx_t *tctx, size_t size) { nstime_t t; nstime_prof_init_update(&t); edata_prof_alloc_time_set(edata, &t); edata_prof_alloc_size_set(edata, size); edata_prof_recent_alloc_init(edata); large_prof_tctx_set(edata, tctx); } redis-8.0.2/deps/jemalloc/src/log.c000066400000000000000000000046751501533116600171010ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/log.h" char log_var_names[JEMALLOC_LOG_VAR_BUFSIZE]; atomic_b_t log_init_done = ATOMIC_INIT(false); /* * Returns true if we were able to pick out a segment. Fills in r_segment_end * with a pointer to the first character after the end of the string. */ static const char * log_var_extract_segment(const char* segment_begin) { const char *end; for (end = segment_begin; *end != '\0' && *end != '|'; end++) { } return end; } static bool log_var_matches_segment(const char *segment_begin, const char *segment_end, const char *log_var_begin, const char *log_var_end) { assert(segment_begin <= segment_end); assert(log_var_begin < log_var_end); ptrdiff_t segment_len = segment_end - segment_begin; ptrdiff_t log_var_len = log_var_end - log_var_begin; /* The special '.' segment matches everything. */ if (segment_len == 1 && *segment_begin == '.') { return true; } if (segment_len == log_var_len) { return strncmp(segment_begin, log_var_begin, segment_len) == 0; } else if (segment_len < log_var_len) { return strncmp(segment_begin, log_var_begin, segment_len) == 0 && log_var_begin[segment_len] == '.'; } else { return false; } } unsigned log_var_update_state(log_var_t *log_var) { const char *log_var_begin = log_var->name; const char *log_var_end = log_var->name + strlen(log_var->name); /* Pointer to one before the beginning of the current segment. */ const char *segment_begin = log_var_names; /* * If log_init done is false, we haven't parsed the malloc conf yet. To * avoid log-spew, we default to not displaying anything. */ if (!atomic_load_b(&log_init_done, ATOMIC_ACQUIRE)) { return LOG_INITIALIZED_NOT_ENABLED; } while (true) { const char *segment_end = log_var_extract_segment( segment_begin); assert(segment_end < log_var_names + JEMALLOC_LOG_VAR_BUFSIZE); if (log_var_matches_segment(segment_begin, segment_end, log_var_begin, log_var_end)) { atomic_store_u(&log_var->state, LOG_ENABLED, ATOMIC_RELAXED); return LOG_ENABLED; } if (*segment_end == '\0') { /* Hit the end of the segment string with no match. */ atomic_store_u(&log_var->state, LOG_INITIALIZED_NOT_ENABLED, ATOMIC_RELAXED); return LOG_INITIALIZED_NOT_ENABLED; } /* Otherwise, skip the delimiter and continue. */ segment_begin = segment_end + 1; } } redis-8.0.2/deps/jemalloc/src/malloc_io.c000066400000000000000000000356071501533116600202550ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/util.h" #ifdef assert # undef assert #endif #ifdef not_reached # undef not_reached #endif #ifdef not_implemented # undef not_implemented #endif #ifdef assert_not_implemented # undef assert_not_implemented #endif /* * Define simple versions of assertion macros that won't recurse in case * of assertion failures in malloc_*printf(). */ #define assert(e) do { \ if (config_debug && !(e)) { \ malloc_write(": Failed assertion\n"); \ abort(); \ } \ } while (0) #define not_reached() do { \ if (config_debug) { \ malloc_write(": Unreachable code reached\n"); \ abort(); \ } \ unreachable(); \ } while (0) #define not_implemented() do { \ if (config_debug) { \ malloc_write(": Not implemented\n"); \ abort(); \ } \ } while (0) #define assert_not_implemented(e) do { \ if (unlikely(config_debug && !(e))) { \ not_implemented(); \ } \ } while (0) /******************************************************************************/ /* Function prototypes for non-inline static functions. */ #define U2S_BUFSIZE ((1U << (LG_SIZEOF_INTMAX_T + 3)) + 1) static char *u2s(uintmax_t x, unsigned base, bool uppercase, char *s, size_t *slen_p); #define D2S_BUFSIZE (1 + U2S_BUFSIZE) static char *d2s(intmax_t x, char sign, char *s, size_t *slen_p); #define O2S_BUFSIZE (1 + U2S_BUFSIZE) static char *o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p); #define X2S_BUFSIZE (2 + U2S_BUFSIZE) static char *x2s(uintmax_t x, bool alt_form, bool uppercase, char *s, size_t *slen_p); /******************************************************************************/ /* malloc_message() setup. */ void wrtmessage(void *cbopaque, const char *s) { malloc_write_fd(STDERR_FILENO, s, strlen(s)); } JEMALLOC_EXPORT void (*je_malloc_message)(void *, const char *s); /* * Wrapper around malloc_message() that avoids the need for * je_malloc_message(...) throughout the code. */ void malloc_write(const char *s) { if (je_malloc_message != NULL) { je_malloc_message(NULL, s); } else { wrtmessage(NULL, s); } } /* * glibc provides a non-standard strerror_r() when _GNU_SOURCE is defined, so * provide a wrapper. */ int buferror(int err, char *buf, size_t buflen) { #ifdef _WIN32 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (LPSTR)buf, (DWORD)buflen, NULL); return 0; #elif defined(JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE) && defined(_GNU_SOURCE) char *b = strerror_r(err, buf, buflen); if (b != buf) { strncpy(buf, b, buflen); buf[buflen-1] = '\0'; } return 0; #else return strerror_r(err, buf, buflen); #endif } uintmax_t malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base) { uintmax_t ret, digit; unsigned b; bool neg; const char *p, *ns; p = nptr; if (base < 0 || base == 1 || base > 36) { ns = p; set_errno(EINVAL); ret = UINTMAX_MAX; goto label_return; } b = base; /* Swallow leading whitespace and get sign, if any. */ neg = false; while (true) { switch (*p) { case '\t': case '\n': case '\v': case '\f': case '\r': case ' ': p++; break; case '-': neg = true; JEMALLOC_FALLTHROUGH; case '+': p++; JEMALLOC_FALLTHROUGH; default: goto label_prefix; } } /* Get prefix, if any. */ label_prefix: /* * Note where the first non-whitespace/sign character is so that it is * possible to tell whether any digits are consumed (e.g., " 0" vs. * " -x"). */ ns = p; if (*p == '0') { switch (p[1]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': if (b == 0) { b = 8; } if (b == 8) { p++; } break; case 'X': case 'x': switch (p[2]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': if (b == 0) { b = 16; } if (b == 16) { p += 2; } break; default: break; } break; default: p++; ret = 0; goto label_return; } } if (b == 0) { b = 10; } /* Convert. */ ret = 0; while ((*p >= '0' && *p <= '9' && (digit = *p - '0') < b) || (*p >= 'A' && *p <= 'Z' && (digit = 10 + *p - 'A') < b) || (*p >= 'a' && *p <= 'z' && (digit = 10 + *p - 'a') < b)) { uintmax_t pret = ret; ret *= b; ret += digit; if (ret < pret) { /* Overflow. */ set_errno(ERANGE); ret = UINTMAX_MAX; goto label_return; } p++; } if (neg) { ret = (uintmax_t)(-((intmax_t)ret)); } if (p == ns) { /* No conversion performed. */ set_errno(EINVAL); ret = UINTMAX_MAX; goto label_return; } label_return: if (endptr != NULL) { if (p == ns) { /* No characters were converted. */ *endptr = (char *)nptr; } else { *endptr = (char *)p; } } return ret; } static char * u2s(uintmax_t x, unsigned base, bool uppercase, char *s, size_t *slen_p) { unsigned i; i = U2S_BUFSIZE - 1; s[i] = '\0'; switch (base) { case 10: do { i--; s[i] = "0123456789"[x % (uint64_t)10]; x /= (uint64_t)10; } while (x > 0); break; case 16: { const char *digits = (uppercase) ? "0123456789ABCDEF" : "0123456789abcdef"; do { i--; s[i] = digits[x & 0xf]; x >>= 4; } while (x > 0); break; } default: { const char *digits = (uppercase) ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" : "0123456789abcdefghijklmnopqrstuvwxyz"; assert(base >= 2 && base <= 36); do { i--; s[i] = digits[x % (uint64_t)base]; x /= (uint64_t)base; } while (x > 0); }} *slen_p = U2S_BUFSIZE - 1 - i; return &s[i]; } static char * d2s(intmax_t x, char sign, char *s, size_t *slen_p) { bool neg; if ((neg = (x < 0))) { x = -x; } s = u2s(x, 10, false, s, slen_p); if (neg) { sign = '-'; } switch (sign) { case '-': if (!neg) { break; } JEMALLOC_FALLTHROUGH; case ' ': case '+': s--; (*slen_p)++; *s = sign; break; default: not_reached(); } return s; } static char * o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p) { s = u2s(x, 8, false, s, slen_p); if (alt_form && *s != '0') { s--; (*slen_p)++; *s = '0'; } return s; } static char * x2s(uintmax_t x, bool alt_form, bool uppercase, char *s, size_t *slen_p) { s = u2s(x, 16, uppercase, s, slen_p); if (alt_form) { s -= 2; (*slen_p) += 2; memcpy(s, uppercase ? "0X" : "0x", 2); } return s; } JEMALLOC_COLD size_t malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap) { size_t i; const char *f; #define APPEND_C(c) do { \ if (i < size) { \ str[i] = (c); \ } \ i++; \ } while (0) #define APPEND_S(s, slen) do { \ if (i < size) { \ size_t cpylen = (slen <= size - i) ? slen : size - i; \ memcpy(&str[i], s, cpylen); \ } \ i += slen; \ } while (0) #define APPEND_PADDED_S(s, slen, width, left_justify) do { \ /* Left padding. */ \ size_t pad_len = (width == -1) ? 0 : ((slen < (size_t)width) ? \ (size_t)width - slen : 0); \ if (!left_justify && pad_len != 0) { \ size_t j; \ for (j = 0; j < pad_len; j++) { \ if (pad_zero) { \ APPEND_C('0'); \ } else { \ APPEND_C(' '); \ } \ } \ } \ /* Value. */ \ APPEND_S(s, slen); \ /* Right padding. */ \ if (left_justify && pad_len != 0) { \ size_t j; \ for (j = 0; j < pad_len; j++) { \ APPEND_C(' '); \ } \ } \ } while (0) #define GET_ARG_NUMERIC(val, len) do { \ switch ((unsigned char)len) { \ case '?': \ val = va_arg(ap, int); \ break; \ case '?' | 0x80: \ val = va_arg(ap, unsigned int); \ break; \ case 'l': \ val = va_arg(ap, long); \ break; \ case 'l' | 0x80: \ val = va_arg(ap, unsigned long); \ break; \ case 'q': \ val = va_arg(ap, long long); \ break; \ case 'q' | 0x80: \ val = va_arg(ap, unsigned long long); \ break; \ case 'j': \ val = va_arg(ap, intmax_t); \ break; \ case 'j' | 0x80: \ val = va_arg(ap, uintmax_t); \ break; \ case 't': \ val = va_arg(ap, ptrdiff_t); \ break; \ case 'z': \ val = va_arg(ap, ssize_t); \ break; \ case 'z' | 0x80: \ val = va_arg(ap, size_t); \ break; \ case 'p': /* Synthetic; used for %p. */ \ val = va_arg(ap, uintptr_t); \ break; \ default: \ not_reached(); \ val = 0; \ } \ } while (0) i = 0; f = format; while (true) { switch (*f) { case '\0': goto label_out; case '%': { bool alt_form = false; bool left_justify = false; bool plus_space = false; bool plus_plus = false; int prec = -1; int width = -1; unsigned char len = '?'; char *s; size_t slen; bool first_width_digit = true; bool pad_zero = false; f++; /* Flags. */ while (true) { switch (*f) { case '#': assert(!alt_form); alt_form = true; break; case '-': assert(!left_justify); left_justify = true; break; case ' ': assert(!plus_space); plus_space = true; break; case '+': assert(!plus_plus); plus_plus = true; break; default: goto label_width; } f++; } /* Width. */ label_width: switch (*f) { case '*': width = va_arg(ap, int); f++; if (width < 0) { left_justify = true; width = -width; } break; case '0': if (first_width_digit) { pad_zero = true; } JEMALLOC_FALLTHROUGH; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { uintmax_t uwidth; set_errno(0); uwidth = malloc_strtoumax(f, (char **)&f, 10); assert(uwidth != UINTMAX_MAX || get_errno() != ERANGE); width = (int)uwidth; first_width_digit = false; break; } default: break; } /* Width/precision separator. */ if (*f == '.') { f++; } else { goto label_length; } /* Precision. */ switch (*f) { case '*': prec = va_arg(ap, int); f++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { uintmax_t uprec; set_errno(0); uprec = malloc_strtoumax(f, (char **)&f, 10); assert(uprec != UINTMAX_MAX || get_errno() != ERANGE); prec = (int)uprec; break; } default: break; } /* Length. */ label_length: switch (*f) { case 'l': f++; if (*f == 'l') { len = 'q'; f++; } else { len = 'l'; } break; case 'q': case 'j': case 't': case 'z': len = *f; f++; break; default: break; } /* Conversion specifier. */ switch (*f) { case '%': /* %% */ APPEND_C(*f); f++; break; case 'd': case 'i': { intmax_t val JEMALLOC_CC_SILENCE_INIT(0); char buf[D2S_BUFSIZE]; /* * Outputting negative, zero-padded numbers * would require a nontrivial rework of the * interaction between the width and padding * (since 0 padding goes between the '-' and the * number, while ' ' padding goes either before * the - or after the number. Since we * currently don't ever need 0-padded negative * numbers, just don't bother supporting it. */ assert(!pad_zero); GET_ARG_NUMERIC(val, len); s = d2s(val, (plus_plus ? '+' : (plus_space ? ' ' : '-')), buf, &slen); APPEND_PADDED_S(s, slen, width, left_justify); f++; break; } case 'o': { uintmax_t val JEMALLOC_CC_SILENCE_INIT(0); char buf[O2S_BUFSIZE]; GET_ARG_NUMERIC(val, len | 0x80); s = o2s(val, alt_form, buf, &slen); APPEND_PADDED_S(s, slen, width, left_justify); f++; break; } case 'u': { uintmax_t val JEMALLOC_CC_SILENCE_INIT(0); char buf[U2S_BUFSIZE]; GET_ARG_NUMERIC(val, len | 0x80); s = u2s(val, 10, false, buf, &slen); APPEND_PADDED_S(s, slen, width, left_justify); f++; break; } case 'x': case 'X': { uintmax_t val JEMALLOC_CC_SILENCE_INIT(0); char buf[X2S_BUFSIZE]; GET_ARG_NUMERIC(val, len | 0x80); s = x2s(val, alt_form, *f == 'X', buf, &slen); APPEND_PADDED_S(s, slen, width, left_justify); f++; break; } case 'c': { unsigned char val; char buf[2]; assert(len == '?' || len == 'l'); assert_not_implemented(len != 'l'); val = va_arg(ap, int); buf[0] = val; buf[1] = '\0'; APPEND_PADDED_S(buf, 1, width, left_justify); f++; break; } case 's': assert(len == '?' || len == 'l'); assert_not_implemented(len != 'l'); s = va_arg(ap, char *); slen = (prec < 0) ? strlen(s) : (size_t)prec; APPEND_PADDED_S(s, slen, width, left_justify); f++; break; case 'p': { uintmax_t val; char buf[X2S_BUFSIZE]; GET_ARG_NUMERIC(val, 'p'); s = x2s(val, true, false, buf, &slen); APPEND_PADDED_S(s, slen, width, left_justify); f++; break; } default: not_reached(); } break; } default: { APPEND_C(*f); f++; break; }} } label_out: if (i < size) { str[i] = '\0'; } else { str[size - 1] = '\0'; } #undef APPEND_C #undef APPEND_S #undef APPEND_PADDED_S #undef GET_ARG_NUMERIC return i; } JEMALLOC_FORMAT_PRINTF(3, 4) size_t malloc_snprintf(char *str, size_t size, const char *format, ...) { size_t ret; va_list ap; va_start(ap, format); ret = malloc_vsnprintf(str, size, format, ap); va_end(ap); return ret; } void malloc_vcprintf(write_cb_t *write_cb, void *cbopaque, const char *format, va_list ap) { char buf[MALLOC_PRINTF_BUFSIZE]; if (write_cb == NULL) { /* * The caller did not provide an alternate write_cb callback * function, so use the default one. malloc_write() is an * inline function, so use malloc_message() directly here. */ write_cb = (je_malloc_message != NULL) ? je_malloc_message : wrtmessage; } malloc_vsnprintf(buf, sizeof(buf), format, ap); write_cb(cbopaque, buf); } /* * Print to a callback function in such a way as to (hopefully) avoid memory * allocation. */ JEMALLOC_FORMAT_PRINTF(3, 4) void malloc_cprintf(write_cb_t *write_cb, void *cbopaque, const char *format, ...) { va_list ap; va_start(ap, format); malloc_vcprintf(write_cb, cbopaque, format, ap); va_end(ap); } /* Print to stderr in such a way as to avoid memory allocation. */ JEMALLOC_FORMAT_PRINTF(1, 2) void malloc_printf(const char *format, ...) { va_list ap; va_start(ap, format); malloc_vcprintf(NULL, NULL, format, ap); va_end(ap); } /* * Restore normal assertion macros, in order to make it possible to compile all * C files as a single concatenation. */ #undef assert #undef not_reached #undef not_implemented #undef assert_not_implemented #include "jemalloc/internal/assert.h" redis-8.0.2/deps/jemalloc/src/mutex.c000066400000000000000000000133431501533116600174520ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/spin.h" #ifndef _CRT_SPINCOUNT #define _CRT_SPINCOUNT 4000 #endif /* * Based on benchmark results, a fixed spin with this amount of retries works * well for our critical sections. */ int64_t opt_mutex_max_spin = 600; /******************************************************************************/ /* Data. */ #ifdef JEMALLOC_LAZY_LOCK bool isthreaded = false; #endif #ifdef JEMALLOC_MUTEX_INIT_CB static bool postpone_init = true; static malloc_mutex_t *postponed_mutexes = NULL; #endif /******************************************************************************/ /* * We intercept pthread_create() calls in order to toggle isthreaded if the * process goes multi-threaded. */ #if defined(JEMALLOC_LAZY_LOCK) && !defined(_WIN32) JEMALLOC_EXPORT int pthread_create(pthread_t *__restrict thread, const pthread_attr_t *__restrict attr, void *(*start_routine)(void *), void *__restrict arg) { return pthread_create_wrapper(thread, attr, start_routine, arg); } #endif /******************************************************************************/ #ifdef JEMALLOC_MUTEX_INIT_CB JEMALLOC_EXPORT int _pthread_mutex_init_calloc_cb(pthread_mutex_t *mutex, void *(calloc_cb)(size_t, size_t)); #endif void malloc_mutex_lock_slow(malloc_mutex_t *mutex) { mutex_prof_data_t *data = &mutex->prof_data; nstime_t before; if (ncpus == 1) { goto label_spin_done; } int cnt = 0; do { spin_cpu_spinwait(); if (!atomic_load_b(&mutex->locked, ATOMIC_RELAXED) && !malloc_mutex_trylock_final(mutex)) { data->n_spin_acquired++; return; } } while (cnt++ < opt_mutex_max_spin || opt_mutex_max_spin == -1); if (!config_stats) { /* Only spin is useful when stats is off. */ malloc_mutex_lock_final(mutex); return; } label_spin_done: nstime_init_update(&before); /* Copy before to after to avoid clock skews. */ nstime_t after; nstime_copy(&after, &before); uint32_t n_thds = atomic_fetch_add_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED) + 1; /* One last try as above two calls may take quite some cycles. */ if (!malloc_mutex_trylock_final(mutex)) { atomic_fetch_sub_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED); data->n_spin_acquired++; return; } /* True slow path. */ malloc_mutex_lock_final(mutex); /* Update more slow-path only counters. */ atomic_fetch_sub_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED); nstime_update(&after); nstime_t delta; nstime_copy(&delta, &after); nstime_subtract(&delta, &before); data->n_wait_times++; nstime_add(&data->tot_wait_time, &delta); if (nstime_compare(&data->max_wait_time, &delta) < 0) { nstime_copy(&data->max_wait_time, &delta); } if (n_thds > data->max_n_thds) { data->max_n_thds = n_thds; } } static void mutex_prof_data_init(mutex_prof_data_t *data) { memset(data, 0, sizeof(mutex_prof_data_t)); nstime_init_zero(&data->max_wait_time); nstime_init_zero(&data->tot_wait_time); data->prev_owner = NULL; } void malloc_mutex_prof_data_reset(tsdn_t *tsdn, malloc_mutex_t *mutex) { malloc_mutex_assert_owner(tsdn, mutex); mutex_prof_data_init(&mutex->prof_data); } static int mutex_addr_comp(const witness_t *witness1, void *mutex1, const witness_t *witness2, void *mutex2) { assert(mutex1 != NULL); assert(mutex2 != NULL); uintptr_t mu1int = (uintptr_t)mutex1; uintptr_t mu2int = (uintptr_t)mutex2; if (mu1int < mu2int) { return -1; } else if (mu1int == mu2int) { return 0; } else { return 1; } } bool malloc_mutex_init(malloc_mutex_t *mutex, const char *name, witness_rank_t rank, malloc_mutex_lock_order_t lock_order) { mutex_prof_data_init(&mutex->prof_data); #ifdef _WIN32 # if _WIN32_WINNT >= 0x0600 InitializeSRWLock(&mutex->lock); # else if (!InitializeCriticalSectionAndSpinCount(&mutex->lock, _CRT_SPINCOUNT)) { return true; } # endif #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) mutex->lock = OS_UNFAIR_LOCK_INIT; #elif (defined(JEMALLOC_MUTEX_INIT_CB)) if (postpone_init) { mutex->postponed_next = postponed_mutexes; postponed_mutexes = mutex; } else { if (_pthread_mutex_init_calloc_cb(&mutex->lock, bootstrap_calloc) != 0) { return true; } } #else pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr) != 0) { return true; } pthread_mutexattr_settype(&attr, MALLOC_MUTEX_TYPE); if (pthread_mutex_init(&mutex->lock, &attr) != 0) { pthread_mutexattr_destroy(&attr); return true; } pthread_mutexattr_destroy(&attr); #endif if (config_debug) { mutex->lock_order = lock_order; if (lock_order == malloc_mutex_address_ordered) { witness_init(&mutex->witness, name, rank, mutex_addr_comp, mutex); } else { witness_init(&mutex->witness, name, rank, NULL, NULL); } } return false; } void malloc_mutex_prefork(tsdn_t *tsdn, malloc_mutex_t *mutex) { malloc_mutex_lock(tsdn, mutex); } void malloc_mutex_postfork_parent(tsdn_t *tsdn, malloc_mutex_t *mutex) { malloc_mutex_unlock(tsdn, mutex); } void malloc_mutex_postfork_child(tsdn_t *tsdn, malloc_mutex_t *mutex) { #ifdef JEMALLOC_MUTEX_INIT_CB malloc_mutex_unlock(tsdn, mutex); #else if (malloc_mutex_init(mutex, mutex->witness.name, mutex->witness.rank, mutex->lock_order)) { malloc_printf(": Error re-initializing mutex in " "child\n"); if (opt_abort) { abort(); } } #endif } bool malloc_mutex_boot(void) { #ifdef JEMALLOC_MUTEX_INIT_CB postpone_init = false; while (postponed_mutexes != NULL) { if (_pthread_mutex_init_calloc_cb(&postponed_mutexes->lock, bootstrap_calloc) != 0) { return true; } postponed_mutexes = postponed_mutexes->postponed_next; } #endif return false; } redis-8.0.2/deps/jemalloc/src/nstime.c000066400000000000000000000144541501533116600176130ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/nstime.h" #include "jemalloc/internal/assert.h" #define BILLION UINT64_C(1000000000) #define MILLION UINT64_C(1000000) static void nstime_set_initialized(nstime_t *time) { #ifdef JEMALLOC_DEBUG time->magic = NSTIME_MAGIC; #endif } static void nstime_assert_initialized(const nstime_t *time) { #ifdef JEMALLOC_DEBUG /* * Some parts (e.g. stats) rely on memset to zero initialize. Treat * these as valid initialization. */ assert(time->magic == NSTIME_MAGIC || (time->magic == 0 && time->ns == 0)); #endif } static void nstime_pair_assert_initialized(const nstime_t *t1, const nstime_t *t2) { nstime_assert_initialized(t1); nstime_assert_initialized(t2); } static void nstime_initialize_operand(nstime_t *time) { /* * Operations like nstime_add may have the initial operand being zero * initialized (covered by the assert below). Full-initialize needed * before changing it to non-zero. */ nstime_assert_initialized(time); nstime_set_initialized(time); } void nstime_init(nstime_t *time, uint64_t ns) { nstime_set_initialized(time); time->ns = ns; } void nstime_init2(nstime_t *time, uint64_t sec, uint64_t nsec) { nstime_set_initialized(time); time->ns = sec * BILLION + nsec; } uint64_t nstime_ns(const nstime_t *time) { nstime_assert_initialized(time); return time->ns; } uint64_t nstime_msec(const nstime_t *time) { nstime_assert_initialized(time); return time->ns / MILLION; } uint64_t nstime_sec(const nstime_t *time) { nstime_assert_initialized(time); return time->ns / BILLION; } uint64_t nstime_nsec(const nstime_t *time) { nstime_assert_initialized(time); return time->ns % BILLION; } void nstime_copy(nstime_t *time, const nstime_t *source) { /* Source is required to be initialized. */ nstime_assert_initialized(source); *time = *source; nstime_assert_initialized(time); } int nstime_compare(const nstime_t *a, const nstime_t *b) { nstime_pair_assert_initialized(a, b); return (a->ns > b->ns) - (a->ns < b->ns); } void nstime_add(nstime_t *time, const nstime_t *addend) { nstime_pair_assert_initialized(time, addend); assert(UINT64_MAX - time->ns >= addend->ns); nstime_initialize_operand(time); time->ns += addend->ns; } void nstime_iadd(nstime_t *time, uint64_t addend) { nstime_assert_initialized(time); assert(UINT64_MAX - time->ns >= addend); nstime_initialize_operand(time); time->ns += addend; } void nstime_subtract(nstime_t *time, const nstime_t *subtrahend) { nstime_pair_assert_initialized(time, subtrahend); assert(nstime_compare(time, subtrahend) >= 0); /* No initialize operand -- subtraction must be initialized. */ time->ns -= subtrahend->ns; } void nstime_isubtract(nstime_t *time, uint64_t subtrahend) { nstime_assert_initialized(time); assert(time->ns >= subtrahend); /* No initialize operand -- subtraction must be initialized. */ time->ns -= subtrahend; } void nstime_imultiply(nstime_t *time, uint64_t multiplier) { nstime_assert_initialized(time); assert((((time->ns | multiplier) & (UINT64_MAX << (sizeof(uint64_t) << 2))) == 0) || ((time->ns * multiplier) / multiplier == time->ns)); nstime_initialize_operand(time); time->ns *= multiplier; } void nstime_idivide(nstime_t *time, uint64_t divisor) { nstime_assert_initialized(time); assert(divisor != 0); nstime_initialize_operand(time); time->ns /= divisor; } uint64_t nstime_divide(const nstime_t *time, const nstime_t *divisor) { nstime_pair_assert_initialized(time, divisor); assert(divisor->ns != 0); /* No initialize operand -- *time itself remains unchanged. */ return time->ns / divisor->ns; } /* Returns time since *past, w/o updating *past. */ uint64_t nstime_ns_since(const nstime_t *past) { nstime_assert_initialized(past); nstime_t now; nstime_copy(&now, past); nstime_update(&now); assert(nstime_compare(&now, past) >= 0); return now.ns - past->ns; } #ifdef _WIN32 # define NSTIME_MONOTONIC true static void nstime_get(nstime_t *time) { FILETIME ft; uint64_t ticks_100ns; GetSystemTimeAsFileTime(&ft); ticks_100ns = (((uint64_t)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; nstime_init(time, ticks_100ns * 100); } #elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE) # define NSTIME_MONOTONIC true static void nstime_get(nstime_t *time) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); nstime_init2(time, ts.tv_sec, ts.tv_nsec); } #elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC) # define NSTIME_MONOTONIC true static void nstime_get(nstime_t *time) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); nstime_init2(time, ts.tv_sec, ts.tv_nsec); } #elif defined(JEMALLOC_HAVE_MACH_ABSOLUTE_TIME) # define NSTIME_MONOTONIC true static void nstime_get(nstime_t *time) { nstime_init(time, mach_absolute_time()); } #else # define NSTIME_MONOTONIC false static void nstime_get(nstime_t *time) { struct timeval tv; gettimeofday(&tv, NULL); nstime_init2(time, tv.tv_sec, tv.tv_usec * 1000); } #endif static bool nstime_monotonic_impl(void) { return NSTIME_MONOTONIC; #undef NSTIME_MONOTONIC } nstime_monotonic_t *JET_MUTABLE nstime_monotonic = nstime_monotonic_impl; prof_time_res_t opt_prof_time_res = prof_time_res_default; const char *prof_time_res_mode_names[] = { "default", "high", }; static void nstime_get_realtime(nstime_t *time) { #if defined(JEMALLOC_HAVE_CLOCK_REALTIME) && !defined(_WIN32) struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); nstime_init2(time, ts.tv_sec, ts.tv_nsec); #else unreachable(); #endif } static void nstime_prof_update_impl(nstime_t *time) { nstime_t old_time; nstime_copy(&old_time, time); if (opt_prof_time_res == prof_time_res_high) { nstime_get_realtime(time); } else { nstime_get(time); } } nstime_prof_update_t *JET_MUTABLE nstime_prof_update = nstime_prof_update_impl; static void nstime_update_impl(nstime_t *time) { nstime_t old_time; nstime_copy(&old_time, time); nstime_get(time); /* Handle non-monotonic clocks. */ if (unlikely(nstime_compare(&old_time, time) > 0)) { nstime_copy(time, &old_time); } } nstime_update_t *JET_MUTABLE nstime_update = nstime_update_impl; void nstime_init_update(nstime_t *time) { nstime_init_zero(time); nstime_update(time); } void nstime_prof_init_update(nstime_t *time) { nstime_init_zero(time); nstime_prof_update(time); } redis-8.0.2/deps/jemalloc/src/pa.c000066400000000000000000000172131501533116600167100ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/hpa.h" static void pa_nactive_add(pa_shard_t *shard, size_t add_pages) { atomic_fetch_add_zu(&shard->nactive, add_pages, ATOMIC_RELAXED); } static void pa_nactive_sub(pa_shard_t *shard, size_t sub_pages) { assert(atomic_load_zu(&shard->nactive, ATOMIC_RELAXED) >= sub_pages); atomic_fetch_sub_zu(&shard->nactive, sub_pages, ATOMIC_RELAXED); } bool pa_central_init(pa_central_t *central, base_t *base, bool hpa, hpa_hooks_t *hpa_hooks) { bool err; if (hpa) { err = hpa_central_init(¢ral->hpa, base, hpa_hooks); if (err) { return true; } } return false; } bool pa_shard_init(tsdn_t *tsdn, pa_shard_t *shard, pa_central_t *central, emap_t *emap, base_t *base, unsigned ind, pa_shard_stats_t *stats, malloc_mutex_t *stats_mtx, nstime_t *cur_time, size_t pac_oversize_threshold, ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms) { /* This will change eventually, but for now it should hold. */ assert(base_ind_get(base) == ind); if (edata_cache_init(&shard->edata_cache, base)) { return true; } if (pac_init(tsdn, &shard->pac, base, emap, &shard->edata_cache, cur_time, pac_oversize_threshold, dirty_decay_ms, muzzy_decay_ms, &stats->pac_stats, stats_mtx)) { return true; } shard->ind = ind; shard->ever_used_hpa = false; atomic_store_b(&shard->use_hpa, false, ATOMIC_RELAXED); atomic_store_zu(&shard->nactive, 0, ATOMIC_RELAXED); shard->stats_mtx = stats_mtx; shard->stats = stats; memset(shard->stats, 0, sizeof(*shard->stats)); shard->central = central; shard->emap = emap; shard->base = base; return false; } bool pa_shard_enable_hpa(tsdn_t *tsdn, pa_shard_t *shard, const hpa_shard_opts_t *hpa_opts, const sec_opts_t *hpa_sec_opts) { if (hpa_shard_init(&shard->hpa_shard, &shard->central->hpa, shard->emap, shard->base, &shard->edata_cache, shard->ind, hpa_opts)) { return true; } if (sec_init(tsdn, &shard->hpa_sec, shard->base, &shard->hpa_shard.pai, hpa_sec_opts)) { return true; } shard->ever_used_hpa = true; atomic_store_b(&shard->use_hpa, true, ATOMIC_RELAXED); return false; } void pa_shard_disable_hpa(tsdn_t *tsdn, pa_shard_t *shard) { atomic_store_b(&shard->use_hpa, false, ATOMIC_RELAXED); if (shard->ever_used_hpa) { sec_disable(tsdn, &shard->hpa_sec); hpa_shard_disable(tsdn, &shard->hpa_shard); } } void pa_shard_reset(tsdn_t *tsdn, pa_shard_t *shard) { atomic_store_zu(&shard->nactive, 0, ATOMIC_RELAXED); if (shard->ever_used_hpa) { sec_flush(tsdn, &shard->hpa_sec); } } static bool pa_shard_uses_hpa(pa_shard_t *shard) { return atomic_load_b(&shard->use_hpa, ATOMIC_RELAXED); } void pa_shard_destroy(tsdn_t *tsdn, pa_shard_t *shard) { pac_destroy(tsdn, &shard->pac); if (shard->ever_used_hpa) { sec_flush(tsdn, &shard->hpa_sec); hpa_shard_disable(tsdn, &shard->hpa_shard); } } static pai_t * pa_get_pai(pa_shard_t *shard, edata_t *edata) { return (edata_pai_get(edata) == EXTENT_PAI_PAC ? &shard->pac.pai : &shard->hpa_sec.pai); } edata_t * pa_alloc(tsdn_t *tsdn, pa_shard_t *shard, size_t size, size_t alignment, bool slab, szind_t szind, bool zero, bool guarded, bool *deferred_work_generated) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); assert(!guarded || alignment <= PAGE); edata_t *edata = NULL; if (!guarded && pa_shard_uses_hpa(shard)) { edata = pai_alloc(tsdn, &shard->hpa_sec.pai, size, alignment, zero, /* guarded */ false, slab, deferred_work_generated); } /* * Fall back to the PAC if the HPA is off or couldn't serve the given * allocation request. */ if (edata == NULL) { edata = pai_alloc(tsdn, &shard->pac.pai, size, alignment, zero, guarded, slab, deferred_work_generated); } if (edata != NULL) { assert(edata_size_get(edata) == size); pa_nactive_add(shard, size >> LG_PAGE); emap_remap(tsdn, shard->emap, edata, szind, slab); edata_szind_set(edata, szind); edata_slab_set(edata, slab); if (slab && (size > 2 * PAGE)) { emap_register_interior(tsdn, shard->emap, edata, szind); } assert(edata_arena_ind_get(edata) == shard->ind); } return edata; } bool pa_expand(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, size_t old_size, size_t new_size, szind_t szind, bool zero, bool *deferred_work_generated) { assert(new_size > old_size); assert(edata_size_get(edata) == old_size); assert((new_size & PAGE_MASK) == 0); if (edata_guarded_get(edata)) { return true; } size_t expand_amount = new_size - old_size; pai_t *pai = pa_get_pai(shard, edata); bool error = pai_expand(tsdn, pai, edata, old_size, new_size, zero, deferred_work_generated); if (error) { return true; } pa_nactive_add(shard, expand_amount >> LG_PAGE); edata_szind_set(edata, szind); emap_remap(tsdn, shard->emap, edata, szind, /* slab */ false); return false; } bool pa_shrink(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, size_t old_size, size_t new_size, szind_t szind, bool *deferred_work_generated) { assert(new_size < old_size); assert(edata_size_get(edata) == old_size); assert((new_size & PAGE_MASK) == 0); if (edata_guarded_get(edata)) { return true; } size_t shrink_amount = old_size - new_size; pai_t *pai = pa_get_pai(shard, edata); bool error = pai_shrink(tsdn, pai, edata, old_size, new_size, deferred_work_generated); if (error) { return true; } pa_nactive_sub(shard, shrink_amount >> LG_PAGE); edata_szind_set(edata, szind); emap_remap(tsdn, shard->emap, edata, szind, /* slab */ false); return false; } void pa_dalloc(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, bool *deferred_work_generated) { emap_remap(tsdn, shard->emap, edata, SC_NSIZES, /* slab */ false); if (edata_slab_get(edata)) { emap_deregister_interior(tsdn, shard->emap, edata); /* * The slab state of the extent isn't cleared. It may be used * by the pai implementation, e.g. to make caching decisions. */ } edata_addr_set(edata, edata_base_get(edata)); edata_szind_set(edata, SC_NSIZES); pa_nactive_sub(shard, edata_size_get(edata) >> LG_PAGE); pai_t *pai = pa_get_pai(shard, edata); pai_dalloc(tsdn, pai, edata, deferred_work_generated); } bool pa_shard_retain_grow_limit_get_set(tsdn_t *tsdn, pa_shard_t *shard, size_t *old_limit, size_t *new_limit) { return pac_retain_grow_limit_get_set(tsdn, &shard->pac, old_limit, new_limit); } bool pa_decay_ms_set(tsdn_t *tsdn, pa_shard_t *shard, extent_state_t state, ssize_t decay_ms, pac_purge_eagerness_t eagerness) { return pac_decay_ms_set(tsdn, &shard->pac, state, decay_ms, eagerness); } ssize_t pa_decay_ms_get(pa_shard_t *shard, extent_state_t state) { return pac_decay_ms_get(&shard->pac, state); } void pa_shard_set_deferral_allowed(tsdn_t *tsdn, pa_shard_t *shard, bool deferral_allowed) { if (pa_shard_uses_hpa(shard)) { hpa_shard_set_deferral_allowed(tsdn, &shard->hpa_shard, deferral_allowed); } } void pa_shard_do_deferred_work(tsdn_t *tsdn, pa_shard_t *shard) { if (pa_shard_uses_hpa(shard)) { hpa_shard_do_deferred_work(tsdn, &shard->hpa_shard); } } /* * Get time until next deferred work ought to happen. If there are multiple * things that have been deferred, this function calculates the time until * the soonest of those things. */ uint64_t pa_shard_time_until_deferred_work(tsdn_t *tsdn, pa_shard_t *shard) { uint64_t time = pai_time_until_deferred_work(tsdn, &shard->pac.pai); if (time == BACKGROUND_THREAD_DEFERRED_MIN) { return time; } if (pa_shard_uses_hpa(shard)) { uint64_t hpa = pai_time_until_deferred_work(tsdn, &shard->hpa_shard.pai); if (hpa < time) { time = hpa; } } return time; } redis-8.0.2/deps/jemalloc/src/pa_extra.c000066400000000000000000000160571501533116600201200ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" /* * This file is logically part of the PA module. While pa.c contains the core * allocator functionality, this file contains boring integration functionality; * things like the pre- and post- fork handlers, and stats merging for CTL * refreshes. */ void pa_shard_prefork0(tsdn_t *tsdn, pa_shard_t *shard) { malloc_mutex_prefork(tsdn, &shard->pac.decay_dirty.mtx); malloc_mutex_prefork(tsdn, &shard->pac.decay_muzzy.mtx); } void pa_shard_prefork2(tsdn_t *tsdn, pa_shard_t *shard) { if (shard->ever_used_hpa) { sec_prefork2(tsdn, &shard->hpa_sec); } } void pa_shard_prefork3(tsdn_t *tsdn, pa_shard_t *shard) { malloc_mutex_prefork(tsdn, &shard->pac.grow_mtx); if (shard->ever_used_hpa) { hpa_shard_prefork3(tsdn, &shard->hpa_shard); } } void pa_shard_prefork4(tsdn_t *tsdn, pa_shard_t *shard) { ecache_prefork(tsdn, &shard->pac.ecache_dirty); ecache_prefork(tsdn, &shard->pac.ecache_muzzy); ecache_prefork(tsdn, &shard->pac.ecache_retained); if (shard->ever_used_hpa) { hpa_shard_prefork4(tsdn, &shard->hpa_shard); } } void pa_shard_prefork5(tsdn_t *tsdn, pa_shard_t *shard) { edata_cache_prefork(tsdn, &shard->edata_cache); } void pa_shard_postfork_parent(tsdn_t *tsdn, pa_shard_t *shard) { edata_cache_postfork_parent(tsdn, &shard->edata_cache); ecache_postfork_parent(tsdn, &shard->pac.ecache_dirty); ecache_postfork_parent(tsdn, &shard->pac.ecache_muzzy); ecache_postfork_parent(tsdn, &shard->pac.ecache_retained); malloc_mutex_postfork_parent(tsdn, &shard->pac.grow_mtx); malloc_mutex_postfork_parent(tsdn, &shard->pac.decay_dirty.mtx); malloc_mutex_postfork_parent(tsdn, &shard->pac.decay_muzzy.mtx); if (shard->ever_used_hpa) { sec_postfork_parent(tsdn, &shard->hpa_sec); hpa_shard_postfork_parent(tsdn, &shard->hpa_shard); } } void pa_shard_postfork_child(tsdn_t *tsdn, pa_shard_t *shard) { edata_cache_postfork_child(tsdn, &shard->edata_cache); ecache_postfork_child(tsdn, &shard->pac.ecache_dirty); ecache_postfork_child(tsdn, &shard->pac.ecache_muzzy); ecache_postfork_child(tsdn, &shard->pac.ecache_retained); malloc_mutex_postfork_child(tsdn, &shard->pac.grow_mtx); malloc_mutex_postfork_child(tsdn, &shard->pac.decay_dirty.mtx); malloc_mutex_postfork_child(tsdn, &shard->pac.decay_muzzy.mtx); if (shard->ever_used_hpa) { sec_postfork_child(tsdn, &shard->hpa_sec); hpa_shard_postfork_child(tsdn, &shard->hpa_shard); } } void pa_shard_basic_stats_merge(pa_shard_t *shard, size_t *nactive, size_t *ndirty, size_t *nmuzzy) { *nactive += atomic_load_zu(&shard->nactive, ATOMIC_RELAXED); *ndirty += ecache_npages_get(&shard->pac.ecache_dirty); *nmuzzy += ecache_npages_get(&shard->pac.ecache_muzzy); } void pa_shard_stats_merge(tsdn_t *tsdn, pa_shard_t *shard, pa_shard_stats_t *pa_shard_stats_out, pac_estats_t *estats_out, hpa_shard_stats_t *hpa_stats_out, sec_stats_t *sec_stats_out, size_t *resident) { cassert(config_stats); pa_shard_stats_out->pac_stats.retained += ecache_npages_get(&shard->pac.ecache_retained) << LG_PAGE; pa_shard_stats_out->edata_avail += atomic_load_zu( &shard->edata_cache.count, ATOMIC_RELAXED); size_t resident_pgs = 0; resident_pgs += atomic_load_zu(&shard->nactive, ATOMIC_RELAXED); resident_pgs += ecache_npages_get(&shard->pac.ecache_dirty); *resident += (resident_pgs << LG_PAGE); /* Dirty decay stats */ locked_inc_u64_unsynchronized( &pa_shard_stats_out->pac_stats.decay_dirty.npurge, locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), &shard->pac.stats->decay_dirty.npurge)); locked_inc_u64_unsynchronized( &pa_shard_stats_out->pac_stats.decay_dirty.nmadvise, locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), &shard->pac.stats->decay_dirty.nmadvise)); locked_inc_u64_unsynchronized( &pa_shard_stats_out->pac_stats.decay_dirty.purged, locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), &shard->pac.stats->decay_dirty.purged)); /* Muzzy decay stats */ locked_inc_u64_unsynchronized( &pa_shard_stats_out->pac_stats.decay_muzzy.npurge, locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), &shard->pac.stats->decay_muzzy.npurge)); locked_inc_u64_unsynchronized( &pa_shard_stats_out->pac_stats.decay_muzzy.nmadvise, locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), &shard->pac.stats->decay_muzzy.nmadvise)); locked_inc_u64_unsynchronized( &pa_shard_stats_out->pac_stats.decay_muzzy.purged, locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), &shard->pac.stats->decay_muzzy.purged)); atomic_load_add_store_zu(&pa_shard_stats_out->pac_stats.abandoned_vm, atomic_load_zu(&shard->pac.stats->abandoned_vm, ATOMIC_RELAXED)); for (pszind_t i = 0; i < SC_NPSIZES; i++) { size_t dirty, muzzy, retained, dirty_bytes, muzzy_bytes, retained_bytes; dirty = ecache_nextents_get(&shard->pac.ecache_dirty, i); muzzy = ecache_nextents_get(&shard->pac.ecache_muzzy, i); retained = ecache_nextents_get(&shard->pac.ecache_retained, i); dirty_bytes = ecache_nbytes_get(&shard->pac.ecache_dirty, i); muzzy_bytes = ecache_nbytes_get(&shard->pac.ecache_muzzy, i); retained_bytes = ecache_nbytes_get(&shard->pac.ecache_retained, i); estats_out[i].ndirty = dirty; estats_out[i].nmuzzy = muzzy; estats_out[i].nretained = retained; estats_out[i].dirty_bytes = dirty_bytes; estats_out[i].muzzy_bytes = muzzy_bytes; estats_out[i].retained_bytes = retained_bytes; } if (shard->ever_used_hpa) { hpa_shard_stats_merge(tsdn, &shard->hpa_shard, hpa_stats_out); sec_stats_merge(tsdn, &shard->hpa_sec, sec_stats_out); } } static void pa_shard_mtx_stats_read_single(tsdn_t *tsdn, mutex_prof_data_t *mutex_prof_data, malloc_mutex_t *mtx, int ind) { malloc_mutex_lock(tsdn, mtx); malloc_mutex_prof_read(tsdn, &mutex_prof_data[ind], mtx); malloc_mutex_unlock(tsdn, mtx); } void pa_shard_mtx_stats_read(tsdn_t *tsdn, pa_shard_t *shard, mutex_prof_data_t mutex_prof_data[mutex_prof_num_arena_mutexes]) { pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->edata_cache.mtx, arena_prof_mutex_extent_avail); pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->pac.ecache_dirty.mtx, arena_prof_mutex_extents_dirty); pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->pac.ecache_muzzy.mtx, arena_prof_mutex_extents_muzzy); pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->pac.ecache_retained.mtx, arena_prof_mutex_extents_retained); pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->pac.decay_dirty.mtx, arena_prof_mutex_decay_dirty); pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->pac.decay_muzzy.mtx, arena_prof_mutex_decay_muzzy); if (shard->ever_used_hpa) { pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->hpa_shard.mtx, arena_prof_mutex_hpa_shard); pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, &shard->hpa_shard.grow_mtx, arena_prof_mutex_hpa_shard_grow); sec_mutex_stats_read(tsdn, &shard->hpa_sec, &mutex_prof_data[arena_prof_mutex_hpa_sec]); } } redis-8.0.2/deps/jemalloc/src/pac.c000066400000000000000000000436201501533116600170540ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/pac.h" #include "jemalloc/internal/san.h" static edata_t *pac_alloc_impl(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated); static bool pac_expand_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); static bool pac_shrink_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated); static void pac_dalloc_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated); static uint64_t pac_time_until_deferred_work(tsdn_t *tsdn, pai_t *self); static inline void pac_decay_data_get(pac_t *pac, extent_state_t state, decay_t **r_decay, pac_decay_stats_t **r_decay_stats, ecache_t **r_ecache) { switch(state) { case extent_state_dirty: *r_decay = &pac->decay_dirty; *r_decay_stats = &pac->stats->decay_dirty; *r_ecache = &pac->ecache_dirty; return; case extent_state_muzzy: *r_decay = &pac->decay_muzzy; *r_decay_stats = &pac->stats->decay_muzzy; *r_ecache = &pac->ecache_muzzy; return; default: unreachable(); } } bool pac_init(tsdn_t *tsdn, pac_t *pac, base_t *base, emap_t *emap, edata_cache_t *edata_cache, nstime_t *cur_time, size_t pac_oversize_threshold, ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms, pac_stats_t *pac_stats, malloc_mutex_t *stats_mtx) { unsigned ind = base_ind_get(base); /* * Delay coalescing for dirty extents despite the disruptive effect on * memory layout for best-fit extent allocation, since cached extents * are likely to be reused soon after deallocation, and the cost of * merging/splitting extents is non-trivial. */ if (ecache_init(tsdn, &pac->ecache_dirty, extent_state_dirty, ind, /* delay_coalesce */ true)) { return true; } /* * Coalesce muzzy extents immediately, because operations on them are in * the critical path much less often than for dirty extents. */ if (ecache_init(tsdn, &pac->ecache_muzzy, extent_state_muzzy, ind, /* delay_coalesce */ false)) { return true; } /* * Coalesce retained extents immediately, in part because they will * never be evicted (and therefore there's no opportunity for delayed * coalescing), but also because operations on retained extents are not * in the critical path. */ if (ecache_init(tsdn, &pac->ecache_retained, extent_state_retained, ind, /* delay_coalesce */ false)) { return true; } exp_grow_init(&pac->exp_grow); if (malloc_mutex_init(&pac->grow_mtx, "extent_grow", WITNESS_RANK_EXTENT_GROW, malloc_mutex_rank_exclusive)) { return true; } atomic_store_zu(&pac->oversize_threshold, pac_oversize_threshold, ATOMIC_RELAXED); if (decay_init(&pac->decay_dirty, cur_time, dirty_decay_ms)) { return true; } if (decay_init(&pac->decay_muzzy, cur_time, muzzy_decay_ms)) { return true; } if (san_bump_alloc_init(&pac->sba)) { return true; } pac->base = base; pac->emap = emap; pac->edata_cache = edata_cache; pac->stats = pac_stats; pac->stats_mtx = stats_mtx; atomic_store_zu(&pac->extent_sn_next, 0, ATOMIC_RELAXED); pac->pai.alloc = &pac_alloc_impl; pac->pai.alloc_batch = &pai_alloc_batch_default; pac->pai.expand = &pac_expand_impl; pac->pai.shrink = &pac_shrink_impl; pac->pai.dalloc = &pac_dalloc_impl; pac->pai.dalloc_batch = &pai_dalloc_batch_default; pac->pai.time_until_deferred_work = &pac_time_until_deferred_work; return false; } static inline bool pac_may_have_muzzy(pac_t *pac) { return pac_decay_ms_get(pac, extent_state_muzzy) != 0; } static edata_t * pac_alloc_real(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, size_t size, size_t alignment, bool zero, bool guarded) { assert(!guarded || alignment <= PAGE); edata_t *edata = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_dirty, NULL, size, alignment, zero, guarded); if (edata == NULL && pac_may_have_muzzy(pac)) { edata = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_muzzy, NULL, size, alignment, zero, guarded); } if (edata == NULL) { edata = ecache_alloc_grow(tsdn, pac, ehooks, &pac->ecache_retained, NULL, size, alignment, zero, guarded); if (config_stats && edata != NULL) { atomic_fetch_add_zu(&pac->stats->pac_mapped, size, ATOMIC_RELAXED); } } return edata; } static edata_t * pac_alloc_new_guarded(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, size_t size, size_t alignment, bool zero, bool frequent_reuse) { assert(alignment <= PAGE); edata_t *edata; if (san_bump_enabled() && frequent_reuse) { edata = san_bump_alloc(tsdn, &pac->sba, pac, ehooks, size, zero); } else { size_t size_with_guards = san_two_side_guarded_sz(size); /* Alloc a non-guarded extent first.*/ edata = pac_alloc_real(tsdn, pac, ehooks, size_with_guards, /* alignment */ PAGE, zero, /* guarded */ false); if (edata != NULL) { /* Add guards around it. */ assert(edata_size_get(edata) == size_with_guards); san_guard_pages_two_sided(tsdn, ehooks, edata, pac->emap, true); } } assert(edata == NULL || (edata_guarded_get(edata) && edata_size_get(edata) == size)); return edata; } static edata_t * pac_alloc_impl(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated) { pac_t *pac = (pac_t *)self; ehooks_t *ehooks = pac_ehooks_get(pac); edata_t *edata = NULL; /* * The condition is an optimization - not frequently reused guarded * allocations are never put in the ecache. pac_alloc_real also * doesn't grow retained for guarded allocations. So pac_alloc_real * for such allocations would always return NULL. * */ if (!guarded || frequent_reuse) { edata = pac_alloc_real(tsdn, pac, ehooks, size, alignment, zero, guarded); } if (edata == NULL && guarded) { /* No cached guarded extents; creating a new one. */ edata = pac_alloc_new_guarded(tsdn, pac, ehooks, size, alignment, zero, frequent_reuse); } return edata; } static bool pac_expand_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated) { pac_t *pac = (pac_t *)self; ehooks_t *ehooks = pac_ehooks_get(pac); size_t mapped_add = 0; size_t expand_amount = new_size - old_size; if (ehooks_merge_will_fail(ehooks)) { return true; } edata_t *trail = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_dirty, edata, expand_amount, PAGE, zero, /* guarded*/ false); if (trail == NULL) { trail = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_muzzy, edata, expand_amount, PAGE, zero, /* guarded*/ false); } if (trail == NULL) { trail = ecache_alloc_grow(tsdn, pac, ehooks, &pac->ecache_retained, edata, expand_amount, PAGE, zero, /* guarded */ false); mapped_add = expand_amount; } if (trail == NULL) { return true; } if (extent_merge_wrapper(tsdn, pac, ehooks, edata, trail)) { extent_dalloc_wrapper(tsdn, pac, ehooks, trail); return true; } if (config_stats && mapped_add > 0) { atomic_fetch_add_zu(&pac->stats->pac_mapped, mapped_add, ATOMIC_RELAXED); } return false; } static bool pac_shrink_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated) { pac_t *pac = (pac_t *)self; ehooks_t *ehooks = pac_ehooks_get(pac); size_t shrink_amount = old_size - new_size; if (ehooks_split_will_fail(ehooks)) { return true; } edata_t *trail = extent_split_wrapper(tsdn, pac, ehooks, edata, new_size, shrink_amount, /* holding_core_locks */ false); if (trail == NULL) { return true; } ecache_dalloc(tsdn, pac, ehooks, &pac->ecache_dirty, trail); *deferred_work_generated = true; return false; } static void pac_dalloc_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated) { pac_t *pac = (pac_t *)self; ehooks_t *ehooks = pac_ehooks_get(pac); if (edata_guarded_get(edata)) { /* * Because cached guarded extents do exact fit only, large * guarded extents are restored on dalloc eagerly (otherwise * they will not be reused efficiently). Slab sizes have a * limited number of size classes, and tend to cycle faster. * * In the case where coalesce is restrained (VirtualFree on * Windows), guarded extents are also not cached -- otherwise * during arena destroy / reset, the retained extents would not * be whole regions (i.e. they are split between regular and * guarded). */ if (!edata_slab_get(edata) || !maps_coalesce) { assert(edata_size_get(edata) >= SC_LARGE_MINCLASS || !maps_coalesce); san_unguard_pages_two_sided(tsdn, ehooks, edata, pac->emap); } } ecache_dalloc(tsdn, pac, ehooks, &pac->ecache_dirty, edata); /* Purging of deallocated pages is deferred */ *deferred_work_generated = true; } static inline uint64_t pac_ns_until_purge(tsdn_t *tsdn, decay_t *decay, size_t npages) { if (malloc_mutex_trylock(tsdn, &decay->mtx)) { /* Use minimal interval if decay is contended. */ return BACKGROUND_THREAD_DEFERRED_MIN; } uint64_t result = decay_ns_until_purge(decay, npages, ARENA_DEFERRED_PURGE_NPAGES_THRESHOLD); malloc_mutex_unlock(tsdn, &decay->mtx); return result; } static uint64_t pac_time_until_deferred_work(tsdn_t *tsdn, pai_t *self) { uint64_t time; pac_t *pac = (pac_t *)self; time = pac_ns_until_purge(tsdn, &pac->decay_dirty, ecache_npages_get(&pac->ecache_dirty)); if (time == BACKGROUND_THREAD_DEFERRED_MIN) { return time; } uint64_t muzzy = pac_ns_until_purge(tsdn, &pac->decay_muzzy, ecache_npages_get(&pac->ecache_muzzy)); if (muzzy < time) { time = muzzy; } return time; } bool pac_retain_grow_limit_get_set(tsdn_t *tsdn, pac_t *pac, size_t *old_limit, size_t *new_limit) { pszind_t new_ind JEMALLOC_CC_SILENCE_INIT(0); if (new_limit != NULL) { size_t limit = *new_limit; /* Grow no more than the new limit. */ if ((new_ind = sz_psz2ind(limit + 1) - 1) >= SC_NPSIZES) { return true; } } malloc_mutex_lock(tsdn, &pac->grow_mtx); if (old_limit != NULL) { *old_limit = sz_pind2sz(pac->exp_grow.limit); } if (new_limit != NULL) { pac->exp_grow.limit = new_ind; } malloc_mutex_unlock(tsdn, &pac->grow_mtx); return false; } static size_t pac_stash_decayed(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, size_t npages_limit, size_t npages_decay_max, edata_list_inactive_t *result) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 0); ehooks_t *ehooks = pac_ehooks_get(pac); /* Stash extents according to npages_limit. */ size_t nstashed = 0; while (nstashed < npages_decay_max) { edata_t *edata = ecache_evict(tsdn, pac, ehooks, ecache, npages_limit); if (edata == NULL) { break; } edata_list_inactive_append(result, edata); nstashed += edata_size_get(edata) >> LG_PAGE; } return nstashed; } static size_t pac_decay_stashed(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay, edata_list_inactive_t *decay_extents) { bool err; size_t nmadvise = 0; size_t nunmapped = 0; size_t npurged = 0; ehooks_t *ehooks = pac_ehooks_get(pac); bool try_muzzy = !fully_decay && pac_decay_ms_get(pac, extent_state_muzzy) != 0; for (edata_t *edata = edata_list_inactive_first(decay_extents); edata != NULL; edata = edata_list_inactive_first(decay_extents)) { edata_list_inactive_remove(decay_extents, edata); size_t size = edata_size_get(edata); size_t npages = size >> LG_PAGE; nmadvise++; npurged += npages; switch (ecache->state) { case extent_state_active: not_reached(); case extent_state_dirty: if (try_muzzy) { err = extent_purge_lazy_wrapper(tsdn, ehooks, edata, /* offset */ 0, size); if (!err) { ecache_dalloc(tsdn, pac, ehooks, &pac->ecache_muzzy, edata); break; } } JEMALLOC_FALLTHROUGH; case extent_state_muzzy: extent_dalloc_wrapper(tsdn, pac, ehooks, edata); nunmapped += npages; break; case extent_state_retained: default: not_reached(); } } if (config_stats) { LOCKEDINT_MTX_LOCK(tsdn, *pac->stats_mtx); locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), &decay_stats->npurge, 1); locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), &decay_stats->nmadvise, nmadvise); locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), &decay_stats->purged, npurged); LOCKEDINT_MTX_UNLOCK(tsdn, *pac->stats_mtx); atomic_fetch_sub_zu(&pac->stats->pac_mapped, nunmapped << LG_PAGE, ATOMIC_RELAXED); } return npurged; } /* * npages_limit: Decay at most npages_decay_max pages without violating the * invariant: (ecache_npages_get(ecache) >= npages_limit). We need an upper * bound on number of pages in order to prevent unbounded growth (namely in * stashed), otherwise unbounded new pages could be added to extents during the * current decay run, so that the purging thread never finishes. */ static void pac_decay_to_limit(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay, size_t npages_limit, size_t npages_decay_max) { witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE, 1); if (decay->purging || npages_decay_max == 0) { return; } decay->purging = true; malloc_mutex_unlock(tsdn, &decay->mtx); edata_list_inactive_t decay_extents; edata_list_inactive_init(&decay_extents); size_t npurge = pac_stash_decayed(tsdn, pac, ecache, npages_limit, npages_decay_max, &decay_extents); if (npurge != 0) { size_t npurged = pac_decay_stashed(tsdn, pac, decay, decay_stats, ecache, fully_decay, &decay_extents); assert(npurged == npurge); } malloc_mutex_lock(tsdn, &decay->mtx); decay->purging = false; } void pac_decay_all(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay) { malloc_mutex_assert_owner(tsdn, &decay->mtx); pac_decay_to_limit(tsdn, pac, decay, decay_stats, ecache, fully_decay, /* npages_limit */ 0, ecache_npages_get(ecache)); } static void pac_decay_try_purge(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, size_t current_npages, size_t npages_limit) { if (current_npages > npages_limit) { pac_decay_to_limit(tsdn, pac, decay, decay_stats, ecache, /* fully_decay */ false, npages_limit, current_npages - npages_limit); } } bool pac_maybe_decay_purge(tsdn_t *tsdn, pac_t *pac, decay_t *decay, pac_decay_stats_t *decay_stats, ecache_t *ecache, pac_purge_eagerness_t eagerness) { malloc_mutex_assert_owner(tsdn, &decay->mtx); /* Purge all or nothing if the option is disabled. */ ssize_t decay_ms = decay_ms_read(decay); if (decay_ms <= 0) { if (decay_ms == 0) { pac_decay_to_limit(tsdn, pac, decay, decay_stats, ecache, /* fully_decay */ false, /* npages_limit */ 0, ecache_npages_get(ecache)); } return false; } /* * If the deadline has been reached, advance to the current epoch and * purge to the new limit if necessary. Note that dirty pages created * during the current epoch are not subject to purge until a future * epoch, so as a result purging only happens during epoch advances, or * being triggered by background threads (scheduled event). */ nstime_t time; nstime_init_update(&time); size_t npages_current = ecache_npages_get(ecache); bool epoch_advanced = decay_maybe_advance_epoch(decay, &time, npages_current); if (eagerness == PAC_PURGE_ALWAYS || (epoch_advanced && eagerness == PAC_PURGE_ON_EPOCH_ADVANCE)) { size_t npages_limit = decay_npages_limit_get(decay); pac_decay_try_purge(tsdn, pac, decay, decay_stats, ecache, npages_current, npages_limit); } return epoch_advanced; } bool pac_decay_ms_set(tsdn_t *tsdn, pac_t *pac, extent_state_t state, ssize_t decay_ms, pac_purge_eagerness_t eagerness) { decay_t *decay; pac_decay_stats_t *decay_stats; ecache_t *ecache; pac_decay_data_get(pac, state, &decay, &decay_stats, &ecache); if (!decay_ms_valid(decay_ms)) { return true; } malloc_mutex_lock(tsdn, &decay->mtx); /* * Restart decay backlog from scratch, which may cause many dirty pages * to be immediately purged. It would conceptually be possible to map * the old backlog onto the new backlog, but there is no justification * for such complexity since decay_ms changes are intended to be * infrequent, either between the {-1, 0, >0} states, or a one-time * arbitrary change during initial arena configuration. */ nstime_t cur_time; nstime_init_update(&cur_time); decay_reinit(decay, &cur_time, decay_ms); pac_maybe_decay_purge(tsdn, pac, decay, decay_stats, ecache, eagerness); malloc_mutex_unlock(tsdn, &decay->mtx); return false; } ssize_t pac_decay_ms_get(pac_t *pac, extent_state_t state) { decay_t *decay; pac_decay_stats_t *decay_stats; ecache_t *ecache; pac_decay_data_get(pac, state, &decay, &decay_stats, &ecache); return decay_ms_read(decay); } void pac_reset(tsdn_t *tsdn, pac_t *pac) { /* * No-op for now; purging is still done at the arena-level. It should * get moved in here, though. */ (void)tsdn; (void)pac; } void pac_destroy(tsdn_t *tsdn, pac_t *pac) { assert(ecache_npages_get(&pac->ecache_dirty) == 0); assert(ecache_npages_get(&pac->ecache_muzzy) == 0); /* * Iterate over the retained extents and destroy them. This gives the * extent allocator underlying the extent hooks an opportunity to unmap * all retained memory without having to keep its own metadata * structures. In practice, virtual memory for dss-allocated extents is * leaked here, so best practice is to avoid dss for arenas to be * destroyed, or provide custom extent hooks that track retained * dss-based extents for later reuse. */ ehooks_t *ehooks = pac_ehooks_get(pac); edata_t *edata; while ((edata = ecache_evict(tsdn, pac, ehooks, &pac->ecache_retained, 0)) != NULL) { extent_destroy_wrapper(tsdn, pac, ehooks, edata); } } redis-8.0.2/deps/jemalloc/src/pages.c000066400000000000000000000502171501533116600174100ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/pages.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/malloc_io.h" #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT #include #ifdef __FreeBSD__ #include #endif #endif #ifdef __NetBSD__ #include /* ilog2 */ #endif #ifdef JEMALLOC_HAVE_VM_MAKE_TAG #define PAGES_FD_TAG VM_MAKE_TAG(101U) #else #define PAGES_FD_TAG -1 #endif /******************************************************************************/ /* Data. */ /* Actual operating system page size, detected during bootstrap, <= PAGE. */ static size_t os_page; #ifndef _WIN32 # define PAGES_PROT_COMMIT (PROT_READ | PROT_WRITE) # define PAGES_PROT_DECOMMIT (PROT_NONE) static int mmap_flags; #endif static bool os_overcommits; const char *thp_mode_names[] = { "default", "always", "never", "not supported" }; thp_mode_t opt_thp = THP_MODE_DEFAULT; thp_mode_t init_system_thp_mode; /* Runtime support for lazy purge. Irrelevant when !pages_can_purge_lazy. */ static bool pages_can_purge_lazy_runtime = true; #ifdef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS static int madvise_dont_need_zeros_is_faulty = -1; /** * Check that MADV_DONTNEED will actually zero pages on subsequent access. * * Since qemu does not support this, yet [1], and you can get very tricky * assert if you will run program with jemalloc in use under qemu: * * : ../contrib/jemalloc/src/extent.c:1195: Failed assertion: "p[i] == 0" * * [1]: https://patchwork.kernel.org/patch/10576637/ */ static int madvise_MADV_DONTNEED_zeroes_pages() { int works = -1; size_t size = PAGE; void * addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (addr == MAP_FAILED) { malloc_write(": Cannot allocate memory for " "MADV_DONTNEED check\n"); if (opt_abort) { abort(); } } memset(addr, 'A', size); if (madvise(addr, size, MADV_DONTNEED) == 0) { works = memchr(addr, 'A', size) == NULL; } else { /* * If madvise() does not support MADV_DONTNEED, then we can * call it anyway, and use it's return code. */ works = 1; } if (munmap(addr, size) != 0) { malloc_write(": Cannot deallocate memory for " "MADV_DONTNEED check\n"); if (opt_abort) { abort(); } } return works; } #endif /******************************************************************************/ /* * Function prototypes for static functions that are referenced prior to * definition. */ static void os_pages_unmap(void *addr, size_t size); /******************************************************************************/ static void * os_pages_map(void *addr, size_t size, size_t alignment, bool *commit) { assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr); assert(ALIGNMENT_CEILING(size, os_page) == size); assert(size != 0); if (os_overcommits) { *commit = true; } void *ret; #ifdef _WIN32 /* * If VirtualAlloc can't allocate at the given address when one is * given, it fails and returns NULL. */ ret = VirtualAlloc(addr, size, MEM_RESERVE | (*commit ? MEM_COMMIT : 0), PAGE_READWRITE); #else /* * We don't use MAP_FIXED here, because it can cause the *replacement* * of existing mappings, and we only want to create new mappings. */ { #ifdef __NetBSD__ /* * On NetBSD PAGE for a platform is defined to the * maximum page size of all machine architectures * for that platform, so that we can use the same * binaries across all machine architectures. */ if (alignment > os_page || PAGE > os_page) { unsigned int a = ilog2(MAX(alignment, PAGE)); mmap_flags |= MAP_ALIGNED(a); } #endif int prot = *commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT; ret = mmap(addr, size, prot, mmap_flags, PAGES_FD_TAG, 0); } assert(ret != NULL); if (ret == MAP_FAILED) { ret = NULL; } else if (addr != NULL && ret != addr) { /* * We succeeded in mapping memory, but not in the right place. */ os_pages_unmap(ret, size); ret = NULL; } #endif assert(ret == NULL || (addr == NULL && ret != addr) || (addr != NULL && ret == addr)); return ret; } static void * os_pages_trim(void *addr, size_t alloc_size, size_t leadsize, size_t size, bool *commit) { void *ret = (void *)((uintptr_t)addr + leadsize); assert(alloc_size >= leadsize + size); #ifdef _WIN32 os_pages_unmap(addr, alloc_size); void *new_addr = os_pages_map(ret, size, PAGE, commit); if (new_addr == ret) { return ret; } if (new_addr != NULL) { os_pages_unmap(new_addr, size); } return NULL; #else size_t trailsize = alloc_size - leadsize - size; if (leadsize != 0) { os_pages_unmap(addr, leadsize); } if (trailsize != 0) { os_pages_unmap((void *)((uintptr_t)ret + size), trailsize); } return ret; #endif } static void os_pages_unmap(void *addr, size_t size) { assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr); assert(ALIGNMENT_CEILING(size, os_page) == size); #ifdef _WIN32 if (VirtualFree(addr, 0, MEM_RELEASE) == 0) #else if (munmap(addr, size) == -1) #endif { char buf[BUFERROR_BUF]; buferror(get_errno(), buf, sizeof(buf)); malloc_printf(": Error in " #ifdef _WIN32 "VirtualFree" #else "munmap" #endif "(): %s\n", buf); if (opt_abort) { abort(); } } } static void * pages_map_slow(size_t size, size_t alignment, bool *commit) { size_t alloc_size = size + alignment - os_page; /* Beware size_t wrap-around. */ if (alloc_size < size) { return NULL; } void *ret; do { void *pages = os_pages_map(NULL, alloc_size, alignment, commit); if (pages == NULL) { return NULL; } size_t leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment) - (uintptr_t)pages; ret = os_pages_trim(pages, alloc_size, leadsize, size, commit); } while (ret == NULL); assert(ret != NULL); assert(PAGE_ADDR2BASE(ret) == ret); return ret; } void * pages_map(void *addr, size_t size, size_t alignment, bool *commit) { assert(alignment >= PAGE); assert(ALIGNMENT_ADDR2BASE(addr, alignment) == addr); #if defined(__FreeBSD__) && defined(MAP_EXCL) /* * FreeBSD has mechanisms both to mmap at specific address without * touching existing mappings, and to mmap with specific alignment. */ { if (os_overcommits) { *commit = true; } int prot = *commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT; int flags = mmap_flags; if (addr != NULL) { flags |= MAP_FIXED | MAP_EXCL; } else { unsigned alignment_bits = ffs_zu(alignment); assert(alignment_bits > 0); flags |= MAP_ALIGNED(alignment_bits); } void *ret = mmap(addr, size, prot, flags, -1, 0); if (ret == MAP_FAILED) { ret = NULL; } return ret; } #endif /* * Ideally, there would be a way to specify alignment to mmap() (like * NetBSD has), but in the absence of such a feature, we have to work * hard to efficiently create aligned mappings. The reliable, but * slow method is to create a mapping that is over-sized, then trim the * excess. However, that always results in one or two calls to * os_pages_unmap(), and it can leave holes in the process's virtual * memory map if memory grows downward. * * Optimistically try mapping precisely the right amount before falling * back to the slow method, with the expectation that the optimistic * approach works most of the time. */ void *ret = os_pages_map(addr, size, os_page, commit); if (ret == NULL || ret == addr) { return ret; } assert(addr == NULL); if (ALIGNMENT_ADDR2OFFSET(ret, alignment) != 0) { os_pages_unmap(ret, size); return pages_map_slow(size, alignment, commit); } assert(PAGE_ADDR2BASE(ret) == ret); return ret; } void pages_unmap(void *addr, size_t size) { assert(PAGE_ADDR2BASE(addr) == addr); assert(PAGE_CEILING(size) == size); os_pages_unmap(addr, size); } static bool os_pages_commit(void *addr, size_t size, bool commit) { assert(PAGE_ADDR2BASE(addr) == addr); assert(PAGE_CEILING(size) == size); #ifdef _WIN32 return (commit ? (addr != VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE)) : (!VirtualFree(addr, size, MEM_DECOMMIT))); #else { int prot = commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT; void *result = mmap(addr, size, prot, mmap_flags | MAP_FIXED, PAGES_FD_TAG, 0); if (result == MAP_FAILED) { return true; } if (result != addr) { /* * We succeeded in mapping memory, but not in the right * place. */ os_pages_unmap(result, size); return true; } return false; } #endif } static bool pages_commit_impl(void *addr, size_t size, bool commit) { if (os_overcommits) { return true; } return os_pages_commit(addr, size, commit); } bool pages_commit(void *addr, size_t size) { return pages_commit_impl(addr, size, true); } bool pages_decommit(void *addr, size_t size) { return pages_commit_impl(addr, size, false); } void pages_mark_guards(void *head, void *tail) { assert(head != NULL || tail != NULL); assert(head == NULL || tail == NULL || (uintptr_t)head < (uintptr_t)tail); #ifdef JEMALLOC_HAVE_MPROTECT if (head != NULL) { mprotect(head, PAGE, PROT_NONE); } if (tail != NULL) { mprotect(tail, PAGE, PROT_NONE); } #else /* Decommit sets to PROT_NONE / MEM_DECOMMIT. */ if (head != NULL) { os_pages_commit(head, PAGE, false); } if (tail != NULL) { os_pages_commit(tail, PAGE, false); } #endif } void pages_unmark_guards(void *head, void *tail) { assert(head != NULL || tail != NULL); assert(head == NULL || tail == NULL || (uintptr_t)head < (uintptr_t)tail); #ifdef JEMALLOC_HAVE_MPROTECT bool head_and_tail = (head != NULL) && (tail != NULL); size_t range = head_and_tail ? (uintptr_t)tail - (uintptr_t)head + PAGE : SIZE_T_MAX; /* * The amount of work that the kernel does in mprotect depends on the * range argument. SC_LARGE_MINCLASS is an arbitrary threshold chosen * to prevent kernel from doing too much work that would outweigh the * savings of performing one less system call. */ bool ranged_mprotect = head_and_tail && range <= SC_LARGE_MINCLASS; if (ranged_mprotect) { mprotect(head, range, PROT_READ | PROT_WRITE); } else { if (head != NULL) { mprotect(head, PAGE, PROT_READ | PROT_WRITE); } if (tail != NULL) { mprotect(tail, PAGE, PROT_READ | PROT_WRITE); } } #else if (head != NULL) { os_pages_commit(head, PAGE, true); } if (tail != NULL) { os_pages_commit(tail, PAGE, true); } #endif } bool pages_purge_lazy(void *addr, size_t size) { assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr); assert(PAGE_CEILING(size) == size); if (!pages_can_purge_lazy) { return true; } if (!pages_can_purge_lazy_runtime) { /* * Built with lazy purge enabled, but detected it was not * supported on the current system. */ return true; } #ifdef _WIN32 VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE); return false; #elif defined(JEMALLOC_PURGE_MADVISE_FREE) return (madvise(addr, size, # ifdef MADV_FREE MADV_FREE # else JEMALLOC_MADV_FREE # endif ) != 0); #elif defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \ !defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS) return (madvise(addr, size, MADV_DONTNEED) != 0); #elif defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED) && \ !defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED_ZEROS) return (posix_madvise(addr, size, POSIX_MADV_DONTNEED) != 0); #else not_reached(); #endif } bool pages_purge_forced(void *addr, size_t size) { assert(PAGE_ADDR2BASE(addr) == addr); assert(PAGE_CEILING(size) == size); if (!pages_can_purge_forced) { return true; } #if defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \ defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS) return (unlikely(madvise_dont_need_zeros_is_faulty) || madvise(addr, size, MADV_DONTNEED) != 0); #elif defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED) && \ defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED_ZEROS) return (unlikely(madvise_dont_need_zeros_is_faulty) || posix_madvise(addr, size, POSIX_MADV_DONTNEED) != 0); #elif defined(JEMALLOC_MAPS_COALESCE) /* Try to overlay a new demand-zeroed mapping. */ return pages_commit(addr, size); #else not_reached(); #endif } static bool pages_huge_impl(void *addr, size_t size, bool aligned) { if (aligned) { assert(HUGEPAGE_ADDR2BASE(addr) == addr); assert(HUGEPAGE_CEILING(size) == size); } #if defined(JEMALLOC_HAVE_MADVISE_HUGE) return (madvise(addr, size, MADV_HUGEPAGE) != 0); #elif defined(JEMALLOC_HAVE_MEMCNTL) struct memcntl_mha m = {0}; m.mha_cmd = MHA_MAPSIZE_VA; m.mha_pagesize = HUGEPAGE; return (memcntl(addr, size, MC_HAT_ADVISE, (caddr_t)&m, 0, 0) == 0); #else return true; #endif } bool pages_huge(void *addr, size_t size) { return pages_huge_impl(addr, size, true); } static bool pages_huge_unaligned(void *addr, size_t size) { return pages_huge_impl(addr, size, false); } static bool pages_nohuge_impl(void *addr, size_t size, bool aligned) { if (aligned) { assert(HUGEPAGE_ADDR2BASE(addr) == addr); assert(HUGEPAGE_CEILING(size) == size); } #ifdef JEMALLOC_HAVE_MADVISE_HUGE return (madvise(addr, size, MADV_NOHUGEPAGE) != 0); #else return false; #endif } bool pages_nohuge(void *addr, size_t size) { return pages_nohuge_impl(addr, size, true); } static bool pages_nohuge_unaligned(void *addr, size_t size) { return pages_nohuge_impl(addr, size, false); } bool pages_dontdump(void *addr, size_t size) { assert(PAGE_ADDR2BASE(addr) == addr); assert(PAGE_CEILING(size) == size); #if defined(JEMALLOC_MADVISE_DONTDUMP) return madvise(addr, size, MADV_DONTDUMP) != 0; #elif defined(JEMALLOC_MADVISE_NOCORE) return madvise(addr, size, MADV_NOCORE) != 0; #else return false; #endif } bool pages_dodump(void *addr, size_t size) { assert(PAGE_ADDR2BASE(addr) == addr); assert(PAGE_CEILING(size) == size); #if defined(JEMALLOC_MADVISE_DONTDUMP) return madvise(addr, size, MADV_DODUMP) != 0; #elif defined(JEMALLOC_MADVISE_NOCORE) return madvise(addr, size, MADV_CORE) != 0; #else return false; #endif } static size_t os_page_detect(void) { #ifdef _WIN32 SYSTEM_INFO si; GetSystemInfo(&si); return si.dwPageSize; #elif defined(__FreeBSD__) /* * This returns the value obtained from * the auxv vector, avoiding a syscall. */ return getpagesize(); #else long result = sysconf(_SC_PAGESIZE); if (result == -1) { return LG_PAGE; } return (size_t)result; #endif } #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT static bool os_overcommits_sysctl(void) { int vm_overcommit; size_t sz; sz = sizeof(vm_overcommit); #if defined(__FreeBSD__) && defined(VM_OVERCOMMIT) int mib[2]; mib[0] = CTL_VM; mib[1] = VM_OVERCOMMIT; if (sysctl(mib, 2, &vm_overcommit, &sz, NULL, 0) != 0) { return false; /* Error. */ } #else if (sysctlbyname("vm.overcommit", &vm_overcommit, &sz, NULL, 0) != 0) { return false; /* Error. */ } #endif return ((vm_overcommit & 0x3) == 0); } #endif #ifdef JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY /* * Use syscall(2) rather than {open,read,close}(2) when possible to avoid * reentry during bootstrapping if another library has interposed system call * wrappers. */ static bool os_overcommits_proc(void) { int fd; char buf[1]; #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_open) #if defined(O_CLOEXEC) fd = (int)syscall(SYS_open, "/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC); #else fd = (int)syscall(SYS_open, "/proc/sys/vm/overcommit_memory", O_RDONLY); if (fd != -1) { fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); } #endif #elif defined(JEMALLOC_USE_SYSCALL) && defined(SYS_openat) #if defined(O_CLOEXEC) fd = (int)syscall(SYS_openat, AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC); #else fd = (int)syscall(SYS_openat, AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY); if (fd != -1) { fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); } #endif #else #if defined(O_CLOEXEC) fd = open("/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC); #else fd = open("/proc/sys/vm/overcommit_memory", O_RDONLY); if (fd != -1) { fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); } #endif #endif if (fd == -1) { return false; /* Error. */ } ssize_t nread = malloc_read_fd(fd, &buf, sizeof(buf)); #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_close) syscall(SYS_close, fd); #else close(fd); #endif if (nread < 1) { return false; /* Error. */ } /* * /proc/sys/vm/overcommit_memory meanings: * 0: Heuristic overcommit. * 1: Always overcommit. * 2: Never overcommit. */ return (buf[0] == '0' || buf[0] == '1'); } #endif void pages_set_thp_state (void *ptr, size_t size) { if (opt_thp == thp_mode_default || opt_thp == init_system_thp_mode) { return; } assert(opt_thp != thp_mode_not_supported && init_system_thp_mode != thp_mode_not_supported); if (opt_thp == thp_mode_always && init_system_thp_mode != thp_mode_never) { assert(init_system_thp_mode == thp_mode_default); pages_huge_unaligned(ptr, size); } else if (opt_thp == thp_mode_never) { assert(init_system_thp_mode == thp_mode_default || init_system_thp_mode == thp_mode_always); pages_nohuge_unaligned(ptr, size); } } static void init_thp_state(void) { if (!have_madvise_huge && !have_memcntl) { if (metadata_thp_enabled() && opt_abort) { malloc_write(": no MADV_HUGEPAGE support\n"); abort(); } goto label_error; } #if defined(JEMALLOC_HAVE_MADVISE_HUGE) static const char sys_state_madvise[] = "always [madvise] never\n"; static const char sys_state_always[] = "[always] madvise never\n"; static const char sys_state_never[] = "always madvise [never]\n"; char buf[sizeof(sys_state_madvise)]; #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_open) int fd = (int)syscall(SYS_open, "/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY); #elif defined(JEMALLOC_USE_SYSCALL) && defined(SYS_openat) int fd = (int)syscall(SYS_openat, AT_FDCWD, "/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY); #else int fd = open("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY); #endif if (fd == -1) { goto label_error; } ssize_t nread = malloc_read_fd(fd, &buf, sizeof(buf)); #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_close) syscall(SYS_close, fd); #else close(fd); #endif if (nread < 0) { goto label_error; } if (strncmp(buf, sys_state_madvise, (size_t)nread) == 0) { init_system_thp_mode = thp_mode_default; } else if (strncmp(buf, sys_state_always, (size_t)nread) == 0) { init_system_thp_mode = thp_mode_always; } else if (strncmp(buf, sys_state_never, (size_t)nread) == 0) { init_system_thp_mode = thp_mode_never; } else { goto label_error; } return; #elif defined(JEMALLOC_HAVE_MEMCNTL) init_system_thp_mode = thp_mode_default; return; #endif label_error: opt_thp = init_system_thp_mode = thp_mode_not_supported; } bool pages_boot(void) { os_page = os_page_detect(); if (os_page > PAGE) { malloc_write(": Unsupported system page size\n"); if (opt_abort) { abort(); } return true; } #ifdef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS if (!opt_trust_madvise) { madvise_dont_need_zeros_is_faulty = !madvise_MADV_DONTNEED_zeroes_pages(); if (madvise_dont_need_zeros_is_faulty) { malloc_write(": MADV_DONTNEED does not work (memset will be used instead)\n"); malloc_write(": (This is the expected behaviour if you are running under QEMU)\n"); } } else { /* In case opt_trust_madvise is disable, * do not do runtime check */ madvise_dont_need_zeros_is_faulty = 0; } #endif #ifndef _WIN32 mmap_flags = MAP_PRIVATE | MAP_ANON; #endif #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT os_overcommits = os_overcommits_sysctl(); #elif defined(JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY) os_overcommits = os_overcommits_proc(); # ifdef MAP_NORESERVE if (os_overcommits) { mmap_flags |= MAP_NORESERVE; } # endif #elif defined(__NetBSD__) os_overcommits = true; #else os_overcommits = false; #endif init_thp_state(); #ifdef __FreeBSD__ /* * FreeBSD doesn't need the check; madvise(2) is known to work. */ #else /* Detect lazy purge runtime support. */ if (pages_can_purge_lazy) { bool committed = false; void *madv_free_page = os_pages_map(NULL, PAGE, PAGE, &committed); if (madv_free_page == NULL) { return true; } assert(pages_can_purge_lazy_runtime); if (pages_purge_lazy(madv_free_page, PAGE)) { pages_can_purge_lazy_runtime = false; } os_pages_unmap(madv_free_page, PAGE); } #endif return false; } redis-8.0.2/deps/jemalloc/src/pai.c000066400000000000000000000020031501533116600170500ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" size_t pai_alloc_batch_default(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated) { for (size_t i = 0; i < nallocs; i++) { bool deferred_by_alloc = false; edata_t *edata = pai_alloc(tsdn, self, size, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_by_alloc); *deferred_work_generated |= deferred_by_alloc; if (edata == NULL) { return i; } edata_list_active_append(results, edata); } return nallocs; } void pai_dalloc_batch_default(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated) { edata_t *edata; while ((edata = edata_list_active_first(list)) != NULL) { bool deferred_by_dalloc = false; edata_list_active_remove(list, edata); pai_dalloc(tsdn, self, edata, &deferred_by_dalloc); *deferred_work_generated |= deferred_by_dalloc; } } redis-8.0.2/deps/jemalloc/src/peak_event.c000066400000000000000000000040161501533116600204260ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/peak_event.h" #include "jemalloc/internal/activity_callback.h" #include "jemalloc/internal/peak.h" /* * Update every 64K by default. We're not exposing this as a configuration * option for now; we don't want to bind ourselves too tightly to any particular * performance requirements for small values, or guarantee that we'll even be * able to provide fine-grained accuracy. */ #define PEAK_EVENT_WAIT (64 * 1024) /* Update the peak with current tsd state. */ void peak_event_update(tsd_t *tsd) { uint64_t alloc = tsd_thread_allocated_get(tsd); uint64_t dalloc = tsd_thread_deallocated_get(tsd); peak_t *peak = tsd_peakp_get(tsd); peak_update(peak, alloc, dalloc); } static void peak_event_activity_callback(tsd_t *tsd) { activity_callback_thunk_t *thunk = tsd_activity_callback_thunkp_get( tsd); uint64_t alloc = tsd_thread_allocated_get(tsd); uint64_t dalloc = tsd_thread_deallocated_get(tsd); if (thunk->callback != NULL) { thunk->callback(thunk->uctx, alloc, dalloc); } } /* Set current state to zero. */ void peak_event_zero(tsd_t *tsd) { uint64_t alloc = tsd_thread_allocated_get(tsd); uint64_t dalloc = tsd_thread_deallocated_get(tsd); peak_t *peak = tsd_peakp_get(tsd); peak_set_zero(peak, alloc, dalloc); } uint64_t peak_event_max(tsd_t *tsd) { peak_t *peak = tsd_peakp_get(tsd); return peak_max(peak); } uint64_t peak_alloc_new_event_wait(tsd_t *tsd) { return PEAK_EVENT_WAIT; } uint64_t peak_alloc_postponed_event_wait(tsd_t *tsd) { return TE_MIN_START_WAIT; } void peak_alloc_event_handler(tsd_t *tsd, uint64_t elapsed) { peak_event_update(tsd); peak_event_activity_callback(tsd); } uint64_t peak_dalloc_new_event_wait(tsd_t *tsd) { return PEAK_EVENT_WAIT; } uint64_t peak_dalloc_postponed_event_wait(tsd_t *tsd) { return TE_MIN_START_WAIT; } void peak_dalloc_event_handler(tsd_t *tsd, uint64_t elapsed) { peak_event_update(tsd); peak_event_activity_callback(tsd); } redis-8.0.2/deps/jemalloc/src/prof.c000066400000000000000000000511201501533116600172510ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/counter.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_log.h" #include "jemalloc/internal/prof_recent.h" #include "jemalloc/internal/prof_stats.h" #include "jemalloc/internal/prof_sys.h" #include "jemalloc/internal/prof_hook.h" #include "jemalloc/internal/thread_event.h" /* * This file implements the profiling "APIs" needed by other parts of jemalloc, * and also manages the relevant "operational" data, mainly options and mutexes; * the core profiling data structures are encapsulated in prof_data.c. */ /******************************************************************************/ /* Data. */ bool opt_prof = false; bool opt_prof_active = true; bool opt_prof_thread_active_init = true; size_t opt_lg_prof_sample = LG_PROF_SAMPLE_DEFAULT; ssize_t opt_lg_prof_interval = LG_PROF_INTERVAL_DEFAULT; bool opt_prof_gdump = false; bool opt_prof_final = false; bool opt_prof_leak = false; bool opt_prof_leak_error = false; bool opt_prof_accum = false; char opt_prof_prefix[PROF_DUMP_FILENAME_LEN]; bool opt_prof_sys_thread_name = false; bool opt_prof_unbias = true; /* Accessed via prof_sample_event_handler(). */ static counter_accum_t prof_idump_accumulated; /* * Initialized as opt_prof_active, and accessed via * prof_active_[gs]et{_unlocked,}(). */ bool prof_active_state; static malloc_mutex_t prof_active_mtx; /* * Initialized as opt_prof_thread_active_init, and accessed via * prof_thread_active_init_[gs]et(). */ static bool prof_thread_active_init; static malloc_mutex_t prof_thread_active_init_mtx; /* * Initialized as opt_prof_gdump, and accessed via * prof_gdump_[gs]et{_unlocked,}(). */ bool prof_gdump_val; static malloc_mutex_t prof_gdump_mtx; uint64_t prof_interval = 0; size_t lg_prof_sample; static uint64_t next_thr_uid; static malloc_mutex_t next_thr_uid_mtx; /* Do not dump any profiles until bootstrapping is complete. */ bool prof_booted = false; /* Logically a prof_backtrace_hook_t. */ atomic_p_t prof_backtrace_hook; /* Logically a prof_dump_hook_t. */ atomic_p_t prof_dump_hook; /******************************************************************************/ void prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx) { cassert(config_prof); if (tsd_reentrancy_level_get(tsd) > 0) { assert((uintptr_t)tctx == (uintptr_t)1U); return; } if ((uintptr_t)tctx > (uintptr_t)1U) { malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); tctx->prepared = false; prof_tctx_try_destroy(tsd, tctx); } } void prof_malloc_sample_object(tsd_t *tsd, const void *ptr, size_t size, size_t usize, prof_tctx_t *tctx) { cassert(config_prof); if (opt_prof_sys_thread_name) { prof_sys_thread_name_fetch(tsd); } edata_t *edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); prof_info_set(tsd, edata, tctx, size); szind_t szind = sz_size2index(usize); malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); /* * We need to do these map lookups while holding the lock, to avoid the * possibility of races with prof_reset calls, which update the map and * then acquire the lock. This actually still leaves a data race on the * contents of the unbias map, but we have not yet gone through and * atomic-ified the prof module, and compilers are not yet causing us * issues. The key thing is to make sure that, if we read garbage data, * the prof_reset call is about to mark our tctx as expired before any * dumping of our corrupted output is attempted. */ size_t shifted_unbiased_cnt = prof_shifted_unbiased_cnt[szind]; size_t unbiased_bytes = prof_unbiased_sz[szind]; tctx->cnts.curobjs++; tctx->cnts.curobjs_shifted_unbiased += shifted_unbiased_cnt; tctx->cnts.curbytes += usize; tctx->cnts.curbytes_unbiased += unbiased_bytes; if (opt_prof_accum) { tctx->cnts.accumobjs++; tctx->cnts.accumobjs_shifted_unbiased += shifted_unbiased_cnt; tctx->cnts.accumbytes += usize; tctx->cnts.accumbytes_unbiased += unbiased_bytes; } bool record_recent = prof_recent_alloc_prepare(tsd, tctx); tctx->prepared = false; malloc_mutex_unlock(tsd_tsdn(tsd), tctx->tdata->lock); if (record_recent) { assert(tctx == edata_prof_tctx_get(edata)); prof_recent_alloc(tsd, edata, size, usize); } if (opt_prof_stats) { prof_stats_inc(tsd, szind, size); } } void prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_info_t *prof_info) { cassert(config_prof); assert(prof_info != NULL); prof_tctx_t *tctx = prof_info->alloc_tctx; assert((uintptr_t)tctx > (uintptr_t)1U); szind_t szind = sz_size2index(usize); malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); assert(tctx->cnts.curobjs > 0); assert(tctx->cnts.curbytes >= usize); /* * It's not correct to do equivalent asserts for unbiased bytes, because * of the potential for races with prof.reset calls. The map contents * should really be atomic, but we have not atomic-ified the prof module * yet. */ tctx->cnts.curobjs--; tctx->cnts.curobjs_shifted_unbiased -= prof_shifted_unbiased_cnt[szind]; tctx->cnts.curbytes -= usize; tctx->cnts.curbytes_unbiased -= prof_unbiased_sz[szind]; prof_try_log(tsd, usize, prof_info); prof_tctx_try_destroy(tsd, tctx); if (opt_prof_stats) { prof_stats_dec(tsd, szind, prof_info->alloc_size); } } prof_tctx_t * prof_tctx_create(tsd_t *tsd) { if (!tsd_nominal(tsd) || tsd_reentrancy_level_get(tsd) > 0) { return NULL; } prof_tdata_t *tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return NULL; } prof_bt_t bt; bt_init(&bt, tdata->vec); prof_backtrace(tsd, &bt); return prof_lookup(tsd, &bt); } /* * The bodies of this function and prof_leakcheck() are compiled out unless heap * profiling is enabled, so that it is possible to compile jemalloc with * floating point support completely disabled. Avoiding floating point code is * important on memory-constrained systems, but it also enables a workaround for * versions of glibc that don't properly save/restore floating point registers * during dynamic lazy symbol loading (which internally calls into whatever * malloc implementation happens to be integrated into the application). Note * that some compilers (e.g. gcc 4.8) may use floating point registers for fast * memory moves, so jemalloc must be compiled with such optimizations disabled * (e.g. * -mno-sse) in order for the workaround to be complete. */ uint64_t prof_sample_new_event_wait(tsd_t *tsd) { #ifdef JEMALLOC_PROF if (lg_prof_sample == 0) { return TE_MIN_START_WAIT; } /* * Compute sample interval as a geometrically distributed random * variable with mean (2^lg_prof_sample). * * __ __ * | log(u) | 1 * bytes_until_sample = | -------- |, where p = --------------- * | log(1-p) | lg_prof_sample * 2 * * For more information on the math, see: * * Non-Uniform Random Variate Generation * Luc Devroye * Springer-Verlag, New York, 1986 * pp 500 * (http://luc.devroye.org/rnbookindex.html) * * In the actual computation, there's a non-zero probability that our * pseudo random number generator generates an exact 0, and to avoid * log(0), we set u to 1.0 in case r is 0. Therefore u effectively is * uniformly distributed in (0, 1] instead of [0, 1). Further, rather * than taking the ceiling, we take the floor and then add 1, since * otherwise bytes_until_sample would be 0 if u is exactly 1.0. */ uint64_t r = prng_lg_range_u64(tsd_prng_statep_get(tsd), 53); double u = (r == 0U) ? 1.0 : (double)r * (1.0/9007199254740992.0L); return (uint64_t)(log(u) / log(1.0 - (1.0 / (double)((uint64_t)1U << lg_prof_sample)))) + (uint64_t)1U; #else not_reached(); return TE_MAX_START_WAIT; #endif } uint64_t prof_sample_postponed_event_wait(tsd_t *tsd) { /* * The postponed wait time for prof sample event is computed as if we * want a new wait time (i.e. as if the event were triggered). If we * instead postpone to the immediate next allocation, like how we're * handling the other events, then we can have sampling bias, if e.g. * the allocation immediately following a reentrancy always comes from * the same stack trace. */ return prof_sample_new_event_wait(tsd); } void prof_sample_event_handler(tsd_t *tsd, uint64_t elapsed) { cassert(config_prof); assert(elapsed > 0 && elapsed != TE_INVALID_ELAPSED); if (prof_interval == 0 || !prof_active_get_unlocked()) { return; } if (counter_accum(tsd_tsdn(tsd), &prof_idump_accumulated, elapsed)) { prof_idump(tsd_tsdn(tsd)); } } static void prof_fdump(void) { tsd_t *tsd; cassert(config_prof); assert(opt_prof_final); if (!prof_booted) { return; } tsd = tsd_fetch(); assert(tsd_reentrancy_level_get(tsd) == 0); prof_fdump_impl(tsd); } static bool prof_idump_accum_init(void) { cassert(config_prof); return counter_accum_init(&prof_idump_accumulated, prof_interval); } void prof_idump(tsdn_t *tsdn) { tsd_t *tsd; prof_tdata_t *tdata; cassert(config_prof); if (!prof_booted || tsdn_null(tsdn) || !prof_active_get_unlocked()) { return; } tsd = tsdn_tsd(tsdn); if (tsd_reentrancy_level_get(tsd) > 0) { return; } tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return; } if (tdata->enq) { tdata->enq_idump = true; return; } prof_idump_impl(tsd); } bool prof_mdump(tsd_t *tsd, const char *filename) { cassert(config_prof); assert(tsd_reentrancy_level_get(tsd) == 0); if (!opt_prof || !prof_booted) { return true; } return prof_mdump_impl(tsd, filename); } void prof_gdump(tsdn_t *tsdn) { tsd_t *tsd; prof_tdata_t *tdata; cassert(config_prof); if (!prof_booted || tsdn_null(tsdn) || !prof_active_get_unlocked()) { return; } tsd = tsdn_tsd(tsdn); if (tsd_reentrancy_level_get(tsd) > 0) { return; } tdata = prof_tdata_get(tsd, false); if (tdata == NULL) { return; } if (tdata->enq) { tdata->enq_gdump = true; return; } prof_gdump_impl(tsd); } static uint64_t prof_thr_uid_alloc(tsdn_t *tsdn) { uint64_t thr_uid; malloc_mutex_lock(tsdn, &next_thr_uid_mtx); thr_uid = next_thr_uid; next_thr_uid++; malloc_mutex_unlock(tsdn, &next_thr_uid_mtx); return thr_uid; } prof_tdata_t * prof_tdata_init(tsd_t *tsd) { return prof_tdata_init_impl(tsd, prof_thr_uid_alloc(tsd_tsdn(tsd)), 0, NULL, prof_thread_active_init_get(tsd_tsdn(tsd))); } prof_tdata_t * prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata) { uint64_t thr_uid = tdata->thr_uid; uint64_t thr_discrim = tdata->thr_discrim + 1; char *thread_name = (tdata->thread_name != NULL) ? prof_thread_name_alloc(tsd, tdata->thread_name) : NULL; bool active = tdata->active; prof_tdata_detach(tsd, tdata); return prof_tdata_init_impl(tsd, thr_uid, thr_discrim, thread_name, active); } void prof_tdata_cleanup(tsd_t *tsd) { prof_tdata_t *tdata; if (!config_prof) { return; } tdata = tsd_prof_tdata_get(tsd); if (tdata != NULL) { prof_tdata_detach(tsd, tdata); } } bool prof_active_get(tsdn_t *tsdn) { bool prof_active_current; prof_active_assert(); malloc_mutex_lock(tsdn, &prof_active_mtx); prof_active_current = prof_active_state; malloc_mutex_unlock(tsdn, &prof_active_mtx); return prof_active_current; } bool prof_active_set(tsdn_t *tsdn, bool active) { bool prof_active_old; prof_active_assert(); malloc_mutex_lock(tsdn, &prof_active_mtx); prof_active_old = prof_active_state; prof_active_state = active; malloc_mutex_unlock(tsdn, &prof_active_mtx); prof_active_assert(); return prof_active_old; } const char * prof_thread_name_get(tsd_t *tsd) { assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t *tdata; tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return ""; } return (tdata->thread_name != NULL ? tdata->thread_name : ""); } int prof_thread_name_set(tsd_t *tsd, const char *thread_name) { if (opt_prof_sys_thread_name) { return ENOENT; } else { return prof_thread_name_set_impl(tsd, thread_name); } } bool prof_thread_active_get(tsd_t *tsd) { assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t *tdata; tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return false; } return tdata->active; } bool prof_thread_active_set(tsd_t *tsd, bool active) { assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t *tdata; tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return true; } tdata->active = active; return false; } bool prof_thread_active_init_get(tsdn_t *tsdn) { bool active_init; malloc_mutex_lock(tsdn, &prof_thread_active_init_mtx); active_init = prof_thread_active_init; malloc_mutex_unlock(tsdn, &prof_thread_active_init_mtx); return active_init; } bool prof_thread_active_init_set(tsdn_t *tsdn, bool active_init) { bool active_init_old; malloc_mutex_lock(tsdn, &prof_thread_active_init_mtx); active_init_old = prof_thread_active_init; prof_thread_active_init = active_init; malloc_mutex_unlock(tsdn, &prof_thread_active_init_mtx); return active_init_old; } bool prof_gdump_get(tsdn_t *tsdn) { bool prof_gdump_current; malloc_mutex_lock(tsdn, &prof_gdump_mtx); prof_gdump_current = prof_gdump_val; malloc_mutex_unlock(tsdn, &prof_gdump_mtx); return prof_gdump_current; } bool prof_gdump_set(tsdn_t *tsdn, bool gdump) { bool prof_gdump_old; malloc_mutex_lock(tsdn, &prof_gdump_mtx); prof_gdump_old = prof_gdump_val; prof_gdump_val = gdump; malloc_mutex_unlock(tsdn, &prof_gdump_mtx); return prof_gdump_old; } void prof_backtrace_hook_set(prof_backtrace_hook_t hook) { atomic_store_p(&prof_backtrace_hook, hook, ATOMIC_RELEASE); } prof_backtrace_hook_t prof_backtrace_hook_get() { return (prof_backtrace_hook_t)atomic_load_p(&prof_backtrace_hook, ATOMIC_ACQUIRE); } void prof_dump_hook_set(prof_dump_hook_t hook) { atomic_store_p(&prof_dump_hook, hook, ATOMIC_RELEASE); } prof_dump_hook_t prof_dump_hook_get() { return (prof_dump_hook_t)atomic_load_p(&prof_dump_hook, ATOMIC_ACQUIRE); } void prof_boot0(void) { cassert(config_prof); memcpy(opt_prof_prefix, PROF_PREFIX_DEFAULT, sizeof(PROF_PREFIX_DEFAULT)); } void prof_boot1(void) { cassert(config_prof); /* * opt_prof must be in its final state before any arenas are * initialized, so this function must be executed early. */ if (opt_prof_leak_error && !opt_prof_leak) { opt_prof_leak = true; } if (opt_prof_leak && !opt_prof) { /* * Enable opt_prof, but in such a way that profiles are never * automatically dumped. */ opt_prof = true; opt_prof_gdump = false; } else if (opt_prof) { if (opt_lg_prof_interval >= 0) { prof_interval = (((uint64_t)1U) << opt_lg_prof_interval); } } } bool prof_boot2(tsd_t *tsd, base_t *base) { cassert(config_prof); /* * Initialize the global mutexes unconditionally to maintain correct * stats when opt_prof is false. */ if (malloc_mutex_init(&prof_active_mtx, "prof_active", WITNESS_RANK_PROF_ACTIVE, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&prof_gdump_mtx, "prof_gdump", WITNESS_RANK_PROF_GDUMP, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&prof_thread_active_init_mtx, "prof_thread_active_init", WITNESS_RANK_PROF_THREAD_ACTIVE_INIT, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&bt2gctx_mtx, "prof_bt2gctx", WITNESS_RANK_PROF_BT2GCTX, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&tdatas_mtx, "prof_tdatas", WITNESS_RANK_PROF_TDATAS, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&next_thr_uid_mtx, "prof_next_thr_uid", WITNESS_RANK_PROF_NEXT_THR_UID, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&prof_stats_mtx, "prof_stats", WITNESS_RANK_PROF_STATS, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&prof_dump_filename_mtx, "prof_dump_filename", WITNESS_RANK_PROF_DUMP_FILENAME, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&prof_dump_mtx, "prof_dump", WITNESS_RANK_PROF_DUMP, malloc_mutex_rank_exclusive)) { return true; } if (opt_prof) { lg_prof_sample = opt_lg_prof_sample; prof_unbias_map_init(); prof_active_state = opt_prof_active; prof_gdump_val = opt_prof_gdump; prof_thread_active_init = opt_prof_thread_active_init; if (prof_data_init(tsd)) { return true; } next_thr_uid = 0; if (prof_idump_accum_init()) { return true; } if (opt_prof_final && opt_prof_prefix[0] != '\0' && atexit(prof_fdump) != 0) { malloc_write(": Error in atexit()\n"); if (opt_abort) { abort(); } } if (prof_log_init(tsd)) { return true; } if (prof_recent_init()) { return true; } prof_base = base; gctx_locks = (malloc_mutex_t *)base_alloc(tsd_tsdn(tsd), base, PROF_NCTX_LOCKS * sizeof(malloc_mutex_t), CACHELINE); if (gctx_locks == NULL) { return true; } for (unsigned i = 0; i < PROF_NCTX_LOCKS; i++) { if (malloc_mutex_init(&gctx_locks[i], "prof_gctx", WITNESS_RANK_PROF_GCTX, malloc_mutex_rank_exclusive)) { return true; } } tdata_locks = (malloc_mutex_t *)base_alloc(tsd_tsdn(tsd), base, PROF_NTDATA_LOCKS * sizeof(malloc_mutex_t), CACHELINE); if (tdata_locks == NULL) { return true; } for (unsigned i = 0; i < PROF_NTDATA_LOCKS; i++) { if (malloc_mutex_init(&tdata_locks[i], "prof_tdata", WITNESS_RANK_PROF_TDATA, malloc_mutex_rank_exclusive)) { return true; } } prof_unwind_init(); prof_hooks_init(); } prof_booted = true; return false; } void prof_prefork0(tsdn_t *tsdn) { if (config_prof && opt_prof) { unsigned i; malloc_mutex_prefork(tsdn, &prof_dump_mtx); malloc_mutex_prefork(tsdn, &bt2gctx_mtx); malloc_mutex_prefork(tsdn, &tdatas_mtx); for (i = 0; i < PROF_NTDATA_LOCKS; i++) { malloc_mutex_prefork(tsdn, &tdata_locks[i]); } malloc_mutex_prefork(tsdn, &log_mtx); for (i = 0; i < PROF_NCTX_LOCKS; i++) { malloc_mutex_prefork(tsdn, &gctx_locks[i]); } malloc_mutex_prefork(tsdn, &prof_recent_dump_mtx); } } void prof_prefork1(tsdn_t *tsdn) { if (config_prof && opt_prof) { counter_prefork(tsdn, &prof_idump_accumulated); malloc_mutex_prefork(tsdn, &prof_active_mtx); malloc_mutex_prefork(tsdn, &prof_dump_filename_mtx); malloc_mutex_prefork(tsdn, &prof_gdump_mtx); malloc_mutex_prefork(tsdn, &prof_recent_alloc_mtx); malloc_mutex_prefork(tsdn, &prof_stats_mtx); malloc_mutex_prefork(tsdn, &next_thr_uid_mtx); malloc_mutex_prefork(tsdn, &prof_thread_active_init_mtx); } } void prof_postfork_parent(tsdn_t *tsdn) { if (config_prof && opt_prof) { unsigned i; malloc_mutex_postfork_parent(tsdn, &prof_thread_active_init_mtx); malloc_mutex_postfork_parent(tsdn, &next_thr_uid_mtx); malloc_mutex_postfork_parent(tsdn, &prof_stats_mtx); malloc_mutex_postfork_parent(tsdn, &prof_recent_alloc_mtx); malloc_mutex_postfork_parent(tsdn, &prof_gdump_mtx); malloc_mutex_postfork_parent(tsdn, &prof_dump_filename_mtx); malloc_mutex_postfork_parent(tsdn, &prof_active_mtx); counter_postfork_parent(tsdn, &prof_idump_accumulated); malloc_mutex_postfork_parent(tsdn, &prof_recent_dump_mtx); for (i = 0; i < PROF_NCTX_LOCKS; i++) { malloc_mutex_postfork_parent(tsdn, &gctx_locks[i]); } malloc_mutex_postfork_parent(tsdn, &log_mtx); for (i = 0; i < PROF_NTDATA_LOCKS; i++) { malloc_mutex_postfork_parent(tsdn, &tdata_locks[i]); } malloc_mutex_postfork_parent(tsdn, &tdatas_mtx); malloc_mutex_postfork_parent(tsdn, &bt2gctx_mtx); malloc_mutex_postfork_parent(tsdn, &prof_dump_mtx); } } void prof_postfork_child(tsdn_t *tsdn) { if (config_prof && opt_prof) { unsigned i; malloc_mutex_postfork_child(tsdn, &prof_thread_active_init_mtx); malloc_mutex_postfork_child(tsdn, &next_thr_uid_mtx); malloc_mutex_postfork_child(tsdn, &prof_stats_mtx); malloc_mutex_postfork_child(tsdn, &prof_recent_alloc_mtx); malloc_mutex_postfork_child(tsdn, &prof_gdump_mtx); malloc_mutex_postfork_child(tsdn, &prof_dump_filename_mtx); malloc_mutex_postfork_child(tsdn, &prof_active_mtx); counter_postfork_child(tsdn, &prof_idump_accumulated); malloc_mutex_postfork_child(tsdn, &prof_recent_dump_mtx); for (i = 0; i < PROF_NCTX_LOCKS; i++) { malloc_mutex_postfork_child(tsdn, &gctx_locks[i]); } malloc_mutex_postfork_child(tsdn, &log_mtx); for (i = 0; i < PROF_NTDATA_LOCKS; i++) { malloc_mutex_postfork_child(tsdn, &tdata_locks[i]); } malloc_mutex_postfork_child(tsdn, &tdatas_mtx); malloc_mutex_postfork_child(tsdn, &bt2gctx_mtx); malloc_mutex_postfork_child(tsdn, &prof_dump_mtx); } } /******************************************************************************/ redis-8.0.2/deps/jemalloc/src/prof_data.c000066400000000000000000001165371501533116600202600ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/ckh.h" #include "jemalloc/internal/hash.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/prof_data.h" /* * This file defines and manages the core profiling data structures. * * Conceptually, profiling data can be imagined as a table with three columns: * thread, stack trace, and current allocation size. (When prof_accum is on, * there's one additional column which is the cumulative allocation size.) * * Implementation wise, each thread maintains a hash recording the stack trace * to allocation size correspondences, which are basically the individual rows * in the table. In addition, two global "indices" are built to make data * aggregation efficient (for dumping): bt2gctx and tdatas, which are basically * the "grouped by stack trace" and "grouped by thread" views of the same table, * respectively. Note that the allocation size is only aggregated to the two * indices at dumping time, so as to optimize for performance. */ /******************************************************************************/ malloc_mutex_t bt2gctx_mtx; malloc_mutex_t tdatas_mtx; malloc_mutex_t prof_dump_mtx; /* * Table of mutexes that are shared among gctx's. These are leaf locks, so * there is no problem with using them for more than one gctx at the same time. * The primary motivation for this sharing though is that gctx's are ephemeral, * and destroying mutexes causes complications for systems that allocate when * creating/destroying mutexes. */ malloc_mutex_t *gctx_locks; static atomic_u_t cum_gctxs; /* Atomic counter. */ /* * Table of mutexes that are shared among tdata's. No operations require * holding multiple tdata locks, so there is no problem with using them for more * than one tdata at the same time, even though a gctx lock may be acquired * while holding a tdata lock. */ malloc_mutex_t *tdata_locks; /* * Global hash of (prof_bt_t *)-->(prof_gctx_t *). This is the master data * structure that knows about all backtraces currently captured. */ static ckh_t bt2gctx; /* * Tree of all extant prof_tdata_t structures, regardless of state, * {attached,detached,expired}. */ static prof_tdata_tree_t tdatas; size_t prof_unbiased_sz[PROF_SC_NSIZES]; size_t prof_shifted_unbiased_cnt[PROF_SC_NSIZES]; /******************************************************************************/ /* Red-black trees. */ static int prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b) { uint64_t a_thr_uid = a->thr_uid; uint64_t b_thr_uid = b->thr_uid; int ret = (a_thr_uid > b_thr_uid) - (a_thr_uid < b_thr_uid); if (ret == 0) { uint64_t a_thr_discrim = a->thr_discrim; uint64_t b_thr_discrim = b->thr_discrim; ret = (a_thr_discrim > b_thr_discrim) - (a_thr_discrim < b_thr_discrim); if (ret == 0) { uint64_t a_tctx_uid = a->tctx_uid; uint64_t b_tctx_uid = b->tctx_uid; ret = (a_tctx_uid > b_tctx_uid) - (a_tctx_uid < b_tctx_uid); } } return ret; } rb_gen(static UNUSED, tctx_tree_, prof_tctx_tree_t, prof_tctx_t, tctx_link, prof_tctx_comp) static int prof_gctx_comp(const prof_gctx_t *a, const prof_gctx_t *b) { unsigned a_len = a->bt.len; unsigned b_len = b->bt.len; unsigned comp_len = (a_len < b_len) ? a_len : b_len; int ret = memcmp(a->bt.vec, b->bt.vec, comp_len * sizeof(void *)); if (ret == 0) { ret = (a_len > b_len) - (a_len < b_len); } return ret; } rb_gen(static UNUSED, gctx_tree_, prof_gctx_tree_t, prof_gctx_t, dump_link, prof_gctx_comp) static int prof_tdata_comp(const prof_tdata_t *a, const prof_tdata_t *b) { int ret; uint64_t a_uid = a->thr_uid; uint64_t b_uid = b->thr_uid; ret = ((a_uid > b_uid) - (a_uid < b_uid)); if (ret == 0) { uint64_t a_discrim = a->thr_discrim; uint64_t b_discrim = b->thr_discrim; ret = ((a_discrim > b_discrim) - (a_discrim < b_discrim)); } return ret; } rb_gen(static UNUSED, tdata_tree_, prof_tdata_tree_t, prof_tdata_t, tdata_link, prof_tdata_comp) /******************************************************************************/ static malloc_mutex_t * prof_gctx_mutex_choose(void) { unsigned ngctxs = atomic_fetch_add_u(&cum_gctxs, 1, ATOMIC_RELAXED); return &gctx_locks[(ngctxs - 1) % PROF_NCTX_LOCKS]; } static malloc_mutex_t * prof_tdata_mutex_choose(uint64_t thr_uid) { return &tdata_locks[thr_uid % PROF_NTDATA_LOCKS]; } bool prof_data_init(tsd_t *tsd) { tdata_tree_new(&tdatas); return ckh_new(tsd, &bt2gctx, PROF_CKH_MINITEMS, prof_bt_hash, prof_bt_keycomp); } static void prof_enter(tsd_t *tsd, prof_tdata_t *tdata) { cassert(config_prof); assert(tdata == prof_tdata_get(tsd, false)); if (tdata != NULL) { assert(!tdata->enq); tdata->enq = true; } malloc_mutex_lock(tsd_tsdn(tsd), &bt2gctx_mtx); } static void prof_leave(tsd_t *tsd, prof_tdata_t *tdata) { cassert(config_prof); assert(tdata == prof_tdata_get(tsd, false)); malloc_mutex_unlock(tsd_tsdn(tsd), &bt2gctx_mtx); if (tdata != NULL) { bool idump, gdump; assert(tdata->enq); tdata->enq = false; idump = tdata->enq_idump; tdata->enq_idump = false; gdump = tdata->enq_gdump; tdata->enq_gdump = false; if (idump) { prof_idump(tsd_tsdn(tsd)); } if (gdump) { prof_gdump(tsd_tsdn(tsd)); } } } static prof_gctx_t * prof_gctx_create(tsdn_t *tsdn, prof_bt_t *bt) { /* * Create a single allocation that has space for vec of length bt->len. */ size_t size = offsetof(prof_gctx_t, vec) + (bt->len * sizeof(void *)); prof_gctx_t *gctx = (prof_gctx_t *)iallocztm(tsdn, size, sz_size2index(size), false, NULL, true, arena_get(TSDN_NULL, 0, true), true); if (gctx == NULL) { return NULL; } gctx->lock = prof_gctx_mutex_choose(); /* * Set nlimbo to 1, in order to avoid a race condition with * prof_tctx_destroy()/prof_gctx_try_destroy(). */ gctx->nlimbo = 1; tctx_tree_new(&gctx->tctxs); /* Duplicate bt. */ memcpy(gctx->vec, bt->vec, bt->len * sizeof(void *)); gctx->bt.vec = gctx->vec; gctx->bt.len = bt->len; return gctx; } static void prof_gctx_try_destroy(tsd_t *tsd, prof_tdata_t *tdata_self, prof_gctx_t *gctx) { cassert(config_prof); /* * Check that gctx is still unused by any thread cache before destroying * it. prof_lookup() increments gctx->nlimbo in order to avoid a race * condition with this function, as does prof_tctx_destroy() in order to * avoid a race between the main body of prof_tctx_destroy() and entry * into this function. */ prof_enter(tsd, tdata_self); malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); assert(gctx->nlimbo != 0); if (tctx_tree_empty(&gctx->tctxs) && gctx->nlimbo == 1) { /* Remove gctx from bt2gctx. */ if (ckh_remove(tsd, &bt2gctx, &gctx->bt, NULL, NULL)) { not_reached(); } prof_leave(tsd, tdata_self); /* Destroy gctx. */ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); idalloctm(tsd_tsdn(tsd), gctx, NULL, NULL, true, true); } else { /* * Compensate for increment in prof_tctx_destroy() or * prof_lookup(). */ gctx->nlimbo--; malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); prof_leave(tsd, tdata_self); } } static bool prof_gctx_should_destroy(prof_gctx_t *gctx) { if (opt_prof_accum) { return false; } if (!tctx_tree_empty(&gctx->tctxs)) { return false; } if (gctx->nlimbo != 0) { return false; } return true; } static bool prof_lookup_global(tsd_t *tsd, prof_bt_t *bt, prof_tdata_t *tdata, void **p_btkey, prof_gctx_t **p_gctx, bool *p_new_gctx) { union { prof_gctx_t *p; void *v; } gctx, tgctx; union { prof_bt_t *p; void *v; } btkey; bool new_gctx; prof_enter(tsd, tdata); if (ckh_search(&bt2gctx, bt, &btkey.v, &gctx.v)) { /* bt has never been seen before. Insert it. */ prof_leave(tsd, tdata); tgctx.p = prof_gctx_create(tsd_tsdn(tsd), bt); if (tgctx.v == NULL) { return true; } prof_enter(tsd, tdata); if (ckh_search(&bt2gctx, bt, &btkey.v, &gctx.v)) { gctx.p = tgctx.p; btkey.p = &gctx.p->bt; if (ckh_insert(tsd, &bt2gctx, btkey.v, gctx.v)) { /* OOM. */ prof_leave(tsd, tdata); idalloctm(tsd_tsdn(tsd), gctx.v, NULL, NULL, true, true); return true; } new_gctx = true; } else { new_gctx = false; } } else { tgctx.v = NULL; new_gctx = false; } if (!new_gctx) { /* * Increment nlimbo, in order to avoid a race condition with * prof_tctx_destroy()/prof_gctx_try_destroy(). */ malloc_mutex_lock(tsd_tsdn(tsd), gctx.p->lock); gctx.p->nlimbo++; malloc_mutex_unlock(tsd_tsdn(tsd), gctx.p->lock); new_gctx = false; if (tgctx.v != NULL) { /* Lost race to insert. */ idalloctm(tsd_tsdn(tsd), tgctx.v, NULL, NULL, true, true); } } prof_leave(tsd, tdata); *p_btkey = btkey.v; *p_gctx = gctx.p; *p_new_gctx = new_gctx; return false; } prof_tctx_t * prof_lookup(tsd_t *tsd, prof_bt_t *bt) { union { prof_tctx_t *p; void *v; } ret; prof_tdata_t *tdata; bool not_found; cassert(config_prof); tdata = prof_tdata_get(tsd, false); assert(tdata != NULL); malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock); not_found = ckh_search(&tdata->bt2tctx, bt, NULL, &ret.v); if (!not_found) { /* Note double negative! */ ret.p->prepared = true; } malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); if (not_found) { void *btkey; prof_gctx_t *gctx; bool new_gctx, error; /* * This thread's cache lacks bt. Look for it in the global * cache. */ if (prof_lookup_global(tsd, bt, tdata, &btkey, &gctx, &new_gctx)) { return NULL; } /* Link a prof_tctx_t into gctx for this thread. */ ret.v = iallocztm(tsd_tsdn(tsd), sizeof(prof_tctx_t), sz_size2index(sizeof(prof_tctx_t)), false, NULL, true, arena_ichoose(tsd, NULL), true); if (ret.p == NULL) { if (new_gctx) { prof_gctx_try_destroy(tsd, tdata, gctx); } return NULL; } ret.p->tdata = tdata; ret.p->thr_uid = tdata->thr_uid; ret.p->thr_discrim = tdata->thr_discrim; ret.p->recent_count = 0; memset(&ret.p->cnts, 0, sizeof(prof_cnt_t)); ret.p->gctx = gctx; ret.p->tctx_uid = tdata->tctx_uid_next++; ret.p->prepared = true; ret.p->state = prof_tctx_state_initializing; malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock); error = ckh_insert(tsd, &tdata->bt2tctx, btkey, ret.v); malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); if (error) { if (new_gctx) { prof_gctx_try_destroy(tsd, tdata, gctx); } idalloctm(tsd_tsdn(tsd), ret.v, NULL, NULL, true, true); return NULL; } malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); ret.p->state = prof_tctx_state_nominal; tctx_tree_insert(&gctx->tctxs, ret.p); gctx->nlimbo--; malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); } return ret.p; } /* Used in unit tests. */ static prof_tdata_t * prof_tdata_count_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, void *arg) { size_t *tdata_count = (size_t *)arg; (*tdata_count)++; return NULL; } /* Used in unit tests. */ size_t prof_tdata_count(void) { size_t tdata_count = 0; tsdn_t *tsdn; tsdn = tsdn_fetch(); malloc_mutex_lock(tsdn, &tdatas_mtx); tdata_tree_iter(&tdatas, NULL, prof_tdata_count_iter, (void *)&tdata_count); malloc_mutex_unlock(tsdn, &tdatas_mtx); return tdata_count; } /* Used in unit tests. */ size_t prof_bt_count(void) { size_t bt_count; tsd_t *tsd; prof_tdata_t *tdata; tsd = tsd_fetch(); tdata = prof_tdata_get(tsd, false); if (tdata == NULL) { return 0; } malloc_mutex_lock(tsd_tsdn(tsd), &bt2gctx_mtx); bt_count = ckh_count(&bt2gctx); malloc_mutex_unlock(tsd_tsdn(tsd), &bt2gctx_mtx); return bt_count; } char * prof_thread_name_alloc(tsd_t *tsd, const char *thread_name) { char *ret; size_t size; if (thread_name == NULL) { return NULL; } size = strlen(thread_name) + 1; if (size == 1) { return ""; } ret = iallocztm(tsd_tsdn(tsd), size, sz_size2index(size), false, NULL, true, arena_get(TSDN_NULL, 0, true), true); if (ret == NULL) { return NULL; } memcpy(ret, thread_name, size); return ret; } int prof_thread_name_set_impl(tsd_t *tsd, const char *thread_name) { assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t *tdata; unsigned i; char *s; tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return EAGAIN; } /* Validate input. */ if (thread_name == NULL) { return EFAULT; } for (i = 0; thread_name[i] != '\0'; i++) { char c = thread_name[i]; if (!isgraph(c) && !isblank(c)) { return EFAULT; } } s = prof_thread_name_alloc(tsd, thread_name); if (s == NULL) { return EAGAIN; } if (tdata->thread_name != NULL) { idalloctm(tsd_tsdn(tsd), tdata->thread_name, NULL, NULL, true, true); tdata->thread_name = NULL; } if (strlen(s) > 0) { tdata->thread_name = s; } return 0; } JEMALLOC_FORMAT_PRINTF(3, 4) static void prof_dump_printf(write_cb_t *prof_dump_write, void *cbopaque, const char *format, ...) { va_list ap; char buf[PROF_PRINTF_BUFSIZE]; va_start(ap, format); malloc_vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); prof_dump_write(cbopaque, buf); } /* * Casting a double to a uint64_t may not necessarily be in range; this can be * UB. I don't think this is practically possible with the cur counters, but * plausibly could be with the accum counters. */ #ifdef JEMALLOC_PROF static uint64_t prof_double_uint64_cast(double d) { /* * Note: UINT64_MAX + 1 is exactly representable as a double on all * reasonable platforms (certainly those we'll support). Writing this * as !(a < b) instead of (a >= b) means that we're NaN-safe. */ double rounded = round(d); if (!(rounded < (double)UINT64_MAX)) { return UINT64_MAX; } return (uint64_t)rounded; } #endif void prof_unbias_map_init() { /* See the comment in prof_sample_new_event_wait */ #ifdef JEMALLOC_PROF for (szind_t i = 0; i < SC_NSIZES; i++) { double sz = (double)sz_index2size(i); double rate = (double)(ZU(1) << lg_prof_sample); double div_val = 1.0 - exp(-sz / rate); double unbiased_sz = sz / div_val; /* * The "true" right value for the unbiased count is * 1.0/(1 - exp(-sz/rate)). The problem is, we keep the counts * as integers (for a variety of reasons -- rounding errors * could trigger asserts, and not all libcs can properly handle * floating point arithmetic during malloc calls inside libc). * Rounding to an integer, though, can lead to rounding errors * of over 30% for sizes close to the sampling rate. So * instead, we multiply by a constant, dividing the maximum * possible roundoff error by that constant. To avoid overflow * in summing up size_t values, the largest safe constant we can * pick is the size of the smallest allocation. */ double cnt_shift = (double)(ZU(1) << SC_LG_TINY_MIN); double shifted_unbiased_cnt = cnt_shift / div_val; prof_unbiased_sz[i] = (size_t)round(unbiased_sz); prof_shifted_unbiased_cnt[i] = (size_t)round( shifted_unbiased_cnt); } #else unreachable(); #endif } /* * The unbiasing story is long. The jeprof unbiasing logic was copied from * pprof. Both shared an issue: they unbiased using the average size of the * allocations at a particular stack trace. This can work out OK if allocations * are mostly of the same size given some stack, but not otherwise. We now * internally track what the unbiased results ought to be. We can't just report * them as they are though; they'll still go through the jeprof unbiasing * process. Instead, we figure out what values we can feed *into* jeprof's * unbiasing mechanism that will lead to getting the right values out. * * It'll unbias count and aggregate size as: * * c_out = c_in * 1/(1-exp(-s_in/c_in/R) * s_out = s_in * 1/(1-exp(-s_in/c_in/R) * * We want to solve for the values of c_in and s_in that will * give the c_out and s_out that we've computed internally. * * Let's do a change of variables (both to make the math easier and to make it * easier to write): * x = s_in / c_in * y = s_in * k = 1/R. * * Then * c_out = y/x * 1/(1-exp(-k*x)) * s_out = y * 1/(1-exp(-k*x)) * * The first equation gives: * y = x * c_out * (1-exp(-k*x)) * The second gives: * y = s_out * (1-exp(-k*x)) * So we have * x = s_out / c_out. * And all the other values fall out from that. * * This is all a fair bit of work. The thing we get out of it is that we don't * break backwards compatibility with jeprof (and the various tools that have * copied its unbiasing logic). Eventually, we anticipate a v3 heap profile * dump format based on JSON, at which point I think much of this logic can get * cleaned up (since we'll be taking a compatibility break there anyways). */ static void prof_do_unbias(uint64_t c_out_shifted_i, uint64_t s_out_i, uint64_t *r_c_in, uint64_t *r_s_in) { #ifdef JEMALLOC_PROF if (c_out_shifted_i == 0 || s_out_i == 0) { *r_c_in = 0; *r_s_in = 0; return; } /* * See the note in prof_unbias_map_init() to see why we take c_out in a * shifted form. */ double c_out = (double)c_out_shifted_i / (double)(ZU(1) << SC_LG_TINY_MIN); double s_out = (double)s_out_i; double R = (double)(ZU(1) << lg_prof_sample); double x = s_out / c_out; double y = s_out * (1.0 - exp(-x / R)); double c_in = y / x; double s_in = y; *r_c_in = prof_double_uint64_cast(c_in); *r_s_in = prof_double_uint64_cast(s_in); #else unreachable(); #endif } static void prof_dump_print_cnts(write_cb_t *prof_dump_write, void *cbopaque, const prof_cnt_t *cnts) { uint64_t curobjs; uint64_t curbytes; uint64_t accumobjs; uint64_t accumbytes; if (opt_prof_unbias) { prof_do_unbias(cnts->curobjs_shifted_unbiased, cnts->curbytes_unbiased, &curobjs, &curbytes); prof_do_unbias(cnts->accumobjs_shifted_unbiased, cnts->accumbytes_unbiased, &accumobjs, &accumbytes); } else { curobjs = cnts->curobjs; curbytes = cnts->curbytes; accumobjs = cnts->accumobjs; accumbytes = cnts->accumbytes; } prof_dump_printf(prof_dump_write, cbopaque, "%"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]", curobjs, curbytes, accumobjs, accumbytes); } static void prof_tctx_merge_tdata(tsdn_t *tsdn, prof_tctx_t *tctx, prof_tdata_t *tdata) { malloc_mutex_assert_owner(tsdn, tctx->tdata->lock); malloc_mutex_lock(tsdn, tctx->gctx->lock); switch (tctx->state) { case prof_tctx_state_initializing: malloc_mutex_unlock(tsdn, tctx->gctx->lock); return; case prof_tctx_state_nominal: tctx->state = prof_tctx_state_dumping; malloc_mutex_unlock(tsdn, tctx->gctx->lock); memcpy(&tctx->dump_cnts, &tctx->cnts, sizeof(prof_cnt_t)); tdata->cnt_summed.curobjs += tctx->dump_cnts.curobjs; tdata->cnt_summed.curobjs_shifted_unbiased += tctx->dump_cnts.curobjs_shifted_unbiased; tdata->cnt_summed.curbytes += tctx->dump_cnts.curbytes; tdata->cnt_summed.curbytes_unbiased += tctx->dump_cnts.curbytes_unbiased; if (opt_prof_accum) { tdata->cnt_summed.accumobjs += tctx->dump_cnts.accumobjs; tdata->cnt_summed.accumobjs_shifted_unbiased += tctx->dump_cnts.accumobjs_shifted_unbiased; tdata->cnt_summed.accumbytes += tctx->dump_cnts.accumbytes; tdata->cnt_summed.accumbytes_unbiased += tctx->dump_cnts.accumbytes_unbiased; } break; case prof_tctx_state_dumping: case prof_tctx_state_purgatory: not_reached(); } } static void prof_tctx_merge_gctx(tsdn_t *tsdn, prof_tctx_t *tctx, prof_gctx_t *gctx) { malloc_mutex_assert_owner(tsdn, gctx->lock); gctx->cnt_summed.curobjs += tctx->dump_cnts.curobjs; gctx->cnt_summed.curobjs_shifted_unbiased += tctx->dump_cnts.curobjs_shifted_unbiased; gctx->cnt_summed.curbytes += tctx->dump_cnts.curbytes; gctx->cnt_summed.curbytes_unbiased += tctx->dump_cnts.curbytes_unbiased; if (opt_prof_accum) { gctx->cnt_summed.accumobjs += tctx->dump_cnts.accumobjs; gctx->cnt_summed.accumobjs_shifted_unbiased += tctx->dump_cnts.accumobjs_shifted_unbiased; gctx->cnt_summed.accumbytes += tctx->dump_cnts.accumbytes; gctx->cnt_summed.accumbytes_unbiased += tctx->dump_cnts.accumbytes_unbiased; } } static prof_tctx_t * prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) { tsdn_t *tsdn = (tsdn_t *)arg; malloc_mutex_assert_owner(tsdn, tctx->gctx->lock); switch (tctx->state) { case prof_tctx_state_nominal: /* New since dumping started; ignore. */ break; case prof_tctx_state_dumping: case prof_tctx_state_purgatory: prof_tctx_merge_gctx(tsdn, tctx, tctx->gctx); break; default: not_reached(); } return NULL; } typedef struct prof_dump_iter_arg_s prof_dump_iter_arg_t; struct prof_dump_iter_arg_s { tsdn_t *tsdn; write_cb_t *prof_dump_write; void *cbopaque; }; static prof_tctx_t * prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *opaque) { prof_dump_iter_arg_t *arg = (prof_dump_iter_arg_t *)opaque; malloc_mutex_assert_owner(arg->tsdn, tctx->gctx->lock); switch (tctx->state) { case prof_tctx_state_initializing: case prof_tctx_state_nominal: /* Not captured by this dump. */ break; case prof_tctx_state_dumping: case prof_tctx_state_purgatory: prof_dump_printf(arg->prof_dump_write, arg->cbopaque, " t%"FMTu64": ", tctx->thr_uid); prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, &tctx->dump_cnts); arg->prof_dump_write(arg->cbopaque, "\n"); break; default: not_reached(); } return NULL; } static prof_tctx_t * prof_tctx_finish_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) { tsdn_t *tsdn = (tsdn_t *)arg; prof_tctx_t *ret; malloc_mutex_assert_owner(tsdn, tctx->gctx->lock); switch (tctx->state) { case prof_tctx_state_nominal: /* New since dumping started; ignore. */ break; case prof_tctx_state_dumping: tctx->state = prof_tctx_state_nominal; break; case prof_tctx_state_purgatory: ret = tctx; goto label_return; default: not_reached(); } ret = NULL; label_return: return ret; } static void prof_dump_gctx_prep(tsdn_t *tsdn, prof_gctx_t *gctx, prof_gctx_tree_t *gctxs) { cassert(config_prof); malloc_mutex_lock(tsdn, gctx->lock); /* * Increment nlimbo so that gctx won't go away before dump. * Additionally, link gctx into the dump list so that it is included in * prof_dump()'s second pass. */ gctx->nlimbo++; gctx_tree_insert(gctxs, gctx); memset(&gctx->cnt_summed, 0, sizeof(prof_cnt_t)); malloc_mutex_unlock(tsdn, gctx->lock); } typedef struct prof_gctx_merge_iter_arg_s prof_gctx_merge_iter_arg_t; struct prof_gctx_merge_iter_arg_s { tsdn_t *tsdn; size_t *leak_ngctx; }; static prof_gctx_t * prof_gctx_merge_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) { prof_gctx_merge_iter_arg_t *arg = (prof_gctx_merge_iter_arg_t *)opaque; malloc_mutex_lock(arg->tsdn, gctx->lock); tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_merge_iter, (void *)arg->tsdn); if (gctx->cnt_summed.curobjs != 0) { (*arg->leak_ngctx)++; } malloc_mutex_unlock(arg->tsdn, gctx->lock); return NULL; } static void prof_gctx_finish(tsd_t *tsd, prof_gctx_tree_t *gctxs) { prof_tdata_t *tdata = prof_tdata_get(tsd, false); prof_gctx_t *gctx; /* * Standard tree iteration won't work here, because as soon as we * decrement gctx->nlimbo and unlock gctx, another thread can * concurrently destroy it, which will corrupt the tree. Therefore, * tear down the tree one node at a time during iteration. */ while ((gctx = gctx_tree_first(gctxs)) != NULL) { gctx_tree_remove(gctxs, gctx); malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); { prof_tctx_t *next; next = NULL; do { prof_tctx_t *to_destroy = tctx_tree_iter(&gctx->tctxs, next, prof_tctx_finish_iter, (void *)tsd_tsdn(tsd)); if (to_destroy != NULL) { next = tctx_tree_next(&gctx->tctxs, to_destroy); tctx_tree_remove(&gctx->tctxs, to_destroy); idalloctm(tsd_tsdn(tsd), to_destroy, NULL, NULL, true, true); } else { next = NULL; } } while (next != NULL); } gctx->nlimbo--; if (prof_gctx_should_destroy(gctx)) { gctx->nlimbo++; malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); prof_gctx_try_destroy(tsd, tdata, gctx); } else { malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); } } } typedef struct prof_tdata_merge_iter_arg_s prof_tdata_merge_iter_arg_t; struct prof_tdata_merge_iter_arg_s { tsdn_t *tsdn; prof_cnt_t *cnt_all; }; static prof_tdata_t * prof_tdata_merge_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, void *opaque) { prof_tdata_merge_iter_arg_t *arg = (prof_tdata_merge_iter_arg_t *)opaque; malloc_mutex_lock(arg->tsdn, tdata->lock); if (!tdata->expired) { size_t tabind; union { prof_tctx_t *p; void *v; } tctx; tdata->dumping = true; memset(&tdata->cnt_summed, 0, sizeof(prof_cnt_t)); for (tabind = 0; !ckh_iter(&tdata->bt2tctx, &tabind, NULL, &tctx.v);) { prof_tctx_merge_tdata(arg->tsdn, tctx.p, tdata); } arg->cnt_all->curobjs += tdata->cnt_summed.curobjs; arg->cnt_all->curobjs_shifted_unbiased += tdata->cnt_summed.curobjs_shifted_unbiased; arg->cnt_all->curbytes += tdata->cnt_summed.curbytes; arg->cnt_all->curbytes_unbiased += tdata->cnt_summed.curbytes_unbiased; if (opt_prof_accum) { arg->cnt_all->accumobjs += tdata->cnt_summed.accumobjs; arg->cnt_all->accumobjs_shifted_unbiased += tdata->cnt_summed.accumobjs_shifted_unbiased; arg->cnt_all->accumbytes += tdata->cnt_summed.accumbytes; arg->cnt_all->accumbytes_unbiased += tdata->cnt_summed.accumbytes_unbiased; } } else { tdata->dumping = false; } malloc_mutex_unlock(arg->tsdn, tdata->lock); return NULL; } static prof_tdata_t * prof_tdata_dump_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, void *opaque) { if (!tdata->dumping) { return NULL; } prof_dump_iter_arg_t *arg = (prof_dump_iter_arg_t *)opaque; prof_dump_printf(arg->prof_dump_write, arg->cbopaque, " t%"FMTu64": ", tdata->thr_uid); prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, &tdata->cnt_summed); if (tdata->thread_name != NULL) { arg->prof_dump_write(arg->cbopaque, " "); arg->prof_dump_write(arg->cbopaque, tdata->thread_name); } arg->prof_dump_write(arg->cbopaque, "\n"); return NULL; } static void prof_dump_header(prof_dump_iter_arg_t *arg, const prof_cnt_t *cnt_all) { prof_dump_printf(arg->prof_dump_write, arg->cbopaque, "heap_v2/%"FMTu64"\n t*: ", ((uint64_t)1U << lg_prof_sample)); prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, cnt_all); arg->prof_dump_write(arg->cbopaque, "\n"); malloc_mutex_lock(arg->tsdn, &tdatas_mtx); tdata_tree_iter(&tdatas, NULL, prof_tdata_dump_iter, arg); malloc_mutex_unlock(arg->tsdn, &tdatas_mtx); } static void prof_dump_gctx(prof_dump_iter_arg_t *arg, prof_gctx_t *gctx, const prof_bt_t *bt, prof_gctx_tree_t *gctxs) { cassert(config_prof); malloc_mutex_assert_owner(arg->tsdn, gctx->lock); /* Avoid dumping such gctx's that have no useful data. */ if ((!opt_prof_accum && gctx->cnt_summed.curobjs == 0) || (opt_prof_accum && gctx->cnt_summed.accumobjs == 0)) { assert(gctx->cnt_summed.curobjs == 0); assert(gctx->cnt_summed.curbytes == 0); /* * These asserts would not be correct -- see the comment on races * in prof.c * assert(gctx->cnt_summed.curobjs_unbiased == 0); * assert(gctx->cnt_summed.curbytes_unbiased == 0); */ assert(gctx->cnt_summed.accumobjs == 0); assert(gctx->cnt_summed.accumobjs_shifted_unbiased == 0); assert(gctx->cnt_summed.accumbytes == 0); assert(gctx->cnt_summed.accumbytes_unbiased == 0); return; } arg->prof_dump_write(arg->cbopaque, "@"); for (unsigned i = 0; i < bt->len; i++) { prof_dump_printf(arg->prof_dump_write, arg->cbopaque, " %#"FMTxPTR, (uintptr_t)bt->vec[i]); } arg->prof_dump_write(arg->cbopaque, "\n t*: "); prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, &gctx->cnt_summed); arg->prof_dump_write(arg->cbopaque, "\n"); tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_dump_iter, arg); } /* * See prof_sample_new_event_wait() comment for why the body of this function * is conditionally compiled. */ static void prof_leakcheck(const prof_cnt_t *cnt_all, size_t leak_ngctx) { #ifdef JEMALLOC_PROF /* * Scaling is equivalent AdjustSamples() in jeprof, but the result may * differ slightly from what jeprof reports, because here we scale the * summary values, whereas jeprof scales each context individually and * reports the sums of the scaled values. */ if (cnt_all->curbytes != 0) { double sample_period = (double)((uint64_t)1 << lg_prof_sample); double ratio = (((double)cnt_all->curbytes) / (double)cnt_all->curobjs) / sample_period; double scale_factor = 1.0 / (1.0 - exp(-ratio)); uint64_t curbytes = (uint64_t)round(((double)cnt_all->curbytes) * scale_factor); uint64_t curobjs = (uint64_t)round(((double)cnt_all->curobjs) * scale_factor); malloc_printf(": Leak approximation summary: ~%"FMTu64 " byte%s, ~%"FMTu64" object%s, >= %zu context%s\n", curbytes, (curbytes != 1) ? "s" : "", curobjs, (curobjs != 1) ? "s" : "", leak_ngctx, (leak_ngctx != 1) ? "s" : ""); malloc_printf( ": Run jeprof on dump output for leak detail\n"); if (opt_prof_leak_error) { malloc_printf( ": Exiting with error code because memory" " leaks were detected\n"); /* * Use _exit() with underscore to avoid calling atexit() * and entering endless cycle. */ _exit(1); } } #endif } static prof_gctx_t * prof_gctx_dump_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) { prof_dump_iter_arg_t *arg = (prof_dump_iter_arg_t *)opaque; malloc_mutex_lock(arg->tsdn, gctx->lock); prof_dump_gctx(arg, gctx, &gctx->bt, gctxs); malloc_mutex_unlock(arg->tsdn, gctx->lock); return NULL; } static void prof_dump_prep(tsd_t *tsd, prof_tdata_t *tdata, prof_cnt_t *cnt_all, size_t *leak_ngctx, prof_gctx_tree_t *gctxs) { size_t tabind; union { prof_gctx_t *p; void *v; } gctx; prof_enter(tsd, tdata); /* * Put gctx's in limbo and clear their counters in preparation for * summing. */ gctx_tree_new(gctxs); for (tabind = 0; !ckh_iter(&bt2gctx, &tabind, NULL, &gctx.v);) { prof_dump_gctx_prep(tsd_tsdn(tsd), gctx.p, gctxs); } /* * Iterate over tdatas, and for the non-expired ones snapshot their tctx * stats and merge them into the associated gctx's. */ memset(cnt_all, 0, sizeof(prof_cnt_t)); prof_tdata_merge_iter_arg_t prof_tdata_merge_iter_arg = {tsd_tsdn(tsd), cnt_all}; malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); tdata_tree_iter(&tdatas, NULL, prof_tdata_merge_iter, &prof_tdata_merge_iter_arg); malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); /* Merge tctx stats into gctx's. */ *leak_ngctx = 0; prof_gctx_merge_iter_arg_t prof_gctx_merge_iter_arg = {tsd_tsdn(tsd), leak_ngctx}; gctx_tree_iter(gctxs, NULL, prof_gctx_merge_iter, &prof_gctx_merge_iter_arg); prof_leave(tsd, tdata); } void prof_dump_impl(tsd_t *tsd, write_cb_t *prof_dump_write, void *cbopaque, prof_tdata_t *tdata, bool leakcheck) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_dump_mtx); prof_cnt_t cnt_all; size_t leak_ngctx; prof_gctx_tree_t gctxs; prof_dump_prep(tsd, tdata, &cnt_all, &leak_ngctx, &gctxs); prof_dump_iter_arg_t prof_dump_iter_arg = {tsd_tsdn(tsd), prof_dump_write, cbopaque}; prof_dump_header(&prof_dump_iter_arg, &cnt_all); gctx_tree_iter(&gctxs, NULL, prof_gctx_dump_iter, &prof_dump_iter_arg); prof_gctx_finish(tsd, &gctxs); if (leakcheck) { prof_leakcheck(&cnt_all, leak_ngctx); } } /* Used in unit tests. */ void prof_cnt_all(prof_cnt_t *cnt_all) { tsd_t *tsd = tsd_fetch(); prof_tdata_t *tdata = prof_tdata_get(tsd, false); if (tdata == NULL) { memset(cnt_all, 0, sizeof(prof_cnt_t)); } else { size_t leak_ngctx; prof_gctx_tree_t gctxs; prof_dump_prep(tsd, tdata, cnt_all, &leak_ngctx, &gctxs); prof_gctx_finish(tsd, &gctxs); } } void prof_bt_hash(const void *key, size_t r_hash[2]) { prof_bt_t *bt = (prof_bt_t *)key; cassert(config_prof); hash(bt->vec, bt->len * sizeof(void *), 0x94122f33U, r_hash); } bool prof_bt_keycomp(const void *k1, const void *k2) { const prof_bt_t *bt1 = (prof_bt_t *)k1; const prof_bt_t *bt2 = (prof_bt_t *)k2; cassert(config_prof); if (bt1->len != bt2->len) { return false; } return (memcmp(bt1->vec, bt2->vec, bt1->len * sizeof(void *)) == 0); } prof_tdata_t * prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim, char *thread_name, bool active) { assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t *tdata; cassert(config_prof); /* Initialize an empty cache for this thread. */ tdata = (prof_tdata_t *)iallocztm(tsd_tsdn(tsd), sizeof(prof_tdata_t), sz_size2index(sizeof(prof_tdata_t)), false, NULL, true, arena_get(TSDN_NULL, 0, true), true); if (tdata == NULL) { return NULL; } tdata->lock = prof_tdata_mutex_choose(thr_uid); tdata->thr_uid = thr_uid; tdata->thr_discrim = thr_discrim; tdata->thread_name = thread_name; tdata->attached = true; tdata->expired = false; tdata->tctx_uid_next = 0; if (ckh_new(tsd, &tdata->bt2tctx, PROF_CKH_MINITEMS, prof_bt_hash, prof_bt_keycomp)) { idalloctm(tsd_tsdn(tsd), tdata, NULL, NULL, true, true); return NULL; } tdata->enq = false; tdata->enq_idump = false; tdata->enq_gdump = false; tdata->dumping = false; tdata->active = active; malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); tdata_tree_insert(&tdatas, tdata); malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); return tdata; } static bool prof_tdata_should_destroy_unlocked(prof_tdata_t *tdata, bool even_if_attached) { if (tdata->attached && !even_if_attached) { return false; } if (ckh_count(&tdata->bt2tctx) != 0) { return false; } return true; } static bool prof_tdata_should_destroy(tsdn_t *tsdn, prof_tdata_t *tdata, bool even_if_attached) { malloc_mutex_assert_owner(tsdn, tdata->lock); return prof_tdata_should_destroy_unlocked(tdata, even_if_attached); } static void prof_tdata_destroy_locked(tsd_t *tsd, prof_tdata_t *tdata, bool even_if_attached) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &tdatas_mtx); malloc_mutex_assert_not_owner(tsd_tsdn(tsd), tdata->lock); tdata_tree_remove(&tdatas, tdata); assert(prof_tdata_should_destroy_unlocked(tdata, even_if_attached)); if (tdata->thread_name != NULL) { idalloctm(tsd_tsdn(tsd), tdata->thread_name, NULL, NULL, true, true); } ckh_delete(tsd, &tdata->bt2tctx); idalloctm(tsd_tsdn(tsd), tdata, NULL, NULL, true, true); } static void prof_tdata_destroy(tsd_t *tsd, prof_tdata_t *tdata, bool even_if_attached) { malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); prof_tdata_destroy_locked(tsd, tdata, even_if_attached); malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); } void prof_tdata_detach(tsd_t *tsd, prof_tdata_t *tdata) { bool destroy_tdata; malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock); if (tdata->attached) { destroy_tdata = prof_tdata_should_destroy(tsd_tsdn(tsd), tdata, true); /* * Only detach if !destroy_tdata, because detaching would allow * another thread to win the race to destroy tdata. */ if (!destroy_tdata) { tdata->attached = false; } tsd_prof_tdata_set(tsd, NULL); } else { destroy_tdata = false; } malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); if (destroy_tdata) { prof_tdata_destroy(tsd, tdata, true); } } static bool prof_tdata_expire(tsdn_t *tsdn, prof_tdata_t *tdata) { bool destroy_tdata; malloc_mutex_lock(tsdn, tdata->lock); if (!tdata->expired) { tdata->expired = true; destroy_tdata = prof_tdata_should_destroy(tsdn, tdata, false); } else { destroy_tdata = false; } malloc_mutex_unlock(tsdn, tdata->lock); return destroy_tdata; } static prof_tdata_t * prof_tdata_reset_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, void *arg) { tsdn_t *tsdn = (tsdn_t *)arg; return (prof_tdata_expire(tsdn, tdata) ? tdata : NULL); } void prof_reset(tsd_t *tsd, size_t lg_sample) { prof_tdata_t *next; assert(lg_sample < (sizeof(uint64_t) << 3)); malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx); malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); lg_prof_sample = lg_sample; prof_unbias_map_init(); next = NULL; do { prof_tdata_t *to_destroy = tdata_tree_iter(&tdatas, next, prof_tdata_reset_iter, (void *)tsd); if (to_destroy != NULL) { next = tdata_tree_next(&tdatas, to_destroy); prof_tdata_destroy_locked(tsd, to_destroy, false); } else { next = NULL; } } while (next != NULL); malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx); } static bool prof_tctx_should_destroy(tsd_t *tsd, prof_tctx_t *tctx) { malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); if (opt_prof_accum) { return false; } if (tctx->cnts.curobjs != 0) { return false; } if (tctx->prepared) { return false; } if (tctx->recent_count != 0) { return false; } return true; } static void prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx) { malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); assert(tctx->cnts.curobjs == 0); assert(tctx->cnts.curbytes == 0); /* * These asserts are not correct -- see the comment about races in * prof.c * * assert(tctx->cnts.curobjs_shifted_unbiased == 0); * assert(tctx->cnts.curbytes_unbiased == 0); */ assert(!opt_prof_accum); assert(tctx->cnts.accumobjs == 0); assert(tctx->cnts.accumbytes == 0); /* * These ones are, since accumbyte counts never go down. Either * prof_accum is off (in which case these should never have changed from * their initial value of zero), or it's on (in which case we shouldn't * be destroying this tctx). */ assert(tctx->cnts.accumobjs_shifted_unbiased == 0); assert(tctx->cnts.accumbytes_unbiased == 0); prof_gctx_t *gctx = tctx->gctx; { prof_tdata_t *tdata = tctx->tdata; tctx->tdata = NULL; ckh_remove(tsd, &tdata->bt2tctx, &gctx->bt, NULL, NULL); bool destroy_tdata = prof_tdata_should_destroy(tsd_tsdn(tsd), tdata, false); malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); if (destroy_tdata) { prof_tdata_destroy(tsd, tdata, false); } } bool destroy_tctx, destroy_gctx; malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); switch (tctx->state) { case prof_tctx_state_nominal: tctx_tree_remove(&gctx->tctxs, tctx); destroy_tctx = true; if (prof_gctx_should_destroy(gctx)) { /* * Increment gctx->nlimbo in order to keep another * thread from winning the race to destroy gctx while * this one has gctx->lock dropped. Without this, it * would be possible for another thread to: * * 1) Sample an allocation associated with gctx. * 2) Deallocate the sampled object. * 3) Successfully prof_gctx_try_destroy(gctx). * * The result would be that gctx no longer exists by the * time this thread accesses it in * prof_gctx_try_destroy(). */ gctx->nlimbo++; destroy_gctx = true; } else { destroy_gctx = false; } break; case prof_tctx_state_dumping: /* * A dumping thread needs tctx to remain valid until dumping * has finished. Change state such that the dumping thread will * complete destruction during a late dump iteration phase. */ tctx->state = prof_tctx_state_purgatory; destroy_tctx = false; destroy_gctx = false; break; default: not_reached(); destroy_tctx = false; destroy_gctx = false; } malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); if (destroy_gctx) { prof_gctx_try_destroy(tsd, prof_tdata_get(tsd, false), gctx); } if (destroy_tctx) { idalloctm(tsd_tsdn(tsd), tctx, NULL, NULL, true, true); } } void prof_tctx_try_destroy(tsd_t *tsd, prof_tctx_t *tctx) { malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); if (prof_tctx_should_destroy(tsd, tctx)) { /* tctx->tdata->lock will be released in prof_tctx_destroy(). */ prof_tctx_destroy(tsd, tctx); } else { malloc_mutex_unlock(tsd_tsdn(tsd), tctx->tdata->lock); } } /******************************************************************************/ redis-8.0.2/deps/jemalloc/src/prof_log.c000066400000000000000000000447001501533116600201200ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/buf_writer.h" #include "jemalloc/internal/ckh.h" #include "jemalloc/internal/emitter.h" #include "jemalloc/internal/hash.h" #include "jemalloc/internal/malloc_io.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_log.h" #include "jemalloc/internal/prof_sys.h" bool opt_prof_log = false; typedef enum prof_logging_state_e prof_logging_state_t; enum prof_logging_state_e { prof_logging_state_stopped, prof_logging_state_started, prof_logging_state_dumping }; /* * - stopped: log_start never called, or previous log_stop has completed. * - started: log_start called, log_stop not called yet. Allocations are logged. * - dumping: log_stop called but not finished; samples are not logged anymore. */ prof_logging_state_t prof_logging_state = prof_logging_state_stopped; /* Used in unit tests. */ static bool prof_log_dummy = false; /* Incremented for every log file that is output. */ static uint64_t log_seq = 0; static char log_filename[ /* Minimize memory bloat for non-prof builds. */ #ifdef JEMALLOC_PROF PATH_MAX + #endif 1]; /* Timestamp for most recent call to log_start(). */ static nstime_t log_start_timestamp; /* Increment these when adding to the log_bt and log_thr linked lists. */ static size_t log_bt_index = 0; static size_t log_thr_index = 0; /* Linked list node definitions. These are only used in this file. */ typedef struct prof_bt_node_s prof_bt_node_t; struct prof_bt_node_s { prof_bt_node_t *next; size_t index; prof_bt_t bt; /* Variable size backtrace vector pointed to by bt. */ void *vec[1]; }; typedef struct prof_thr_node_s prof_thr_node_t; struct prof_thr_node_s { prof_thr_node_t *next; size_t index; uint64_t thr_uid; /* Variable size based on thr_name_sz. */ char name[1]; }; typedef struct prof_alloc_node_s prof_alloc_node_t; /* This is output when logging sampled allocations. */ struct prof_alloc_node_s { prof_alloc_node_t *next; /* Indices into an array of thread data. */ size_t alloc_thr_ind; size_t free_thr_ind; /* Indices into an array of backtraces. */ size_t alloc_bt_ind; size_t free_bt_ind; uint64_t alloc_time_ns; uint64_t free_time_ns; size_t usize; }; /* * Created on the first call to prof_try_log and deleted on prof_log_stop. * These are the backtraces and threads that have already been logged by an * allocation. */ static bool log_tables_initialized = false; static ckh_t log_bt_node_set; static ckh_t log_thr_node_set; /* Store linked lists for logged data. */ static prof_bt_node_t *log_bt_first = NULL; static prof_bt_node_t *log_bt_last = NULL; static prof_thr_node_t *log_thr_first = NULL; static prof_thr_node_t *log_thr_last = NULL; static prof_alloc_node_t *log_alloc_first = NULL; static prof_alloc_node_t *log_alloc_last = NULL; /* Protects the prof_logging_state and any log_{...} variable. */ malloc_mutex_t log_mtx; /******************************************************************************/ /* * Function prototypes for static functions that are referenced prior to * definition. */ /* Hashtable functions for log_bt_node_set and log_thr_node_set. */ static void prof_thr_node_hash(const void *key, size_t r_hash[2]); static bool prof_thr_node_keycomp(const void *k1, const void *k2); static void prof_bt_node_hash(const void *key, size_t r_hash[2]); static bool prof_bt_node_keycomp(const void *k1, const void *k2); /******************************************************************************/ static size_t prof_log_bt_index(tsd_t *tsd, prof_bt_t *bt) { assert(prof_logging_state == prof_logging_state_started); malloc_mutex_assert_owner(tsd_tsdn(tsd), &log_mtx); prof_bt_node_t dummy_node; dummy_node.bt = *bt; prof_bt_node_t *node; /* See if this backtrace is already cached in the table. */ if (ckh_search(&log_bt_node_set, (void *)(&dummy_node), (void **)(&node), NULL)) { size_t sz = offsetof(prof_bt_node_t, vec) + (bt->len * sizeof(void *)); prof_bt_node_t *new_node = (prof_bt_node_t *) iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, true, arena_get(TSDN_NULL, 0, true), true); if (log_bt_first == NULL) { log_bt_first = new_node; log_bt_last = new_node; } else { log_bt_last->next = new_node; log_bt_last = new_node; } new_node->next = NULL; new_node->index = log_bt_index; /* * Copy the backtrace: bt is inside a tdata or gctx, which * might die before prof_log_stop is called. */ new_node->bt.len = bt->len; memcpy(new_node->vec, bt->vec, bt->len * sizeof(void *)); new_node->bt.vec = new_node->vec; log_bt_index++; ckh_insert(tsd, &log_bt_node_set, (void *)new_node, NULL); return new_node->index; } else { return node->index; } } static size_t prof_log_thr_index(tsd_t *tsd, uint64_t thr_uid, const char *name) { assert(prof_logging_state == prof_logging_state_started); malloc_mutex_assert_owner(tsd_tsdn(tsd), &log_mtx); prof_thr_node_t dummy_node; dummy_node.thr_uid = thr_uid; prof_thr_node_t *node; /* See if this thread is already cached in the table. */ if (ckh_search(&log_thr_node_set, (void *)(&dummy_node), (void **)(&node), NULL)) { size_t sz = offsetof(prof_thr_node_t, name) + strlen(name) + 1; prof_thr_node_t *new_node = (prof_thr_node_t *) iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, true, arena_get(TSDN_NULL, 0, true), true); if (log_thr_first == NULL) { log_thr_first = new_node; log_thr_last = new_node; } else { log_thr_last->next = new_node; log_thr_last = new_node; } new_node->next = NULL; new_node->index = log_thr_index; new_node->thr_uid = thr_uid; strcpy(new_node->name, name); log_thr_index++; ckh_insert(tsd, &log_thr_node_set, (void *)new_node, NULL); return new_node->index; } else { return node->index; } } JEMALLOC_COLD void prof_try_log(tsd_t *tsd, size_t usize, prof_info_t *prof_info) { cassert(config_prof); prof_tctx_t *tctx = prof_info->alloc_tctx; malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); prof_tdata_t *cons_tdata = prof_tdata_get(tsd, false); if (cons_tdata == NULL) { /* * We decide not to log these allocations. cons_tdata will be * NULL only when the current thread is in a weird state (e.g. * it's being destroyed). */ return; } malloc_mutex_lock(tsd_tsdn(tsd), &log_mtx); if (prof_logging_state != prof_logging_state_started) { goto label_done; } if (!log_tables_initialized) { bool err1 = ckh_new(tsd, &log_bt_node_set, PROF_CKH_MINITEMS, prof_bt_node_hash, prof_bt_node_keycomp); bool err2 = ckh_new(tsd, &log_thr_node_set, PROF_CKH_MINITEMS, prof_thr_node_hash, prof_thr_node_keycomp); if (err1 || err2) { goto label_done; } log_tables_initialized = true; } nstime_t alloc_time = prof_info->alloc_time; nstime_t free_time; nstime_prof_init_update(&free_time); size_t sz = sizeof(prof_alloc_node_t); prof_alloc_node_t *new_node = (prof_alloc_node_t *) iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, true, arena_get(TSDN_NULL, 0, true), true); const char *prod_thr_name = (tctx->tdata->thread_name == NULL)? "" : tctx->tdata->thread_name; const char *cons_thr_name = prof_thread_name_get(tsd); prof_bt_t bt; /* Initialize the backtrace, using the buffer in tdata to store it. */ bt_init(&bt, cons_tdata->vec); prof_backtrace(tsd, &bt); prof_bt_t *cons_bt = &bt; /* We haven't destroyed tctx yet, so gctx should be good to read. */ prof_bt_t *prod_bt = &tctx->gctx->bt; new_node->next = NULL; new_node->alloc_thr_ind = prof_log_thr_index(tsd, tctx->tdata->thr_uid, prod_thr_name); new_node->free_thr_ind = prof_log_thr_index(tsd, cons_tdata->thr_uid, cons_thr_name); new_node->alloc_bt_ind = prof_log_bt_index(tsd, prod_bt); new_node->free_bt_ind = prof_log_bt_index(tsd, cons_bt); new_node->alloc_time_ns = nstime_ns(&alloc_time); new_node->free_time_ns = nstime_ns(&free_time); new_node->usize = usize; if (log_alloc_first == NULL) { log_alloc_first = new_node; log_alloc_last = new_node; } else { log_alloc_last->next = new_node; log_alloc_last = new_node; } label_done: malloc_mutex_unlock(tsd_tsdn(tsd), &log_mtx); } static void prof_bt_node_hash(const void *key, size_t r_hash[2]) { const prof_bt_node_t *bt_node = (prof_bt_node_t *)key; prof_bt_hash((void *)(&bt_node->bt), r_hash); } static bool prof_bt_node_keycomp(const void *k1, const void *k2) { const prof_bt_node_t *bt_node1 = (prof_bt_node_t *)k1; const prof_bt_node_t *bt_node2 = (prof_bt_node_t *)k2; return prof_bt_keycomp((void *)(&bt_node1->bt), (void *)(&bt_node2->bt)); } static void prof_thr_node_hash(const void *key, size_t r_hash[2]) { const prof_thr_node_t *thr_node = (prof_thr_node_t *)key; hash(&thr_node->thr_uid, sizeof(uint64_t), 0x94122f35U, r_hash); } static bool prof_thr_node_keycomp(const void *k1, const void *k2) { const prof_thr_node_t *thr_node1 = (prof_thr_node_t *)k1; const prof_thr_node_t *thr_node2 = (prof_thr_node_t *)k2; return thr_node1->thr_uid == thr_node2->thr_uid; } /* Used in unit tests. */ size_t prof_log_bt_count(void) { cassert(config_prof); size_t cnt = 0; prof_bt_node_t *node = log_bt_first; while (node != NULL) { cnt++; node = node->next; } return cnt; } /* Used in unit tests. */ size_t prof_log_alloc_count(void) { cassert(config_prof); size_t cnt = 0; prof_alloc_node_t *node = log_alloc_first; while (node != NULL) { cnt++; node = node->next; } return cnt; } /* Used in unit tests. */ size_t prof_log_thr_count(void) { cassert(config_prof); size_t cnt = 0; prof_thr_node_t *node = log_thr_first; while (node != NULL) { cnt++; node = node->next; } return cnt; } /* Used in unit tests. */ bool prof_log_is_logging(void) { cassert(config_prof); return prof_logging_state == prof_logging_state_started; } /* Used in unit tests. */ bool prof_log_rep_check(void) { cassert(config_prof); if (prof_logging_state == prof_logging_state_stopped && log_tables_initialized) { return true; } if (log_bt_last != NULL && log_bt_last->next != NULL) { return true; } if (log_thr_last != NULL && log_thr_last->next != NULL) { return true; } if (log_alloc_last != NULL && log_alloc_last->next != NULL) { return true; } size_t bt_count = prof_log_bt_count(); size_t thr_count = prof_log_thr_count(); size_t alloc_count = prof_log_alloc_count(); if (prof_logging_state == prof_logging_state_stopped) { if (bt_count != 0 || thr_count != 0 || alloc_count || 0) { return true; } } prof_alloc_node_t *node = log_alloc_first; while (node != NULL) { if (node->alloc_bt_ind >= bt_count) { return true; } if (node->free_bt_ind >= bt_count) { return true; } if (node->alloc_thr_ind >= thr_count) { return true; } if (node->free_thr_ind >= thr_count) { return true; } if (node->alloc_time_ns > node->free_time_ns) { return true; } node = node->next; } return false; } /* Used in unit tests. */ void prof_log_dummy_set(bool new_value) { cassert(config_prof); prof_log_dummy = new_value; } /* Used as an atexit function to stop logging on exit. */ static void prof_log_stop_final(void) { tsd_t *tsd = tsd_fetch(); prof_log_stop(tsd_tsdn(tsd)); } JEMALLOC_COLD bool prof_log_start(tsdn_t *tsdn, const char *filename) { cassert(config_prof); if (!opt_prof) { return true; } bool ret = false; malloc_mutex_lock(tsdn, &log_mtx); static bool prof_log_atexit_called = false; if (!prof_log_atexit_called) { prof_log_atexit_called = true; if (atexit(prof_log_stop_final) != 0) { malloc_write(": Error in atexit() " "for logging\n"); if (opt_abort) { abort(); } ret = true; goto label_done; } } if (prof_logging_state != prof_logging_state_stopped) { ret = true; } else if (filename == NULL) { /* Make default name. */ prof_get_default_filename(tsdn, log_filename, log_seq); log_seq++; prof_logging_state = prof_logging_state_started; } else if (strlen(filename) >= PROF_DUMP_FILENAME_LEN) { ret = true; } else { strcpy(log_filename, filename); prof_logging_state = prof_logging_state_started; } if (!ret) { nstime_prof_init_update(&log_start_timestamp); } label_done: malloc_mutex_unlock(tsdn, &log_mtx); return ret; } struct prof_emitter_cb_arg_s { int fd; ssize_t ret; }; static void prof_emitter_write_cb(void *opaque, const char *to_write) { struct prof_emitter_cb_arg_s *arg = (struct prof_emitter_cb_arg_s *)opaque; size_t bytes = strlen(to_write); if (prof_log_dummy) { return; } arg->ret = malloc_write_fd(arg->fd, to_write, bytes); } /* * prof_log_emit_{...} goes through the appropriate linked list, emitting each * node to the json and deallocating it. */ static void prof_log_emit_threads(tsd_t *tsd, emitter_t *emitter) { emitter_json_array_kv_begin(emitter, "threads"); prof_thr_node_t *thr_node = log_thr_first; prof_thr_node_t *thr_old_node; while (thr_node != NULL) { emitter_json_object_begin(emitter); emitter_json_kv(emitter, "thr_uid", emitter_type_uint64, &thr_node->thr_uid); char *thr_name = thr_node->name; emitter_json_kv(emitter, "thr_name", emitter_type_string, &thr_name); emitter_json_object_end(emitter); thr_old_node = thr_node; thr_node = thr_node->next; idalloctm(tsd_tsdn(tsd), thr_old_node, NULL, NULL, true, true); } emitter_json_array_end(emitter); } static void prof_log_emit_traces(tsd_t *tsd, emitter_t *emitter) { emitter_json_array_kv_begin(emitter, "stack_traces"); prof_bt_node_t *bt_node = log_bt_first; prof_bt_node_t *bt_old_node; /* * Calculate how many hex digits we need: twice number of bytes, two for * "0x", and then one more for terminating '\0'. */ char buf[2 * sizeof(intptr_t) + 3]; size_t buf_sz = sizeof(buf); while (bt_node != NULL) { emitter_json_array_begin(emitter); size_t i; for (i = 0; i < bt_node->bt.len; i++) { malloc_snprintf(buf, buf_sz, "%p", bt_node->bt.vec[i]); char *trace_str = buf; emitter_json_value(emitter, emitter_type_string, &trace_str); } emitter_json_array_end(emitter); bt_old_node = bt_node; bt_node = bt_node->next; idalloctm(tsd_tsdn(tsd), bt_old_node, NULL, NULL, true, true); } emitter_json_array_end(emitter); } static void prof_log_emit_allocs(tsd_t *tsd, emitter_t *emitter) { emitter_json_array_kv_begin(emitter, "allocations"); prof_alloc_node_t *alloc_node = log_alloc_first; prof_alloc_node_t *alloc_old_node; while (alloc_node != NULL) { emitter_json_object_begin(emitter); emitter_json_kv(emitter, "alloc_thread", emitter_type_size, &alloc_node->alloc_thr_ind); emitter_json_kv(emitter, "free_thread", emitter_type_size, &alloc_node->free_thr_ind); emitter_json_kv(emitter, "alloc_trace", emitter_type_size, &alloc_node->alloc_bt_ind); emitter_json_kv(emitter, "free_trace", emitter_type_size, &alloc_node->free_bt_ind); emitter_json_kv(emitter, "alloc_timestamp", emitter_type_uint64, &alloc_node->alloc_time_ns); emitter_json_kv(emitter, "free_timestamp", emitter_type_uint64, &alloc_node->free_time_ns); emitter_json_kv(emitter, "usize", emitter_type_uint64, &alloc_node->usize); emitter_json_object_end(emitter); alloc_old_node = alloc_node; alloc_node = alloc_node->next; idalloctm(tsd_tsdn(tsd), alloc_old_node, NULL, NULL, true, true); } emitter_json_array_end(emitter); } static void prof_log_emit_metadata(emitter_t *emitter) { emitter_json_object_kv_begin(emitter, "info"); nstime_t now; nstime_prof_init_update(&now); uint64_t ns = nstime_ns(&now) - nstime_ns(&log_start_timestamp); emitter_json_kv(emitter, "duration", emitter_type_uint64, &ns); char *vers = JEMALLOC_VERSION; emitter_json_kv(emitter, "version", emitter_type_string, &vers); emitter_json_kv(emitter, "lg_sample_rate", emitter_type_int, &lg_prof_sample); const char *res_type = prof_time_res_mode_names[opt_prof_time_res]; emitter_json_kv(emitter, "prof_time_resolution", emitter_type_string, &res_type); int pid = prof_getpid(); emitter_json_kv(emitter, "pid", emitter_type_int, &pid); emitter_json_object_end(emitter); } #define PROF_LOG_STOP_BUFSIZE PROF_DUMP_BUFSIZE JEMALLOC_COLD bool prof_log_stop(tsdn_t *tsdn) { cassert(config_prof); if (!opt_prof || !prof_booted) { return true; } tsd_t *tsd = tsdn_tsd(tsdn); malloc_mutex_lock(tsdn, &log_mtx); if (prof_logging_state != prof_logging_state_started) { malloc_mutex_unlock(tsdn, &log_mtx); return true; } /* * Set the state to dumping. We'll set it to stopped when we're done. * Since other threads won't be able to start/stop/log when the state is * dumping, we don't have to hold the lock during the whole method. */ prof_logging_state = prof_logging_state_dumping; malloc_mutex_unlock(tsdn, &log_mtx); emitter_t emitter; /* Create a file. */ int fd; if (prof_log_dummy) { fd = 0; } else { fd = creat(log_filename, 0644); } if (fd == -1) { malloc_printf(": creat() for log file \"%s\" " " failed with %d\n", log_filename, errno); if (opt_abort) { abort(); } return true; } struct prof_emitter_cb_arg_s arg; arg.fd = fd; buf_writer_t buf_writer; buf_writer_init(tsdn, &buf_writer, prof_emitter_write_cb, &arg, NULL, PROF_LOG_STOP_BUFSIZE); emitter_init(&emitter, emitter_output_json_compact, buf_writer_cb, &buf_writer); emitter_begin(&emitter); prof_log_emit_metadata(&emitter); prof_log_emit_threads(tsd, &emitter); prof_log_emit_traces(tsd, &emitter); prof_log_emit_allocs(tsd, &emitter); emitter_end(&emitter); buf_writer_terminate(tsdn, &buf_writer); /* Reset global state. */ if (log_tables_initialized) { ckh_delete(tsd, &log_bt_node_set); ckh_delete(tsd, &log_thr_node_set); } log_tables_initialized = false; log_bt_index = 0; log_thr_index = 0; log_bt_first = NULL; log_bt_last = NULL; log_thr_first = NULL; log_thr_last = NULL; log_alloc_first = NULL; log_alloc_last = NULL; malloc_mutex_lock(tsdn, &log_mtx); prof_logging_state = prof_logging_state_stopped; malloc_mutex_unlock(tsdn, &log_mtx); if (prof_log_dummy) { return false; } return close(fd) || arg.ret == -1; } #undef PROF_LOG_STOP_BUFSIZE JEMALLOC_COLD bool prof_log_init(tsd_t *tsd) { cassert(config_prof); if (malloc_mutex_init(&log_mtx, "prof_log", WITNESS_RANK_PROF_LOG, malloc_mutex_rank_exclusive)) { return true; } if (opt_prof_log) { prof_log_start(tsd_tsdn(tsd), NULL); } return false; } /******************************************************************************/ redis-8.0.2/deps/jemalloc/src/prof_recent.c000066400000000000000000000473051501533116600206230ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/buf_writer.h" #include "jemalloc/internal/emitter.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_recent.h" ssize_t opt_prof_recent_alloc_max = PROF_RECENT_ALLOC_MAX_DEFAULT; malloc_mutex_t prof_recent_alloc_mtx; /* Protects the fields below */ static atomic_zd_t prof_recent_alloc_max; static ssize_t prof_recent_alloc_count = 0; prof_recent_list_t prof_recent_alloc_list; malloc_mutex_t prof_recent_dump_mtx; /* Protects dumping. */ static void prof_recent_alloc_max_init() { atomic_store_zd(&prof_recent_alloc_max, opt_prof_recent_alloc_max, ATOMIC_RELAXED); } static inline ssize_t prof_recent_alloc_max_get_no_lock() { return atomic_load_zd(&prof_recent_alloc_max, ATOMIC_RELAXED); } static inline ssize_t prof_recent_alloc_max_get(tsd_t *tsd) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); return prof_recent_alloc_max_get_no_lock(); } static inline ssize_t prof_recent_alloc_max_update(tsd_t *tsd, ssize_t max) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); ssize_t old_max = prof_recent_alloc_max_get(tsd); atomic_store_zd(&prof_recent_alloc_max, max, ATOMIC_RELAXED); return old_max; } static prof_recent_t * prof_recent_allocate_node(tsdn_t *tsdn) { return (prof_recent_t *)iallocztm(tsdn, sizeof(prof_recent_t), sz_size2index(sizeof(prof_recent_t)), false, NULL, true, arena_get(tsdn, 0, false), true); } static void prof_recent_free_node(tsdn_t *tsdn, prof_recent_t *node) { assert(node != NULL); assert(isalloc(tsdn, node) == sz_s2u(sizeof(prof_recent_t))); idalloctm(tsdn, node, NULL, NULL, true, true); } static inline void increment_recent_count(tsd_t *tsd, prof_tctx_t *tctx) { malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); ++tctx->recent_count; assert(tctx->recent_count > 0); } bool prof_recent_alloc_prepare(tsd_t *tsd, prof_tctx_t *tctx) { cassert(config_prof); assert(opt_prof && prof_booted); malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); /* * Check whether last-N mode is turned on without trying to acquire the * lock, so as to optimize for the following two scenarios: * (1) Last-N mode is switched off; * (2) Dumping, during which last-N mode is temporarily turned off so * as not to block sampled allocations. */ if (prof_recent_alloc_max_get_no_lock() == 0) { return false; } /* * Increment recent_count to hold the tctx so that it won't be gone * even after tctx->tdata->lock is released. This acts as a * "placeholder"; the real recording of the allocation requires a lock * on prof_recent_alloc_mtx and is done in prof_recent_alloc (when * tctx->tdata->lock has been released). */ increment_recent_count(tsd, tctx); return true; } static void decrement_recent_count(tsd_t *tsd, prof_tctx_t *tctx) { malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); assert(tctx != NULL); malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); assert(tctx->recent_count > 0); --tctx->recent_count; prof_tctx_try_destroy(tsd, tctx); } static inline edata_t * prof_recent_alloc_edata_get_no_lock(const prof_recent_t *n) { return (edata_t *)atomic_load_p(&n->alloc_edata, ATOMIC_ACQUIRE); } edata_t * prof_recent_alloc_edata_get_no_lock_test(const prof_recent_t *n) { cassert(config_prof); return prof_recent_alloc_edata_get_no_lock(n); } static inline edata_t * prof_recent_alloc_edata_get(tsd_t *tsd, const prof_recent_t *n) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); return prof_recent_alloc_edata_get_no_lock(n); } static void prof_recent_alloc_edata_set(tsd_t *tsd, prof_recent_t *n, edata_t *edata) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); atomic_store_p(&n->alloc_edata, edata, ATOMIC_RELEASE); } void edata_prof_recent_alloc_init(edata_t *edata) { cassert(config_prof); edata_prof_recent_alloc_set_dont_call_directly(edata, NULL); } static inline prof_recent_t * edata_prof_recent_alloc_get_no_lock(const edata_t *edata) { cassert(config_prof); return edata_prof_recent_alloc_get_dont_call_directly(edata); } prof_recent_t * edata_prof_recent_alloc_get_no_lock_test(const edata_t *edata) { cassert(config_prof); return edata_prof_recent_alloc_get_no_lock(edata); } static inline prof_recent_t * edata_prof_recent_alloc_get(tsd_t *tsd, const edata_t *edata) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_t *recent_alloc = edata_prof_recent_alloc_get_no_lock(edata); assert(recent_alloc == NULL || prof_recent_alloc_edata_get(tsd, recent_alloc) == edata); return recent_alloc; } static prof_recent_t * edata_prof_recent_alloc_update_internal(tsd_t *tsd, edata_t *edata, prof_recent_t *recent_alloc) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_t *old_recent_alloc = edata_prof_recent_alloc_get(tsd, edata); edata_prof_recent_alloc_set_dont_call_directly(edata, recent_alloc); return old_recent_alloc; } static void edata_prof_recent_alloc_set(tsd_t *tsd, edata_t *edata, prof_recent_t *recent_alloc) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); assert(recent_alloc != NULL); prof_recent_t *old_recent_alloc = edata_prof_recent_alloc_update_internal(tsd, edata, recent_alloc); assert(old_recent_alloc == NULL); prof_recent_alloc_edata_set(tsd, recent_alloc, edata); } static void edata_prof_recent_alloc_reset(tsd_t *tsd, edata_t *edata, prof_recent_t *recent_alloc) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); assert(recent_alloc != NULL); prof_recent_t *old_recent_alloc = edata_prof_recent_alloc_update_internal(tsd, edata, NULL); assert(old_recent_alloc == recent_alloc); assert(edata == prof_recent_alloc_edata_get(tsd, recent_alloc)); prof_recent_alloc_edata_set(tsd, recent_alloc, NULL); } /* * This function should be called right before an allocation is released, so * that the associated recent allocation record can contain the following * information: * (1) The allocation is released; * (2) The time of the deallocation; and * (3) The prof_tctx associated with the deallocation. */ void prof_recent_alloc_reset(tsd_t *tsd, edata_t *edata) { cassert(config_prof); /* * Check whether the recent allocation record still exists without * trying to acquire the lock. */ if (edata_prof_recent_alloc_get_no_lock(edata) == NULL) { return; } prof_tctx_t *dalloc_tctx = prof_tctx_create(tsd); /* * In case dalloc_tctx is NULL, e.g. due to OOM, we will not record the * deallocation time / tctx, which is handled later, after we check * again when holding the lock. */ if (dalloc_tctx != NULL) { malloc_mutex_lock(tsd_tsdn(tsd), dalloc_tctx->tdata->lock); increment_recent_count(tsd, dalloc_tctx); dalloc_tctx->prepared = false; malloc_mutex_unlock(tsd_tsdn(tsd), dalloc_tctx->tdata->lock); } malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); /* Check again after acquiring the lock. */ prof_recent_t *recent = edata_prof_recent_alloc_get(tsd, edata); if (recent != NULL) { assert(nstime_equals_zero(&recent->dalloc_time)); assert(recent->dalloc_tctx == NULL); if (dalloc_tctx != NULL) { nstime_prof_update(&recent->dalloc_time); recent->dalloc_tctx = dalloc_tctx; dalloc_tctx = NULL; } edata_prof_recent_alloc_reset(tsd, edata, recent); } malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); if (dalloc_tctx != NULL) { /* We lost the rase - the allocation record was just gone. */ decrement_recent_count(tsd, dalloc_tctx); } } static void prof_recent_alloc_evict_edata(tsd_t *tsd, prof_recent_t *recent_alloc) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); edata_t *edata = prof_recent_alloc_edata_get(tsd, recent_alloc); if (edata != NULL) { edata_prof_recent_alloc_reset(tsd, edata, recent_alloc); } } static bool prof_recent_alloc_is_empty(tsd_t *tsd) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); if (ql_empty(&prof_recent_alloc_list)) { assert(prof_recent_alloc_count == 0); return true; } else { assert(prof_recent_alloc_count > 0); return false; } } static void prof_recent_alloc_assert_count(tsd_t *tsd) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); if (!config_debug) { return; } ssize_t count = 0; prof_recent_t *n; ql_foreach(n, &prof_recent_alloc_list, link) { ++count; } assert(count == prof_recent_alloc_count); assert(prof_recent_alloc_max_get(tsd) == -1 || count <= prof_recent_alloc_max_get(tsd)); } void prof_recent_alloc(tsd_t *tsd, edata_t *edata, size_t size, size_t usize) { cassert(config_prof); assert(edata != NULL); prof_tctx_t *tctx = edata_prof_tctx_get(edata); malloc_mutex_assert_not_owner(tsd_tsdn(tsd), tctx->tdata->lock); malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_alloc_assert_count(tsd); /* * Reserve a new prof_recent_t node if needed. If needed, we release * the prof_recent_alloc_mtx lock and allocate. Then, rather than * immediately checking for OOM, we regain the lock and try to make use * of the reserve node if needed. There are six scenarios: * * \ now | no need | need but OOMed | need and allocated * later \ | | | * ------------------------------------------------------------ * no need | (1) | (2) | (3) * ------------------------------------------------------------ * need | (4) | (5) | (6) * * First, "(4)" never happens, because we don't release the lock in the * middle if there's no need for a new node; in such cases "(1)" always * takes place, which is trivial. * * Out of the remaining four scenarios, "(6)" is the common case and is * trivial. "(5)" is also trivial, in which case we'll rollback the * effect of prof_recent_alloc_prepare() as expected. * * "(2)" / "(3)" occurs when the need for a new node is gone after we * regain the lock. If the new node is successfully allocated, i.e. in * the case of "(3)", we'll release it in the end; otherwise, i.e. in * the case of "(2)", we do nothing - we're lucky that the OOM ends up * doing no harm at all. * * Therefore, the only performance cost of the "release lock" -> * "allocate" -> "regain lock" design is the "(3)" case, but it happens * very rarely, so the cost is relatively small compared to the gain of * not having to have the lock order of prof_recent_alloc_mtx above all * the allocation locks. */ prof_recent_t *reserve = NULL; if (prof_recent_alloc_max_get(tsd) == -1 || prof_recent_alloc_count < prof_recent_alloc_max_get(tsd)) { assert(prof_recent_alloc_max_get(tsd) != 0); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); reserve = prof_recent_allocate_node(tsd_tsdn(tsd)); malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_alloc_assert_count(tsd); } if (prof_recent_alloc_max_get(tsd) == 0) { assert(prof_recent_alloc_is_empty(tsd)); goto label_rollback; } prof_tctx_t *old_alloc_tctx, *old_dalloc_tctx; if (prof_recent_alloc_count == prof_recent_alloc_max_get(tsd)) { /* If upper limit is reached, rotate the head. */ assert(prof_recent_alloc_max_get(tsd) != -1); assert(!prof_recent_alloc_is_empty(tsd)); prof_recent_t *head = ql_first(&prof_recent_alloc_list); old_alloc_tctx = head->alloc_tctx; assert(old_alloc_tctx != NULL); old_dalloc_tctx = head->dalloc_tctx; prof_recent_alloc_evict_edata(tsd, head); ql_rotate(&prof_recent_alloc_list, link); } else { /* Otherwise make use of the new node. */ assert(prof_recent_alloc_max_get(tsd) == -1 || prof_recent_alloc_count < prof_recent_alloc_max_get(tsd)); if (reserve == NULL) { goto label_rollback; } ql_elm_new(reserve, link); ql_tail_insert(&prof_recent_alloc_list, reserve, link); reserve = NULL; old_alloc_tctx = NULL; old_dalloc_tctx = NULL; ++prof_recent_alloc_count; } /* Fill content into the tail node. */ prof_recent_t *tail = ql_last(&prof_recent_alloc_list, link); assert(tail != NULL); tail->size = size; tail->usize = usize; nstime_copy(&tail->alloc_time, edata_prof_alloc_time_get(edata)); tail->alloc_tctx = tctx; nstime_init_zero(&tail->dalloc_time); tail->dalloc_tctx = NULL; edata_prof_recent_alloc_set(tsd, edata, tail); assert(!prof_recent_alloc_is_empty(tsd)); prof_recent_alloc_assert_count(tsd); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); if (reserve != NULL) { prof_recent_free_node(tsd_tsdn(tsd), reserve); } /* * Asynchronously handle the tctx of the old node, so that there's no * simultaneous holdings of prof_recent_alloc_mtx and tdata->lock. * In the worst case this may delay the tctx release but it's better * than holding prof_recent_alloc_mtx for longer. */ if (old_alloc_tctx != NULL) { decrement_recent_count(tsd, old_alloc_tctx); } if (old_dalloc_tctx != NULL) { decrement_recent_count(tsd, old_dalloc_tctx); } return; label_rollback: assert(edata_prof_recent_alloc_get(tsd, edata) == NULL); prof_recent_alloc_assert_count(tsd); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); if (reserve != NULL) { prof_recent_free_node(tsd_tsdn(tsd), reserve); } decrement_recent_count(tsd, tctx); } ssize_t prof_recent_alloc_max_ctl_read() { cassert(config_prof); /* Don't bother to acquire the lock. */ return prof_recent_alloc_max_get_no_lock(); } static void prof_recent_alloc_restore_locked(tsd_t *tsd, prof_recent_list_t *to_delete) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); ssize_t max = prof_recent_alloc_max_get(tsd); if (max == -1 || prof_recent_alloc_count <= max) { /* Easy case - no need to alter the list. */ ql_new(to_delete); prof_recent_alloc_assert_count(tsd); return; } prof_recent_t *node; ql_foreach(node, &prof_recent_alloc_list, link) { if (prof_recent_alloc_count == max) { break; } prof_recent_alloc_evict_edata(tsd, node); --prof_recent_alloc_count; } assert(prof_recent_alloc_count == max); ql_move(to_delete, &prof_recent_alloc_list); if (max == 0) { assert(node == NULL); } else { assert(node != NULL); ql_split(to_delete, node, &prof_recent_alloc_list, link); } assert(!ql_empty(to_delete)); prof_recent_alloc_assert_count(tsd); } static void prof_recent_alloc_async_cleanup(tsd_t *tsd, prof_recent_list_t *to_delete) { malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_dump_mtx); malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); while (!ql_empty(to_delete)) { prof_recent_t *node = ql_first(to_delete); ql_remove(to_delete, node, link); decrement_recent_count(tsd, node->alloc_tctx); if (node->dalloc_tctx != NULL) { decrement_recent_count(tsd, node->dalloc_tctx); } prof_recent_free_node(tsd_tsdn(tsd), node); } } ssize_t prof_recent_alloc_max_ctl_write(tsd_t *tsd, ssize_t max) { cassert(config_prof); assert(max >= -1); malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_alloc_assert_count(tsd); const ssize_t old_max = prof_recent_alloc_max_update(tsd, max); prof_recent_list_t to_delete; prof_recent_alloc_restore_locked(tsd, &to_delete); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_alloc_async_cleanup(tsd, &to_delete); return old_max; } static void prof_recent_alloc_dump_bt(emitter_t *emitter, prof_tctx_t *tctx) { char bt_buf[2 * sizeof(intptr_t) + 3]; char *s = bt_buf; assert(tctx != NULL); prof_bt_t *bt = &tctx->gctx->bt; for (size_t i = 0; i < bt->len; ++i) { malloc_snprintf(bt_buf, sizeof(bt_buf), "%p", bt->vec[i]); emitter_json_value(emitter, emitter_type_string, &s); } } static void prof_recent_alloc_dump_node(emitter_t *emitter, prof_recent_t *node) { emitter_json_object_begin(emitter); emitter_json_kv(emitter, "size", emitter_type_size, &node->size); emitter_json_kv(emitter, "usize", emitter_type_size, &node->usize); bool released = prof_recent_alloc_edata_get_no_lock(node) == NULL; emitter_json_kv(emitter, "released", emitter_type_bool, &released); emitter_json_kv(emitter, "alloc_thread_uid", emitter_type_uint64, &node->alloc_tctx->thr_uid); prof_tdata_t *alloc_tdata = node->alloc_tctx->tdata; assert(alloc_tdata != NULL); if (alloc_tdata->thread_name != NULL) { emitter_json_kv(emitter, "alloc_thread_name", emitter_type_string, &alloc_tdata->thread_name); } uint64_t alloc_time_ns = nstime_ns(&node->alloc_time); emitter_json_kv(emitter, "alloc_time", emitter_type_uint64, &alloc_time_ns); emitter_json_array_kv_begin(emitter, "alloc_trace"); prof_recent_alloc_dump_bt(emitter, node->alloc_tctx); emitter_json_array_end(emitter); if (released && node->dalloc_tctx != NULL) { emitter_json_kv(emitter, "dalloc_thread_uid", emitter_type_uint64, &node->dalloc_tctx->thr_uid); prof_tdata_t *dalloc_tdata = node->dalloc_tctx->tdata; assert(dalloc_tdata != NULL); if (dalloc_tdata->thread_name != NULL) { emitter_json_kv(emitter, "dalloc_thread_name", emitter_type_string, &dalloc_tdata->thread_name); } assert(!nstime_equals_zero(&node->dalloc_time)); uint64_t dalloc_time_ns = nstime_ns(&node->dalloc_time); emitter_json_kv(emitter, "dalloc_time", emitter_type_uint64, &dalloc_time_ns); emitter_json_array_kv_begin(emitter, "dalloc_trace"); prof_recent_alloc_dump_bt(emitter, node->dalloc_tctx); emitter_json_array_end(emitter); } emitter_json_object_end(emitter); } #define PROF_RECENT_PRINT_BUFSIZE 65536 JEMALLOC_COLD void prof_recent_alloc_dump(tsd_t *tsd, write_cb_t *write_cb, void *cbopaque) { cassert(config_prof); malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_dump_mtx); buf_writer_t buf_writer; buf_writer_init(tsd_tsdn(tsd), &buf_writer, write_cb, cbopaque, NULL, PROF_RECENT_PRINT_BUFSIZE); emitter_t emitter; emitter_init(&emitter, emitter_output_json_compact, buf_writer_cb, &buf_writer); prof_recent_list_t temp_list; malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_alloc_assert_count(tsd); ssize_t dump_max = prof_recent_alloc_max_get(tsd); ql_move(&temp_list, &prof_recent_alloc_list); ssize_t dump_count = prof_recent_alloc_count; prof_recent_alloc_count = 0; prof_recent_alloc_assert_count(tsd); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); emitter_begin(&emitter); uint64_t sample_interval = (uint64_t)1U << lg_prof_sample; emitter_json_kv(&emitter, "sample_interval", emitter_type_uint64, &sample_interval); emitter_json_kv(&emitter, "recent_alloc_max", emitter_type_ssize, &dump_max); emitter_json_array_kv_begin(&emitter, "recent_alloc"); prof_recent_t *node; ql_foreach(node, &temp_list, link) { prof_recent_alloc_dump_node(&emitter, node); } emitter_json_array_end(&emitter); emitter_end(&emitter); malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); prof_recent_alloc_assert_count(tsd); ql_concat(&temp_list, &prof_recent_alloc_list, link); ql_move(&prof_recent_alloc_list, &temp_list); prof_recent_alloc_count += dump_count; prof_recent_alloc_restore_locked(tsd, &temp_list); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); buf_writer_terminate(tsd_tsdn(tsd), &buf_writer); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_dump_mtx); prof_recent_alloc_async_cleanup(tsd, &temp_list); } #undef PROF_RECENT_PRINT_BUFSIZE bool prof_recent_init() { cassert(config_prof); prof_recent_alloc_max_init(); if (malloc_mutex_init(&prof_recent_alloc_mtx, "prof_recent_alloc", WITNESS_RANK_PROF_RECENT_ALLOC, malloc_mutex_rank_exclusive)) { return true; } if (malloc_mutex_init(&prof_recent_dump_mtx, "prof_recent_dump", WITNESS_RANK_PROF_RECENT_DUMP, malloc_mutex_rank_exclusive)) { return true; } ql_new(&prof_recent_alloc_list); return false; } redis-8.0.2/deps/jemalloc/src/prof_stats.c000066400000000000000000000027461501533116600205010ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/prof_stats.h" bool opt_prof_stats = false; malloc_mutex_t prof_stats_mtx; static prof_stats_t prof_stats_live[PROF_SC_NSIZES]; static prof_stats_t prof_stats_accum[PROF_SC_NSIZES]; static void prof_stats_enter(tsd_t *tsd, szind_t ind) { assert(opt_prof && opt_prof_stats); assert(ind < SC_NSIZES); malloc_mutex_lock(tsd_tsdn(tsd), &prof_stats_mtx); } static void prof_stats_leave(tsd_t *tsd) { malloc_mutex_unlock(tsd_tsdn(tsd), &prof_stats_mtx); } void prof_stats_inc(tsd_t *tsd, szind_t ind, size_t size) { cassert(config_prof); prof_stats_enter(tsd, ind); prof_stats_live[ind].req_sum += size; prof_stats_live[ind].count++; prof_stats_accum[ind].req_sum += size; prof_stats_accum[ind].count++; prof_stats_leave(tsd); } void prof_stats_dec(tsd_t *tsd, szind_t ind, size_t size) { cassert(config_prof); prof_stats_enter(tsd, ind); prof_stats_live[ind].req_sum -= size; prof_stats_live[ind].count--; prof_stats_leave(tsd); } void prof_stats_get_live(tsd_t *tsd, szind_t ind, prof_stats_t *stats) { cassert(config_prof); prof_stats_enter(tsd, ind); memcpy(stats, &prof_stats_live[ind], sizeof(prof_stats_t)); prof_stats_leave(tsd); } void prof_stats_get_accum(tsd_t *tsd, szind_t ind, prof_stats_t *stats) { cassert(config_prof); prof_stats_enter(tsd, ind); memcpy(stats, &prof_stats_accum[ind], sizeof(prof_stats_t)); prof_stats_leave(tsd); } redis-8.0.2/deps/jemalloc/src/prof_sys.c000066400000000000000000000363711501533116600201620ustar00rootroot00000000000000#define JEMALLOC_PROF_SYS_C_ #include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/buf_writer.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_sys.h" #ifdef JEMALLOC_PROF_LIBUNWIND #define UNW_LOCAL_ONLY #include #endif #ifdef JEMALLOC_PROF_LIBGCC /* * We have a circular dependency -- jemalloc_internal.h tells us if we should * use libgcc's unwinding functionality, but after we've included that, we've * already hooked _Unwind_Backtrace. We'll temporarily disable hooking. */ #undef _Unwind_Backtrace #include #define _Unwind_Backtrace JEMALLOC_TEST_HOOK(_Unwind_Backtrace, test_hooks_libc_hook) #endif /******************************************************************************/ malloc_mutex_t prof_dump_filename_mtx; bool prof_do_mock = false; static uint64_t prof_dump_seq; static uint64_t prof_dump_iseq; static uint64_t prof_dump_mseq; static uint64_t prof_dump_useq; static char *prof_prefix = NULL; /* The fallback allocator profiling functionality will use. */ base_t *prof_base; void bt_init(prof_bt_t *bt, void **vec) { cassert(config_prof); bt->vec = vec; bt->len = 0; } #ifdef JEMALLOC_PROF_LIBUNWIND static void prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { int nframes; cassert(config_prof); assert(*len == 0); assert(vec != NULL); assert(max_len == PROF_BT_MAX); nframes = unw_backtrace(vec, PROF_BT_MAX); if (nframes <= 0) { return; } *len = nframes; } #elif (defined(JEMALLOC_PROF_LIBGCC)) static _Unwind_Reason_Code prof_unwind_init_callback(struct _Unwind_Context *context, void *arg) { cassert(config_prof); return _URC_NO_REASON; } static _Unwind_Reason_Code prof_unwind_callback(struct _Unwind_Context *context, void *arg) { prof_unwind_data_t *data = (prof_unwind_data_t *)arg; void *ip; cassert(config_prof); ip = (void *)_Unwind_GetIP(context); if (ip == NULL) { return _URC_END_OF_STACK; } data->vec[*data->len] = ip; (*data->len)++; if (*data->len == data->max) { return _URC_END_OF_STACK; } return _URC_NO_REASON; } static void prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { prof_unwind_data_t data = {vec, len, max_len}; cassert(config_prof); assert(vec != NULL); assert(max_len == PROF_BT_MAX); _Unwind_Backtrace(prof_unwind_callback, &data); } #elif (defined(JEMALLOC_PROF_GCC)) static void prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { #define BT_FRAME(i) \ if ((i) < max_len) { \ void *p; \ if (__builtin_frame_address(i) == 0) { \ return; \ } \ p = __builtin_return_address(i); \ if (p == NULL) { \ return; \ } \ vec[(i)] = p; \ *len = (i) + 1; \ } else { \ return; \ } cassert(config_prof); assert(vec != NULL); assert(max_len == PROF_BT_MAX); BT_FRAME(0) BT_FRAME(1) BT_FRAME(2) BT_FRAME(3) BT_FRAME(4) BT_FRAME(5) BT_FRAME(6) BT_FRAME(7) BT_FRAME(8) BT_FRAME(9) BT_FRAME(10) BT_FRAME(11) BT_FRAME(12) BT_FRAME(13) BT_FRAME(14) BT_FRAME(15) BT_FRAME(16) BT_FRAME(17) BT_FRAME(18) BT_FRAME(19) BT_FRAME(20) BT_FRAME(21) BT_FRAME(22) BT_FRAME(23) BT_FRAME(24) BT_FRAME(25) BT_FRAME(26) BT_FRAME(27) BT_FRAME(28) BT_FRAME(29) BT_FRAME(30) BT_FRAME(31) BT_FRAME(32) BT_FRAME(33) BT_FRAME(34) BT_FRAME(35) BT_FRAME(36) BT_FRAME(37) BT_FRAME(38) BT_FRAME(39) BT_FRAME(40) BT_FRAME(41) BT_FRAME(42) BT_FRAME(43) BT_FRAME(44) BT_FRAME(45) BT_FRAME(46) BT_FRAME(47) BT_FRAME(48) BT_FRAME(49) BT_FRAME(50) BT_FRAME(51) BT_FRAME(52) BT_FRAME(53) BT_FRAME(54) BT_FRAME(55) BT_FRAME(56) BT_FRAME(57) BT_FRAME(58) BT_FRAME(59) BT_FRAME(60) BT_FRAME(61) BT_FRAME(62) BT_FRAME(63) BT_FRAME(64) BT_FRAME(65) BT_FRAME(66) BT_FRAME(67) BT_FRAME(68) BT_FRAME(69) BT_FRAME(70) BT_FRAME(71) BT_FRAME(72) BT_FRAME(73) BT_FRAME(74) BT_FRAME(75) BT_FRAME(76) BT_FRAME(77) BT_FRAME(78) BT_FRAME(79) BT_FRAME(80) BT_FRAME(81) BT_FRAME(82) BT_FRAME(83) BT_FRAME(84) BT_FRAME(85) BT_FRAME(86) BT_FRAME(87) BT_FRAME(88) BT_FRAME(89) BT_FRAME(90) BT_FRAME(91) BT_FRAME(92) BT_FRAME(93) BT_FRAME(94) BT_FRAME(95) BT_FRAME(96) BT_FRAME(97) BT_FRAME(98) BT_FRAME(99) BT_FRAME(100) BT_FRAME(101) BT_FRAME(102) BT_FRAME(103) BT_FRAME(104) BT_FRAME(105) BT_FRAME(106) BT_FRAME(107) BT_FRAME(108) BT_FRAME(109) BT_FRAME(110) BT_FRAME(111) BT_FRAME(112) BT_FRAME(113) BT_FRAME(114) BT_FRAME(115) BT_FRAME(116) BT_FRAME(117) BT_FRAME(118) BT_FRAME(119) BT_FRAME(120) BT_FRAME(121) BT_FRAME(122) BT_FRAME(123) BT_FRAME(124) BT_FRAME(125) BT_FRAME(126) BT_FRAME(127) #undef BT_FRAME } #else static void prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { cassert(config_prof); not_reached(); } #endif void prof_backtrace(tsd_t *tsd, prof_bt_t *bt) { cassert(config_prof); prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get(); assert(prof_backtrace_hook != NULL); pre_reentrancy(tsd, NULL); prof_backtrace_hook(bt->vec, &bt->len, PROF_BT_MAX); post_reentrancy(tsd); } void prof_hooks_init() { prof_backtrace_hook_set(&prof_backtrace_impl); prof_dump_hook_set(NULL); } void prof_unwind_init() { #ifdef JEMALLOC_PROF_LIBGCC /* * Cause the backtracing machinery to allocate its internal * state before enabling profiling. */ _Unwind_Backtrace(prof_unwind_init_callback, NULL); #endif } static int prof_sys_thread_name_read_impl(char *buf, size_t limit) { #if defined(JEMALLOC_HAVE_PTHREAD_GETNAME_NP) return pthread_getname_np(pthread_self(), buf, limit); #elif defined(JEMALLOC_HAVE_PTHREAD_GET_NAME_NP) pthread_get_name_np(pthread_self(), buf, limit); return 0; #else return ENOSYS; #endif } prof_sys_thread_name_read_t *JET_MUTABLE prof_sys_thread_name_read = prof_sys_thread_name_read_impl; void prof_sys_thread_name_fetch(tsd_t *tsd) { #define THREAD_NAME_MAX_LEN 16 char buf[THREAD_NAME_MAX_LEN]; if (!prof_sys_thread_name_read(buf, THREAD_NAME_MAX_LEN)) { prof_thread_name_set_impl(tsd, buf); } #undef THREAD_NAME_MAX_LEN } int prof_getpid(void) { #ifdef _WIN32 return GetCurrentProcessId(); #else return getpid(); #endif } /* * This buffer is rather large for stack allocation, so use a single buffer for * all profile dumps; protected by prof_dump_mtx. */ static char prof_dump_buf[PROF_DUMP_BUFSIZE]; typedef struct prof_dump_arg_s prof_dump_arg_t; struct prof_dump_arg_s { /* * Whether error should be handled locally: if true, then we print out * error message as well as abort (if opt_abort is true) when an error * occurred, and we also report the error back to the caller in the end; * if false, then we only report the error back to the caller in the * end. */ const bool handle_error_locally; /* * Whether there has been an error in the dumping process, which could * have happened either in file opening or in file writing. When an * error has already occurred, we will stop further writing to the file. */ bool error; /* File descriptor of the dump file. */ int prof_dump_fd; }; static void prof_dump_check_possible_error(prof_dump_arg_t *arg, bool err_cond, const char *format, ...) { assert(!arg->error); if (!err_cond) { return; } arg->error = true; if (!arg->handle_error_locally) { return; } va_list ap; char buf[PROF_PRINTF_BUFSIZE]; va_start(ap, format); malloc_vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); malloc_write(buf); if (opt_abort) { abort(); } } static int prof_dump_open_file_impl(const char *filename, int mode) { return creat(filename, mode); } prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file = prof_dump_open_file_impl; static void prof_dump_open(prof_dump_arg_t *arg, const char *filename) { arg->prof_dump_fd = prof_dump_open_file(filename, 0644); prof_dump_check_possible_error(arg, arg->prof_dump_fd == -1, ": failed to open \"%s\"\n", filename); } prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd; static void prof_dump_flush(void *opaque, const char *s) { cassert(config_prof); prof_dump_arg_t *arg = (prof_dump_arg_t *)opaque; if (!arg->error) { ssize_t err = prof_dump_write_file(arg->prof_dump_fd, s, strlen(s)); prof_dump_check_possible_error(arg, err == -1, ": failed to write during heap profile flush\n"); } } static void prof_dump_close(prof_dump_arg_t *arg) { if (arg->prof_dump_fd != -1) { close(arg->prof_dump_fd); } } #ifndef _WIN32 JEMALLOC_FORMAT_PRINTF(1, 2) static int prof_open_maps_internal(const char *format, ...) { int mfd; va_list ap; char filename[PATH_MAX + 1]; va_start(ap, format); malloc_vsnprintf(filename, sizeof(filename), format, ap); va_end(ap); #if defined(O_CLOEXEC) mfd = open(filename, O_RDONLY | O_CLOEXEC); #else mfd = open(filename, O_RDONLY); if (mfd != -1) { fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC); } #endif return mfd; } #endif static int prof_dump_open_maps_impl() { int mfd; cassert(config_prof); #if defined(__FreeBSD__) || defined(__DragonFly__) mfd = prof_open_maps_internal("/proc/curproc/map"); #elif defined(_WIN32) mfd = -1; // Not implemented #else int pid = prof_getpid(); mfd = prof_open_maps_internal("/proc/%d/task/%d/maps", pid, pid); if (mfd == -1) { mfd = prof_open_maps_internal("/proc/%d/maps", pid); } #endif return mfd; } prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps = prof_dump_open_maps_impl; static ssize_t prof_dump_read_maps_cb(void *read_cbopaque, void *buf, size_t limit) { int mfd = *(int *)read_cbopaque; assert(mfd != -1); return malloc_read_fd(mfd, buf, limit); } static void prof_dump_maps(buf_writer_t *buf_writer) { int mfd = prof_dump_open_maps(); if (mfd == -1) { return; } buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n"); buf_writer_pipe(buf_writer, prof_dump_read_maps_cb, &mfd); close(mfd); } static bool prof_dump(tsd_t *tsd, bool propagate_err, const char *filename, bool leakcheck) { cassert(config_prof); assert(tsd_reentrancy_level_get(tsd) == 0); prof_tdata_t * tdata = prof_tdata_get(tsd, true); if (tdata == NULL) { return true; } prof_dump_arg_t arg = {/* handle_error_locally */ !propagate_err, /* error */ false, /* prof_dump_fd */ -1}; pre_reentrancy(tsd, NULL); malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx); prof_dump_open(&arg, filename); buf_writer_t buf_writer; bool err = buf_writer_init(tsd_tsdn(tsd), &buf_writer, prof_dump_flush, &arg, prof_dump_buf, PROF_DUMP_BUFSIZE); assert(!err); prof_dump_impl(tsd, buf_writer_cb, &buf_writer, tdata, leakcheck); prof_dump_maps(&buf_writer); buf_writer_terminate(tsd_tsdn(tsd), &buf_writer); prof_dump_close(&arg); prof_dump_hook_t dump_hook = prof_dump_hook_get(); if (dump_hook != NULL) { dump_hook(filename); } malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx); post_reentrancy(tsd); return arg.error; } /* * If profiling is off, then PROF_DUMP_FILENAME_LEN is 1, so we'll end up * calling strncpy with a size of 0, which triggers a -Wstringop-truncation * warning (strncpy can never actually be called in this case, since we bail out * much earlier when config_prof is false). This function works around the * warning to let us leave the warning on. */ static inline void prof_strncpy(char *UNUSED dest, const char *UNUSED src, size_t UNUSED size) { cassert(config_prof); #ifdef JEMALLOC_PROF strncpy(dest, src, size); #endif } static const char * prof_prefix_get(tsdn_t* tsdn) { malloc_mutex_assert_owner(tsdn, &prof_dump_filename_mtx); return prof_prefix == NULL ? opt_prof_prefix : prof_prefix; } static bool prof_prefix_is_empty(tsdn_t *tsdn) { malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); bool ret = (prof_prefix_get(tsdn)[0] == '\0'); malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); return ret; } #define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1) #define VSEQ_INVALID UINT64_C(0xffffffffffffffff) static void prof_dump_filename(tsd_t *tsd, char *filename, char v, uint64_t vseq) { cassert(config_prof); assert(tsd_reentrancy_level_get(tsd) == 0); const char *prefix = prof_prefix_get(tsd_tsdn(tsd)); if (vseq != VSEQ_INVALID) { /* "...v.heap" */ malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE, "%s.%d.%"FMTu64".%c%"FMTu64".heap", prefix, prof_getpid(), prof_dump_seq, v, vseq); } else { /* "....heap" */ malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE, "%s.%d.%"FMTu64".%c.heap", prefix, prof_getpid(), prof_dump_seq, v); } prof_dump_seq++; } void prof_get_default_filename(tsdn_t *tsdn, char *filename, uint64_t ind) { malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN, "%s.%d.%"FMTu64".json", prof_prefix_get(tsdn), prof_getpid(), ind); malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); } void prof_fdump_impl(tsd_t *tsd) { char filename[DUMP_FILENAME_BUFSIZE]; assert(!prof_prefix_is_empty(tsd_tsdn(tsd))); malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx); prof_dump_filename(tsd, filename, 'f', VSEQ_INVALID); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); prof_dump(tsd, false, filename, opt_prof_leak); } bool prof_prefix_set(tsdn_t *tsdn, const char *prefix) { cassert(config_prof); ctl_mtx_assert_held(tsdn); malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); if (prof_prefix == NULL) { malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); /* Everything is still guarded by ctl_mtx. */ char *buffer = base_alloc(tsdn, prof_base, PROF_DUMP_FILENAME_LEN, QUANTUM); if (buffer == NULL) { return true; } malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); prof_prefix = buffer; } assert(prof_prefix != NULL); prof_strncpy(prof_prefix, prefix, PROF_DUMP_FILENAME_LEN - 1); prof_prefix[PROF_DUMP_FILENAME_LEN - 1] = '\0'; malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); return false; } void prof_idump_impl(tsd_t *tsd) { malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx); if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') { malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); return; } char filename[PATH_MAX + 1]; prof_dump_filename(tsd, filename, 'i', prof_dump_iseq); prof_dump_iseq++; malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); prof_dump(tsd, false, filename, false); } bool prof_mdump_impl(tsd_t *tsd, const char *filename) { char filename_buf[DUMP_FILENAME_BUFSIZE]; if (filename == NULL) { /* No filename specified, so automatically generate one. */ malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx); if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') { malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); return true; } prof_dump_filename(tsd, filename_buf, 'm', prof_dump_mseq); prof_dump_mseq++; malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); filename = filename_buf; } return prof_dump(tsd, true, filename, false); } void prof_gdump_impl(tsd_t *tsd) { tsdn_t *tsdn = tsd_tsdn(tsd); malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); if (prof_prefix_get(tsdn)[0] == '\0') { malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); return; } char filename[DUMP_FILENAME_BUFSIZE]; prof_dump_filename(tsd, filename, 'u', prof_dump_useq); prof_dump_useq++; malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); prof_dump(tsd, false, filename, false); } redis-8.0.2/deps/jemalloc/src/psset.c000066400000000000000000000273121501533116600174470ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/psset.h" #include "jemalloc/internal/fb.h" void psset_init(psset_t *psset) { for (unsigned i = 0; i < PSSET_NPSIZES; i++) { hpdata_age_heap_new(&psset->pageslabs[i]); } fb_init(psset->pageslab_bitmap, PSSET_NPSIZES); memset(&psset->merged_stats, 0, sizeof(psset->merged_stats)); memset(&psset->stats, 0, sizeof(psset->stats)); hpdata_empty_list_init(&psset->empty); for (int i = 0; i < PSSET_NPURGE_LISTS; i++) { hpdata_purge_list_init(&psset->to_purge[i]); } fb_init(psset->purge_bitmap, PSSET_NPURGE_LISTS); hpdata_hugify_list_init(&psset->to_hugify); } static void psset_bin_stats_accum(psset_bin_stats_t *dst, psset_bin_stats_t *src) { dst->npageslabs += src->npageslabs; dst->nactive += src->nactive; dst->ndirty += src->ndirty; } void psset_stats_accum(psset_stats_t *dst, psset_stats_t *src) { psset_bin_stats_accum(&dst->full_slabs[0], &src->full_slabs[0]); psset_bin_stats_accum(&dst->full_slabs[1], &src->full_slabs[1]); psset_bin_stats_accum(&dst->empty_slabs[0], &src->empty_slabs[0]); psset_bin_stats_accum(&dst->empty_slabs[1], &src->empty_slabs[1]); for (pszind_t i = 0; i < PSSET_NPSIZES; i++) { psset_bin_stats_accum(&dst->nonfull_slabs[i][0], &src->nonfull_slabs[i][0]); psset_bin_stats_accum(&dst->nonfull_slabs[i][1], &src->nonfull_slabs[i][1]); } } /* * The stats maintenance strategy is to remove a pageslab's contribution to the * stats when we call psset_update_begin, and re-add it (to a potentially new * bin) when we call psset_update_end. */ JEMALLOC_ALWAYS_INLINE void psset_bin_stats_insert_remove(psset_t *psset, psset_bin_stats_t *binstats, hpdata_t *ps, bool insert) { size_t mul = insert ? (size_t)1 : (size_t)-1; size_t huge_idx = (size_t)hpdata_huge_get(ps); binstats[huge_idx].npageslabs += mul * 1; binstats[huge_idx].nactive += mul * hpdata_nactive_get(ps); binstats[huge_idx].ndirty += mul * hpdata_ndirty_get(ps); psset->merged_stats.npageslabs += mul * 1; psset->merged_stats.nactive += mul * hpdata_nactive_get(ps); psset->merged_stats.ndirty += mul * hpdata_ndirty_get(ps); if (config_debug) { psset_bin_stats_t check_stats = {0}; for (size_t huge = 0; huge <= 1; huge++) { psset_bin_stats_accum(&check_stats, &psset->stats.full_slabs[huge]); psset_bin_stats_accum(&check_stats, &psset->stats.empty_slabs[huge]); for (pszind_t pind = 0; pind < PSSET_NPSIZES; pind++) { psset_bin_stats_accum(&check_stats, &psset->stats.nonfull_slabs[pind][huge]); } } assert(psset->merged_stats.npageslabs == check_stats.npageslabs); assert(psset->merged_stats.nactive == check_stats.nactive); assert(psset->merged_stats.ndirty == check_stats.ndirty); } } static void psset_bin_stats_insert(psset_t *psset, psset_bin_stats_t *binstats, hpdata_t *ps) { psset_bin_stats_insert_remove(psset, binstats, ps, true); } static void psset_bin_stats_remove(psset_t *psset, psset_bin_stats_t *binstats, hpdata_t *ps) { psset_bin_stats_insert_remove(psset, binstats, ps, false); } static void psset_hpdata_heap_remove(psset_t *psset, pszind_t pind, hpdata_t *ps) { hpdata_age_heap_remove(&psset->pageslabs[pind], ps); if (hpdata_age_heap_empty(&psset->pageslabs[pind])) { fb_unset(psset->pageslab_bitmap, PSSET_NPSIZES, (size_t)pind); } } static void psset_hpdata_heap_insert(psset_t *psset, pszind_t pind, hpdata_t *ps) { if (hpdata_age_heap_empty(&psset->pageslabs[pind])) { fb_set(psset->pageslab_bitmap, PSSET_NPSIZES, (size_t)pind); } hpdata_age_heap_insert(&psset->pageslabs[pind], ps); } static void psset_stats_insert(psset_t* psset, hpdata_t *ps) { if (hpdata_empty(ps)) { psset_bin_stats_insert(psset, psset->stats.empty_slabs, ps); } else if (hpdata_full(ps)) { psset_bin_stats_insert(psset, psset->stats.full_slabs, ps); } else { size_t longest_free_range = hpdata_longest_free_range_get(ps); pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( longest_free_range << LG_PAGE)); assert(pind < PSSET_NPSIZES); psset_bin_stats_insert(psset, psset->stats.nonfull_slabs[pind], ps); } } static void psset_stats_remove(psset_t *psset, hpdata_t *ps) { if (hpdata_empty(ps)) { psset_bin_stats_remove(psset, psset->stats.empty_slabs, ps); } else if (hpdata_full(ps)) { psset_bin_stats_remove(psset, psset->stats.full_slabs, ps); } else { size_t longest_free_range = hpdata_longest_free_range_get(ps); pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( longest_free_range << LG_PAGE)); assert(pind < PSSET_NPSIZES); psset_bin_stats_remove(psset, psset->stats.nonfull_slabs[pind], ps); } } /* * Put ps into some container so that it can be found during future allocation * requests. */ static void psset_alloc_container_insert(psset_t *psset, hpdata_t *ps) { assert(!hpdata_in_psset_alloc_container_get(ps)); hpdata_in_psset_alloc_container_set(ps, true); if (hpdata_empty(ps)) { /* * This prepend, paired with popping the head in psset_fit, * means we implement LIFO ordering for the empty slabs set, * which seems reasonable. */ hpdata_empty_list_prepend(&psset->empty, ps); } else if (hpdata_full(ps)) { /* * We don't need to keep track of the full slabs; we're never * going to return them from a psset_pick_alloc call. */ } else { size_t longest_free_range = hpdata_longest_free_range_get(ps); pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( longest_free_range << LG_PAGE)); assert(pind < PSSET_NPSIZES); psset_hpdata_heap_insert(psset, pind, ps); } } /* Remove ps from those collections. */ static void psset_alloc_container_remove(psset_t *psset, hpdata_t *ps) { assert(hpdata_in_psset_alloc_container_get(ps)); hpdata_in_psset_alloc_container_set(ps, false); if (hpdata_empty(ps)) { hpdata_empty_list_remove(&psset->empty, ps); } else if (hpdata_full(ps)) { /* Same as above -- do nothing in this case. */ } else { size_t longest_free_range = hpdata_longest_free_range_get(ps); pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( longest_free_range << LG_PAGE)); assert(pind < PSSET_NPSIZES); psset_hpdata_heap_remove(psset, pind, ps); } } static size_t psset_purge_list_ind(hpdata_t *ps) { size_t ndirty = hpdata_ndirty_get(ps); /* Shouldn't have something with no dirty pages purgeable. */ assert(ndirty > 0); /* * Higher indices correspond to lists we'd like to purge earlier; make * the two highest indices correspond to empty lists, which we attempt * to purge before purging any non-empty list. This has two advantages: * - Empty page slabs are the least likely to get reused (we'll only * pick them for an allocation if we have no other choice). * - Empty page slabs can purge every dirty page they contain in a * single call, which is not usually the case. * * We purge hugeified empty slabs before nonhugeified ones, on the basis * that they are fully dirty, while nonhugified slabs might not be, so * we free up more pages more easily. */ if (hpdata_nactive_get(ps) == 0) { if (hpdata_huge_get(ps)) { return PSSET_NPURGE_LISTS - 1; } else { return PSSET_NPURGE_LISTS - 2; } } pszind_t pind = sz_psz2ind(sz_psz_quantize_floor(ndirty << LG_PAGE)); /* * For non-empty slabs, we may reuse them again. Prefer purging * non-hugeified slabs before hugeified ones then, among pages of * similar dirtiness. We still get some benefit from the hugification. */ return (size_t)pind * 2 + (hpdata_huge_get(ps) ? 0 : 1); } static void psset_maybe_remove_purge_list(psset_t *psset, hpdata_t *ps) { /* * Remove the hpdata from its purge list (if it's in one). Even if it's * going to stay in the same one, by appending it during * psset_update_end, we move it to the end of its queue, so that we * purge LRU within a given dirtiness bucket. */ if (hpdata_purge_allowed_get(ps)) { size_t ind = psset_purge_list_ind(ps); hpdata_purge_list_t *purge_list = &psset->to_purge[ind]; hpdata_purge_list_remove(purge_list, ps); if (hpdata_purge_list_empty(purge_list)) { fb_unset(psset->purge_bitmap, PSSET_NPURGE_LISTS, ind); } } } static void psset_maybe_insert_purge_list(psset_t *psset, hpdata_t *ps) { if (hpdata_purge_allowed_get(ps)) { size_t ind = psset_purge_list_ind(ps); hpdata_purge_list_t *purge_list = &psset->to_purge[ind]; if (hpdata_purge_list_empty(purge_list)) { fb_set(psset->purge_bitmap, PSSET_NPURGE_LISTS, ind); } hpdata_purge_list_append(purge_list, ps); } } void psset_update_begin(psset_t *psset, hpdata_t *ps) { hpdata_assert_consistent(ps); assert(hpdata_in_psset_get(ps)); hpdata_updating_set(ps, true); psset_stats_remove(psset, ps); if (hpdata_in_psset_alloc_container_get(ps)) { /* * Some metadata updates can break alloc container invariants * (e.g. the longest free range determines the hpdata_heap_t the * pageslab lives in). */ assert(hpdata_alloc_allowed_get(ps)); psset_alloc_container_remove(psset, ps); } psset_maybe_remove_purge_list(psset, ps); /* * We don't update presence in the hugify list; we try to keep it FIFO, * even in the presence of other metadata updates. We'll update * presence at the end of the metadata update if necessary. */ } void psset_update_end(psset_t *psset, hpdata_t *ps) { assert(hpdata_in_psset_get(ps)); hpdata_updating_set(ps, false); psset_stats_insert(psset, ps); /* * The update begin should have removed ps from whatever alloc container * it was in. */ assert(!hpdata_in_psset_alloc_container_get(ps)); if (hpdata_alloc_allowed_get(ps)) { psset_alloc_container_insert(psset, ps); } psset_maybe_insert_purge_list(psset, ps); if (hpdata_hugify_allowed_get(ps) && !hpdata_in_psset_hugify_container_get(ps)) { hpdata_in_psset_hugify_container_set(ps, true); hpdata_hugify_list_append(&psset->to_hugify, ps); } else if (!hpdata_hugify_allowed_get(ps) && hpdata_in_psset_hugify_container_get(ps)) { hpdata_in_psset_hugify_container_set(ps, false); hpdata_hugify_list_remove(&psset->to_hugify, ps); } hpdata_assert_consistent(ps); } hpdata_t * psset_pick_alloc(psset_t *psset, size_t size) { assert((size & PAGE_MASK) == 0); assert(size <= HUGEPAGE); pszind_t min_pind = sz_psz2ind(sz_psz_quantize_ceil(size)); pszind_t pind = (pszind_t)fb_ffs(psset->pageslab_bitmap, PSSET_NPSIZES, (size_t)min_pind); if (pind == PSSET_NPSIZES) { return hpdata_empty_list_first(&psset->empty); } hpdata_t *ps = hpdata_age_heap_first(&psset->pageslabs[pind]); if (ps == NULL) { return NULL; } hpdata_assert_consistent(ps); return ps; } hpdata_t * psset_pick_purge(psset_t *psset) { ssize_t ind_ssz = fb_fls(psset->purge_bitmap, PSSET_NPURGE_LISTS, PSSET_NPURGE_LISTS - 1); if (ind_ssz < 0) { return NULL; } pszind_t ind = (pszind_t)ind_ssz; assert(ind < PSSET_NPURGE_LISTS); hpdata_t *ps = hpdata_purge_list_first(&psset->to_purge[ind]); assert(ps != NULL); return ps; } hpdata_t * psset_pick_hugify(psset_t *psset) { return hpdata_hugify_list_first(&psset->to_hugify); } void psset_insert(psset_t *psset, hpdata_t *ps) { hpdata_in_psset_set(ps, true); psset_stats_insert(psset, ps); if (hpdata_alloc_allowed_get(ps)) { psset_alloc_container_insert(psset, ps); } psset_maybe_insert_purge_list(psset, ps); if (hpdata_hugify_allowed_get(ps)) { hpdata_in_psset_hugify_container_set(ps, true); hpdata_hugify_list_append(&psset->to_hugify, ps); } } void psset_remove(psset_t *psset, hpdata_t *ps) { hpdata_in_psset_set(ps, false); psset_stats_remove(psset, ps); if (hpdata_in_psset_alloc_container_get(ps)) { psset_alloc_container_remove(psset, ps); } psset_maybe_remove_purge_list(psset, ps); if (hpdata_in_psset_hugify_container_get(ps)) { hpdata_in_psset_hugify_container_set(ps, false); hpdata_hugify_list_remove(&psset->to_hugify, ps); } } redis-8.0.2/deps/jemalloc/src/rtree.c000066400000000000000000000160211501533116600174250ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/mutex.h" /* * Only the most significant bits of keys passed to rtree_{read,write}() are * used. */ bool rtree_new(rtree_t *rtree, base_t *base, bool zeroed) { #ifdef JEMALLOC_JET if (!zeroed) { memset(rtree, 0, sizeof(rtree_t)); /* Clear root. */ } #else assert(zeroed); #endif rtree->base = base; if (malloc_mutex_init(&rtree->init_lock, "rtree", WITNESS_RANK_RTREE, malloc_mutex_rank_exclusive)) { return true; } return false; } static rtree_node_elm_t * rtree_node_alloc(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) { return (rtree_node_elm_t *)base_alloc(tsdn, rtree->base, nelms * sizeof(rtree_node_elm_t), CACHELINE); } static rtree_leaf_elm_t * rtree_leaf_alloc(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) { return (rtree_leaf_elm_t *)base_alloc(tsdn, rtree->base, nelms * sizeof(rtree_leaf_elm_t), CACHELINE); } static rtree_node_elm_t * rtree_node_init(tsdn_t *tsdn, rtree_t *rtree, unsigned level, atomic_p_t *elmp) { malloc_mutex_lock(tsdn, &rtree->init_lock); /* * If *elmp is non-null, then it was initialized with the init lock * held, so we can get by with 'relaxed' here. */ rtree_node_elm_t *node = atomic_load_p(elmp, ATOMIC_RELAXED); if (node == NULL) { node = rtree_node_alloc(tsdn, rtree, ZU(1) << rtree_levels[level].bits); if (node == NULL) { malloc_mutex_unlock(tsdn, &rtree->init_lock); return NULL; } /* * Even though we hold the lock, a later reader might not; we * need release semantics. */ atomic_store_p(elmp, node, ATOMIC_RELEASE); } malloc_mutex_unlock(tsdn, &rtree->init_lock); return node; } static rtree_leaf_elm_t * rtree_leaf_init(tsdn_t *tsdn, rtree_t *rtree, atomic_p_t *elmp) { malloc_mutex_lock(tsdn, &rtree->init_lock); /* * If *elmp is non-null, then it was initialized with the init lock * held, so we can get by with 'relaxed' here. */ rtree_leaf_elm_t *leaf = atomic_load_p(elmp, ATOMIC_RELAXED); if (leaf == NULL) { leaf = rtree_leaf_alloc(tsdn, rtree, ZU(1) << rtree_levels[RTREE_HEIGHT-1].bits); if (leaf == NULL) { malloc_mutex_unlock(tsdn, &rtree->init_lock); return NULL; } /* * Even though we hold the lock, a later reader might not; we * need release semantics. */ atomic_store_p(elmp, leaf, ATOMIC_RELEASE); } malloc_mutex_unlock(tsdn, &rtree->init_lock); return leaf; } static bool rtree_node_valid(rtree_node_elm_t *node) { return ((uintptr_t)node != (uintptr_t)0); } static bool rtree_leaf_valid(rtree_leaf_elm_t *leaf) { return ((uintptr_t)leaf != (uintptr_t)0); } static rtree_node_elm_t * rtree_child_node_tryread(rtree_node_elm_t *elm, bool dependent) { rtree_node_elm_t *node; if (dependent) { node = (rtree_node_elm_t *)atomic_load_p(&elm->child, ATOMIC_RELAXED); } else { node = (rtree_node_elm_t *)atomic_load_p(&elm->child, ATOMIC_ACQUIRE); } assert(!dependent || node != NULL); return node; } static rtree_node_elm_t * rtree_child_node_read(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *elm, unsigned level, bool dependent) { rtree_node_elm_t *node; node = rtree_child_node_tryread(elm, dependent); if (!dependent && unlikely(!rtree_node_valid(node))) { node = rtree_node_init(tsdn, rtree, level + 1, &elm->child); } assert(!dependent || node != NULL); return node; } static rtree_leaf_elm_t * rtree_child_leaf_tryread(rtree_node_elm_t *elm, bool dependent) { rtree_leaf_elm_t *leaf; if (dependent) { leaf = (rtree_leaf_elm_t *)atomic_load_p(&elm->child, ATOMIC_RELAXED); } else { leaf = (rtree_leaf_elm_t *)atomic_load_p(&elm->child, ATOMIC_ACQUIRE); } assert(!dependent || leaf != NULL); return leaf; } static rtree_leaf_elm_t * rtree_child_leaf_read(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *elm, unsigned level, bool dependent) { rtree_leaf_elm_t *leaf; leaf = rtree_child_leaf_tryread(elm, dependent); if (!dependent && unlikely(!rtree_leaf_valid(leaf))) { leaf = rtree_leaf_init(tsdn, rtree, &elm->child); } assert(!dependent || leaf != NULL); return leaf; } rtree_leaf_elm_t * rtree_leaf_elm_lookup_hard(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key, bool dependent, bool init_missing) { rtree_node_elm_t *node; rtree_leaf_elm_t *leaf; #if RTREE_HEIGHT > 1 node = rtree->root; #else leaf = rtree->root; #endif if (config_debug) { uintptr_t leafkey = rtree_leafkey(key); for (unsigned i = 0; i < RTREE_CTX_NCACHE; i++) { assert(rtree_ctx->cache[i].leafkey != leafkey); } for (unsigned i = 0; i < RTREE_CTX_NCACHE_L2; i++) { assert(rtree_ctx->l2_cache[i].leafkey != leafkey); } } #define RTREE_GET_CHILD(level) { \ assert(level < RTREE_HEIGHT-1); \ if (level != 0 && !dependent && \ unlikely(!rtree_node_valid(node))) { \ return NULL; \ } \ uintptr_t subkey = rtree_subkey(key, level); \ if (level + 2 < RTREE_HEIGHT) { \ node = init_missing ? \ rtree_child_node_read(tsdn, rtree, \ &node[subkey], level, dependent) : \ rtree_child_node_tryread(&node[subkey], \ dependent); \ } else { \ leaf = init_missing ? \ rtree_child_leaf_read(tsdn, rtree, \ &node[subkey], level, dependent) : \ rtree_child_leaf_tryread(&node[subkey], \ dependent); \ } \ } /* * Cache replacement upon hard lookup (i.e. L1 & L2 rtree cache miss): * (1) evict last entry in L2 cache; (2) move the collision slot from L1 * cache down to L2; and 3) fill L1. */ #define RTREE_GET_LEAF(level) { \ assert(level == RTREE_HEIGHT-1); \ if (!dependent && unlikely(!rtree_leaf_valid(leaf))) { \ return NULL; \ } \ if (RTREE_CTX_NCACHE_L2 > 1) { \ memmove(&rtree_ctx->l2_cache[1], \ &rtree_ctx->l2_cache[0], \ sizeof(rtree_ctx_cache_elm_t) * \ (RTREE_CTX_NCACHE_L2 - 1)); \ } \ size_t slot = rtree_cache_direct_map(key); \ rtree_ctx->l2_cache[0].leafkey = \ rtree_ctx->cache[slot].leafkey; \ rtree_ctx->l2_cache[0].leaf = \ rtree_ctx->cache[slot].leaf; \ uintptr_t leafkey = rtree_leafkey(key); \ rtree_ctx->cache[slot].leafkey = leafkey; \ rtree_ctx->cache[slot].leaf = leaf; \ uintptr_t subkey = rtree_subkey(key, level); \ return &leaf[subkey]; \ } if (RTREE_HEIGHT > 1) { RTREE_GET_CHILD(0) } if (RTREE_HEIGHT > 2) { RTREE_GET_CHILD(1) } if (RTREE_HEIGHT > 3) { for (unsigned i = 2; i < RTREE_HEIGHT-1; i++) { RTREE_GET_CHILD(i) } } RTREE_GET_LEAF(RTREE_HEIGHT-1) #undef RTREE_GET_CHILD #undef RTREE_GET_LEAF not_reached(); } void rtree_ctx_data_init(rtree_ctx_t *ctx) { for (unsigned i = 0; i < RTREE_CTX_NCACHE; i++) { rtree_ctx_cache_elm_t *cache = &ctx->cache[i]; cache->leafkey = RTREE_LEAFKEY_INVALID; cache->leaf = NULL; } for (unsigned i = 0; i < RTREE_CTX_NCACHE_L2; i++) { rtree_ctx_cache_elm_t *cache = &ctx->l2_cache[i]; cache->leafkey = RTREE_LEAFKEY_INVALID; cache->leaf = NULL; } } redis-8.0.2/deps/jemalloc/src/safety_check.c000066400000000000000000000021561501533116600207400ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" static safety_check_abort_hook_t safety_check_abort; void safety_check_fail_sized_dealloc(bool current_dealloc, const void *ptr, size_t true_size, size_t input_size) { char *src = current_dealloc ? "the current pointer being freed" : "in thread cache, possibly from previous deallocations"; safety_check_fail(": size mismatch detected (true size %zu " "vs input size %zu), likely caused by application sized " "deallocation bugs (source address: %p, %s). Suggest building with " "--enable-debug or address sanitizer for debugging. Abort.\n", true_size, input_size, ptr, src); } void safety_check_set_abort(safety_check_abort_hook_t abort_fn) { safety_check_abort = abort_fn; } void safety_check_fail(const char *format, ...) { char buf[MALLOC_PRINTF_BUFSIZE]; va_list ap; va_start(ap, format); malloc_vsnprintf(buf, MALLOC_PRINTF_BUFSIZE, format, ap); va_end(ap); if (safety_check_abort == NULL) { malloc_write(buf); abort(); } else { safety_check_abort(buf); } } redis-8.0.2/deps/jemalloc/src/san.c000066400000000000000000000133331501533116600170700ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/tsd.h" /* The sanitizer options. */ size_t opt_san_guard_large = SAN_GUARD_LARGE_EVERY_N_EXTENTS_DEFAULT; size_t opt_san_guard_small = SAN_GUARD_SMALL_EVERY_N_EXTENTS_DEFAULT; /* Aligned (-1 is off) ptrs will be junked & stashed on dealloc. */ ssize_t opt_lg_san_uaf_align = SAN_LG_UAF_ALIGN_DEFAULT; /* * Initialized in san_init(). When disabled, the mask is set to (uintptr_t)-1 * to always fail the nonfast_align check. */ uintptr_t san_cache_bin_nonfast_mask = SAN_CACHE_BIN_NONFAST_MASK_DEFAULT; static inline void san_find_guarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2, uintptr_t *addr, size_t size, bool left, bool right) { assert(!edata_guarded_get(edata)); assert(size % PAGE == 0); *addr = (uintptr_t)edata_base_get(edata); if (left) { *guard1 = *addr; *addr += SAN_PAGE_GUARD; } else { *guard1 = 0; } if (right) { *guard2 = *addr + size; } else { *guard2 = 0; } } static inline void san_find_unguarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2, uintptr_t *addr, size_t size, bool left, bool right) { assert(edata_guarded_get(edata)); assert(size % PAGE == 0); *addr = (uintptr_t)edata_base_get(edata); if (right) { *guard2 = *addr + size; } else { *guard2 = 0; } if (left) { *guard1 = *addr - SAN_PAGE_GUARD; assert(*guard1 != 0); *addr = *guard1; } else { *guard1 = 0; } } void san_guard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, bool left, bool right, bool remap) { assert(left || right); if (remap) { emap_deregister_boundary(tsdn, emap, edata); } size_t size_with_guards = edata_size_get(edata); size_t usize = (left && right) ? san_two_side_unguarded_sz(size_with_guards) : san_one_side_unguarded_sz(size_with_guards); uintptr_t guard1, guard2, addr; san_find_guarded_addr(edata, &guard1, &guard2, &addr, usize, left, right); assert(edata_state_get(edata) == extent_state_active); ehooks_guard(tsdn, ehooks, (void *)guard1, (void *)guard2); /* Update the guarded addr and usable size of the edata. */ edata_size_set(edata, usize); edata_addr_set(edata, (void *)addr); edata_guarded_set(edata, true); if (remap) { emap_register_boundary(tsdn, emap, edata, SC_NSIZES, /* slab */ false); } } static void san_unguard_pages_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, bool left, bool right, bool remap) { assert(left || right); /* Remove the inner boundary which no longer exists. */ if (remap) { assert(edata_state_get(edata) == extent_state_active); emap_deregister_boundary(tsdn, emap, edata); } else { assert(edata_state_get(edata) == extent_state_retained); } size_t size = edata_size_get(edata); size_t size_with_guards = (left && right) ? san_two_side_guarded_sz(size) : san_one_side_guarded_sz(size); uintptr_t guard1, guard2, addr; san_find_unguarded_addr(edata, &guard1, &guard2, &addr, size, left, right); ehooks_unguard(tsdn, ehooks, (void *)guard1, (void *)guard2); /* Update the true addr and usable size of the edata. */ edata_size_set(edata, size_with_guards); edata_addr_set(edata, (void *)addr); edata_guarded_set(edata, false); /* * Then re-register the outer boundary including the guards, if * requested. */ if (remap) { emap_register_boundary(tsdn, emap, edata, SC_NSIZES, /* slab */ false); } } void san_unguard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, bool left, bool right) { san_unguard_pages_impl(tsdn, ehooks, edata, emap, left, right, /* remap */ true); } void san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap) { emap_assert_not_mapped(tsdn, emap, edata); /* * We don't want to touch the emap of about to be destroyed extents, as * they have been unmapped upon eviction from the retained ecache. Also, * we unguard the extents to the right, because retained extents only * own their right guard page per san_bump_alloc's logic. */ san_unguard_pages_impl(tsdn, ehooks, edata, emap, /* left */ false, /* right */ true, /* remap */ false); } static bool san_stashed_corrupted(void *ptr, size_t size) { if (san_junk_ptr_should_slow()) { for (size_t i = 0; i < size; i++) { if (((char *)ptr)[i] != (char)uaf_detect_junk) { return true; } } return false; } void *first, *mid, *last; san_junk_ptr_locations(ptr, size, &first, &mid, &last); if (*(uintptr_t *)first != uaf_detect_junk || *(uintptr_t *)mid != uaf_detect_junk || *(uintptr_t *)last != uaf_detect_junk) { return true; } return false; } void san_check_stashed_ptrs(void **ptrs, size_t nstashed, size_t usize) { /* * Verify that the junked-filled & stashed pointers remain unchanged, to * detect write-after-free. */ for (size_t n = 0; n < nstashed; n++) { void *stashed = ptrs[n]; assert(stashed != NULL); assert(cache_bin_nonfast_aligned(stashed)); if (unlikely(san_stashed_corrupted(stashed, usize))) { safety_check_fail(": Write-after-free " "detected on deallocated pointer %p (size %zu).\n", stashed, usize); } } } void tsd_san_init(tsd_t *tsd) { *tsd_san_extents_until_guard_smallp_get(tsd) = opt_san_guard_small; *tsd_san_extents_until_guard_largep_get(tsd) = opt_san_guard_large; } void san_init(ssize_t lg_san_uaf_align) { assert(lg_san_uaf_align == -1 || lg_san_uaf_align >= LG_PAGE); if (lg_san_uaf_align == -1) { san_cache_bin_nonfast_mask = (uintptr_t)-1; return; } san_cache_bin_nonfast_mask = ((uintptr_t)1 << lg_san_uaf_align) - 1; } redis-8.0.2/deps/jemalloc/src/san_bump.c000066400000000000000000000055211501533116600201130ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/san_bump.h" #include "jemalloc/internal/pac.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/ehooks.h" #include "jemalloc/internal/edata_cache.h" static bool san_bump_grow_locked(tsdn_t *tsdn, san_bump_alloc_t *sba, pac_t *pac, ehooks_t *ehooks, size_t size); edata_t * san_bump_alloc(tsdn_t *tsdn, san_bump_alloc_t* sba, pac_t *pac, ehooks_t *ehooks, size_t size, bool zero) { assert(san_bump_enabled()); edata_t* to_destroy; size_t guarded_size = san_one_side_guarded_sz(size); malloc_mutex_lock(tsdn, &sba->mtx); if (sba->curr_reg == NULL || edata_size_get(sba->curr_reg) < guarded_size) { /* * If the current region can't accommodate the allocation, * try replacing it with a larger one and destroy current if the * replacement succeeds. */ to_destroy = sba->curr_reg; bool err = san_bump_grow_locked(tsdn, sba, pac, ehooks, guarded_size); if (err) { goto label_err; } } else { to_destroy = NULL; } assert(guarded_size <= edata_size_get(sba->curr_reg)); size_t trail_size = edata_size_get(sba->curr_reg) - guarded_size; edata_t* edata; if (trail_size != 0) { edata_t* curr_reg_trail = extent_split_wrapper(tsdn, pac, ehooks, sba->curr_reg, guarded_size, trail_size, /* holding_core_locks */ true); if (curr_reg_trail == NULL) { goto label_err; } edata = sba->curr_reg; sba->curr_reg = curr_reg_trail; } else { edata = sba->curr_reg; sba->curr_reg = NULL; } malloc_mutex_unlock(tsdn, &sba->mtx); assert(!edata_guarded_get(edata)); assert(sba->curr_reg == NULL || !edata_guarded_get(sba->curr_reg)); assert(to_destroy == NULL || !edata_guarded_get(to_destroy)); if (to_destroy != NULL) { extent_destroy_wrapper(tsdn, pac, ehooks, to_destroy); } san_guard_pages(tsdn, ehooks, edata, pac->emap, /* left */ false, /* right */ true, /* remap */ true); if (extent_commit_zero(tsdn, ehooks, edata, /* commit */ true, zero, /* growing_retained */ false)) { extent_record(tsdn, pac, ehooks, &pac->ecache_retained, edata); return NULL; } if (config_prof) { extent_gdump_add(tsdn, edata); } return edata; label_err: malloc_mutex_unlock(tsdn, &sba->mtx); return NULL; } static bool san_bump_grow_locked(tsdn_t *tsdn, san_bump_alloc_t *sba, pac_t *pac, ehooks_t *ehooks, size_t size) { malloc_mutex_assert_owner(tsdn, &sba->mtx); bool committed = false, zeroed = false; size_t alloc_size = size > SBA_RETAINED_ALLOC_SIZE ? size : SBA_RETAINED_ALLOC_SIZE; assert((alloc_size & PAGE_MASK) == 0); sba->curr_reg = extent_alloc_wrapper(tsdn, pac, ehooks, NULL, alloc_size, PAGE, zeroed, &committed, /* growing_retained */ true); if (sba->curr_reg == NULL) { return true; } return false; } redis-8.0.2/deps/jemalloc/src/sc.c000066400000000000000000000201631501533116600167130ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/bit_util.h" #include "jemalloc/internal/bitmap.h" #include "jemalloc/internal/pages.h" #include "jemalloc/internal/sc.h" /* * This module computes the size classes used to satisfy allocations. The logic * here was ported more or less line-by-line from a shell script, and because of * that is not the most idiomatic C. Eventually we should fix this, but for now * at least the damage is compartmentalized to this file. */ size_t reg_size_compute(int lg_base, int lg_delta, int ndelta) { return (ZU(1) << lg_base) + (ZU(ndelta) << lg_delta); } /* Returns the number of pages in the slab. */ static int slab_size(int lg_page, int lg_base, int lg_delta, int ndelta) { size_t page = (ZU(1) << lg_page); size_t reg_size = reg_size_compute(lg_base, lg_delta, ndelta); size_t try_slab_size = page; size_t try_nregs = try_slab_size / reg_size; size_t perfect_slab_size = 0; bool perfect = false; /* * This loop continues until we find the least common multiple of the * page size and size class size. Size classes are all of the form * base + ndelta * delta == (ndelta + base/ndelta) * delta, which is * (ndelta + ngroup) * delta. The way we choose slabbing strategies * means that delta is at most the page size and ndelta < ngroup. So * the loop executes for at most 2 * ngroup - 1 iterations, which is * also the bound on the number of pages in a slab chosen by default. * With the current default settings, this is at most 7. */ while (!perfect) { perfect_slab_size = try_slab_size; size_t perfect_nregs = try_nregs; try_slab_size += page; try_nregs = try_slab_size / reg_size; if (perfect_slab_size == perfect_nregs * reg_size) { perfect = true; } } return (int)(perfect_slab_size / page); } static void size_class( /* Output. */ sc_t *sc, /* Configuration decisions. */ int lg_max_lookup, int lg_page, int lg_ngroup, /* Inputs specific to the size class. */ int index, int lg_base, int lg_delta, int ndelta) { sc->index = index; sc->lg_base = lg_base; sc->lg_delta = lg_delta; sc->ndelta = ndelta; size_t size = reg_size_compute(lg_base, lg_delta, ndelta); sc->psz = (size % (ZU(1) << lg_page) == 0); if (index == 0) { assert(!sc->psz); } if (size < (ZU(1) << (lg_page + lg_ngroup))) { sc->bin = true; sc->pgs = slab_size(lg_page, lg_base, lg_delta, ndelta); } else { sc->bin = false; sc->pgs = 0; } if (size <= (ZU(1) << lg_max_lookup)) { sc->lg_delta_lookup = lg_delta; } else { sc->lg_delta_lookup = 0; } } static void size_classes( /* Output. */ sc_data_t *sc_data, /* Determined by the system. */ size_t lg_ptr_size, int lg_quantum, /* Configuration decisions. */ int lg_tiny_min, int lg_max_lookup, int lg_page, int lg_ngroup) { int ptr_bits = (1 << lg_ptr_size) * 8; int ngroup = (1 << lg_ngroup); int ntiny = 0; int nlbins = 0; int lg_tiny_maxclass = (unsigned)-1; int nbins = 0; int npsizes = 0; int index = 0; int ndelta = 0; int lg_base = lg_tiny_min; int lg_delta = lg_base; /* Outputs that we update as we go. */ size_t lookup_maxclass = 0; size_t small_maxclass = 0; int lg_large_minclass = 0; size_t large_maxclass = 0; /* Tiny size classes. */ while (lg_base < lg_quantum) { sc_t *sc = &sc_data->sc[index]; size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta); if (sc->lg_delta_lookup != 0) { nlbins = index + 1; } if (sc->psz) { npsizes++; } if (sc->bin) { nbins++; } ntiny++; /* Final written value is correct. */ lg_tiny_maxclass = lg_base; index++; lg_delta = lg_base; lg_base++; } /* First non-tiny (pseudo) group. */ if (ntiny != 0) { sc_t *sc = &sc_data->sc[index]; /* * See the note in sc.h; the first non-tiny size class has an * unusual encoding. */ lg_base--; ndelta = 1; size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta); index++; lg_base++; lg_delta++; if (sc->psz) { npsizes++; } if (sc->bin) { nbins++; } } while (ndelta < ngroup) { sc_t *sc = &sc_data->sc[index]; size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta); index++; ndelta++; if (sc->psz) { npsizes++; } if (sc->bin) { nbins++; } } /* All remaining groups. */ lg_base = lg_base + lg_ngroup; while (lg_base < ptr_bits - 1) { ndelta = 1; int ndelta_limit; if (lg_base == ptr_bits - 2) { ndelta_limit = ngroup - 1; } else { ndelta_limit = ngroup; } while (ndelta <= ndelta_limit) { sc_t *sc = &sc_data->sc[index]; size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, lg_base, lg_delta, ndelta); if (sc->lg_delta_lookup != 0) { nlbins = index + 1; /* Final written value is correct. */ lookup_maxclass = (ZU(1) << lg_base) + (ZU(ndelta) << lg_delta); } if (sc->psz) { npsizes++; } if (sc->bin) { nbins++; /* Final written value is correct. */ small_maxclass = (ZU(1) << lg_base) + (ZU(ndelta) << lg_delta); if (lg_ngroup > 0) { lg_large_minclass = lg_base + 1; } else { lg_large_minclass = lg_base + 2; } } large_maxclass = (ZU(1) << lg_base) + (ZU(ndelta) << lg_delta); index++; ndelta++; } lg_base++; lg_delta++; } /* Additional outputs. */ int nsizes = index; unsigned lg_ceil_nsizes = lg_ceil(nsizes); /* Fill in the output data. */ sc_data->ntiny = ntiny; sc_data->nlbins = nlbins; sc_data->nbins = nbins; sc_data->nsizes = nsizes; sc_data->lg_ceil_nsizes = lg_ceil_nsizes; sc_data->npsizes = npsizes; sc_data->lg_tiny_maxclass = lg_tiny_maxclass; sc_data->lookup_maxclass = lookup_maxclass; sc_data->small_maxclass = small_maxclass; sc_data->lg_large_minclass = lg_large_minclass; sc_data->large_minclass = (ZU(1) << lg_large_minclass); sc_data->large_maxclass = large_maxclass; /* * We compute these values in two ways: * - Incrementally, as above. * - In macros, in sc.h. * The computation is easier when done incrementally, but putting it in * a constant makes it available to the fast paths without having to * touch the extra global cacheline. We assert, however, that the two * computations are equivalent. */ assert(sc_data->npsizes == SC_NPSIZES); assert(sc_data->lg_tiny_maxclass == SC_LG_TINY_MAXCLASS); assert(sc_data->small_maxclass == SC_SMALL_MAXCLASS); assert(sc_data->large_minclass == SC_LARGE_MINCLASS); assert(sc_data->lg_large_minclass == SC_LG_LARGE_MINCLASS); assert(sc_data->large_maxclass == SC_LARGE_MAXCLASS); /* * In the allocation fastpath, we want to assume that we can * unconditionally subtract the requested allocation size from * a ssize_t, and detect passing through 0 correctly. This * results in optimal generated code. For this to work, the * maximum allocation size must be less than SSIZE_MAX. */ assert(SC_LARGE_MAXCLASS < SSIZE_MAX); } void sc_data_init(sc_data_t *sc_data) { size_classes(sc_data, LG_SIZEOF_PTR, LG_QUANTUM, SC_LG_TINY_MIN, SC_LG_MAX_LOOKUP, LG_PAGE, SC_LG_NGROUP); sc_data->initialized = true; } static void sc_data_update_sc_slab_size(sc_t *sc, size_t reg_size, size_t pgs_guess) { size_t min_pgs = reg_size / PAGE; if (reg_size % PAGE != 0) { min_pgs++; } /* * BITMAP_MAXBITS is actually determined by putting the smallest * possible size-class on one page, so this can never be 0. */ size_t max_pgs = BITMAP_MAXBITS * reg_size / PAGE; assert(min_pgs <= max_pgs); assert(min_pgs > 0); assert(max_pgs >= 1); if (pgs_guess < min_pgs) { sc->pgs = (int)min_pgs; } else if (pgs_guess > max_pgs) { sc->pgs = (int)max_pgs; } else { sc->pgs = (int)pgs_guess; } } void sc_data_update_slab_size(sc_data_t *data, size_t begin, size_t end, int pgs) { assert(data->initialized); for (int i = 0; i < data->nsizes; i++) { sc_t *sc = &data->sc[i]; if (!sc->bin) { break; } size_t reg_size = reg_size_compute(sc->lg_base, sc->lg_delta, sc->ndelta); if (begin <= reg_size && reg_size <= end) { sc_data_update_sc_slab_size(sc, reg_size, pgs); } } } void sc_boot(sc_data_t *data) { sc_data_init(data); } redis-8.0.2/deps/jemalloc/src/sec.c000066400000000000000000000315251501533116600170640ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/sec.h" static edata_t *sec_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated); static bool sec_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); static bool sec_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated); static void sec_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated); static void sec_bin_init(sec_bin_t *bin) { bin->being_batch_filled = false; bin->bytes_cur = 0; edata_list_active_init(&bin->freelist); } bool sec_init(tsdn_t *tsdn, sec_t *sec, base_t *base, pai_t *fallback, const sec_opts_t *opts) { assert(opts->max_alloc >= PAGE); size_t max_alloc = PAGE_FLOOR(opts->max_alloc); pszind_t npsizes = sz_psz2ind(max_alloc) + 1; size_t sz_shards = opts->nshards * sizeof(sec_shard_t); size_t sz_bins = opts->nshards * (size_t)npsizes * sizeof(sec_bin_t); size_t sz_alloc = sz_shards + sz_bins; void *dynalloc = base_alloc(tsdn, base, sz_alloc, CACHELINE); if (dynalloc == NULL) { return true; } sec_shard_t *shard_cur = (sec_shard_t *)dynalloc; sec->shards = shard_cur; sec_bin_t *bin_cur = (sec_bin_t *)&shard_cur[opts->nshards]; /* Just for asserts, below. */ sec_bin_t *bin_start = bin_cur; for (size_t i = 0; i < opts->nshards; i++) { sec_shard_t *shard = shard_cur; shard_cur++; bool err = malloc_mutex_init(&shard->mtx, "sec_shard", WITNESS_RANK_SEC_SHARD, malloc_mutex_rank_exclusive); if (err) { return true; } shard->enabled = true; shard->bins = bin_cur; for (pszind_t j = 0; j < npsizes; j++) { sec_bin_init(&shard->bins[j]); bin_cur++; } shard->bytes_cur = 0; shard->to_flush_next = 0; } /* * Should have exactly matched the bin_start to the first unused byte * after the shards. */ assert((void *)shard_cur == (void *)bin_start); /* And the last bin to use up the last bytes of the allocation. */ assert((char *)bin_cur == ((char *)dynalloc + sz_alloc)); sec->fallback = fallback; sec->opts = *opts; sec->npsizes = npsizes; /* * Initialize these last so that an improper use of an SEC whose * initialization failed will segfault in an easy-to-spot way. */ sec->pai.alloc = &sec_alloc; sec->pai.alloc_batch = &pai_alloc_batch_default; sec->pai.expand = &sec_expand; sec->pai.shrink = &sec_shrink; sec->pai.dalloc = &sec_dalloc; sec->pai.dalloc_batch = &pai_dalloc_batch_default; return false; } static sec_shard_t * sec_shard_pick(tsdn_t *tsdn, sec_t *sec) { /* * Eventually, we should implement affinity, tracking source shard using * the edata_t's newly freed up fields. For now, just randomly * distribute across all shards. */ if (tsdn_null(tsdn)) { return &sec->shards[0]; } tsd_t *tsd = tsdn_tsd(tsdn); uint8_t *idxp = tsd_sec_shardp_get(tsd); if (*idxp == (uint8_t)-1) { /* * First use; initialize using the trick from Daniel Lemire's * "A fast alternative to the modulo reduction. Use a 64 bit * number to store 32 bits, since we'll deliberately overflow * when we multiply by the number of shards. */ uint64_t rand32 = prng_lg_range_u64(tsd_prng_statep_get(tsd), 32); uint32_t idx = (uint32_t)((rand32 * (uint64_t)sec->opts.nshards) >> 32); assert(idx < (uint32_t)sec->opts.nshards); *idxp = (uint8_t)idx; } return &sec->shards[*idxp]; } /* * Perhaps surprisingly, this can be called on the alloc pathways; if we hit an * empty cache, we'll try to fill it, which can push the shard over it's limit. */ static void sec_flush_some_and_unlock(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); edata_list_active_t to_flush; edata_list_active_init(&to_flush); while (shard->bytes_cur > sec->opts.bytes_after_flush) { /* Pick a victim. */ sec_bin_t *bin = &shard->bins[shard->to_flush_next]; /* Update our victim-picking state. */ shard->to_flush_next++; if (shard->to_flush_next == sec->npsizes) { shard->to_flush_next = 0; } assert(shard->bytes_cur >= bin->bytes_cur); if (bin->bytes_cur != 0) { shard->bytes_cur -= bin->bytes_cur; bin->bytes_cur = 0; edata_list_active_concat(&to_flush, &bin->freelist); } /* * Either bin->bytes_cur was 0, in which case we didn't touch * the bin list but it should be empty anyways (or else we * missed a bytes_cur update on a list modification), or it * *was* 0 and we emptied it ourselves. Either way, it should * be empty now. */ assert(edata_list_active_empty(&bin->freelist)); } malloc_mutex_unlock(tsdn, &shard->mtx); bool deferred_work_generated = false; pai_dalloc_batch(tsdn, sec->fallback, &to_flush, &deferred_work_generated); } static edata_t * sec_shard_alloc_locked(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard, sec_bin_t *bin) { malloc_mutex_assert_owner(tsdn, &shard->mtx); if (!shard->enabled) { return NULL; } edata_t *edata = edata_list_active_first(&bin->freelist); if (edata != NULL) { edata_list_active_remove(&bin->freelist, edata); assert(edata_size_get(edata) <= bin->bytes_cur); bin->bytes_cur -= edata_size_get(edata); assert(edata_size_get(edata) <= shard->bytes_cur); shard->bytes_cur -= edata_size_get(edata); } return edata; } static edata_t * sec_batch_fill_and_alloc(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard, sec_bin_t *bin, size_t size) { malloc_mutex_assert_not_owner(tsdn, &shard->mtx); edata_list_active_t result; edata_list_active_init(&result); bool deferred_work_generated = false; size_t nalloc = pai_alloc_batch(tsdn, sec->fallback, size, 1 + sec->opts.batch_fill_extra, &result, &deferred_work_generated); edata_t *ret = edata_list_active_first(&result); if (ret != NULL) { edata_list_active_remove(&result, ret); } malloc_mutex_lock(tsdn, &shard->mtx); bin->being_batch_filled = false; /* * Handle the easy case first: nothing to cache. Note that this can * only happen in case of OOM, since sec_alloc checks the expected * number of allocs, and doesn't bother going down the batch_fill * pathway if there won't be anything left to cache. So to be in this * code path, we must have asked for > 1 alloc, but only gotten 1 back. */ if (nalloc <= 1) { malloc_mutex_unlock(tsdn, &shard->mtx); return ret; } size_t new_cached_bytes = (nalloc - 1) * size; edata_list_active_concat(&bin->freelist, &result); bin->bytes_cur += new_cached_bytes; shard->bytes_cur += new_cached_bytes; if (shard->bytes_cur > sec->opts.max_bytes) { sec_flush_some_and_unlock(tsdn, sec, shard); } else { malloc_mutex_unlock(tsdn, &shard->mtx); } return ret; } static edata_t * sec_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated) { assert((size & PAGE_MASK) == 0); assert(!guarded); sec_t *sec = (sec_t *)self; if (zero || alignment > PAGE || sec->opts.nshards == 0 || size > sec->opts.max_alloc) { return pai_alloc(tsdn, sec->fallback, size, alignment, zero, /* guarded */ false, frequent_reuse, deferred_work_generated); } pszind_t pszind = sz_psz2ind(size); assert(pszind < sec->npsizes); sec_shard_t *shard = sec_shard_pick(tsdn, sec); sec_bin_t *bin = &shard->bins[pszind]; bool do_batch_fill = false; malloc_mutex_lock(tsdn, &shard->mtx); edata_t *edata = sec_shard_alloc_locked(tsdn, sec, shard, bin); if (edata == NULL) { if (!bin->being_batch_filled && sec->opts.batch_fill_extra > 0) { bin->being_batch_filled = true; do_batch_fill = true; } } malloc_mutex_unlock(tsdn, &shard->mtx); if (edata == NULL) { if (do_batch_fill) { edata = sec_batch_fill_and_alloc(tsdn, sec, shard, bin, size); } else { edata = pai_alloc(tsdn, sec->fallback, size, alignment, zero, /* guarded */ false, frequent_reuse, deferred_work_generated); } } return edata; } static bool sec_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated) { sec_t *sec = (sec_t *)self; return pai_expand(tsdn, sec->fallback, edata, old_size, new_size, zero, deferred_work_generated); } static bool sec_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated) { sec_t *sec = (sec_t *)self; return pai_shrink(tsdn, sec->fallback, edata, old_size, new_size, deferred_work_generated); } static void sec_flush_all_locked(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard) { malloc_mutex_assert_owner(tsdn, &shard->mtx); shard->bytes_cur = 0; edata_list_active_t to_flush; edata_list_active_init(&to_flush); for (pszind_t i = 0; i < sec->npsizes; i++) { sec_bin_t *bin = &shard->bins[i]; bin->bytes_cur = 0; edata_list_active_concat(&to_flush, &bin->freelist); } /* * Ordinarily we would try to avoid doing the batch deallocation while * holding the shard mutex, but the flush_all pathways only happen when * we're disabling the HPA or resetting the arena, both of which are * rare pathways. */ bool deferred_work_generated = false; pai_dalloc_batch(tsdn, sec->fallback, &to_flush, &deferred_work_generated); } static void sec_shard_dalloc_and_unlock(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard, edata_t *edata) { malloc_mutex_assert_owner(tsdn, &shard->mtx); assert(shard->bytes_cur <= sec->opts.max_bytes); size_t size = edata_size_get(edata); pszind_t pszind = sz_psz2ind(size); assert(pszind < sec->npsizes); /* * Prepending here results in LIFO allocation per bin, which seems * reasonable. */ sec_bin_t *bin = &shard->bins[pszind]; edata_list_active_prepend(&bin->freelist, edata); bin->bytes_cur += size; shard->bytes_cur += size; if (shard->bytes_cur > sec->opts.max_bytes) { /* * We've exceeded the shard limit. We make two nods in the * direction of fragmentation avoidance: we flush everything in * the shard, rather than one particular bin, and we hold the * lock while flushing (in case one of the extents we flush is * highly preferred from a fragmentation-avoidance perspective * in the backing allocator). This has the extra advantage of * not requiring advanced cache balancing strategies. */ sec_flush_some_and_unlock(tsdn, sec, shard); malloc_mutex_assert_not_owner(tsdn, &shard->mtx); } else { malloc_mutex_unlock(tsdn, &shard->mtx); } } static void sec_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated) { sec_t *sec = (sec_t *)self; if (sec->opts.nshards == 0 || edata_size_get(edata) > sec->opts.max_alloc) { pai_dalloc(tsdn, sec->fallback, edata, deferred_work_generated); return; } sec_shard_t *shard = sec_shard_pick(tsdn, sec); malloc_mutex_lock(tsdn, &shard->mtx); if (shard->enabled) { sec_shard_dalloc_and_unlock(tsdn, sec, shard, edata); } else { malloc_mutex_unlock(tsdn, &shard->mtx); pai_dalloc(tsdn, sec->fallback, edata, deferred_work_generated); } } void sec_flush(tsdn_t *tsdn, sec_t *sec) { for (size_t i = 0; i < sec->opts.nshards; i++) { malloc_mutex_lock(tsdn, &sec->shards[i].mtx); sec_flush_all_locked(tsdn, sec, &sec->shards[i]); malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); } } void sec_disable(tsdn_t *tsdn, sec_t *sec) { for (size_t i = 0; i < sec->opts.nshards; i++) { malloc_mutex_lock(tsdn, &sec->shards[i].mtx); sec->shards[i].enabled = false; sec_flush_all_locked(tsdn, sec, &sec->shards[i]); malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); } } void sec_stats_merge(tsdn_t *tsdn, sec_t *sec, sec_stats_t *stats) { size_t sum = 0; for (size_t i = 0; i < sec->opts.nshards; i++) { /* * We could save these lock acquisitions by making bytes_cur * atomic, but stats collection is rare anyways and we expect * the number and type of stats to get more interesting. */ malloc_mutex_lock(tsdn, &sec->shards[i].mtx); sum += sec->shards[i].bytes_cur; malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); } stats->bytes += sum; } void sec_mutex_stats_read(tsdn_t *tsdn, sec_t *sec, mutex_prof_data_t *mutex_prof_data) { for (size_t i = 0; i < sec->opts.nshards; i++) { malloc_mutex_lock(tsdn, &sec->shards[i].mtx); malloc_mutex_prof_accum(tsdn, mutex_prof_data, &sec->shards[i].mtx); malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); } } void sec_prefork2(tsdn_t *tsdn, sec_t *sec) { for (size_t i = 0; i < sec->opts.nshards; i++) { malloc_mutex_prefork(tsdn, &sec->shards[i].mtx); } } void sec_postfork_parent(tsdn_t *tsdn, sec_t *sec) { for (size_t i = 0; i < sec->opts.nshards; i++) { malloc_mutex_postfork_parent(tsdn, &sec->shards[i].mtx); } } void sec_postfork_child(tsdn_t *tsdn, sec_t *sec) { for (size_t i = 0; i < sec->opts.nshards; i++) { malloc_mutex_postfork_child(tsdn, &sec->shards[i].mtx); } } redis-8.0.2/deps/jemalloc/src/stats.c000066400000000000000000002010331501533116600174410ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/emitter.h" #include "jemalloc/internal/fxp.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/mutex_prof.h" #include "jemalloc/internal/prof_stats.h" const char *global_mutex_names[mutex_prof_num_global_mutexes] = { #define OP(mtx) #mtx, MUTEX_PROF_GLOBAL_MUTEXES #undef OP }; const char *arena_mutex_names[mutex_prof_num_arena_mutexes] = { #define OP(mtx) #mtx, MUTEX_PROF_ARENA_MUTEXES #undef OP }; #define CTL_GET(n, v, t) do { \ size_t sz = sizeof(t); \ xmallctl(n, (void *)v, &sz, NULL, 0); \ } while (0) #define CTL_LEAF_PREPARE(mib, miblen, name) do { \ assert(miblen < CTL_MAX_DEPTH); \ size_t miblen_new = CTL_MAX_DEPTH; \ xmallctlmibnametomib(mib, miblen, name, &miblen_new); \ assert(miblen_new > miblen); \ } while (0) #define CTL_LEAF(mib, miblen, leaf, v, t) do { \ assert(miblen < CTL_MAX_DEPTH); \ size_t miblen_new = CTL_MAX_DEPTH; \ size_t sz = sizeof(t); \ xmallctlbymibname(mib, miblen, leaf, &miblen_new, (void *)v, \ &sz, NULL, 0); \ assert(miblen_new == miblen + 1); \ } while (0) #define CTL_M2_GET(n, i, v, t) do { \ size_t mib[CTL_MAX_DEPTH]; \ size_t miblen = sizeof(mib) / sizeof(size_t); \ size_t sz = sizeof(t); \ xmallctlnametomib(n, mib, &miblen); \ mib[2] = (i); \ xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0); \ } while (0) /******************************************************************************/ /* Data. */ bool opt_stats_print = false; char opt_stats_print_opts[stats_print_tot_num_options+1] = ""; int64_t opt_stats_interval = STATS_INTERVAL_DEFAULT; char opt_stats_interval_opts[stats_print_tot_num_options+1] = ""; static counter_accum_t stats_interval_accumulated; /* Per thread batch accum size for stats_interval. */ static uint64_t stats_interval_accum_batch; /******************************************************************************/ static uint64_t rate_per_second(uint64_t value, uint64_t uptime_ns) { uint64_t billion = 1000000000; if (uptime_ns == 0 || value == 0) { return 0; } if (uptime_ns < billion) { return value; } else { uint64_t uptime_s = uptime_ns / billion; return value / uptime_s; } } /* Calculate x.yyy and output a string (takes a fixed sized char array). */ static bool get_rate_str(uint64_t dividend, uint64_t divisor, char str[6]) { if (divisor == 0 || dividend > divisor) { /* The rate is not supposed to be greater than 1. */ return true; } if (dividend > 0) { assert(UINT64_MAX / dividend >= 1000); } unsigned n = (unsigned)((dividend * 1000) / divisor); if (n < 10) { malloc_snprintf(str, 6, "0.00%u", n); } else if (n < 100) { malloc_snprintf(str, 6, "0.0%u", n); } else if (n < 1000) { malloc_snprintf(str, 6, "0.%u", n); } else { malloc_snprintf(str, 6, "1"); } return false; } static void mutex_stats_init_cols(emitter_row_t *row, const char *table_name, emitter_col_t *name, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) { mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0; mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0; emitter_col_t *col; if (name != NULL) { emitter_col_init(name, row); name->justify = emitter_justify_left; name->width = 21; name->type = emitter_type_title; name->str_val = table_name; } #define WIDTH_uint32_t 12 #define WIDTH_uint64_t 16 #define OP(counter, counter_type, human, derived, base_counter) \ col = &col_##counter_type[k_##counter_type]; \ ++k_##counter_type; \ emitter_col_init(col, row); \ col->justify = emitter_justify_right; \ col->width = derived ? 8 : WIDTH_##counter_type; \ col->type = emitter_type_title; \ col->str_val = human; MUTEX_PROF_COUNTERS #undef OP #undef WIDTH_uint32_t #undef WIDTH_uint64_t col_uint64_t[mutex_counter_total_wait_time_ps].width = 10; } static void mutex_stats_read_global(size_t mib[], size_t miblen, const char *name, emitter_col_t *col_name, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], uint64_t uptime) { CTL_LEAF_PREPARE(mib, miblen, name); size_t miblen_name = miblen + 1; col_name->str_val = name; emitter_col_t *dst; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, counter_type, human, derived, base_counter) \ dst = &col_##counter_type[mutex_counter_##counter]; \ dst->type = EMITTER_TYPE_##counter_type; \ if (!derived) { \ CTL_LEAF(mib, miblen_name, #counter, \ (counter_type *)&dst->bool_val, counter_type); \ } else { \ emitter_col_t *base = \ &col_##counter_type[mutex_counter_##base_counter]; \ dst->counter_type##_val = \ (counter_type)rate_per_second( \ base->counter_type##_val, uptime); \ } MUTEX_PROF_COUNTERS #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } static void mutex_stats_read_arena(size_t mib[], size_t miblen, const char *name, emitter_col_t *col_name, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], uint64_t uptime) { CTL_LEAF_PREPARE(mib, miblen, name); size_t miblen_name = miblen + 1; col_name->str_val = name; emitter_col_t *dst; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, counter_type, human, derived, base_counter) \ dst = &col_##counter_type[mutex_counter_##counter]; \ dst->type = EMITTER_TYPE_##counter_type; \ if (!derived) { \ CTL_LEAF(mib, miblen_name, #counter, \ (counter_type *)&dst->bool_val, counter_type); \ } else { \ emitter_col_t *base = \ &col_##counter_type[mutex_counter_##base_counter]; \ dst->counter_type##_val = \ (counter_type)rate_per_second( \ base->counter_type##_val, uptime); \ } MUTEX_PROF_COUNTERS #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } static void mutex_stats_read_arena_bin(size_t mib[], size_t miblen, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], uint64_t uptime) { CTL_LEAF_PREPARE(mib, miblen, "mutex"); size_t miblen_mutex = miblen + 1; emitter_col_t *dst; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, counter_type, human, derived, base_counter) \ dst = &col_##counter_type[mutex_counter_##counter]; \ dst->type = EMITTER_TYPE_##counter_type; \ if (!derived) { \ CTL_LEAF(mib, miblen_mutex, #counter, \ (counter_type *)&dst->bool_val, counter_type); \ } else { \ emitter_col_t *base = \ &col_##counter_type[mutex_counter_##base_counter]; \ dst->counter_type##_val = \ (counter_type)rate_per_second( \ base->counter_type##_val, uptime); \ } MUTEX_PROF_COUNTERS #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } /* "row" can be NULL to avoid emitting in table mode. */ static void mutex_stats_emit(emitter_t *emitter, emitter_row_t *row, emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) { if (row != NULL) { emitter_table_row(emitter, row); } mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0; mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0; emitter_col_t *col; #define EMITTER_TYPE_uint32_t emitter_type_uint32 #define EMITTER_TYPE_uint64_t emitter_type_uint64 #define OP(counter, type, human, derived, base_counter) \ if (!derived) { \ col = &col_##type[k_##type]; \ ++k_##type; \ emitter_json_kv(emitter, #counter, EMITTER_TYPE_##type, \ (const void *)&col->bool_val); \ } MUTEX_PROF_COUNTERS; #undef OP #undef EMITTER_TYPE_uint32_t #undef EMITTER_TYPE_uint64_t } #define COL_DECLARE(column_name) \ emitter_col_t col_##column_name; #define COL_INIT(row_name, column_name, left_or_right, col_width, etype)\ emitter_col_init(&col_##column_name, &row_name); \ col_##column_name.justify = emitter_justify_##left_or_right; \ col_##column_name.width = col_width; \ col_##column_name.type = emitter_type_##etype; #define COL(row_name, column_name, left_or_right, col_width, etype) \ COL_DECLARE(column_name); \ COL_INIT(row_name, column_name, left_or_right, col_width, etype) #define COL_HDR_DECLARE(column_name) \ COL_DECLARE(column_name); \ emitter_col_t header_##column_name; #define COL_HDR_INIT(row_name, column_name, human, left_or_right, \ col_width, etype) \ COL_INIT(row_name, column_name, left_or_right, col_width, etype)\ emitter_col_init(&header_##column_name, &header_##row_name); \ header_##column_name.justify = emitter_justify_##left_or_right; \ header_##column_name.width = col_width; \ header_##column_name.type = emitter_type_title; \ header_##column_name.str_val = human ? human : #column_name; #define COL_HDR(row_name, column_name, human, left_or_right, col_width, \ etype) \ COL_HDR_DECLARE(column_name) \ COL_HDR_INIT(row_name, column_name, human, left_or_right, \ col_width, etype) JEMALLOC_COLD static void stats_arena_bins_print(emitter_t *emitter, bool mutex, unsigned i, uint64_t uptime) { size_t page; bool in_gap, in_gap_prev; unsigned nbins, j; CTL_GET("arenas.page", &page, size_t); CTL_GET("arenas.nbins", &nbins, unsigned); emitter_row_t header_row; emitter_row_init(&header_row); emitter_row_t row; emitter_row_init(&row); bool prof_stats_on = config_prof && opt_prof && opt_prof_stats && i == MALLCTL_ARENAS_ALL; COL_HDR(row, size, NULL, right, 20, size) COL_HDR(row, ind, NULL, right, 4, unsigned) COL_HDR(row, allocated, NULL, right, 13, uint64) COL_HDR(row, nmalloc, NULL, right, 13, uint64) COL_HDR(row, nmalloc_ps, "(#/sec)", right, 8, uint64) COL_HDR(row, ndalloc, NULL, right, 13, uint64) COL_HDR(row, ndalloc_ps, "(#/sec)", right, 8, uint64) COL_HDR(row, nrequests, NULL, right, 13, uint64) COL_HDR(row, nrequests_ps, "(#/sec)", right, 10, uint64) COL_HDR_DECLARE(prof_live_requested); COL_HDR_DECLARE(prof_live_count); COL_HDR_DECLARE(prof_accum_requested); COL_HDR_DECLARE(prof_accum_count); if (prof_stats_on) { COL_HDR_INIT(row, prof_live_requested, NULL, right, 21, uint64) COL_HDR_INIT(row, prof_live_count, NULL, right, 17, uint64) COL_HDR_INIT(row, prof_accum_requested, NULL, right, 21, uint64) COL_HDR_INIT(row, prof_accum_count, NULL, right, 17, uint64) } COL_HDR(row, nshards, NULL, right, 9, unsigned) COL_HDR(row, curregs, NULL, right, 13, size) COL_HDR(row, curslabs, NULL, right, 13, size) COL_HDR(row, nonfull_slabs, NULL, right, 15, size) COL_HDR(row, regs, NULL, right, 5, unsigned) COL_HDR(row, pgs, NULL, right, 4, size) /* To buffer a right- and left-justified column. */ COL_HDR(row, justify_spacer, NULL, right, 1, title) COL_HDR(row, util, NULL, right, 6, title) COL_HDR(row, nfills, NULL, right, 13, uint64) COL_HDR(row, nfills_ps, "(#/sec)", right, 8, uint64) COL_HDR(row, nflushes, NULL, right, 13, uint64) COL_HDR(row, nflushes_ps, "(#/sec)", right, 8, uint64) COL_HDR(row, nslabs, NULL, right, 13, uint64) COL_HDR(row, nreslabs, NULL, right, 13, uint64) COL_HDR(row, nreslabs_ps, "(#/sec)", right, 8, uint64) /* Don't want to actually print the name. */ header_justify_spacer.str_val = " "; col_justify_spacer.str_val = " "; emitter_col_t col_mutex64[mutex_prof_num_uint64_t_counters]; emitter_col_t col_mutex32[mutex_prof_num_uint32_t_counters]; emitter_col_t header_mutex64[mutex_prof_num_uint64_t_counters]; emitter_col_t header_mutex32[mutex_prof_num_uint32_t_counters]; if (mutex) { mutex_stats_init_cols(&row, NULL, NULL, col_mutex64, col_mutex32); mutex_stats_init_cols(&header_row, NULL, NULL, header_mutex64, header_mutex32); } /* * We print a "bins:" header as part of the table row; we need to adjust * the header size column to compensate. */ header_size.width -=5; emitter_table_printf(emitter, "bins:"); emitter_table_row(emitter, &header_row); emitter_json_array_kv_begin(emitter, "bins"); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "bins"); size_t arenas_bin_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(arenas_bin_mib, 0, "arenas.bin"); size_t prof_stats_mib[CTL_MAX_DEPTH]; if (prof_stats_on) { CTL_LEAF_PREPARE(prof_stats_mib, 0, "prof.stats.bins"); } for (j = 0, in_gap = false; j < nbins; j++) { uint64_t nslabs; size_t reg_size, slab_size, curregs; size_t curslabs; size_t nonfull_slabs; uint32_t nregs, nshards; uint64_t nmalloc, ndalloc, nrequests, nfills, nflushes; uint64_t nreslabs; prof_stats_t prof_live; prof_stats_t prof_accum; stats_arenas_mib[4] = j; arenas_bin_mib[2] = j; CTL_LEAF(stats_arenas_mib, 5, "nslabs", &nslabs, uint64_t); if (prof_stats_on) { prof_stats_mib[3] = j; CTL_LEAF(prof_stats_mib, 4, "live", &prof_live, prof_stats_t); CTL_LEAF(prof_stats_mib, 4, "accum", &prof_accum, prof_stats_t); } in_gap_prev = in_gap; if (prof_stats_on) { in_gap = (nslabs == 0 && prof_accum.count == 0); } else { in_gap = (nslabs == 0); } if (in_gap_prev && !in_gap) { emitter_table_printf(emitter, " ---\n"); } if (in_gap && !emitter_outputs_json(emitter)) { continue; } CTL_LEAF(arenas_bin_mib, 3, "size", ®_size, size_t); CTL_LEAF(arenas_bin_mib, 3, "nregs", &nregs, uint32_t); CTL_LEAF(arenas_bin_mib, 3, "slab_size", &slab_size, size_t); CTL_LEAF(arenas_bin_mib, 3, "nshards", &nshards, uint32_t); CTL_LEAF(stats_arenas_mib, 5, "nmalloc", &nmalloc, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "ndalloc", &ndalloc, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "curregs", &curregs, size_t); CTL_LEAF(stats_arenas_mib, 5, "nrequests", &nrequests, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "nfills", &nfills, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "nflushes", &nflushes, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "nreslabs", &nreslabs, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "curslabs", &curslabs, size_t); CTL_LEAF(stats_arenas_mib, 5, "nonfull_slabs", &nonfull_slabs, size_t); if (mutex) { mutex_stats_read_arena_bin(stats_arenas_mib, 5, col_mutex64, col_mutex32, uptime); } emitter_json_object_begin(emitter); emitter_json_kv(emitter, "nmalloc", emitter_type_uint64, &nmalloc); emitter_json_kv(emitter, "ndalloc", emitter_type_uint64, &ndalloc); emitter_json_kv(emitter, "curregs", emitter_type_size, &curregs); emitter_json_kv(emitter, "nrequests", emitter_type_uint64, &nrequests); if (prof_stats_on) { emitter_json_kv(emitter, "prof_live_requested", emitter_type_uint64, &prof_live.req_sum); emitter_json_kv(emitter, "prof_live_count", emitter_type_uint64, &prof_live.count); emitter_json_kv(emitter, "prof_accum_requested", emitter_type_uint64, &prof_accum.req_sum); emitter_json_kv(emitter, "prof_accum_count", emitter_type_uint64, &prof_accum.count); } emitter_json_kv(emitter, "nfills", emitter_type_uint64, &nfills); emitter_json_kv(emitter, "nflushes", emitter_type_uint64, &nflushes); emitter_json_kv(emitter, "nreslabs", emitter_type_uint64, &nreslabs); emitter_json_kv(emitter, "curslabs", emitter_type_size, &curslabs); emitter_json_kv(emitter, "nonfull_slabs", emitter_type_size, &nonfull_slabs); if (mutex) { emitter_json_object_kv_begin(emitter, "mutex"); mutex_stats_emit(emitter, NULL, col_mutex64, col_mutex32); emitter_json_object_end(emitter); } emitter_json_object_end(emitter); size_t availregs = nregs * curslabs; char util[6]; if (get_rate_str((uint64_t)curregs, (uint64_t)availregs, util)) { if (availregs == 0) { malloc_snprintf(util, sizeof(util), "1"); } else if (curregs > availregs) { /* * Race detected: the counters were read in * separate mallctl calls and concurrent * operations happened in between. In this case * no meaningful utilization can be computed. */ malloc_snprintf(util, sizeof(util), " race"); } else { not_reached(); } } col_size.size_val = reg_size; col_ind.unsigned_val = j; col_allocated.size_val = curregs * reg_size; col_nmalloc.uint64_val = nmalloc; col_nmalloc_ps.uint64_val = rate_per_second(nmalloc, uptime); col_ndalloc.uint64_val = ndalloc; col_ndalloc_ps.uint64_val = rate_per_second(ndalloc, uptime); col_nrequests.uint64_val = nrequests; col_nrequests_ps.uint64_val = rate_per_second(nrequests, uptime); if (prof_stats_on) { col_prof_live_requested.uint64_val = prof_live.req_sum; col_prof_live_count.uint64_val = prof_live.count; col_prof_accum_requested.uint64_val = prof_accum.req_sum; col_prof_accum_count.uint64_val = prof_accum.count; } col_nshards.unsigned_val = nshards; col_curregs.size_val = curregs; col_curslabs.size_val = curslabs; col_nonfull_slabs.size_val = nonfull_slabs; col_regs.unsigned_val = nregs; col_pgs.size_val = slab_size / page; col_util.str_val = util; col_nfills.uint64_val = nfills; col_nfills_ps.uint64_val = rate_per_second(nfills, uptime); col_nflushes.uint64_val = nflushes; col_nflushes_ps.uint64_val = rate_per_second(nflushes, uptime); col_nslabs.uint64_val = nslabs; col_nreslabs.uint64_val = nreslabs; col_nreslabs_ps.uint64_val = rate_per_second(nreslabs, uptime); /* * Note that mutex columns were initialized above, if mutex == * true. */ emitter_table_row(emitter, &row); } emitter_json_array_end(emitter); /* Close "bins". */ if (in_gap) { emitter_table_printf(emitter, " ---\n"); } } JEMALLOC_COLD static void stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) { unsigned nbins, nlextents, j; bool in_gap, in_gap_prev; CTL_GET("arenas.nbins", &nbins, unsigned); CTL_GET("arenas.nlextents", &nlextents, unsigned); emitter_row_t header_row; emitter_row_init(&header_row); emitter_row_t row; emitter_row_init(&row); bool prof_stats_on = config_prof && opt_prof && opt_prof_stats && i == MALLCTL_ARENAS_ALL; COL_HDR(row, size, NULL, right, 20, size) COL_HDR(row, ind, NULL, right, 4, unsigned) COL_HDR(row, allocated, NULL, right, 13, size) COL_HDR(row, nmalloc, NULL, right, 13, uint64) COL_HDR(row, nmalloc_ps, "(#/sec)", right, 8, uint64) COL_HDR(row, ndalloc, NULL, right, 13, uint64) COL_HDR(row, ndalloc_ps, "(#/sec)", right, 8, uint64) COL_HDR(row, nrequests, NULL, right, 13, uint64) COL_HDR(row, nrequests_ps, "(#/sec)", right, 8, uint64) COL_HDR_DECLARE(prof_live_requested) COL_HDR_DECLARE(prof_live_count) COL_HDR_DECLARE(prof_accum_requested) COL_HDR_DECLARE(prof_accum_count) if (prof_stats_on) { COL_HDR_INIT(row, prof_live_requested, NULL, right, 21, uint64) COL_HDR_INIT(row, prof_live_count, NULL, right, 17, uint64) COL_HDR_INIT(row, prof_accum_requested, NULL, right, 21, uint64) COL_HDR_INIT(row, prof_accum_count, NULL, right, 17, uint64) } COL_HDR(row, curlextents, NULL, right, 13, size) /* As with bins, we label the large extents table. */ header_size.width -= 6; emitter_table_printf(emitter, "large:"); emitter_table_row(emitter, &header_row); emitter_json_array_kv_begin(emitter, "lextents"); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "lextents"); size_t arenas_lextent_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(arenas_lextent_mib, 0, "arenas.lextent"); size_t prof_stats_mib[CTL_MAX_DEPTH]; if (prof_stats_on) { CTL_LEAF_PREPARE(prof_stats_mib, 0, "prof.stats.lextents"); } for (j = 0, in_gap = false; j < nlextents; j++) { uint64_t nmalloc, ndalloc, nrequests; size_t lextent_size, curlextents; prof_stats_t prof_live; prof_stats_t prof_accum; stats_arenas_mib[4] = j; arenas_lextent_mib[2] = j; CTL_LEAF(stats_arenas_mib, 5, "nmalloc", &nmalloc, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "ndalloc", &ndalloc, uint64_t); CTL_LEAF(stats_arenas_mib, 5, "nrequests", &nrequests, uint64_t); in_gap_prev = in_gap; in_gap = (nrequests == 0); if (in_gap_prev && !in_gap) { emitter_table_printf(emitter, " ---\n"); } CTL_LEAF(arenas_lextent_mib, 3, "size", &lextent_size, size_t); CTL_LEAF(stats_arenas_mib, 5, "curlextents", &curlextents, size_t); if (prof_stats_on) { prof_stats_mib[3] = j; CTL_LEAF(prof_stats_mib, 4, "live", &prof_live, prof_stats_t); CTL_LEAF(prof_stats_mib, 4, "accum", &prof_accum, prof_stats_t); } emitter_json_object_begin(emitter); if (prof_stats_on) { emitter_json_kv(emitter, "prof_live_requested", emitter_type_uint64, &prof_live.req_sum); emitter_json_kv(emitter, "prof_live_count", emitter_type_uint64, &prof_live.count); emitter_json_kv(emitter, "prof_accum_requested", emitter_type_uint64, &prof_accum.req_sum); emitter_json_kv(emitter, "prof_accum_count", emitter_type_uint64, &prof_accum.count); } emitter_json_kv(emitter, "curlextents", emitter_type_size, &curlextents); emitter_json_object_end(emitter); col_size.size_val = lextent_size; col_ind.unsigned_val = nbins + j; col_allocated.size_val = curlextents * lextent_size; col_nmalloc.uint64_val = nmalloc; col_nmalloc_ps.uint64_val = rate_per_second(nmalloc, uptime); col_ndalloc.uint64_val = ndalloc; col_ndalloc_ps.uint64_val = rate_per_second(ndalloc, uptime); col_nrequests.uint64_val = nrequests; col_nrequests_ps.uint64_val = rate_per_second(nrequests, uptime); if (prof_stats_on) { col_prof_live_requested.uint64_val = prof_live.req_sum; col_prof_live_count.uint64_val = prof_live.count; col_prof_accum_requested.uint64_val = prof_accum.req_sum; col_prof_accum_count.uint64_val = prof_accum.count; } col_curlextents.size_val = curlextents; if (!in_gap) { emitter_table_row(emitter, &row); } } emitter_json_array_end(emitter); /* Close "lextents". */ if (in_gap) { emitter_table_printf(emitter, " ---\n"); } } JEMALLOC_COLD static void stats_arena_extents_print(emitter_t *emitter, unsigned i) { unsigned j; bool in_gap, in_gap_prev; emitter_row_t header_row; emitter_row_init(&header_row); emitter_row_t row; emitter_row_init(&row); COL_HDR(row, size, NULL, right, 20, size) COL_HDR(row, ind, NULL, right, 4, unsigned) COL_HDR(row, ndirty, NULL, right, 13, size) COL_HDR(row, dirty, NULL, right, 13, size) COL_HDR(row, nmuzzy, NULL, right, 13, size) COL_HDR(row, muzzy, NULL, right, 13, size) COL_HDR(row, nretained, NULL, right, 13, size) COL_HDR(row, retained, NULL, right, 13, size) COL_HDR(row, ntotal, NULL, right, 13, size) COL_HDR(row, total, NULL, right, 13, size) /* Label this section. */ header_size.width -= 8; emitter_table_printf(emitter, "extents:"); emitter_table_row(emitter, &header_row); emitter_json_array_kv_begin(emitter, "extents"); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "extents"); in_gap = false; for (j = 0; j < SC_NPSIZES; j++) { size_t ndirty, nmuzzy, nretained, total, dirty_bytes, muzzy_bytes, retained_bytes, total_bytes; stats_arenas_mib[4] = j; CTL_LEAF(stats_arenas_mib, 5, "ndirty", &ndirty, size_t); CTL_LEAF(stats_arenas_mib, 5, "nmuzzy", &nmuzzy, size_t); CTL_LEAF(stats_arenas_mib, 5, "nretained", &nretained, size_t); CTL_LEAF(stats_arenas_mib, 5, "dirty_bytes", &dirty_bytes, size_t); CTL_LEAF(stats_arenas_mib, 5, "muzzy_bytes", &muzzy_bytes, size_t); CTL_LEAF(stats_arenas_mib, 5, "retained_bytes", &retained_bytes, size_t); total = ndirty + nmuzzy + nretained; total_bytes = dirty_bytes + muzzy_bytes + retained_bytes; in_gap_prev = in_gap; in_gap = (total == 0); if (in_gap_prev && !in_gap) { emitter_table_printf(emitter, " ---\n"); } emitter_json_object_begin(emitter); emitter_json_kv(emitter, "ndirty", emitter_type_size, &ndirty); emitter_json_kv(emitter, "nmuzzy", emitter_type_size, &nmuzzy); emitter_json_kv(emitter, "nretained", emitter_type_size, &nretained); emitter_json_kv(emitter, "dirty_bytes", emitter_type_size, &dirty_bytes); emitter_json_kv(emitter, "muzzy_bytes", emitter_type_size, &muzzy_bytes); emitter_json_kv(emitter, "retained_bytes", emitter_type_size, &retained_bytes); emitter_json_object_end(emitter); col_size.size_val = sz_pind2sz(j); col_ind.size_val = j; col_ndirty.size_val = ndirty; col_dirty.size_val = dirty_bytes; col_nmuzzy.size_val = nmuzzy; col_muzzy.size_val = muzzy_bytes; col_nretained.size_val = nretained; col_retained.size_val = retained_bytes; col_ntotal.size_val = total; col_total.size_val = total_bytes; if (!in_gap) { emitter_table_row(emitter, &row); } } emitter_json_array_end(emitter); /* Close "extents". */ if (in_gap) { emitter_table_printf(emitter, " ---\n"); } } static void stats_arena_hpa_shard_print(emitter_t *emitter, unsigned i, uint64_t uptime) { emitter_row_t header_row; emitter_row_init(&header_row); emitter_row_t row; emitter_row_init(&row); uint64_t npurge_passes; uint64_t npurges; uint64_t nhugifies; uint64_t ndehugifies; CTL_M2_GET("stats.arenas.0.hpa_shard.npurge_passes", i, &npurge_passes, uint64_t); CTL_M2_GET("stats.arenas.0.hpa_shard.npurges", i, &npurges, uint64_t); CTL_M2_GET("stats.arenas.0.hpa_shard.nhugifies", i, &nhugifies, uint64_t); CTL_M2_GET("stats.arenas.0.hpa_shard.ndehugifies", i, &ndehugifies, uint64_t); size_t npageslabs_huge; size_t nactive_huge; size_t ndirty_huge; size_t npageslabs_nonhuge; size_t nactive_nonhuge; size_t ndirty_nonhuge; size_t nretained_nonhuge; size_t sec_bytes; CTL_M2_GET("stats.arenas.0.hpa_sec_bytes", i, &sec_bytes, size_t); emitter_kv(emitter, "sec_bytes", "Bytes in small extent cache", emitter_type_size, &sec_bytes); /* First, global stats. */ emitter_table_printf(emitter, "HPA shard stats:\n" " Purge passes: %" FMTu64 " (%" FMTu64 " / sec)\n" " Purges: %" FMTu64 " (%" FMTu64 " / sec)\n" " Hugeifies: %" FMTu64 " (%" FMTu64 " / sec)\n" " Dehugifies: %" FMTu64 " (%" FMTu64 " / sec)\n" "\n", npurge_passes, rate_per_second(npurge_passes, uptime), npurges, rate_per_second(npurges, uptime), nhugifies, rate_per_second(nhugifies, uptime), ndehugifies, rate_per_second(ndehugifies, uptime)); emitter_json_object_kv_begin(emitter, "hpa_shard"); emitter_json_kv(emitter, "npurge_passes", emitter_type_uint64, &npurge_passes); emitter_json_kv(emitter, "npurges", emitter_type_uint64, &npurges); emitter_json_kv(emitter, "nhugifies", emitter_type_uint64, &nhugifies); emitter_json_kv(emitter, "ndehugifies", emitter_type_uint64, &ndehugifies); /* Next, full slab stats. */ CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.npageslabs_huge", i, &npageslabs_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.nactive_huge", i, &nactive_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.ndirty_huge", i, &ndirty_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.npageslabs_nonhuge", i, &npageslabs_nonhuge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.nactive_nonhuge", i, &nactive_nonhuge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.ndirty_nonhuge", i, &ndirty_nonhuge, size_t); nretained_nonhuge = npageslabs_nonhuge * HUGEPAGE_PAGES - nactive_nonhuge - ndirty_nonhuge; emitter_table_printf(emitter, " In full slabs:\n" " npageslabs: %zu huge, %zu nonhuge\n" " nactive: %zu huge, %zu nonhuge \n" " ndirty: %zu huge, %zu nonhuge \n" " nretained: 0 huge, %zu nonhuge \n", npageslabs_huge, npageslabs_nonhuge, nactive_huge, nactive_nonhuge, ndirty_huge, ndirty_nonhuge, nretained_nonhuge); emitter_json_object_kv_begin(emitter, "full_slabs"); emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size, &npageslabs_huge); emitter_json_kv(emitter, "nactive_huge", emitter_type_size, &nactive_huge); emitter_json_kv(emitter, "nactive_huge", emitter_type_size, &nactive_huge); emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size, &npageslabs_nonhuge); emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size, &nactive_nonhuge); emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size, &ndirty_nonhuge); emitter_json_object_end(emitter); /* End "full_slabs" */ /* Next, empty slab stats. */ CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.npageslabs_huge", i, &npageslabs_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.nactive_huge", i, &nactive_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.ndirty_huge", i, &ndirty_huge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.npageslabs_nonhuge", i, &npageslabs_nonhuge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.nactive_nonhuge", i, &nactive_nonhuge, size_t); CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.ndirty_nonhuge", i, &ndirty_nonhuge, size_t); nretained_nonhuge = npageslabs_nonhuge * HUGEPAGE_PAGES - nactive_nonhuge - ndirty_nonhuge; emitter_table_printf(emitter, " In empty slabs:\n" " npageslabs: %zu huge, %zu nonhuge\n" " nactive: %zu huge, %zu nonhuge \n" " ndirty: %zu huge, %zu nonhuge \n" " nretained: 0 huge, %zu nonhuge \n" "\n", npageslabs_huge, npageslabs_nonhuge, nactive_huge, nactive_nonhuge, ndirty_huge, ndirty_nonhuge, nretained_nonhuge); emitter_json_object_kv_begin(emitter, "empty_slabs"); emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size, &npageslabs_huge); emitter_json_kv(emitter, "nactive_huge", emitter_type_size, &nactive_huge); emitter_json_kv(emitter, "nactive_huge", emitter_type_size, &nactive_huge); emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size, &npageslabs_nonhuge); emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size, &nactive_nonhuge); emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size, &ndirty_nonhuge); emitter_json_object_end(emitter); /* End "empty_slabs" */ COL_HDR(row, size, NULL, right, 20, size) COL_HDR(row, ind, NULL, right, 4, unsigned) COL_HDR(row, npageslabs_huge, NULL, right, 16, size) COL_HDR(row, nactive_huge, NULL, right, 16, size) COL_HDR(row, ndirty_huge, NULL, right, 16, size) COL_HDR(row, npageslabs_nonhuge, NULL, right, 20, size) COL_HDR(row, nactive_nonhuge, NULL, right, 20, size) COL_HDR(row, ndirty_nonhuge, NULL, right, 20, size) COL_HDR(row, nretained_nonhuge, NULL, right, 20, size) size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = i; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "hpa_shard.nonfull_slabs"); emitter_table_row(emitter, &header_row); emitter_json_array_kv_begin(emitter, "nonfull_slabs"); bool in_gap = false; for (pszind_t j = 0; j < PSSET_NPSIZES && j < SC_NPSIZES; j++) { stats_arenas_mib[5] = j; CTL_LEAF(stats_arenas_mib, 6, "npageslabs_huge", &npageslabs_huge, size_t); CTL_LEAF(stats_arenas_mib, 6, "nactive_huge", &nactive_huge, size_t); CTL_LEAF(stats_arenas_mib, 6, "ndirty_huge", &ndirty_huge, size_t); CTL_LEAF(stats_arenas_mib, 6, "npageslabs_nonhuge", &npageslabs_nonhuge, size_t); CTL_LEAF(stats_arenas_mib, 6, "nactive_nonhuge", &nactive_nonhuge, size_t); CTL_LEAF(stats_arenas_mib, 6, "ndirty_nonhuge", &ndirty_nonhuge, size_t); nretained_nonhuge = npageslabs_nonhuge * HUGEPAGE_PAGES - nactive_nonhuge - ndirty_nonhuge; bool in_gap_prev = in_gap; in_gap = (npageslabs_huge == 0 && npageslabs_nonhuge == 0); if (in_gap_prev && !in_gap) { emitter_table_printf(emitter, " ---\n"); } col_size.size_val = sz_pind2sz(j); col_ind.size_val = j; col_npageslabs_huge.size_val = npageslabs_huge; col_nactive_huge.size_val = nactive_huge; col_ndirty_huge.size_val = ndirty_huge; col_npageslabs_nonhuge.size_val = npageslabs_nonhuge; col_nactive_nonhuge.size_val = nactive_nonhuge; col_ndirty_nonhuge.size_val = ndirty_nonhuge; col_nretained_nonhuge.size_val = nretained_nonhuge; if (!in_gap) { emitter_table_row(emitter, &row); } emitter_json_object_begin(emitter); emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size, &npageslabs_huge); emitter_json_kv(emitter, "nactive_huge", emitter_type_size, &nactive_huge); emitter_json_kv(emitter, "ndirty_huge", emitter_type_size, &ndirty_huge); emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size, &npageslabs_nonhuge); emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size, &nactive_nonhuge); emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size, &ndirty_nonhuge); emitter_json_object_end(emitter); } emitter_json_array_end(emitter); /* End "nonfull_slabs" */ emitter_json_object_end(emitter); /* End "hpa_shard" */ if (in_gap) { emitter_table_printf(emitter, " ---\n"); } } static void stats_arena_mutexes_print(emitter_t *emitter, unsigned arena_ind, uint64_t uptime) { emitter_row_t row; emitter_col_t col_name; emitter_col_t col64[mutex_prof_num_uint64_t_counters]; emitter_col_t col32[mutex_prof_num_uint32_t_counters]; emitter_row_init(&row); mutex_stats_init_cols(&row, "", &col_name, col64, col32); emitter_json_object_kv_begin(emitter, "mutexes"); emitter_table_row(emitter, &row); size_t stats_arenas_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); stats_arenas_mib[2] = arena_ind; CTL_LEAF_PREPARE(stats_arenas_mib, 3, "mutexes"); for (mutex_prof_arena_ind_t i = 0; i < mutex_prof_num_arena_mutexes; i++) { const char *name = arena_mutex_names[i]; emitter_json_object_kv_begin(emitter, name); mutex_stats_read_arena(stats_arenas_mib, 4, name, &col_name, col64, col32, uptime); mutex_stats_emit(emitter, &row, col64, col32); emitter_json_object_end(emitter); /* Close the mutex dict. */ } emitter_json_object_end(emitter); /* End "mutexes". */ } JEMALLOC_COLD static void stats_arena_print(emitter_t *emitter, unsigned i, bool bins, bool large, bool mutex, bool extents, bool hpa) { unsigned nthreads; const char *dss; ssize_t dirty_decay_ms, muzzy_decay_ms; size_t page, pactive, pdirty, pmuzzy, mapped, retained; size_t base, internal, resident, metadata_thp, extent_avail; uint64_t dirty_npurge, dirty_nmadvise, dirty_purged; uint64_t muzzy_npurge, muzzy_nmadvise, muzzy_purged; size_t small_allocated; uint64_t small_nmalloc, small_ndalloc, small_nrequests, small_nfills, small_nflushes; size_t large_allocated; uint64_t large_nmalloc, large_ndalloc, large_nrequests, large_nfills, large_nflushes; size_t tcache_bytes, tcache_stashed_bytes, abandoned_vm; uint64_t uptime; CTL_GET("arenas.page", &page, size_t); CTL_M2_GET("stats.arenas.0.nthreads", i, &nthreads, unsigned); emitter_kv(emitter, "nthreads", "assigned threads", emitter_type_unsigned, &nthreads); CTL_M2_GET("stats.arenas.0.uptime", i, &uptime, uint64_t); emitter_kv(emitter, "uptime_ns", "uptime", emitter_type_uint64, &uptime); CTL_M2_GET("stats.arenas.0.dss", i, &dss, const char *); emitter_kv(emitter, "dss", "dss allocation precedence", emitter_type_string, &dss); CTL_M2_GET("stats.arenas.0.dirty_decay_ms", i, &dirty_decay_ms, ssize_t); CTL_M2_GET("stats.arenas.0.muzzy_decay_ms", i, &muzzy_decay_ms, ssize_t); CTL_M2_GET("stats.arenas.0.pactive", i, &pactive, size_t); CTL_M2_GET("stats.arenas.0.pdirty", i, &pdirty, size_t); CTL_M2_GET("stats.arenas.0.pmuzzy", i, &pmuzzy, size_t); CTL_M2_GET("stats.arenas.0.dirty_npurge", i, &dirty_npurge, uint64_t); CTL_M2_GET("stats.arenas.0.dirty_nmadvise", i, &dirty_nmadvise, uint64_t); CTL_M2_GET("stats.arenas.0.dirty_purged", i, &dirty_purged, uint64_t); CTL_M2_GET("stats.arenas.0.muzzy_npurge", i, &muzzy_npurge, uint64_t); CTL_M2_GET("stats.arenas.0.muzzy_nmadvise", i, &muzzy_nmadvise, uint64_t); CTL_M2_GET("stats.arenas.0.muzzy_purged", i, &muzzy_purged, uint64_t); emitter_row_t decay_row; emitter_row_init(&decay_row); /* JSON-style emission. */ emitter_json_kv(emitter, "dirty_decay_ms", emitter_type_ssize, &dirty_decay_ms); emitter_json_kv(emitter, "muzzy_decay_ms", emitter_type_ssize, &muzzy_decay_ms); emitter_json_kv(emitter, "pactive", emitter_type_size, &pactive); emitter_json_kv(emitter, "pdirty", emitter_type_size, &pdirty); emitter_json_kv(emitter, "pmuzzy", emitter_type_size, &pmuzzy); emitter_json_kv(emitter, "dirty_npurge", emitter_type_uint64, &dirty_npurge); emitter_json_kv(emitter, "dirty_nmadvise", emitter_type_uint64, &dirty_nmadvise); emitter_json_kv(emitter, "dirty_purged", emitter_type_uint64, &dirty_purged); emitter_json_kv(emitter, "muzzy_npurge", emitter_type_uint64, &muzzy_npurge); emitter_json_kv(emitter, "muzzy_nmadvise", emitter_type_uint64, &muzzy_nmadvise); emitter_json_kv(emitter, "muzzy_purged", emitter_type_uint64, &muzzy_purged); /* Table-style emission. */ COL(decay_row, decay_type, right, 9, title); col_decay_type.str_val = "decaying:"; COL(decay_row, decay_time, right, 6, title); col_decay_time.str_val = "time"; COL(decay_row, decay_npages, right, 13, title); col_decay_npages.str_val = "npages"; COL(decay_row, decay_sweeps, right, 13, title); col_decay_sweeps.str_val = "sweeps"; COL(decay_row, decay_madvises, right, 13, title); col_decay_madvises.str_val = "madvises"; COL(decay_row, decay_purged, right, 13, title); col_decay_purged.str_val = "purged"; /* Title row. */ emitter_table_row(emitter, &decay_row); /* Dirty row. */ col_decay_type.str_val = "dirty:"; if (dirty_decay_ms >= 0) { col_decay_time.type = emitter_type_ssize; col_decay_time.ssize_val = dirty_decay_ms; } else { col_decay_time.type = emitter_type_title; col_decay_time.str_val = "N/A"; } col_decay_npages.type = emitter_type_size; col_decay_npages.size_val = pdirty; col_decay_sweeps.type = emitter_type_uint64; col_decay_sweeps.uint64_val = dirty_npurge; col_decay_madvises.type = emitter_type_uint64; col_decay_madvises.uint64_val = dirty_nmadvise; col_decay_purged.type = emitter_type_uint64; col_decay_purged.uint64_val = dirty_purged; emitter_table_row(emitter, &decay_row); /* Muzzy row. */ col_decay_type.str_val = "muzzy:"; if (muzzy_decay_ms >= 0) { col_decay_time.type = emitter_type_ssize; col_decay_time.ssize_val = muzzy_decay_ms; } else { col_decay_time.type = emitter_type_title; col_decay_time.str_val = "N/A"; } col_decay_npages.type = emitter_type_size; col_decay_npages.size_val = pmuzzy; col_decay_sweeps.type = emitter_type_uint64; col_decay_sweeps.uint64_val = muzzy_npurge; col_decay_madvises.type = emitter_type_uint64; col_decay_madvises.uint64_val = muzzy_nmadvise; col_decay_purged.type = emitter_type_uint64; col_decay_purged.uint64_val = muzzy_purged; emitter_table_row(emitter, &decay_row); /* Small / large / total allocation counts. */ emitter_row_t alloc_count_row; emitter_row_init(&alloc_count_row); COL(alloc_count_row, count_title, left, 21, title); col_count_title.str_val = ""; COL(alloc_count_row, count_allocated, right, 16, title); col_count_allocated.str_val = "allocated"; COL(alloc_count_row, count_nmalloc, right, 16, title); col_count_nmalloc.str_val = "nmalloc"; COL(alloc_count_row, count_nmalloc_ps, right, 10, title); col_count_nmalloc_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_ndalloc, right, 16, title); col_count_ndalloc.str_val = "ndalloc"; COL(alloc_count_row, count_ndalloc_ps, right, 10, title); col_count_ndalloc_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_nrequests, right, 16, title); col_count_nrequests.str_val = "nrequests"; COL(alloc_count_row, count_nrequests_ps, right, 10, title); col_count_nrequests_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_nfills, right, 16, title); col_count_nfills.str_val = "nfill"; COL(alloc_count_row, count_nfills_ps, right, 10, title); col_count_nfills_ps.str_val = "(#/sec)"; COL(alloc_count_row, count_nflushes, right, 16, title); col_count_nflushes.str_val = "nflush"; COL(alloc_count_row, count_nflushes_ps, right, 10, title); col_count_nflushes_ps.str_val = "(#/sec)"; emitter_table_row(emitter, &alloc_count_row); col_count_nmalloc_ps.type = emitter_type_uint64; col_count_ndalloc_ps.type = emitter_type_uint64; col_count_nrequests_ps.type = emitter_type_uint64; col_count_nfills_ps.type = emitter_type_uint64; col_count_nflushes_ps.type = emitter_type_uint64; #define GET_AND_EMIT_ALLOC_STAT(small_or_large, name, valtype) \ CTL_M2_GET("stats.arenas.0." #small_or_large "." #name, i, \ &small_or_large##_##name, valtype##_t); \ emitter_json_kv(emitter, #name, emitter_type_##valtype, \ &small_or_large##_##name); \ col_count_##name.type = emitter_type_##valtype; \ col_count_##name.valtype##_val = small_or_large##_##name; emitter_json_object_kv_begin(emitter, "small"); col_count_title.str_val = "small:"; GET_AND_EMIT_ALLOC_STAT(small, allocated, size) GET_AND_EMIT_ALLOC_STAT(small, nmalloc, uint64) col_count_nmalloc_ps.uint64_val = rate_per_second(col_count_nmalloc.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(small, ndalloc, uint64) col_count_ndalloc_ps.uint64_val = rate_per_second(col_count_ndalloc.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(small, nrequests, uint64) col_count_nrequests_ps.uint64_val = rate_per_second(col_count_nrequests.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(small, nfills, uint64) col_count_nfills_ps.uint64_val = rate_per_second(col_count_nfills.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(small, nflushes, uint64) col_count_nflushes_ps.uint64_val = rate_per_second(col_count_nflushes.uint64_val, uptime); emitter_table_row(emitter, &alloc_count_row); emitter_json_object_end(emitter); /* Close "small". */ emitter_json_object_kv_begin(emitter, "large"); col_count_title.str_val = "large:"; GET_AND_EMIT_ALLOC_STAT(large, allocated, size) GET_AND_EMIT_ALLOC_STAT(large, nmalloc, uint64) col_count_nmalloc_ps.uint64_val = rate_per_second(col_count_nmalloc.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(large, ndalloc, uint64) col_count_ndalloc_ps.uint64_val = rate_per_second(col_count_ndalloc.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(large, nrequests, uint64) col_count_nrequests_ps.uint64_val = rate_per_second(col_count_nrequests.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(large, nfills, uint64) col_count_nfills_ps.uint64_val = rate_per_second(col_count_nfills.uint64_val, uptime); GET_AND_EMIT_ALLOC_STAT(large, nflushes, uint64) col_count_nflushes_ps.uint64_val = rate_per_second(col_count_nflushes.uint64_val, uptime); emitter_table_row(emitter, &alloc_count_row); emitter_json_object_end(emitter); /* Close "large". */ #undef GET_AND_EMIT_ALLOC_STAT /* Aggregated small + large stats are emitter only in table mode. */ col_count_title.str_val = "total:"; col_count_allocated.size_val = small_allocated + large_allocated; col_count_nmalloc.uint64_val = small_nmalloc + large_nmalloc; col_count_ndalloc.uint64_val = small_ndalloc + large_ndalloc; col_count_nrequests.uint64_val = small_nrequests + large_nrequests; col_count_nfills.uint64_val = small_nfills + large_nfills; col_count_nflushes.uint64_val = small_nflushes + large_nflushes; col_count_nmalloc_ps.uint64_val = rate_per_second(col_count_nmalloc.uint64_val, uptime); col_count_ndalloc_ps.uint64_val = rate_per_second(col_count_ndalloc.uint64_val, uptime); col_count_nrequests_ps.uint64_val = rate_per_second(col_count_nrequests.uint64_val, uptime); col_count_nfills_ps.uint64_val = rate_per_second(col_count_nfills.uint64_val, uptime); col_count_nflushes_ps.uint64_val = rate_per_second(col_count_nflushes.uint64_val, uptime); emitter_table_row(emitter, &alloc_count_row); emitter_row_t mem_count_row; emitter_row_init(&mem_count_row); emitter_col_t mem_count_title; emitter_col_init(&mem_count_title, &mem_count_row); mem_count_title.justify = emitter_justify_left; mem_count_title.width = 21; mem_count_title.type = emitter_type_title; mem_count_title.str_val = ""; emitter_col_t mem_count_val; emitter_col_init(&mem_count_val, &mem_count_row); mem_count_val.justify = emitter_justify_right; mem_count_val.width = 16; mem_count_val.type = emitter_type_title; mem_count_val.str_val = ""; emitter_table_row(emitter, &mem_count_row); mem_count_val.type = emitter_type_size; /* Active count in bytes is emitted only in table mode. */ mem_count_title.str_val = "active:"; mem_count_val.size_val = pactive * page; emitter_table_row(emitter, &mem_count_row); #define GET_AND_EMIT_MEM_STAT(stat) \ CTL_M2_GET("stats.arenas.0."#stat, i, &stat, size_t); \ emitter_json_kv(emitter, #stat, emitter_type_size, &stat); \ mem_count_title.str_val = #stat":"; \ mem_count_val.size_val = stat; \ emitter_table_row(emitter, &mem_count_row); GET_AND_EMIT_MEM_STAT(mapped) GET_AND_EMIT_MEM_STAT(retained) GET_AND_EMIT_MEM_STAT(base) GET_AND_EMIT_MEM_STAT(internal) GET_AND_EMIT_MEM_STAT(metadata_thp) GET_AND_EMIT_MEM_STAT(tcache_bytes) GET_AND_EMIT_MEM_STAT(tcache_stashed_bytes) GET_AND_EMIT_MEM_STAT(resident) GET_AND_EMIT_MEM_STAT(abandoned_vm) GET_AND_EMIT_MEM_STAT(extent_avail) #undef GET_AND_EMIT_MEM_STAT if (mutex) { stats_arena_mutexes_print(emitter, i, uptime); } if (bins) { stats_arena_bins_print(emitter, mutex, i, uptime); } if (large) { stats_arena_lextents_print(emitter, i, uptime); } if (extents) { stats_arena_extents_print(emitter, i); } if (hpa) { stats_arena_hpa_shard_print(emitter, i, uptime); } } JEMALLOC_COLD static void stats_general_print(emitter_t *emitter) { const char *cpv; bool bv, bv2; unsigned uv; uint32_t u32v; uint64_t u64v; int64_t i64v; ssize_t ssv, ssv2; size_t sv, bsz, usz, u32sz, u64sz, i64sz, ssz, sssz, cpsz; bsz = sizeof(bool); usz = sizeof(unsigned); ssz = sizeof(size_t); sssz = sizeof(ssize_t); cpsz = sizeof(const char *); u32sz = sizeof(uint32_t); i64sz = sizeof(int64_t); u64sz = sizeof(uint64_t); CTL_GET("version", &cpv, const char *); emitter_kv(emitter, "version", "Version", emitter_type_string, &cpv); /* config. */ emitter_dict_begin(emitter, "config", "Build-time option settings"); #define CONFIG_WRITE_BOOL(name) \ do { \ CTL_GET("config."#name, &bv, bool); \ emitter_kv(emitter, #name, "config."#name, \ emitter_type_bool, &bv); \ } while (0) CONFIG_WRITE_BOOL(cache_oblivious); CONFIG_WRITE_BOOL(debug); CONFIG_WRITE_BOOL(fill); CONFIG_WRITE_BOOL(lazy_lock); emitter_kv(emitter, "malloc_conf", "config.malloc_conf", emitter_type_string, &config_malloc_conf); CONFIG_WRITE_BOOL(opt_safety_checks); CONFIG_WRITE_BOOL(prof); CONFIG_WRITE_BOOL(prof_libgcc); CONFIG_WRITE_BOOL(prof_libunwind); CONFIG_WRITE_BOOL(stats); CONFIG_WRITE_BOOL(utrace); CONFIG_WRITE_BOOL(xmalloc); #undef CONFIG_WRITE_BOOL emitter_dict_end(emitter); /* Close "config" dict. */ /* opt. */ #define OPT_WRITE(name, var, size, emitter_type) \ if (je_mallctl("opt."name, (void *)&var, &size, NULL, 0) == \ 0) { \ emitter_kv(emitter, name, "opt."name, emitter_type, \ &var); \ } #define OPT_WRITE_MUTABLE(name, var1, var2, size, emitter_type, \ altname) \ if (je_mallctl("opt."name, (void *)&var1, &size, NULL, 0) == \ 0 && je_mallctl(altname, (void *)&var2, &size, NULL, 0) \ == 0) { \ emitter_kv_note(emitter, name, "opt."name, \ emitter_type, &var1, altname, emitter_type, \ &var2); \ } #define OPT_WRITE_BOOL(name) OPT_WRITE(name, bv, bsz, emitter_type_bool) #define OPT_WRITE_BOOL_MUTABLE(name, altname) \ OPT_WRITE_MUTABLE(name, bv, bv2, bsz, emitter_type_bool, altname) #define OPT_WRITE_UNSIGNED(name) \ OPT_WRITE(name, uv, usz, emitter_type_unsigned) #define OPT_WRITE_INT64(name) \ OPT_WRITE(name, i64v, i64sz, emitter_type_int64) #define OPT_WRITE_UINT64(name) \ OPT_WRITE(name, u64v, u64sz, emitter_type_uint64) #define OPT_WRITE_SIZE_T(name) \ OPT_WRITE(name, sv, ssz, emitter_type_size) #define OPT_WRITE_SSIZE_T(name) \ OPT_WRITE(name, ssv, sssz, emitter_type_ssize) #define OPT_WRITE_SSIZE_T_MUTABLE(name, altname) \ OPT_WRITE_MUTABLE(name, ssv, ssv2, sssz, emitter_type_ssize, \ altname) #define OPT_WRITE_CHAR_P(name) \ OPT_WRITE(name, cpv, cpsz, emitter_type_string) emitter_dict_begin(emitter, "opt", "Run-time option settings"); OPT_WRITE_BOOL("abort") OPT_WRITE_BOOL("abort_conf") OPT_WRITE_BOOL("cache_oblivious") OPT_WRITE_BOOL("confirm_conf") OPT_WRITE_BOOL("retain") OPT_WRITE_CHAR_P("dss") OPT_WRITE_UNSIGNED("narenas") OPT_WRITE_CHAR_P("percpu_arena") OPT_WRITE_SIZE_T("oversize_threshold") OPT_WRITE_BOOL("hpa") OPT_WRITE_SIZE_T("hpa_slab_max_alloc") OPT_WRITE_SIZE_T("hpa_hugification_threshold") OPT_WRITE_UINT64("hpa_hugify_delay_ms") OPT_WRITE_UINT64("hpa_min_purge_interval_ms") if (je_mallctl("opt.hpa_dirty_mult", (void *)&u32v, &u32sz, NULL, 0) == 0) { /* * We cheat a little and "know" the secret meaning of this * representation. */ if (u32v == (uint32_t)-1) { const char *neg1 = "-1"; emitter_kv(emitter, "hpa_dirty_mult", "opt.hpa_dirty_mult", emitter_type_string, &neg1); } else { char buf[FXP_BUF_SIZE]; fxp_print(u32v, buf); const char *bufp = buf; emitter_kv(emitter, "hpa_dirty_mult", "opt.hpa_dirty_mult", emitter_type_string, &bufp); } } OPT_WRITE_SIZE_T("hpa_sec_nshards") OPT_WRITE_SIZE_T("hpa_sec_max_alloc") OPT_WRITE_SIZE_T("hpa_sec_max_bytes") OPT_WRITE_SIZE_T("hpa_sec_bytes_after_flush") OPT_WRITE_SIZE_T("hpa_sec_batch_fill_extra") OPT_WRITE_CHAR_P("metadata_thp") OPT_WRITE_INT64("mutex_max_spin") OPT_WRITE_BOOL_MUTABLE("background_thread", "background_thread") OPT_WRITE_SSIZE_T_MUTABLE("dirty_decay_ms", "arenas.dirty_decay_ms") OPT_WRITE_SSIZE_T_MUTABLE("muzzy_decay_ms", "arenas.muzzy_decay_ms") OPT_WRITE_SIZE_T("lg_extent_max_active_fit") OPT_WRITE_CHAR_P("junk") OPT_WRITE_BOOL("zero") OPT_WRITE_BOOL("utrace") OPT_WRITE_BOOL("xmalloc") OPT_WRITE_BOOL("experimental_infallible_new") OPT_WRITE_BOOL("tcache") OPT_WRITE_SIZE_T("tcache_max") OPT_WRITE_UNSIGNED("tcache_nslots_small_min") OPT_WRITE_UNSIGNED("tcache_nslots_small_max") OPT_WRITE_UNSIGNED("tcache_nslots_large") OPT_WRITE_SSIZE_T("lg_tcache_nslots_mul") OPT_WRITE_SIZE_T("tcache_gc_incr_bytes") OPT_WRITE_SIZE_T("tcache_gc_delay_bytes") OPT_WRITE_UNSIGNED("lg_tcache_flush_small_div") OPT_WRITE_UNSIGNED("lg_tcache_flush_large_div") OPT_WRITE_CHAR_P("thp") OPT_WRITE_BOOL("prof") OPT_WRITE_CHAR_P("prof_prefix") OPT_WRITE_BOOL_MUTABLE("prof_active", "prof.active") OPT_WRITE_BOOL_MUTABLE("prof_thread_active_init", "prof.thread_active_init") OPT_WRITE_SSIZE_T_MUTABLE("lg_prof_sample", "prof.lg_sample") OPT_WRITE_BOOL("prof_accum") OPT_WRITE_SSIZE_T("lg_prof_interval") OPT_WRITE_BOOL("prof_gdump") OPT_WRITE_BOOL("prof_final") OPT_WRITE_BOOL("prof_leak") OPT_WRITE_BOOL("prof_leak_error") OPT_WRITE_BOOL("stats_print") OPT_WRITE_CHAR_P("stats_print_opts") OPT_WRITE_BOOL("stats_print") OPT_WRITE_CHAR_P("stats_print_opts") OPT_WRITE_INT64("stats_interval") OPT_WRITE_CHAR_P("stats_interval_opts") OPT_WRITE_CHAR_P("zero_realloc") emitter_dict_end(emitter); #undef OPT_WRITE #undef OPT_WRITE_MUTABLE #undef OPT_WRITE_BOOL #undef OPT_WRITE_BOOL_MUTABLE #undef OPT_WRITE_UNSIGNED #undef OPT_WRITE_SSIZE_T #undef OPT_WRITE_SSIZE_T_MUTABLE #undef OPT_WRITE_CHAR_P /* prof. */ if (config_prof) { emitter_dict_begin(emitter, "prof", "Profiling settings"); CTL_GET("prof.thread_active_init", &bv, bool); emitter_kv(emitter, "thread_active_init", "prof.thread_active_init", emitter_type_bool, &bv); CTL_GET("prof.active", &bv, bool); emitter_kv(emitter, "active", "prof.active", emitter_type_bool, &bv); CTL_GET("prof.gdump", &bv, bool); emitter_kv(emitter, "gdump", "prof.gdump", emitter_type_bool, &bv); CTL_GET("prof.interval", &u64v, uint64_t); emitter_kv(emitter, "interval", "prof.interval", emitter_type_uint64, &u64v); CTL_GET("prof.lg_sample", &ssv, ssize_t); emitter_kv(emitter, "lg_sample", "prof.lg_sample", emitter_type_ssize, &ssv); emitter_dict_end(emitter); /* Close "prof". */ } /* arenas. */ /* * The json output sticks arena info into an "arenas" dict; the table * output puts them at the top-level. */ emitter_json_object_kv_begin(emitter, "arenas"); CTL_GET("arenas.narenas", &uv, unsigned); emitter_kv(emitter, "narenas", "Arenas", emitter_type_unsigned, &uv); /* * Decay settings are emitted only in json mode; in table mode, they're * emitted as notes with the opt output, above. */ CTL_GET("arenas.dirty_decay_ms", &ssv, ssize_t); emitter_json_kv(emitter, "dirty_decay_ms", emitter_type_ssize, &ssv); CTL_GET("arenas.muzzy_decay_ms", &ssv, ssize_t); emitter_json_kv(emitter, "muzzy_decay_ms", emitter_type_ssize, &ssv); CTL_GET("arenas.quantum", &sv, size_t); emitter_kv(emitter, "quantum", "Quantum size", emitter_type_size, &sv); CTL_GET("arenas.page", &sv, size_t); emitter_kv(emitter, "page", "Page size", emitter_type_size, &sv); if (je_mallctl("arenas.tcache_max", (void *)&sv, &ssz, NULL, 0) == 0) { emitter_kv(emitter, "tcache_max", "Maximum thread-cached size class", emitter_type_size, &sv); } unsigned arenas_nbins; CTL_GET("arenas.nbins", &arenas_nbins, unsigned); emitter_kv(emitter, "nbins", "Number of bin size classes", emitter_type_unsigned, &arenas_nbins); unsigned arenas_nhbins; CTL_GET("arenas.nhbins", &arenas_nhbins, unsigned); emitter_kv(emitter, "nhbins", "Number of thread-cache bin size classes", emitter_type_unsigned, &arenas_nhbins); /* * We do enough mallctls in a loop that we actually want to omit them * (not just omit the printing). */ if (emitter_outputs_json(emitter)) { emitter_json_array_kv_begin(emitter, "bin"); size_t arenas_bin_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(arenas_bin_mib, 0, "arenas.bin"); for (unsigned i = 0; i < arenas_nbins; i++) { arenas_bin_mib[2] = i; emitter_json_object_begin(emitter); CTL_LEAF(arenas_bin_mib, 3, "size", &sv, size_t); emitter_json_kv(emitter, "size", emitter_type_size, &sv); CTL_LEAF(arenas_bin_mib, 3, "nregs", &u32v, uint32_t); emitter_json_kv(emitter, "nregs", emitter_type_uint32, &u32v); CTL_LEAF(arenas_bin_mib, 3, "slab_size", &sv, size_t); emitter_json_kv(emitter, "slab_size", emitter_type_size, &sv); CTL_LEAF(arenas_bin_mib, 3, "nshards", &u32v, uint32_t); emitter_json_kv(emitter, "nshards", emitter_type_uint32, &u32v); emitter_json_object_end(emitter); } emitter_json_array_end(emitter); /* Close "bin". */ } unsigned nlextents; CTL_GET("arenas.nlextents", &nlextents, unsigned); emitter_kv(emitter, "nlextents", "Number of large size classes", emitter_type_unsigned, &nlextents); if (emitter_outputs_json(emitter)) { emitter_json_array_kv_begin(emitter, "lextent"); size_t arenas_lextent_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(arenas_lextent_mib, 0, "arenas.lextent"); for (unsigned i = 0; i < nlextents; i++) { arenas_lextent_mib[2] = i; emitter_json_object_begin(emitter); CTL_LEAF(arenas_lextent_mib, 3, "size", &sv, size_t); emitter_json_kv(emitter, "size", emitter_type_size, &sv); emitter_json_object_end(emitter); } emitter_json_array_end(emitter); /* Close "lextent". */ } emitter_json_object_end(emitter); /* Close "arenas" */ } JEMALLOC_COLD static void stats_print_helper(emitter_t *emitter, bool merged, bool destroyed, bool unmerged, bool bins, bool large, bool mutex, bool extents, bool hpa) { /* * These should be deleted. We keep them around for a while, to aid in * the transition to the emitter code. */ size_t allocated, active, metadata, metadata_thp, resident, mapped, retained; size_t num_background_threads; size_t zero_reallocs; uint64_t background_thread_num_runs, background_thread_run_interval; CTL_GET("stats.allocated", &allocated, size_t); CTL_GET("stats.active", &active, size_t); CTL_GET("stats.metadata", &metadata, size_t); CTL_GET("stats.metadata_thp", &metadata_thp, size_t); CTL_GET("stats.resident", &resident, size_t); CTL_GET("stats.mapped", &mapped, size_t); CTL_GET("stats.retained", &retained, size_t); CTL_GET("stats.zero_reallocs", &zero_reallocs, size_t); if (have_background_thread) { CTL_GET("stats.background_thread.num_threads", &num_background_threads, size_t); CTL_GET("stats.background_thread.num_runs", &background_thread_num_runs, uint64_t); CTL_GET("stats.background_thread.run_interval", &background_thread_run_interval, uint64_t); } else { num_background_threads = 0; background_thread_num_runs = 0; background_thread_run_interval = 0; } /* Generic global stats. */ emitter_json_object_kv_begin(emitter, "stats"); emitter_json_kv(emitter, "allocated", emitter_type_size, &allocated); emitter_json_kv(emitter, "active", emitter_type_size, &active); emitter_json_kv(emitter, "metadata", emitter_type_size, &metadata); emitter_json_kv(emitter, "metadata_thp", emitter_type_size, &metadata_thp); emitter_json_kv(emitter, "resident", emitter_type_size, &resident); emitter_json_kv(emitter, "mapped", emitter_type_size, &mapped); emitter_json_kv(emitter, "retained", emitter_type_size, &retained); emitter_json_kv(emitter, "zero_reallocs", emitter_type_size, &zero_reallocs); emitter_table_printf(emitter, "Allocated: %zu, active: %zu, " "metadata: %zu (n_thp %zu), resident: %zu, mapped: %zu, " "retained: %zu\n", allocated, active, metadata, metadata_thp, resident, mapped, retained); /* Strange behaviors */ emitter_table_printf(emitter, "Count of realloc(non-null-ptr, 0) calls: %zu\n", zero_reallocs); /* Background thread stats. */ emitter_json_object_kv_begin(emitter, "background_thread"); emitter_json_kv(emitter, "num_threads", emitter_type_size, &num_background_threads); emitter_json_kv(emitter, "num_runs", emitter_type_uint64, &background_thread_num_runs); emitter_json_kv(emitter, "run_interval", emitter_type_uint64, &background_thread_run_interval); emitter_json_object_end(emitter); /* Close "background_thread". */ emitter_table_printf(emitter, "Background threads: %zu, " "num_runs: %"FMTu64", run_interval: %"FMTu64" ns\n", num_background_threads, background_thread_num_runs, background_thread_run_interval); if (mutex) { emitter_row_t row; emitter_col_t name; emitter_col_t col64[mutex_prof_num_uint64_t_counters]; emitter_col_t col32[mutex_prof_num_uint32_t_counters]; uint64_t uptime; emitter_row_init(&row); mutex_stats_init_cols(&row, "", &name, col64, col32); emitter_table_row(emitter, &row); emitter_json_object_kv_begin(emitter, "mutexes"); CTL_M2_GET("stats.arenas.0.uptime", 0, &uptime, uint64_t); size_t stats_mutexes_mib[CTL_MAX_DEPTH]; CTL_LEAF_PREPARE(stats_mutexes_mib, 0, "stats.mutexes"); for (int i = 0; i < mutex_prof_num_global_mutexes; i++) { mutex_stats_read_global(stats_mutexes_mib, 2, global_mutex_names[i], &name, col64, col32, uptime); emitter_json_object_kv_begin(emitter, global_mutex_names[i]); mutex_stats_emit(emitter, &row, col64, col32); emitter_json_object_end(emitter); } emitter_json_object_end(emitter); /* Close "mutexes". */ } emitter_json_object_end(emitter); /* Close "stats". */ if (merged || destroyed || unmerged) { unsigned narenas; emitter_json_object_kv_begin(emitter, "stats.arenas"); CTL_GET("arenas.narenas", &narenas, unsigned); size_t mib[3]; size_t miblen = sizeof(mib) / sizeof(size_t); size_t sz; VARIABLE_ARRAY(bool, initialized, narenas); bool destroyed_initialized; unsigned i, j, ninitialized; xmallctlnametomib("arena.0.initialized", mib, &miblen); for (i = ninitialized = 0; i < narenas; i++) { mib[1] = i; sz = sizeof(bool); xmallctlbymib(mib, miblen, &initialized[i], &sz, NULL, 0); if (initialized[i]) { ninitialized++; } } mib[1] = MALLCTL_ARENAS_DESTROYED; sz = sizeof(bool); xmallctlbymib(mib, miblen, &destroyed_initialized, &sz, NULL, 0); /* Merged stats. */ if (merged && (ninitialized > 1 || !unmerged)) { /* Print merged arena stats. */ emitter_table_printf(emitter, "Merged arenas stats:\n"); emitter_json_object_kv_begin(emitter, "merged"); stats_arena_print(emitter, MALLCTL_ARENAS_ALL, bins, large, mutex, extents, hpa); emitter_json_object_end(emitter); /* Close "merged". */ } /* Destroyed stats. */ if (destroyed_initialized && destroyed) { /* Print destroyed arena stats. */ emitter_table_printf(emitter, "Destroyed arenas stats:\n"); emitter_json_object_kv_begin(emitter, "destroyed"); stats_arena_print(emitter, MALLCTL_ARENAS_DESTROYED, bins, large, mutex, extents, hpa); emitter_json_object_end(emitter); /* Close "destroyed". */ } /* Unmerged stats. */ if (unmerged) { for (i = j = 0; i < narenas; i++) { if (initialized[i]) { char arena_ind_str[20]; malloc_snprintf(arena_ind_str, sizeof(arena_ind_str), "%u", i); emitter_json_object_kv_begin(emitter, arena_ind_str); emitter_table_printf(emitter, "arenas[%s]:\n", arena_ind_str); stats_arena_print(emitter, i, bins, large, mutex, extents, hpa); /* Close "". */ emitter_json_object_end(emitter); } } } emitter_json_object_end(emitter); /* Close "stats.arenas". */ } } void stats_print(write_cb_t *write_cb, void *cbopaque, const char *opts) { int err; uint64_t epoch; size_t u64sz; #define OPTION(o, v, d, s) bool v = d; STATS_PRINT_OPTIONS #undef OPTION /* * Refresh stats, in case mallctl() was called by the application. * * Check for OOM here, since refreshing the ctl cache can trigger * allocation. In practice, none of the subsequent mallctl()-related * calls in this function will cause OOM if this one succeeds. * */ epoch = 1; u64sz = sizeof(uint64_t); err = je_mallctl("epoch", (void *)&epoch, &u64sz, (void *)&epoch, sizeof(uint64_t)); if (err != 0) { if (err == EAGAIN) { malloc_write(": Memory allocation failure in " "mallctl(\"epoch\", ...)\n"); return; } malloc_write(": Failure in mallctl(\"epoch\", " "...)\n"); abort(); } if (opts != NULL) { for (unsigned i = 0; opts[i] != '\0'; i++) { switch (opts[i]) { #define OPTION(o, v, d, s) case o: v = s; break; STATS_PRINT_OPTIONS #undef OPTION default:; } } } emitter_t emitter; emitter_init(&emitter, json ? emitter_output_json_compact : emitter_output_table, write_cb, cbopaque); emitter_begin(&emitter); emitter_table_printf(&emitter, "___ Begin jemalloc statistics ___\n"); emitter_json_object_kv_begin(&emitter, "jemalloc"); if (general) { stats_general_print(&emitter); } if (config_stats) { stats_print_helper(&emitter, merged, destroyed, unmerged, bins, large, mutex, extents, hpa); } emitter_json_object_end(&emitter); /* Closes the "jemalloc" dict. */ emitter_table_printf(&emitter, "--- End jemalloc statistics ---\n"); emitter_end(&emitter); } uint64_t stats_interval_new_event_wait(tsd_t *tsd) { return stats_interval_accum_batch; } uint64_t stats_interval_postponed_event_wait(tsd_t *tsd) { return TE_MIN_START_WAIT; } void stats_interval_event_handler(tsd_t *tsd, uint64_t elapsed) { assert(elapsed > 0 && elapsed != TE_INVALID_ELAPSED); if (counter_accum(tsd_tsdn(tsd), &stats_interval_accumulated, elapsed)) { je_malloc_stats_print(NULL, NULL, opt_stats_interval_opts); } } bool stats_boot(void) { uint64_t stats_interval; if (opt_stats_interval < 0) { assert(opt_stats_interval == -1); stats_interval = 0; stats_interval_accum_batch = 0; } else{ /* See comments in stats.h */ stats_interval = (opt_stats_interval > 0) ? opt_stats_interval : 1; uint64_t batch = stats_interval >> STATS_INTERVAL_ACCUM_LG_BATCH_SIZE; if (batch > STATS_INTERVAL_ACCUM_BATCH_MAX) { batch = STATS_INTERVAL_ACCUM_BATCH_MAX; } else if (batch == 0) { batch = 1; } stats_interval_accum_batch = batch; } return counter_accum_init(&stats_interval_accumulated, stats_interval); } void stats_prefork(tsdn_t *tsdn) { counter_prefork(tsdn, &stats_interval_accumulated); } void stats_postfork_parent(tsdn_t *tsdn) { counter_postfork_parent(tsdn, &stats_interval_accumulated); } void stats_postfork_child(tsdn_t *tsdn) { counter_postfork_child(tsdn, &stats_interval_accumulated); } redis-8.0.2/deps/jemalloc/src/sz.c000066400000000000000000000061161501533116600167440ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/sz.h" JEMALLOC_ALIGNED(CACHELINE) size_t sz_pind2sz_tab[SC_NPSIZES+1]; size_t sz_large_pad; size_t sz_psz_quantize_floor(size_t size) { size_t ret; pszind_t pind; assert(size > 0); assert((size & PAGE_MASK) == 0); pind = sz_psz2ind(size - sz_large_pad + 1); if (pind == 0) { /* * Avoid underflow. This short-circuit would also do the right * thing for all sizes in the range for which there are * PAGE-spaced size classes, but it's simplest to just handle * the one case that would cause erroneous results. */ return size; } ret = sz_pind2sz(pind - 1) + sz_large_pad; assert(ret <= size); return ret; } size_t sz_psz_quantize_ceil(size_t size) { size_t ret; assert(size > 0); assert(size - sz_large_pad <= SC_LARGE_MAXCLASS); assert((size & PAGE_MASK) == 0); ret = sz_psz_quantize_floor(size); if (ret < size) { /* * Skip a quantization that may have an adequately large extent, * because under-sized extents may be mixed in. This only * happens when an unusual size is requested, i.e. for aligned * allocation, and is just one of several places where linear * search would potentially find sufficiently aligned available * memory somewhere lower. */ ret = sz_pind2sz(sz_psz2ind(ret - sz_large_pad + 1)) + sz_large_pad; } return ret; } static void sz_boot_pind2sz_tab(const sc_data_t *sc_data) { int pind = 0; for (unsigned i = 0; i < SC_NSIZES; i++) { const sc_t *sc = &sc_data->sc[i]; if (sc->psz) { sz_pind2sz_tab[pind] = (ZU(1) << sc->lg_base) + (ZU(sc->ndelta) << sc->lg_delta); pind++; } } for (int i = pind; i <= (int)SC_NPSIZES; i++) { sz_pind2sz_tab[pind] = sc_data->large_maxclass + PAGE; } } JEMALLOC_ALIGNED(CACHELINE) size_t sz_index2size_tab[SC_NSIZES]; static void sz_boot_index2size_tab(const sc_data_t *sc_data) { for (unsigned i = 0; i < SC_NSIZES; i++) { const sc_t *sc = &sc_data->sc[i]; sz_index2size_tab[i] = (ZU(1) << sc->lg_base) + (ZU(sc->ndelta) << (sc->lg_delta)); } } /* * To keep this table small, we divide sizes by the tiny min size, which gives * the smallest interval for which the result can change. */ JEMALLOC_ALIGNED(CACHELINE) uint8_t sz_size2index_tab[(SC_LOOKUP_MAXCLASS >> SC_LG_TINY_MIN) + 1]; static void sz_boot_size2index_tab(const sc_data_t *sc_data) { size_t dst_max = (SC_LOOKUP_MAXCLASS >> SC_LG_TINY_MIN) + 1; size_t dst_ind = 0; for (unsigned sc_ind = 0; sc_ind < SC_NSIZES && dst_ind < dst_max; sc_ind++) { const sc_t *sc = &sc_data->sc[sc_ind]; size_t sz = (ZU(1) << sc->lg_base) + (ZU(sc->ndelta) << sc->lg_delta); size_t max_ind = ((sz + (ZU(1) << SC_LG_TINY_MIN) - 1) >> SC_LG_TINY_MIN); for (; dst_ind <= max_ind && dst_ind < dst_max; dst_ind++) { sz_size2index_tab[dst_ind] = sc_ind; } } } void sz_boot(const sc_data_t *sc_data, bool cache_oblivious) { sz_large_pad = cache_oblivious ? PAGE : 0; sz_boot_pind2sz_tab(sc_data); sz_boot_index2size_tab(sc_data); sz_boot_size2index_tab(sc_data); } redis-8.0.2/deps/jemalloc/src/tcache.c000066400000000000000000001002661501533116600175400ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/safety_check.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/sc.h" /******************************************************************************/ /* Data. */ bool opt_tcache = true; /* tcache_maxclass is set to 32KB by default. */ size_t opt_tcache_max = ((size_t)1) << 15; /* Reasonable defaults for min and max values. */ unsigned opt_tcache_nslots_small_min = 20; unsigned opt_tcache_nslots_small_max = 200; unsigned opt_tcache_nslots_large = 20; /* * We attempt to make the number of slots in a tcache bin for a given size class * equal to the number of objects in a slab times some multiplier. By default, * the multiplier is 2 (i.e. we set the maximum number of objects in the tcache * to twice the number of objects in a slab). * This is bounded by some other constraints as well, like the fact that it * must be even, must be less than opt_tcache_nslots_small_max, etc.. */ ssize_t opt_lg_tcache_nslots_mul = 1; /* * Number of allocation bytes between tcache incremental GCs. Again, this * default just seems to work well; more tuning is possible. */ size_t opt_tcache_gc_incr_bytes = 65536; /* * With default settings, we may end up flushing small bins frequently with * small flush amounts. To limit this tendency, we can set a number of bytes to * "delay" by. If we try to flush N M-byte items, we decrease that size-class's * delay by N * M. So, if delay is 1024 and we're looking at the 64-byte size * class, we won't do any flushing until we've been asked to flush 1024/64 == 16 * items. This can happen in any configuration (i.e. being asked to flush 16 * items once, or 4 items 4 times). * * Practically, this is stored as a count of items in a uint8_t, so the * effective maximum value for a size class is 255 * sz. */ size_t opt_tcache_gc_delay_bytes = 0; /* * When a cache bin is flushed because it's full, how much of it do we flush? * By default, we flush half the maximum number of items. */ unsigned opt_lg_tcache_flush_small_div = 1; unsigned opt_lg_tcache_flush_large_div = 1; cache_bin_info_t *tcache_bin_info; /* Total stack size required (per tcache). Include the padding above. */ static size_t tcache_bin_alloc_size; static size_t tcache_bin_alloc_alignment; /* Number of cache bins enabled, including both large and small. */ unsigned nhbins; /* Max size class to be cached (can be small or large). */ size_t tcache_maxclass; tcaches_t *tcaches; /* Index of first element within tcaches that has never been used. */ static unsigned tcaches_past; /* Head of singly linked list tracking available tcaches elements. */ static tcaches_t *tcaches_avail; /* Protects tcaches{,_past,_avail}. */ static malloc_mutex_t tcaches_mtx; /******************************************************************************/ size_t tcache_salloc(tsdn_t *tsdn, const void *ptr) { return arena_salloc(tsdn, ptr); } uint64_t tcache_gc_new_event_wait(tsd_t *tsd) { return opt_tcache_gc_incr_bytes; } uint64_t tcache_gc_postponed_event_wait(tsd_t *tsd) { return TE_MIN_START_WAIT; } uint64_t tcache_gc_dalloc_new_event_wait(tsd_t *tsd) { return opt_tcache_gc_incr_bytes; } uint64_t tcache_gc_dalloc_postponed_event_wait(tsd_t *tsd) { return TE_MIN_START_WAIT; } static uint8_t tcache_gc_item_delay_compute(szind_t szind) { assert(szind < SC_NBINS); size_t sz = sz_index2size(szind); size_t item_delay = opt_tcache_gc_delay_bytes / sz; size_t delay_max = ZU(1) << (sizeof(((tcache_slow_t *)NULL)->bin_flush_delay_items[0]) * 8); if (item_delay >= delay_max) { item_delay = delay_max - 1; } return (uint8_t)item_delay; } static void tcache_gc_small(tsd_t *tsd, tcache_slow_t *tcache_slow, tcache_t *tcache, szind_t szind) { /* Aim to flush 3/4 of items below low-water. */ assert(szind < SC_NBINS); cache_bin_t *cache_bin = &tcache->bins[szind]; cache_bin_sz_t ncached = cache_bin_ncached_get_local(cache_bin, &tcache_bin_info[szind]); cache_bin_sz_t low_water = cache_bin_low_water_get(cache_bin, &tcache_bin_info[szind]); assert(!tcache_slow->bin_refilled[szind]); size_t nflush = low_water - (low_water >> 2); if (nflush < tcache_slow->bin_flush_delay_items[szind]) { /* Workaround for a conversion warning. */ uint8_t nflush_uint8 = (uint8_t)nflush; assert(sizeof(tcache_slow->bin_flush_delay_items[0]) == sizeof(nflush_uint8)); tcache_slow->bin_flush_delay_items[szind] -= nflush_uint8; return; } else { tcache_slow->bin_flush_delay_items[szind] = tcache_gc_item_delay_compute(szind); } tcache_bin_flush_small(tsd, tcache, cache_bin, szind, (unsigned)(ncached - nflush)); /* * Reduce fill count by 2X. Limit lg_fill_div such that * the fill count is always at least 1. */ if ((cache_bin_info_ncached_max(&tcache_bin_info[szind]) >> (tcache_slow->lg_fill_div[szind] + 1)) >= 1) { tcache_slow->lg_fill_div[szind]++; } } static void tcache_gc_large(tsd_t *tsd, tcache_slow_t *tcache_slow, tcache_t *tcache, szind_t szind) { /* Like the small GC; flush 3/4 of untouched items. */ assert(szind >= SC_NBINS); cache_bin_t *cache_bin = &tcache->bins[szind]; cache_bin_sz_t ncached = cache_bin_ncached_get_local(cache_bin, &tcache_bin_info[szind]); cache_bin_sz_t low_water = cache_bin_low_water_get(cache_bin, &tcache_bin_info[szind]); tcache_bin_flush_large(tsd, tcache, cache_bin, szind, (unsigned)(ncached - low_water + (low_water >> 2))); } static void tcache_event(tsd_t *tsd) { tcache_t *tcache = tcache_get(tsd); if (tcache == NULL) { return; } tcache_slow_t *tcache_slow = tsd_tcache_slowp_get(tsd); szind_t szind = tcache_slow->next_gc_bin; bool is_small = (szind < SC_NBINS); cache_bin_t *cache_bin = &tcache->bins[szind]; tcache_bin_flush_stashed(tsd, tcache, cache_bin, szind, is_small); cache_bin_sz_t low_water = cache_bin_low_water_get(cache_bin, &tcache_bin_info[szind]); if (low_water > 0) { if (is_small) { tcache_gc_small(tsd, tcache_slow, tcache, szind); } else { tcache_gc_large(tsd, tcache_slow, tcache, szind); } } else if (is_small && tcache_slow->bin_refilled[szind]) { assert(low_water == 0); /* * Increase fill count by 2X for small bins. Make sure * lg_fill_div stays greater than 0. */ if (tcache_slow->lg_fill_div[szind] > 1) { tcache_slow->lg_fill_div[szind]--; } tcache_slow->bin_refilled[szind] = false; } cache_bin_low_water_set(cache_bin); tcache_slow->next_gc_bin++; if (tcache_slow->next_gc_bin == nhbins) { tcache_slow->next_gc_bin = 0; } } void tcache_gc_event_handler(tsd_t *tsd, uint64_t elapsed) { assert(elapsed == TE_INVALID_ELAPSED); tcache_event(tsd); } void tcache_gc_dalloc_event_handler(tsd_t *tsd, uint64_t elapsed) { assert(elapsed == TE_INVALID_ELAPSED); tcache_event(tsd); } void * tcache_alloc_small_hard(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, bool *tcache_success) { tcache_slow_t *tcache_slow = tcache->tcache_slow; void *ret; assert(tcache_slow->arena != NULL); unsigned nfill = cache_bin_info_ncached_max(&tcache_bin_info[binind]) >> tcache_slow->lg_fill_div[binind]; arena_cache_bin_fill_small(tsdn, arena, cache_bin, &tcache_bin_info[binind], binind, nfill); tcache_slow->bin_refilled[binind] = true; ret = cache_bin_alloc(cache_bin, tcache_success); return ret; } static const void * tcache_bin_flush_ptr_getter(void *arr_ctx, size_t ind) { cache_bin_ptr_array_t *arr = (cache_bin_ptr_array_t *)arr_ctx; return arr->ptr[ind]; } static void tcache_bin_flush_metadata_visitor(void *szind_sum_ctx, emap_full_alloc_ctx_t *alloc_ctx) { size_t *szind_sum = (size_t *)szind_sum_ctx; *szind_sum -= alloc_ctx->szind; util_prefetch_write_range(alloc_ctx->edata, sizeof(edata_t)); } JEMALLOC_NOINLINE static void tcache_bin_flush_size_check_fail(cache_bin_ptr_array_t *arr, szind_t szind, size_t nptrs, emap_batch_lookup_result_t *edatas) { bool found_mismatch = false; for (size_t i = 0; i < nptrs; i++) { szind_t true_szind = edata_szind_get(edatas[i].edata); if (true_szind != szind) { found_mismatch = true; safety_check_fail_sized_dealloc( /* current_dealloc */ false, /* ptr */ tcache_bin_flush_ptr_getter(arr, i), /* true_size */ sz_index2size(true_szind), /* input_size */ sz_index2size(szind)); } } assert(found_mismatch); } static void tcache_bin_flush_edatas_lookup(tsd_t *tsd, cache_bin_ptr_array_t *arr, szind_t binind, size_t nflush, emap_batch_lookup_result_t *edatas) { /* * This gets compiled away when config_opt_safety_checks is false. * Checks for sized deallocation bugs, failing early rather than * corrupting metadata. */ size_t szind_sum = binind * nflush; emap_edata_lookup_batch(tsd, &arena_emap_global, nflush, &tcache_bin_flush_ptr_getter, (void *)arr, &tcache_bin_flush_metadata_visitor, (void *)&szind_sum, edatas); if (config_opt_safety_checks && unlikely(szind_sum != 0)) { tcache_bin_flush_size_check_fail(arr, binind, nflush, edatas); } } JEMALLOC_ALWAYS_INLINE bool tcache_bin_flush_match(edata_t *edata, unsigned cur_arena_ind, unsigned cur_binshard, bool small) { if (small) { return edata_arena_ind_get(edata) == cur_arena_ind && edata_binshard_get(edata) == cur_binshard; } else { return edata_arena_ind_get(edata) == cur_arena_ind; } } JEMALLOC_ALWAYS_INLINE void tcache_bin_flush_impl(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, cache_bin_ptr_array_t *ptrs, unsigned nflush, bool small) { tcache_slow_t *tcache_slow = tcache->tcache_slow; /* * A couple lookup calls take tsdn; declare it once for convenience * instead of calling tsd_tsdn(tsd) all the time. */ tsdn_t *tsdn = tsd_tsdn(tsd); if (small) { assert(binind < SC_NBINS); } else { assert(binind < nhbins); } arena_t *tcache_arena = tcache_slow->arena; assert(tcache_arena != NULL); /* * Variable length array must have > 0 length; the last element is never * touched (it's just included to satisfy the no-zero-length rule). */ VARIABLE_ARRAY(emap_batch_lookup_result_t, item_edata, nflush + 1); tcache_bin_flush_edatas_lookup(tsd, ptrs, binind, nflush, item_edata); /* * The slabs where we freed the last remaining object in the slab (and * so need to free the slab itself). * Used only if small == true. */ unsigned dalloc_count = 0; VARIABLE_ARRAY(edata_t *, dalloc_slabs, nflush + 1); /* * We're about to grab a bunch of locks. If one of them happens to be * the one guarding the arena-level stats counters we flush our * thread-local ones to, we do so under one critical section. */ bool merged_stats = false; while (nflush > 0) { /* Lock the arena, or bin, associated with the first object. */ edata_t *edata = item_edata[0].edata; unsigned cur_arena_ind = edata_arena_ind_get(edata); arena_t *cur_arena = arena_get(tsdn, cur_arena_ind, false); /* * These assignments are always overwritten when small is true, * and their values are always ignored when small is false, but * to avoid the technical UB when we pass them as parameters, we * need to intialize them. */ unsigned cur_binshard = 0; bin_t *cur_bin = NULL; if (small) { cur_binshard = edata_binshard_get(edata); cur_bin = arena_get_bin(cur_arena, binind, cur_binshard); assert(cur_binshard < bin_infos[binind].n_shards); /* * If you're looking at profiles, you might think this * is a good place to prefetch the bin stats, which are * often a cache miss. This turns out not to be * helpful on the workloads we've looked at, with moving * the bin stats next to the lock seeming to do better. */ } if (small) { malloc_mutex_lock(tsdn, &cur_bin->lock); } if (!small && !arena_is_auto(cur_arena)) { malloc_mutex_lock(tsdn, &cur_arena->large_mtx); } /* * If we acquired the right lock and have some stats to flush, * flush them. */ if (config_stats && tcache_arena == cur_arena && !merged_stats) { merged_stats = true; if (small) { cur_bin->stats.nflushes++; cur_bin->stats.nrequests += cache_bin->tstats.nrequests; cache_bin->tstats.nrequests = 0; } else { arena_stats_large_flush_nrequests_add(tsdn, &tcache_arena->stats, binind, cache_bin->tstats.nrequests); cache_bin->tstats.nrequests = 0; } } /* * Large allocations need special prep done. Afterwards, we can * drop the large lock. */ if (!small) { for (unsigned i = 0; i < nflush; i++) { void *ptr = ptrs->ptr[i]; edata = item_edata[i].edata; assert(ptr != NULL && edata != NULL); if (tcache_bin_flush_match(edata, cur_arena_ind, cur_binshard, small)) { large_dalloc_prep_locked(tsdn, edata); } } } if (!small && !arena_is_auto(cur_arena)) { malloc_mutex_unlock(tsdn, &cur_arena->large_mtx); } /* Deallocate whatever we can. */ unsigned ndeferred = 0; /* Init only to avoid used-uninitialized warning. */ arena_dalloc_bin_locked_info_t dalloc_bin_info = {0}; if (small) { arena_dalloc_bin_locked_begin(&dalloc_bin_info, binind); } for (unsigned i = 0; i < nflush; i++) { void *ptr = ptrs->ptr[i]; edata = item_edata[i].edata; assert(ptr != NULL && edata != NULL); if (!tcache_bin_flush_match(edata, cur_arena_ind, cur_binshard, small)) { /* * The object was allocated either via a * different arena, or a different bin in this * arena. Either way, stash the object so that * it can be handled in a future pass. */ ptrs->ptr[ndeferred] = ptr; item_edata[ndeferred].edata = edata; ndeferred++; continue; } if (small) { if (arena_dalloc_bin_locked_step(tsdn, cur_arena, cur_bin, &dalloc_bin_info, binind, edata, ptr)) { dalloc_slabs[dalloc_count] = edata; dalloc_count++; } } else { if (large_dalloc_safety_checks(edata, ptr, binind)) { /* See the comment in isfree. */ continue; } large_dalloc_finish(tsdn, edata); } } if (small) { arena_dalloc_bin_locked_finish(tsdn, cur_arena, cur_bin, &dalloc_bin_info); malloc_mutex_unlock(tsdn, &cur_bin->lock); } arena_decay_ticks(tsdn, cur_arena, nflush - ndeferred); nflush = ndeferred; } /* Handle all deferred slab dalloc. */ assert(small || dalloc_count == 0); for (unsigned i = 0; i < dalloc_count; i++) { edata_t *slab = dalloc_slabs[i]; arena_slab_dalloc(tsdn, arena_get_from_edata(slab), slab); } if (config_stats && !merged_stats) { if (small) { /* * The flush loop didn't happen to flush to this * thread's arena, so the stats didn't get merged. * Manually do so now. */ bin_t *bin = arena_bin_choose(tsdn, tcache_arena, binind, NULL); malloc_mutex_lock(tsdn, &bin->lock); bin->stats.nflushes++; bin->stats.nrequests += cache_bin->tstats.nrequests; cache_bin->tstats.nrequests = 0; malloc_mutex_unlock(tsdn, &bin->lock); } else { arena_stats_large_flush_nrequests_add(tsdn, &tcache_arena->stats, binind, cache_bin->tstats.nrequests); cache_bin->tstats.nrequests = 0; } } } JEMALLOC_ALWAYS_INLINE void tcache_bin_flush_bottom(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, unsigned rem, bool small) { tcache_bin_flush_stashed(tsd, tcache, cache_bin, binind, small); cache_bin_sz_t ncached = cache_bin_ncached_get_local(cache_bin, &tcache_bin_info[binind]); assert((cache_bin_sz_t)rem <= ncached); unsigned nflush = ncached - rem; CACHE_BIN_PTR_ARRAY_DECLARE(ptrs, nflush); cache_bin_init_ptr_array_for_flush(cache_bin, &tcache_bin_info[binind], &ptrs, nflush); tcache_bin_flush_impl(tsd, tcache, cache_bin, binind, &ptrs, nflush, small); cache_bin_finish_flush(cache_bin, &tcache_bin_info[binind], &ptrs, ncached - rem); } void tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, unsigned rem) { tcache_bin_flush_bottom(tsd, tcache, cache_bin, binind, rem, true); } void tcache_bin_flush_large(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, unsigned rem) { tcache_bin_flush_bottom(tsd, tcache, cache_bin, binind, rem, false); } /* * Flushing stashed happens when 1) tcache fill, 2) tcache flush, or 3) tcache * GC event. This makes sure that the stashed items do not hold memory for too * long, and new buffers can only be allocated when nothing is stashed. * * The downside is, the time between stash and flush may be relatively short, * especially when the request rate is high. It lowers the chance of detecting * write-after-free -- however that is a delayed detection anyway, and is less * of a focus than the memory overhead. */ void tcache_bin_flush_stashed(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, bool is_small) { cache_bin_info_t *info = &tcache_bin_info[binind]; /* * The two below are for assertion only. The content of original cached * items remain unchanged -- the stashed items reside on the other end * of the stack. Checking the stack head and ncached to verify. */ void *head_content = *cache_bin->stack_head; cache_bin_sz_t orig_cached = cache_bin_ncached_get_local(cache_bin, info); cache_bin_sz_t nstashed = cache_bin_nstashed_get_local(cache_bin, info); assert(orig_cached + nstashed <= cache_bin_info_ncached_max(info)); if (nstashed == 0) { return; } CACHE_BIN_PTR_ARRAY_DECLARE(ptrs, nstashed); cache_bin_init_ptr_array_for_stashed(cache_bin, binind, info, &ptrs, nstashed); san_check_stashed_ptrs(ptrs.ptr, nstashed, sz_index2size(binind)); tcache_bin_flush_impl(tsd, tcache, cache_bin, binind, &ptrs, nstashed, is_small); cache_bin_finish_flush_stashed(cache_bin, info); assert(cache_bin_nstashed_get_local(cache_bin, info) == 0); assert(cache_bin_ncached_get_local(cache_bin, info) == orig_cached); assert(head_content == *cache_bin->stack_head); } void tcache_arena_associate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, tcache_t *tcache, arena_t *arena) { assert(tcache_slow->arena == NULL); tcache_slow->arena = arena; if (config_stats) { /* Link into list of extant tcaches. */ malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); ql_elm_new(tcache_slow, link); ql_tail_insert(&arena->tcache_ql, tcache_slow, link); cache_bin_array_descriptor_init( &tcache_slow->cache_bin_array_descriptor, tcache->bins); ql_tail_insert(&arena->cache_bin_array_descriptor_ql, &tcache_slow->cache_bin_array_descriptor, link); malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); } } static void tcache_arena_dissociate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, tcache_t *tcache) { arena_t *arena = tcache_slow->arena; assert(arena != NULL); if (config_stats) { /* Unlink from list of extant tcaches. */ malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); if (config_debug) { bool in_ql = false; tcache_slow_t *iter; ql_foreach(iter, &arena->tcache_ql, link) { if (iter == tcache_slow) { in_ql = true; break; } } assert(in_ql); } ql_remove(&arena->tcache_ql, tcache_slow, link); ql_remove(&arena->cache_bin_array_descriptor_ql, &tcache_slow->cache_bin_array_descriptor, link); tcache_stats_merge(tsdn, tcache_slow->tcache, arena); malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); } tcache_slow->arena = NULL; } void tcache_arena_reassociate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, tcache_t *tcache, arena_t *arena) { tcache_arena_dissociate(tsdn, tcache_slow, tcache); tcache_arena_associate(tsdn, tcache_slow, tcache, arena); } bool tsd_tcache_enabled_data_init(tsd_t *tsd) { /* Called upon tsd initialization. */ tsd_tcache_enabled_set(tsd, opt_tcache); tsd_slow_update(tsd); if (opt_tcache) { /* Trigger tcache init. */ tsd_tcache_data_init(tsd); } return false; } static void tcache_init(tsd_t *tsd, tcache_slow_t *tcache_slow, tcache_t *tcache, void *mem) { tcache->tcache_slow = tcache_slow; tcache_slow->tcache = tcache; memset(&tcache_slow->link, 0, sizeof(ql_elm(tcache_t))); tcache_slow->next_gc_bin = 0; tcache_slow->arena = NULL; tcache_slow->dyn_alloc = mem; /* * We reserve cache bins for all small size classes, even if some may * not get used (i.e. bins higher than nhbins). This allows the fast * and common paths to access cache bin metadata safely w/o worrying * about which ones are disabled. */ unsigned n_reserved_bins = nhbins < SC_NBINS ? SC_NBINS : nhbins; memset(tcache->bins, 0, sizeof(cache_bin_t) * n_reserved_bins); size_t cur_offset = 0; cache_bin_preincrement(tcache_bin_info, nhbins, mem, &cur_offset); for (unsigned i = 0; i < nhbins; i++) { if (i < SC_NBINS) { tcache_slow->lg_fill_div[i] = 1; tcache_slow->bin_refilled[i] = false; tcache_slow->bin_flush_delay_items[i] = tcache_gc_item_delay_compute(i); } cache_bin_t *cache_bin = &tcache->bins[i]; cache_bin_init(cache_bin, &tcache_bin_info[i], mem, &cur_offset); } /* * For small size classes beyond tcache_maxclass (i.e. nhbins < NBINS), * their cache bins are initialized to a state to safely and efficiently * fail all fastpath alloc / free, so that no additional check around * nhbins is needed on fastpath. */ for (unsigned i = nhbins; i < SC_NBINS; i++) { /* Disabled small bins. */ cache_bin_t *cache_bin = &tcache->bins[i]; void *fake_stack = mem; size_t fake_offset = 0; cache_bin_init(cache_bin, &tcache_bin_info[i], fake_stack, &fake_offset); assert(tcache_small_bin_disabled(i, cache_bin)); } cache_bin_postincrement(tcache_bin_info, nhbins, mem, &cur_offset); /* Sanity check that the whole stack is used. */ assert(cur_offset == tcache_bin_alloc_size); } /* Initialize auto tcache (embedded in TSD). */ bool tsd_tcache_data_init(tsd_t *tsd) { tcache_slow_t *tcache_slow = tsd_tcache_slowp_get_unsafe(tsd); tcache_t *tcache = tsd_tcachep_get_unsafe(tsd); assert(cache_bin_still_zero_initialized(&tcache->bins[0])); size_t alignment = tcache_bin_alloc_alignment; size_t size = sz_sa2u(tcache_bin_alloc_size, alignment); void *mem = ipallocztm(tsd_tsdn(tsd), size, alignment, true, NULL, true, arena_get(TSDN_NULL, 0, true)); if (mem == NULL) { return true; } tcache_init(tsd, tcache_slow, tcache, mem); /* * Initialization is a bit tricky here. After malloc init is done, all * threads can rely on arena_choose and associate tcache accordingly. * However, the thread that does actual malloc bootstrapping relies on * functional tsd, and it can only rely on a0. In that case, we * associate its tcache to a0 temporarily, and later on * arena_choose_hard() will re-associate properly. */ tcache_slow->arena = NULL; arena_t *arena; if (!malloc_initialized()) { /* If in initialization, assign to a0. */ arena = arena_get(tsd_tsdn(tsd), 0, false); tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, tcache, arena); } else { arena = arena_choose(tsd, NULL); /* This may happen if thread.tcache.enabled is used. */ if (tcache_slow->arena == NULL) { tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, tcache, arena); } } assert(arena == tcache_slow->arena); return false; } /* Created manual tcache for tcache.create mallctl. */ tcache_t * tcache_create_explicit(tsd_t *tsd) { /* * We place the cache bin stacks, then the tcache_t, then a pointer to * the beginning of the whole allocation (for freeing). The makes sure * the cache bins have the requested alignment. */ size_t size = tcache_bin_alloc_size + sizeof(tcache_t) + sizeof(tcache_slow_t); /* Naturally align the pointer stacks. */ size = PTR_CEILING(size); size = sz_sa2u(size, tcache_bin_alloc_alignment); void *mem = ipallocztm(tsd_tsdn(tsd), size, tcache_bin_alloc_alignment, true, NULL, true, arena_get(TSDN_NULL, 0, true)); if (mem == NULL) { return NULL; } tcache_t *tcache = (void *)((uintptr_t)mem + tcache_bin_alloc_size); tcache_slow_t *tcache_slow = (void *)((uintptr_t)mem + tcache_bin_alloc_size + sizeof(tcache_t)); tcache_init(tsd, tcache_slow, tcache, mem); tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, tcache, arena_ichoose(tsd, NULL)); return tcache; } static void tcache_flush_cache(tsd_t *tsd, tcache_t *tcache) { tcache_slow_t *tcache_slow = tcache->tcache_slow; assert(tcache_slow->arena != NULL); for (unsigned i = 0; i < nhbins; i++) { cache_bin_t *cache_bin = &tcache->bins[i]; if (i < SC_NBINS) { tcache_bin_flush_small(tsd, tcache, cache_bin, i, 0); } else { tcache_bin_flush_large(tsd, tcache, cache_bin, i, 0); } if (config_stats) { assert(cache_bin->tstats.nrequests == 0); } } } void tcache_flush(tsd_t *tsd) { assert(tcache_available(tsd)); tcache_flush_cache(tsd, tsd_tcachep_get(tsd)); } static void tcache_destroy(tsd_t *tsd, tcache_t *tcache, bool tsd_tcache) { tcache_slow_t *tcache_slow = tcache->tcache_slow; tcache_flush_cache(tsd, tcache); arena_t *arena = tcache_slow->arena; tcache_arena_dissociate(tsd_tsdn(tsd), tcache_slow, tcache); if (tsd_tcache) { cache_bin_t *cache_bin = &tcache->bins[0]; cache_bin_assert_empty(cache_bin, &tcache_bin_info[0]); } idalloctm(tsd_tsdn(tsd), tcache_slow->dyn_alloc, NULL, NULL, true, true); /* * The deallocation and tcache flush above may not trigger decay since * we are on the tcache shutdown path (potentially with non-nominal * tsd). Manually trigger decay to avoid pathological cases. Also * include arena 0 because the tcache array is allocated from it. */ arena_decay(tsd_tsdn(tsd), arena_get(tsd_tsdn(tsd), 0, false), false, false); if (arena_nthreads_get(arena, false) == 0 && !background_thread_enabled()) { /* Force purging when no threads assigned to the arena anymore. */ arena_decay(tsd_tsdn(tsd), arena, /* is_background_thread */ false, /* all */ true); } else { arena_decay(tsd_tsdn(tsd), arena, /* is_background_thread */ false, /* all */ false); } } /* For auto tcache (embedded in TSD) only. */ void tcache_cleanup(tsd_t *tsd) { tcache_t *tcache = tsd_tcachep_get(tsd); if (!tcache_available(tsd)) { assert(tsd_tcache_enabled_get(tsd) == false); assert(cache_bin_still_zero_initialized(&tcache->bins[0])); return; } assert(tsd_tcache_enabled_get(tsd)); assert(!cache_bin_still_zero_initialized(&tcache->bins[0])); tcache_destroy(tsd, tcache, true); if (config_debug) { /* * For debug testing only, we want to pretend we're still in the * zero-initialized state. */ memset(tcache->bins, 0, sizeof(cache_bin_t) * nhbins); } } void tcache_stats_merge(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) { cassert(config_stats); /* Merge and reset tcache stats. */ for (unsigned i = 0; i < nhbins; i++) { cache_bin_t *cache_bin = &tcache->bins[i]; if (i < SC_NBINS) { bin_t *bin = arena_bin_choose(tsdn, arena, i, NULL); malloc_mutex_lock(tsdn, &bin->lock); bin->stats.nrequests += cache_bin->tstats.nrequests; malloc_mutex_unlock(tsdn, &bin->lock); } else { arena_stats_large_flush_nrequests_add(tsdn, &arena->stats, i, cache_bin->tstats.nrequests); } cache_bin->tstats.nrequests = 0; } } static bool tcaches_create_prep(tsd_t *tsd, base_t *base) { bool err; malloc_mutex_assert_owner(tsd_tsdn(tsd), &tcaches_mtx); if (tcaches == NULL) { tcaches = base_alloc(tsd_tsdn(tsd), base, sizeof(tcache_t *) * (MALLOCX_TCACHE_MAX+1), CACHELINE); if (tcaches == NULL) { err = true; goto label_return; } } if (tcaches_avail == NULL && tcaches_past > MALLOCX_TCACHE_MAX) { err = true; goto label_return; } err = false; label_return: return err; } bool tcaches_create(tsd_t *tsd, base_t *base, unsigned *r_ind) { witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0); bool err; malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx); if (tcaches_create_prep(tsd, base)) { err = true; goto label_return; } tcache_t *tcache = tcache_create_explicit(tsd); if (tcache == NULL) { err = true; goto label_return; } tcaches_t *elm; if (tcaches_avail != NULL) { elm = tcaches_avail; tcaches_avail = tcaches_avail->next; elm->tcache = tcache; *r_ind = (unsigned)(elm - tcaches); } else { elm = &tcaches[tcaches_past]; elm->tcache = tcache; *r_ind = tcaches_past; tcaches_past++; } err = false; label_return: malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx); witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0); return err; } static tcache_t * tcaches_elm_remove(tsd_t *tsd, tcaches_t *elm, bool allow_reinit) { malloc_mutex_assert_owner(tsd_tsdn(tsd), &tcaches_mtx); if (elm->tcache == NULL) { return NULL; } tcache_t *tcache = elm->tcache; if (allow_reinit) { elm->tcache = TCACHES_ELM_NEED_REINIT; } else { elm->tcache = NULL; } if (tcache == TCACHES_ELM_NEED_REINIT) { return NULL; } return tcache; } void tcaches_flush(tsd_t *tsd, unsigned ind) { malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx); tcache_t *tcache = tcaches_elm_remove(tsd, &tcaches[ind], true); malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx); if (tcache != NULL) { /* Destroy the tcache; recreate in tcaches_get() if needed. */ tcache_destroy(tsd, tcache, false); } } void tcaches_destroy(tsd_t *tsd, unsigned ind) { malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx); tcaches_t *elm = &tcaches[ind]; tcache_t *tcache = tcaches_elm_remove(tsd, elm, false); elm->next = tcaches_avail; tcaches_avail = elm; malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx); if (tcache != NULL) { tcache_destroy(tsd, tcache, false); } } static unsigned tcache_ncached_max_compute(szind_t szind) { if (szind >= SC_NBINS) { assert(szind < nhbins); return opt_tcache_nslots_large; } unsigned slab_nregs = bin_infos[szind].nregs; /* We may modify these values; start with the opt versions. */ unsigned nslots_small_min = opt_tcache_nslots_small_min; unsigned nslots_small_max = opt_tcache_nslots_small_max; /* * Clamp values to meet our constraints -- even, nonzero, min < max, and * suitable for a cache bin size. */ if (opt_tcache_nslots_small_max > CACHE_BIN_NCACHED_MAX) { nslots_small_max = CACHE_BIN_NCACHED_MAX; } if (nslots_small_min % 2 != 0) { nslots_small_min++; } if (nslots_small_max % 2 != 0) { nslots_small_max--; } if (nslots_small_min < 2) { nslots_small_min = 2; } if (nslots_small_max < 2) { nslots_small_max = 2; } if (nslots_small_min > nslots_small_max) { nslots_small_min = nslots_small_max; } unsigned candidate; if (opt_lg_tcache_nslots_mul < 0) { candidate = slab_nregs >> (-opt_lg_tcache_nslots_mul); } else { candidate = slab_nregs << opt_lg_tcache_nslots_mul; } if (candidate % 2 != 0) { /* * We need the candidate size to be even -- we assume that we * can divide by two and get a positive number (e.g. when * flushing). */ ++candidate; } if (candidate <= nslots_small_min) { return nslots_small_min; } else if (candidate <= nslots_small_max) { return candidate; } else { return nslots_small_max; } } bool tcache_boot(tsdn_t *tsdn, base_t *base) { tcache_maxclass = sz_s2u(opt_tcache_max); assert(tcache_maxclass <= TCACHE_MAXCLASS_LIMIT); nhbins = sz_size2index(tcache_maxclass) + 1; if (malloc_mutex_init(&tcaches_mtx, "tcaches", WITNESS_RANK_TCACHES, malloc_mutex_rank_exclusive)) { return true; } /* Initialize tcache_bin_info. See comments in tcache_init(). */ unsigned n_reserved_bins = nhbins < SC_NBINS ? SC_NBINS : nhbins; size_t size = n_reserved_bins * sizeof(cache_bin_info_t); tcache_bin_info = (cache_bin_info_t *)base_alloc(tsdn, base, size, CACHELINE); if (tcache_bin_info == NULL) { return true; } for (szind_t i = 0; i < nhbins; i++) { unsigned ncached_max = tcache_ncached_max_compute(i); cache_bin_info_init(&tcache_bin_info[i], ncached_max); } for (szind_t i = nhbins; i < SC_NBINS; i++) { /* Disabled small bins. */ cache_bin_info_init(&tcache_bin_info[i], 0); assert(tcache_small_bin_disabled(i, NULL)); } cache_bin_info_compute_alloc(tcache_bin_info, nhbins, &tcache_bin_alloc_size, &tcache_bin_alloc_alignment); return false; } void tcache_prefork(tsdn_t *tsdn) { malloc_mutex_prefork(tsdn, &tcaches_mtx); } void tcache_postfork_parent(tsdn_t *tsdn) { malloc_mutex_postfork_parent(tsdn, &tcaches_mtx); } void tcache_postfork_child(tsdn_t *tsdn) { malloc_mutex_postfork_child(tsdn, &tcaches_mtx); } void tcache_assert_initialized(tcache_t *tcache) { assert(!cache_bin_still_zero_initialized(&tcache->bins[0])); } redis-8.0.2/deps/jemalloc/src/test_hooks.c000066400000000000000000000006271501533116600204730ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" /* * The hooks are a little bit screwy -- they're not genuinely exported in the * sense that we want them available to end-users, but we do want them visible * from outside the generated library, so that we can use them in test code. */ JEMALLOC_EXPORT void (*test_hooks_arena_new_hook)() = NULL; JEMALLOC_EXPORT void (*test_hooks_libc_hook)() = NULL; redis-8.0.2/deps/jemalloc/src/thread_event.c000066400000000000000000000271361501533116600207650ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/thread_event.h" /* * Signatures for event specific functions. These functions should be defined * by the modules owning each event. The signatures here verify that the * definitions follow the right format. * * The first two are functions computing new / postponed event wait time. New * event wait time is the time till the next event if an event is currently * being triggered; postponed event wait time is the time till the next event * if an event should be triggered but needs to be postponed, e.g. when the TSD * is not nominal or during reentrancy. * * The third is the event handler function, which is called whenever an event * is triggered. The parameter is the elapsed time since the last time an * event of the same type was triggered. */ #define E(event, condition_unused, is_alloc_event_unused) \ uint64_t event##_new_event_wait(tsd_t *tsd); \ uint64_t event##_postponed_event_wait(tsd_t *tsd); \ void event##_event_handler(tsd_t *tsd, uint64_t elapsed); ITERATE_OVER_ALL_EVENTS #undef E /* Signatures for internal functions fetching elapsed time. */ #define E(event, condition_unused, is_alloc_event_unused) \ static uint64_t event##_fetch_elapsed(tsd_t *tsd); ITERATE_OVER_ALL_EVENTS #undef E static uint64_t tcache_gc_fetch_elapsed(tsd_t *tsd) { return TE_INVALID_ELAPSED; } static uint64_t tcache_gc_dalloc_fetch_elapsed(tsd_t *tsd) { return TE_INVALID_ELAPSED; } static uint64_t prof_sample_fetch_elapsed(tsd_t *tsd) { uint64_t last_event = thread_allocated_last_event_get(tsd); uint64_t last_sample_event = prof_sample_last_event_get(tsd); prof_sample_last_event_set(tsd, last_event); return last_event - last_sample_event; } static uint64_t stats_interval_fetch_elapsed(tsd_t *tsd) { uint64_t last_event = thread_allocated_last_event_get(tsd); uint64_t last_stats_event = stats_interval_last_event_get(tsd); stats_interval_last_event_set(tsd, last_event); return last_event - last_stats_event; } static uint64_t peak_alloc_fetch_elapsed(tsd_t *tsd) { return TE_INVALID_ELAPSED; } static uint64_t peak_dalloc_fetch_elapsed(tsd_t *tsd) { return TE_INVALID_ELAPSED; } /* Per event facilities done. */ static bool te_ctx_has_active_events(te_ctx_t *ctx) { assert(config_debug); #define E(event, condition, alloc_event) \ if (condition && alloc_event == ctx->is_alloc) { \ return true; \ } ITERATE_OVER_ALL_EVENTS #undef E return false; } static uint64_t te_next_event_compute(tsd_t *tsd, bool is_alloc) { uint64_t wait = TE_MAX_START_WAIT; #define E(event, condition, alloc_event) \ if (is_alloc == alloc_event && condition) { \ uint64_t event_wait = \ event##_event_wait_get(tsd); \ assert(event_wait <= TE_MAX_START_WAIT); \ if (event_wait > 0U && event_wait < wait) { \ wait = event_wait; \ } \ } ITERATE_OVER_ALL_EVENTS #undef E assert(wait <= TE_MAX_START_WAIT); return wait; } static void te_assert_invariants_impl(tsd_t *tsd, te_ctx_t *ctx) { uint64_t current_bytes = te_ctx_current_bytes_get(ctx); uint64_t last_event = te_ctx_last_event_get(ctx); uint64_t next_event = te_ctx_next_event_get(ctx); uint64_t next_event_fast = te_ctx_next_event_fast_get(ctx); assert(last_event != next_event); if (next_event > TE_NEXT_EVENT_FAST_MAX || !tsd_fast(tsd)) { assert(next_event_fast == 0U); } else { assert(next_event_fast == next_event); } /* The subtraction is intentionally susceptible to underflow. */ uint64_t interval = next_event - last_event; /* The subtraction is intentionally susceptible to underflow. */ assert(current_bytes - last_event < interval); uint64_t min_wait = te_next_event_compute(tsd, te_ctx_is_alloc(ctx)); /* * next_event should have been pushed up only except when no event is * on and the TSD is just initialized. The last_event == 0U guard * below is stronger than needed, but having an exactly accurate guard * is more complicated to implement. */ assert((!te_ctx_has_active_events(ctx) && last_event == 0U) || interval == min_wait || (interval < min_wait && interval == TE_MAX_INTERVAL)); } void te_assert_invariants_debug(tsd_t *tsd) { te_ctx_t ctx; te_ctx_get(tsd, &ctx, true); te_assert_invariants_impl(tsd, &ctx); te_ctx_get(tsd, &ctx, false); te_assert_invariants_impl(tsd, &ctx); } /* * Synchronization around the fast threshold in tsd -- * There are two threads to consider in the synchronization here: * - The owner of the tsd being updated by a slow path change * - The remote thread, doing that slow path change. * * As a design constraint, we want to ensure that a slow-path transition cannot * be ignored for arbitrarily long, and that if the remote thread causes a * slow-path transition and then communicates with the owner thread that it has * occurred, then the owner will go down the slow path on the next allocator * operation (so that we don't want to just wait until the owner hits its slow * path reset condition on its own). * * Here's our strategy to do that: * * The remote thread will update the slow-path stores to TSD variables, issue a * SEQ_CST fence, and then update the TSD next_event_fast counter. The owner * thread will update next_event_fast, issue an SEQ_CST fence, and then check * its TSD to see if it's on the slow path. * This is fairly straightforward when 64-bit atomics are supported. Assume that * the remote fence is sandwiched between two owner fences in the reset pathway. * The case where there is no preceding or trailing owner fence (i.e. because * the owner thread is near the beginning or end of its life) can be analyzed * similarly. The owner store to next_event_fast preceding the earlier owner * fence will be earlier in coherence order than the remote store to it, so that * the owner thread will go down the slow path once the store becomes visible to * it, which is no later than the time of the second fence. * The case where we don't support 64-bit atomics is trickier, since word * tearing is possible. We'll repeat the same analysis, and look at the two * owner fences sandwiching the remote fence. The next_event_fast stores done * alongside the earlier owner fence cannot overwrite any of the remote stores * (since they precede the earlier owner fence in sb, which precedes the remote * fence in sc, which precedes the remote stores in sb). After the second owner * fence there will be a re-check of the slow-path variables anyways, so the * "owner will notice that it's on the slow path eventually" guarantee is * satisfied. To make sure that the out-of-band-messaging constraint is as well, * note that either the message passing is sequenced before the second owner * fence (in which case the remote stores happen before the second set of owner * stores, so malloc sees a value of zero for next_event_fast and goes down the * slow path), or it is not (in which case the owner sees the tsd slow-path * writes on its previous update). This leaves open the possibility that the * remote thread will (at some arbitrary point in the future) zero out one half * of the owner thread's next_event_fast, but that's always safe (it just sends * it down the slow path earlier). */ static void te_ctx_next_event_fast_update(te_ctx_t *ctx) { uint64_t next_event = te_ctx_next_event_get(ctx); uint64_t next_event_fast = (next_event <= TE_NEXT_EVENT_FAST_MAX) ? next_event : 0U; te_ctx_next_event_fast_set(ctx, next_event_fast); } void te_recompute_fast_threshold(tsd_t *tsd) { if (tsd_state_get(tsd) != tsd_state_nominal) { /* Check first because this is also called on purgatory. */ te_next_event_fast_set_non_nominal(tsd); return; } te_ctx_t ctx; te_ctx_get(tsd, &ctx, true); te_ctx_next_event_fast_update(&ctx); te_ctx_get(tsd, &ctx, false); te_ctx_next_event_fast_update(&ctx); atomic_fence(ATOMIC_SEQ_CST); if (tsd_state_get(tsd) != tsd_state_nominal) { te_next_event_fast_set_non_nominal(tsd); } } static void te_adjust_thresholds_helper(tsd_t *tsd, te_ctx_t *ctx, uint64_t wait) { /* * The next threshold based on future events can only be adjusted after * progressing the last_event counter (which is set to current). */ assert(te_ctx_current_bytes_get(ctx) == te_ctx_last_event_get(ctx)); assert(wait <= TE_MAX_START_WAIT); uint64_t next_event = te_ctx_last_event_get(ctx) + (wait <= TE_MAX_INTERVAL ? wait : TE_MAX_INTERVAL); te_ctx_next_event_set(tsd, ctx, next_event); } static uint64_t te_clip_event_wait(uint64_t event_wait) { assert(event_wait > 0U); if (TE_MIN_START_WAIT > 1U && unlikely(event_wait < TE_MIN_START_WAIT)) { event_wait = TE_MIN_START_WAIT; } if (TE_MAX_START_WAIT < UINT64_MAX && unlikely(event_wait > TE_MAX_START_WAIT)) { event_wait = TE_MAX_START_WAIT; } return event_wait; } void te_event_trigger(tsd_t *tsd, te_ctx_t *ctx) { /* usize has already been added to thread_allocated. */ uint64_t bytes_after = te_ctx_current_bytes_get(ctx); /* The subtraction is intentionally susceptible to underflow. */ uint64_t accumbytes = bytes_after - te_ctx_last_event_get(ctx); te_ctx_last_event_set(ctx, bytes_after); bool allow_event_trigger = tsd_nominal(tsd) && tsd_reentrancy_level_get(tsd) == 0; bool is_alloc = ctx->is_alloc; uint64_t wait = TE_MAX_START_WAIT; #define E(event, condition, alloc_event) \ bool is_##event##_triggered = false; \ if (is_alloc == alloc_event && condition) { \ uint64_t event_wait = event##_event_wait_get(tsd); \ assert(event_wait <= TE_MAX_START_WAIT); \ if (event_wait > accumbytes) { \ event_wait -= accumbytes; \ } else if (!allow_event_trigger) { \ event_wait = event##_postponed_event_wait(tsd); \ } else { \ is_##event##_triggered = true; \ event_wait = event##_new_event_wait(tsd); \ } \ event_wait = te_clip_event_wait(event_wait); \ event##_event_wait_set(tsd, event_wait); \ if (event_wait < wait) { \ wait = event_wait; \ } \ } ITERATE_OVER_ALL_EVENTS #undef E assert(wait <= TE_MAX_START_WAIT); te_adjust_thresholds_helper(tsd, ctx, wait); te_assert_invariants(tsd); #define E(event, condition, alloc_event) \ if (is_alloc == alloc_event && condition && \ is_##event##_triggered) { \ assert(allow_event_trigger); \ uint64_t elapsed = event##_fetch_elapsed(tsd); \ event##_event_handler(tsd, elapsed); \ } ITERATE_OVER_ALL_EVENTS #undef E te_assert_invariants(tsd); } static void te_init(tsd_t *tsd, bool is_alloc) { te_ctx_t ctx; te_ctx_get(tsd, &ctx, is_alloc); /* * Reset the last event to current, which starts the events from a clean * state. This is necessary when re-init the tsd event counters. * * The event counters maintain a relationship with the current bytes: * last_event <= current < next_event. When a reinit happens (e.g. * reincarnated tsd), the last event needs progressing because all * events start fresh from the current bytes. */ te_ctx_last_event_set(&ctx, te_ctx_current_bytes_get(&ctx)); uint64_t wait = TE_MAX_START_WAIT; #define E(event, condition, alloc_event) \ if (is_alloc == alloc_event && condition) { \ uint64_t event_wait = event##_new_event_wait(tsd); \ event_wait = te_clip_event_wait(event_wait); \ event##_event_wait_set(tsd, event_wait); \ if (event_wait < wait) { \ wait = event_wait; \ } \ } ITERATE_OVER_ALL_EVENTS #undef E te_adjust_thresholds_helper(tsd, &ctx, wait); } void tsd_te_init(tsd_t *tsd) { /* Make sure no overflow for the bytes accumulated on event_trigger. */ assert(TE_MAX_INTERVAL <= UINT64_MAX - SC_LARGE_MAXCLASS + 1); te_init(tsd, true); te_init(tsd, false); te_assert_invariants(tsd); } redis-8.0.2/deps/jemalloc/src/ticker.c000066400000000000000000000026031501533116600175660ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" /* * To avoid using floating point math down core paths (still necessary because * versions of the glibc dynamic loader that did not preserve xmm registers are * still somewhat common, requiring us to be compilable with -mno-sse), and also * to avoid generally expensive library calls, we use a precomputed table of * values. We want to sample U uniformly on [0, 1], and then compute * ceil(log(u)/log(1-1/nticks)). We're mostly interested in the case where * nticks is reasonably big, so 1/log(1-1/nticks) is well-approximated by * -nticks. * * To compute log(u), we sample an integer in [1, 64] and divide, then just look * up results in a table. As a space-compression mechanism, we store these as * uint8_t by dividing the range (255) by the highest-magnitude value the log * can take on, and using that as a multiplier. We then have to divide by that * multiplier at the end of the computation. * * The values here are computed in src/ticker.py */ const uint8_t ticker_geom_table[1 << TICKER_GEOM_NBITS] = { 254, 211, 187, 169, 156, 144, 135, 127, 120, 113, 107, 102, 97, 93, 89, 85, 81, 77, 74, 71, 68, 65, 62, 60, 57, 55, 53, 50, 48, 46, 44, 42, 40, 39, 37, 35, 33, 32, 30, 29, 27, 26, 24, 23, 21, 20, 19, 18, 16, 15, 14, 13, 12, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; redis-8.0.2/deps/jemalloc/src/ticker.py000077500000000000000000000006141501533116600177770ustar00rootroot00000000000000#!/usr/bin/env python3 import math # Must match TICKER_GEOM_NBITS lg_table_size = 6 table_size = 2**lg_table_size byte_max = 255 mul = math.floor(-byte_max/math.log(1 / table_size)) values = [round(-mul * math.log(i / table_size)) for i in range(1, table_size+1)] print("mul =", mul) print("values:") for i in range(table_size // 8): print(", ".join((str(x) for x in values[i*8 : i*8 + 8]))) redis-8.0.2/deps/jemalloc/src/tsd.c000066400000000000000000000351051501533116600171020ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/mutex.h" #include "jemalloc/internal/rtree.h" /******************************************************************************/ /* Data. */ /* TSD_INITIALIZER triggers "-Wmissing-field-initializer" */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS #ifdef JEMALLOC_MALLOC_THREAD_CLEANUP JEMALLOC_TSD_TYPE_ATTR(tsd_t) tsd_tls = TSD_INITIALIZER; JEMALLOC_TSD_TYPE_ATTR(bool) JEMALLOC_TLS_MODEL tsd_initialized = false; bool tsd_booted = false; #elif (defined(JEMALLOC_TLS)) JEMALLOC_TSD_TYPE_ATTR(tsd_t) tsd_tls = TSD_INITIALIZER; pthread_key_t tsd_tsd; bool tsd_booted = false; #elif (defined(_WIN32)) DWORD tsd_tsd; tsd_wrapper_t tsd_boot_wrapper = {false, TSD_INITIALIZER}; bool tsd_booted = false; #else /* * This contains a mutex, but it's pretty convenient to allow the mutex code to * have a dependency on tsd. So we define the struct here, and only refer to it * by pointer in the header. */ struct tsd_init_head_s { ql_head(tsd_init_block_t) blocks; malloc_mutex_t lock; }; pthread_key_t tsd_tsd; tsd_init_head_t tsd_init_head = { ql_head_initializer(blocks), MALLOC_MUTEX_INITIALIZER }; tsd_wrapper_t tsd_boot_wrapper = { false, TSD_INITIALIZER }; bool tsd_booted = false; #endif JEMALLOC_DIAGNOSTIC_POP /******************************************************************************/ /* A list of all the tsds in the nominal state. */ typedef ql_head(tsd_t) tsd_list_t; static tsd_list_t tsd_nominal_tsds = ql_head_initializer(tsd_nominal_tsds); static malloc_mutex_t tsd_nominal_tsds_lock; /* How many slow-path-enabling features are turned on. */ static atomic_u32_t tsd_global_slow_count = ATOMIC_INIT(0); static bool tsd_in_nominal_list(tsd_t *tsd) { tsd_t *tsd_list; bool found = false; /* * We don't know that tsd is nominal; it might not be safe to get data * out of it here. */ malloc_mutex_lock(TSDN_NULL, &tsd_nominal_tsds_lock); ql_foreach(tsd_list, &tsd_nominal_tsds, TSD_MANGLE(tsd_link)) { if (tsd == tsd_list) { found = true; break; } } malloc_mutex_unlock(TSDN_NULL, &tsd_nominal_tsds_lock); return found; } static void tsd_add_nominal(tsd_t *tsd) { assert(!tsd_in_nominal_list(tsd)); assert(tsd_state_get(tsd) <= tsd_state_nominal_max); ql_elm_new(tsd, TSD_MANGLE(tsd_link)); malloc_mutex_lock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); ql_tail_insert(&tsd_nominal_tsds, tsd, TSD_MANGLE(tsd_link)); malloc_mutex_unlock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); } static void tsd_remove_nominal(tsd_t *tsd) { assert(tsd_in_nominal_list(tsd)); assert(tsd_state_get(tsd) <= tsd_state_nominal_max); malloc_mutex_lock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); ql_remove(&tsd_nominal_tsds, tsd, TSD_MANGLE(tsd_link)); malloc_mutex_unlock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); } static void tsd_force_recompute(tsdn_t *tsdn) { /* * The stores to tsd->state here need to synchronize with the exchange * in tsd_slow_update. */ atomic_fence(ATOMIC_RELEASE); malloc_mutex_lock(tsdn, &tsd_nominal_tsds_lock); tsd_t *remote_tsd; ql_foreach(remote_tsd, &tsd_nominal_tsds, TSD_MANGLE(tsd_link)) { assert(tsd_atomic_load(&remote_tsd->state, ATOMIC_RELAXED) <= tsd_state_nominal_max); tsd_atomic_store(&remote_tsd->state, tsd_state_nominal_recompute, ATOMIC_RELAXED); /* See comments in te_recompute_fast_threshold(). */ atomic_fence(ATOMIC_SEQ_CST); te_next_event_fast_set_non_nominal(remote_tsd); } malloc_mutex_unlock(tsdn, &tsd_nominal_tsds_lock); } void tsd_global_slow_inc(tsdn_t *tsdn) { atomic_fetch_add_u32(&tsd_global_slow_count, 1, ATOMIC_RELAXED); /* * We unconditionally force a recompute, even if the global slow count * was already positive. If we didn't, then it would be possible for us * to return to the user, have the user synchronize externally with some * other thread, and then have that other thread not have picked up the * update yet (since the original incrementing thread might still be * making its way through the tsd list). */ tsd_force_recompute(tsdn); } void tsd_global_slow_dec(tsdn_t *tsdn) { atomic_fetch_sub_u32(&tsd_global_slow_count, 1, ATOMIC_RELAXED); /* See the note in ..._inc(). */ tsd_force_recompute(tsdn); } static bool tsd_local_slow(tsd_t *tsd) { return !tsd_tcache_enabled_get(tsd) || tsd_reentrancy_level_get(tsd) > 0; } bool tsd_global_slow() { return atomic_load_u32(&tsd_global_slow_count, ATOMIC_RELAXED) > 0; } /******************************************************************************/ static uint8_t tsd_state_compute(tsd_t *tsd) { if (!tsd_nominal(tsd)) { return tsd_state_get(tsd); } /* We're in *a* nominal state; but which one? */ if (malloc_slow || tsd_local_slow(tsd) || tsd_global_slow()) { return tsd_state_nominal_slow; } else { return tsd_state_nominal; } } void tsd_slow_update(tsd_t *tsd) { uint8_t old_state; do { uint8_t new_state = tsd_state_compute(tsd); old_state = tsd_atomic_exchange(&tsd->state, new_state, ATOMIC_ACQUIRE); } while (old_state == tsd_state_nominal_recompute); te_recompute_fast_threshold(tsd); } void tsd_state_set(tsd_t *tsd, uint8_t new_state) { /* Only the tsd module can change the state *to* recompute. */ assert(new_state != tsd_state_nominal_recompute); uint8_t old_state = tsd_atomic_load(&tsd->state, ATOMIC_RELAXED); if (old_state > tsd_state_nominal_max) { /* * Not currently in the nominal list, but it might need to be * inserted there. */ assert(!tsd_in_nominal_list(tsd)); tsd_atomic_store(&tsd->state, new_state, ATOMIC_RELAXED); if (new_state <= tsd_state_nominal_max) { tsd_add_nominal(tsd); } } else { /* * We're currently nominal. If the new state is non-nominal, * great; we take ourselves off the list and just enter the new * state. */ assert(tsd_in_nominal_list(tsd)); if (new_state > tsd_state_nominal_max) { tsd_remove_nominal(tsd); tsd_atomic_store(&tsd->state, new_state, ATOMIC_RELAXED); } else { /* * This is the tricky case. We're transitioning from * one nominal state to another. The caller can't know * about any races that are occurring at the same time, * so we always have to recompute no matter what. */ tsd_slow_update(tsd); } } te_recompute_fast_threshold(tsd); } static void tsd_prng_state_init(tsd_t *tsd) { /* * A nondeterministic seed based on the address of tsd reduces * the likelihood of lockstep non-uniform cache index * utilization among identical concurrent processes, but at the * cost of test repeatability. For debug builds, instead use a * deterministic seed. */ *tsd_prng_statep_get(tsd) = config_debug ? 0 : (uint64_t)(uintptr_t)tsd; } static bool tsd_data_init(tsd_t *tsd) { /* * We initialize the rtree context first (before the tcache), since the * tcache initialization depends on it. */ rtree_ctx_data_init(tsd_rtree_ctxp_get_unsafe(tsd)); tsd_prng_state_init(tsd); tsd_te_init(tsd); /* event_init may use the prng state above. */ tsd_san_init(tsd); return tsd_tcache_enabled_data_init(tsd); } static void assert_tsd_data_cleanup_done(tsd_t *tsd) { assert(!tsd_nominal(tsd)); assert(!tsd_in_nominal_list(tsd)); assert(*tsd_arenap_get_unsafe(tsd) == NULL); assert(*tsd_iarenap_get_unsafe(tsd) == NULL); assert(*tsd_tcache_enabledp_get_unsafe(tsd) == false); assert(*tsd_prof_tdatap_get_unsafe(tsd) == NULL); } static bool tsd_data_init_nocleanup(tsd_t *tsd) { assert(tsd_state_get(tsd) == tsd_state_reincarnated || tsd_state_get(tsd) == tsd_state_minimal_initialized); /* * During reincarnation, there is no guarantee that the cleanup function * will be called (deallocation may happen after all tsd destructors). * We set up tsd in a way that no cleanup is needed. */ rtree_ctx_data_init(tsd_rtree_ctxp_get_unsafe(tsd)); *tsd_tcache_enabledp_get_unsafe(tsd) = false; *tsd_reentrancy_levelp_get(tsd) = 1; tsd_prng_state_init(tsd); tsd_te_init(tsd); /* event_init may use the prng state above. */ tsd_san_init(tsd); assert_tsd_data_cleanup_done(tsd); return false; } tsd_t * tsd_fetch_slow(tsd_t *tsd, bool minimal) { assert(!tsd_fast(tsd)); if (tsd_state_get(tsd) == tsd_state_nominal_slow) { /* * On slow path but no work needed. Note that we can't * necessarily *assert* that we're slow, because we might be * slow because of an asynchronous modification to global state, * which might be asynchronously modified *back*. */ } else if (tsd_state_get(tsd) == tsd_state_nominal_recompute) { tsd_slow_update(tsd); } else if (tsd_state_get(tsd) == tsd_state_uninitialized) { if (!minimal) { if (tsd_booted) { tsd_state_set(tsd, tsd_state_nominal); tsd_slow_update(tsd); /* Trigger cleanup handler registration. */ tsd_set(tsd); tsd_data_init(tsd); } } else { tsd_state_set(tsd, tsd_state_minimal_initialized); tsd_set(tsd); tsd_data_init_nocleanup(tsd); } } else if (tsd_state_get(tsd) == tsd_state_minimal_initialized) { if (!minimal) { /* Switch to fully initialized. */ tsd_state_set(tsd, tsd_state_nominal); assert(*tsd_reentrancy_levelp_get(tsd) >= 1); (*tsd_reentrancy_levelp_get(tsd))--; tsd_slow_update(tsd); tsd_data_init(tsd); } else { assert_tsd_data_cleanup_done(tsd); } } else if (tsd_state_get(tsd) == tsd_state_purgatory) { tsd_state_set(tsd, tsd_state_reincarnated); tsd_set(tsd); tsd_data_init_nocleanup(tsd); } else { assert(tsd_state_get(tsd) == tsd_state_reincarnated); } return tsd; } void * malloc_tsd_malloc(size_t size) { return a0malloc(CACHELINE_CEILING(size)); } void malloc_tsd_dalloc(void *wrapper) { a0dalloc(wrapper); } #if defined(JEMALLOC_MALLOC_THREAD_CLEANUP) || defined(_WIN32) static unsigned ncleanups; static malloc_tsd_cleanup_t cleanups[MALLOC_TSD_CLEANUPS_MAX]; #ifndef _WIN32 JEMALLOC_EXPORT #endif void _malloc_thread_cleanup(void) { bool pending[MALLOC_TSD_CLEANUPS_MAX], again; unsigned i; for (i = 0; i < ncleanups; i++) { pending[i] = true; } do { again = false; for (i = 0; i < ncleanups; i++) { if (pending[i]) { pending[i] = cleanups[i](); if (pending[i]) { again = true; } } } } while (again); } #ifndef _WIN32 JEMALLOC_EXPORT #endif void _malloc_tsd_cleanup_register(bool (*f)(void)) { assert(ncleanups < MALLOC_TSD_CLEANUPS_MAX); cleanups[ncleanups] = f; ncleanups++; } #endif static void tsd_do_data_cleanup(tsd_t *tsd) { prof_tdata_cleanup(tsd); iarena_cleanup(tsd); arena_cleanup(tsd); tcache_cleanup(tsd); witnesses_cleanup(tsd_witness_tsdp_get_unsafe(tsd)); *tsd_reentrancy_levelp_get(tsd) = 1; } void tsd_cleanup(void *arg) { tsd_t *tsd = (tsd_t *)arg; switch (tsd_state_get(tsd)) { case tsd_state_uninitialized: /* Do nothing. */ break; case tsd_state_minimal_initialized: /* This implies the thread only did free() in its life time. */ /* Fall through. */ case tsd_state_reincarnated: /* * Reincarnated means another destructor deallocated memory * after the destructor was called. Cleanup isn't required but * is still called for testing and completeness. */ assert_tsd_data_cleanup_done(tsd); JEMALLOC_FALLTHROUGH; case tsd_state_nominal: case tsd_state_nominal_slow: tsd_do_data_cleanup(tsd); tsd_state_set(tsd, tsd_state_purgatory); tsd_set(tsd); break; case tsd_state_purgatory: /* * The previous time this destructor was called, we set the * state to tsd_state_purgatory so that other destructors * wouldn't cause re-creation of the tsd. This time, do * nothing, and do not request another callback. */ break; default: not_reached(); } #ifdef JEMALLOC_JET test_callback_t test_callback = *tsd_test_callbackp_get_unsafe(tsd); int *data = tsd_test_datap_get_unsafe(tsd); if (test_callback != NULL) { test_callback(data); } #endif } tsd_t * malloc_tsd_boot0(void) { tsd_t *tsd; #if defined(JEMALLOC_MALLOC_THREAD_CLEANUP) || defined(_WIN32) ncleanups = 0; #endif if (malloc_mutex_init(&tsd_nominal_tsds_lock, "tsd_nominal_tsds_lock", WITNESS_RANK_OMIT, malloc_mutex_rank_exclusive)) { return NULL; } if (tsd_boot0()) { return NULL; } tsd = tsd_fetch(); return tsd; } void malloc_tsd_boot1(void) { tsd_boot1(); tsd_t *tsd = tsd_fetch(); /* malloc_slow has been set properly. Update tsd_slow. */ tsd_slow_update(tsd); } #ifdef _WIN32 static BOOL WINAPI _tls_callback(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { #ifdef JEMALLOC_LAZY_LOCK case DLL_THREAD_ATTACH: isthreaded = true; break; #endif case DLL_THREAD_DETACH: _malloc_thread_cleanup(); break; default: break; } return true; } /* * We need to be able to say "read" here (in the "pragma section"), but have * hooked "read". We won't read for the rest of the file, so we can get away * with unhooking. */ #ifdef read # undef read #endif #ifdef _MSC_VER # ifdef _M_IX86 # pragma comment(linker, "/INCLUDE:__tls_used") # pragma comment(linker, "/INCLUDE:_tls_callback") # else # pragma comment(linker, "/INCLUDE:_tls_used") # pragma comment(linker, "/INCLUDE:" STRINGIFY(tls_callback) ) # endif # pragma section(".CRT$XLY",long,read) #endif JEMALLOC_SECTION(".CRT$XLY") JEMALLOC_ATTR(used) BOOL (WINAPI *const tls_callback)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) = _tls_callback; #endif #if (!defined(JEMALLOC_MALLOC_THREAD_CLEANUP) && !defined(JEMALLOC_TLS) && \ !defined(_WIN32)) void * tsd_init_check_recursion(tsd_init_head_t *head, tsd_init_block_t *block) { pthread_t self = pthread_self(); tsd_init_block_t *iter; /* Check whether this thread has already inserted into the list. */ malloc_mutex_lock(TSDN_NULL, &head->lock); ql_foreach(iter, &head->blocks, link) { if (iter->thread == self) { malloc_mutex_unlock(TSDN_NULL, &head->lock); return iter->data; } } /* Insert block into list. */ ql_elm_new(block, link); block->thread = self; ql_tail_insert(&head->blocks, block, link); malloc_mutex_unlock(TSDN_NULL, &head->lock); return NULL; } void tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block) { malloc_mutex_lock(TSDN_NULL, &head->lock); ql_remove(&head->blocks, block, link); malloc_mutex_unlock(TSDN_NULL, &head->lock); } #endif void tsd_prefork(tsd_t *tsd) { malloc_mutex_prefork(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); } void tsd_postfork_parent(tsd_t *tsd) { malloc_mutex_postfork_parent(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); } void tsd_postfork_child(tsd_t *tsd) { malloc_mutex_postfork_child(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); ql_new(&tsd_nominal_tsds); if (tsd_state_get(tsd) <= tsd_state_nominal_max) { tsd_add_nominal(tsd); } } redis-8.0.2/deps/jemalloc/src/witness.c000066400000000000000000000056401501533116600200050ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #include "jemalloc/internal/malloc_io.h" void witness_init(witness_t *witness, const char *name, witness_rank_t rank, witness_comp_t *comp, void *opaque) { witness->name = name; witness->rank = rank; witness->comp = comp; witness->opaque = opaque; } static void witness_print_witness(witness_t *w, unsigned n) { assert(n > 0); if (n == 1) { malloc_printf(" %s(%u)", w->name, w->rank); } else { malloc_printf(" %s(%u)X%u", w->name, w->rank, n); } } static void witness_print_witnesses(const witness_list_t *witnesses) { witness_t *w, *last = NULL; unsigned n = 0; ql_foreach(w, witnesses, link) { if (last != NULL && w->rank > last->rank) { assert(w->name != last->name); witness_print_witness(last, n); n = 0; } else if (last != NULL) { assert(w->rank == last->rank); assert(w->name == last->name); } last = w; ++n; } if (last != NULL) { witness_print_witness(last, n); } } static void witness_lock_error_impl(const witness_list_t *witnesses, const witness_t *witness) { malloc_printf(": Lock rank order reversal:"); witness_print_witnesses(witnesses); malloc_printf(" %s(%u)\n", witness->name, witness->rank); abort(); } witness_lock_error_t *JET_MUTABLE witness_lock_error = witness_lock_error_impl; static void witness_owner_error_impl(const witness_t *witness) { malloc_printf(": Should own %s(%u)\n", witness->name, witness->rank); abort(); } witness_owner_error_t *JET_MUTABLE witness_owner_error = witness_owner_error_impl; static void witness_not_owner_error_impl(const witness_t *witness) { malloc_printf(": Should not own %s(%u)\n", witness->name, witness->rank); abort(); } witness_not_owner_error_t *JET_MUTABLE witness_not_owner_error = witness_not_owner_error_impl; static void witness_depth_error_impl(const witness_list_t *witnesses, witness_rank_t rank_inclusive, unsigned depth) { malloc_printf(": Should own %u lock%s of rank >= %u:", depth, (depth != 1) ? "s" : "", rank_inclusive); witness_print_witnesses(witnesses); malloc_printf("\n"); abort(); } witness_depth_error_t *JET_MUTABLE witness_depth_error = witness_depth_error_impl; void witnesses_cleanup(witness_tsd_t *witness_tsd) { witness_assert_lockless(witness_tsd_tsdn(witness_tsd)); /* Do nothing. */ } void witness_prefork(witness_tsd_t *witness_tsd) { if (!config_debug) { return; } witness_tsd->forking = true; } void witness_postfork_parent(witness_tsd_t *witness_tsd) { if (!config_debug) { return; } witness_tsd->forking = false; } void witness_postfork_child(witness_tsd_t *witness_tsd) { if (!config_debug) { return; } #ifndef JEMALLOC_MUTEX_INIT_CB witness_list_t *witnesses; witnesses = &witness_tsd->witnesses; ql_new(witnesses); #endif witness_tsd->forking = false; } redis-8.0.2/deps/jemalloc/src/zone.c000066400000000000000000000351411501533116600172630ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_preamble.h" #include "jemalloc/internal/jemalloc_internal_includes.h" #include "jemalloc/internal/assert.h" #ifndef JEMALLOC_ZONE # error "This source file is for zones on Darwin (OS X)." #endif /* Definitions of the following structs in malloc/malloc.h might be too old * for the built binary to run on newer versions of OSX. So use the newest * possible version of those structs. */ typedef struct _malloc_zone_t { void *reserved1; void *reserved2; size_t (*size)(struct _malloc_zone_t *, const void *); void *(*malloc)(struct _malloc_zone_t *, size_t); void *(*calloc)(struct _malloc_zone_t *, size_t, size_t); void *(*valloc)(struct _malloc_zone_t *, size_t); void (*free)(struct _malloc_zone_t *, void *); void *(*realloc)(struct _malloc_zone_t *, void *, size_t); void (*destroy)(struct _malloc_zone_t *); const char *zone_name; unsigned (*batch_malloc)(struct _malloc_zone_t *, size_t, void **, unsigned); void (*batch_free)(struct _malloc_zone_t *, void **, unsigned); struct malloc_introspection_t *introspect; unsigned version; void *(*memalign)(struct _malloc_zone_t *, size_t, size_t); void (*free_definite_size)(struct _malloc_zone_t *, void *, size_t); size_t (*pressure_relief)(struct _malloc_zone_t *, size_t); } malloc_zone_t; typedef struct { vm_address_t address; vm_size_t size; } vm_range_t; typedef struct malloc_statistics_t { unsigned blocks_in_use; size_t size_in_use; size_t max_size_in_use; size_t size_allocated; } malloc_statistics_t; typedef kern_return_t memory_reader_t(task_t, vm_address_t, vm_size_t, void **); typedef void vm_range_recorder_t(task_t, void *, unsigned type, vm_range_t *, unsigned); typedef struct malloc_introspection_t { kern_return_t (*enumerator)(task_t, void *, unsigned, vm_address_t, memory_reader_t, vm_range_recorder_t); size_t (*good_size)(malloc_zone_t *, size_t); boolean_t (*check)(malloc_zone_t *); void (*print)(malloc_zone_t *, boolean_t); void (*log)(malloc_zone_t *, void *); void (*force_lock)(malloc_zone_t *); void (*force_unlock)(malloc_zone_t *); void (*statistics)(malloc_zone_t *, malloc_statistics_t *); boolean_t (*zone_locked)(malloc_zone_t *); boolean_t (*enable_discharge_checking)(malloc_zone_t *); boolean_t (*disable_discharge_checking)(malloc_zone_t *); void (*discharge)(malloc_zone_t *, void *); #ifdef __BLOCKS__ void (*enumerate_discharged_pointers)(malloc_zone_t *, void (^)(void *, void *)); #else void *enumerate_unavailable_without_blocks; #endif void (*reinit_lock)(malloc_zone_t *); } malloc_introspection_t; extern kern_return_t malloc_get_all_zones(task_t, memory_reader_t, vm_address_t **, unsigned *); extern malloc_zone_t *malloc_default_zone(void); extern void malloc_zone_register(malloc_zone_t *zone); extern void malloc_zone_unregister(malloc_zone_t *zone); /* * The malloc_default_purgeable_zone() function is only available on >= 10.6. * We need to check whether it is present at runtime, thus the weak_import. */ extern malloc_zone_t *malloc_default_purgeable_zone(void) JEMALLOC_ATTR(weak_import); /******************************************************************************/ /* Data. */ static malloc_zone_t *default_zone, *purgeable_zone; static malloc_zone_t jemalloc_zone; static struct malloc_introspection_t jemalloc_zone_introspect; static pid_t zone_force_lock_pid = -1; /******************************************************************************/ /* Function prototypes for non-inline static functions. */ static size_t zone_size(malloc_zone_t *zone, const void *ptr); static void *zone_malloc(malloc_zone_t *zone, size_t size); static void *zone_calloc(malloc_zone_t *zone, size_t num, size_t size); static void *zone_valloc(malloc_zone_t *zone, size_t size); static void zone_free(malloc_zone_t *zone, void *ptr); static void *zone_realloc(malloc_zone_t *zone, void *ptr, size_t size); static void *zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size); static void zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size); static void zone_destroy(malloc_zone_t *zone); static unsigned zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); static void zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); static size_t zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal); static size_t zone_good_size(malloc_zone_t *zone, size_t size); static kern_return_t zone_enumerator(task_t task, void *data, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder); static boolean_t zone_check(malloc_zone_t *zone); static void zone_print(malloc_zone_t *zone, boolean_t verbose); static void zone_log(malloc_zone_t *zone, void *address); static void zone_force_lock(malloc_zone_t *zone); static void zone_force_unlock(malloc_zone_t *zone); static void zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats); static boolean_t zone_locked(malloc_zone_t *zone); static void zone_reinit_lock(malloc_zone_t *zone); /******************************************************************************/ /* * Functions. */ static size_t zone_size(malloc_zone_t *zone, const void *ptr) { /* * There appear to be places within Darwin (such as setenv(3)) that * cause calls to this function with pointers that *no* zone owns. If * we knew that all pointers were owned by *some* zone, we could split * our zone into two parts, and use one as the default allocator and * the other as the default deallocator/reallocator. Since that will * not work in practice, we must check all pointers to assure that they * reside within a mapped extent before determining size. */ return ivsalloc(tsdn_fetch(), ptr); } static void * zone_malloc(malloc_zone_t *zone, size_t size) { return je_malloc(size); } static void * zone_calloc(malloc_zone_t *zone, size_t num, size_t size) { return je_calloc(num, size); } static void * zone_valloc(malloc_zone_t *zone, size_t size) { void *ret = NULL; /* Assignment avoids useless compiler warning. */ je_posix_memalign(&ret, PAGE, size); return ret; } static void zone_free(malloc_zone_t *zone, void *ptr) { if (ivsalloc(tsdn_fetch(), ptr) != 0) { je_free(ptr); return; } free(ptr); } static void * zone_realloc(malloc_zone_t *zone, void *ptr, size_t size) { if (ivsalloc(tsdn_fetch(), ptr) != 0) { return je_realloc(ptr, size); } return realloc(ptr, size); } static void * zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size) { void *ret = NULL; /* Assignment avoids useless compiler warning. */ je_posix_memalign(&ret, alignment, size); return ret; } static void zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size) { size_t alloc_size; alloc_size = ivsalloc(tsdn_fetch(), ptr); if (alloc_size != 0) { assert(alloc_size == size); je_free(ptr); return; } free(ptr); } static void zone_destroy(malloc_zone_t *zone) { /* This function should never be called. */ not_reached(); } static unsigned zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested) { unsigned i; for (i = 0; i < num_requested; i++) { results[i] = je_malloc(size); if (!results[i]) break; } return i; } static void zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed) { unsigned i; for (i = 0; i < num_to_be_freed; i++) { zone_free(zone, to_be_freed[i]); to_be_freed[i] = NULL; } } static size_t zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal) { return 0; } static size_t zone_good_size(malloc_zone_t *zone, size_t size) { if (size == 0) { size = 1; } return sz_s2u(size); } static kern_return_t zone_enumerator(task_t task, void *data, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder) { return KERN_SUCCESS; } static boolean_t zone_check(malloc_zone_t *zone) { return true; } static void zone_print(malloc_zone_t *zone, boolean_t verbose) { } static void zone_log(malloc_zone_t *zone, void *address) { } static void zone_force_lock(malloc_zone_t *zone) { if (isthreaded) { /* * See the note in zone_force_unlock, below, to see why we need * this. */ assert(zone_force_lock_pid == -1); zone_force_lock_pid = getpid(); jemalloc_prefork(); } } static void zone_force_unlock(malloc_zone_t *zone) { /* * zone_force_lock and zone_force_unlock are the entry points to the * forking machinery on OS X. The tricky thing is, the child is not * allowed to unlock mutexes locked in the parent, even if owned by the * forking thread (and the mutex type we use in OS X will fail an assert * if we try). In the child, we can get away with reinitializing all * the mutexes, which has the effect of unlocking them. In the parent, * doing this would mean we wouldn't wake any waiters blocked on the * mutexes we unlock. So, we record the pid of the current thread in * zone_force_lock, and use that to detect if we're in the parent or * child here, to decide which unlock logic we need. */ if (isthreaded) { assert(zone_force_lock_pid != -1); if (getpid() == zone_force_lock_pid) { jemalloc_postfork_parent(); } else { jemalloc_postfork_child(); } zone_force_lock_pid = -1; } } static void zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { /* We make no effort to actually fill the values */ stats->blocks_in_use = 0; stats->size_in_use = 0; stats->max_size_in_use = 0; stats->size_allocated = 0; } static boolean_t zone_locked(malloc_zone_t *zone) { /* Pretend no lock is being held */ return false; } static void zone_reinit_lock(malloc_zone_t *zone) { /* As of OSX 10.12, this function is only used when force_unlock would * be used if the zone version were < 9. So just use force_unlock. */ zone_force_unlock(zone); } static void zone_init(void) { jemalloc_zone.size = zone_size; jemalloc_zone.malloc = zone_malloc; jemalloc_zone.calloc = zone_calloc; jemalloc_zone.valloc = zone_valloc; jemalloc_zone.free = zone_free; jemalloc_zone.realloc = zone_realloc; jemalloc_zone.destroy = zone_destroy; jemalloc_zone.zone_name = "jemalloc_zone"; jemalloc_zone.batch_malloc = zone_batch_malloc; jemalloc_zone.batch_free = zone_batch_free; jemalloc_zone.introspect = &jemalloc_zone_introspect; jemalloc_zone.version = 9; jemalloc_zone.memalign = zone_memalign; jemalloc_zone.free_definite_size = zone_free_definite_size; jemalloc_zone.pressure_relief = zone_pressure_relief; jemalloc_zone_introspect.enumerator = zone_enumerator; jemalloc_zone_introspect.good_size = zone_good_size; jemalloc_zone_introspect.check = zone_check; jemalloc_zone_introspect.print = zone_print; jemalloc_zone_introspect.log = zone_log; jemalloc_zone_introspect.force_lock = zone_force_lock; jemalloc_zone_introspect.force_unlock = zone_force_unlock; jemalloc_zone_introspect.statistics = zone_statistics; jemalloc_zone_introspect.zone_locked = zone_locked; jemalloc_zone_introspect.enable_discharge_checking = NULL; jemalloc_zone_introspect.disable_discharge_checking = NULL; jemalloc_zone_introspect.discharge = NULL; #ifdef __BLOCKS__ jemalloc_zone_introspect.enumerate_discharged_pointers = NULL; #else jemalloc_zone_introspect.enumerate_unavailable_without_blocks = NULL; #endif jemalloc_zone_introspect.reinit_lock = zone_reinit_lock; } static malloc_zone_t * zone_default_get(void) { malloc_zone_t **zones = NULL; unsigned int num_zones = 0; /* * On OSX 10.12, malloc_default_zone returns a special zone that is not * present in the list of registered zones. That zone uses a "lite zone" * if one is present (apparently enabled when malloc stack logging is * enabled), or the first registered zone otherwise. In practice this * means unless malloc stack logging is enabled, the first registered * zone is the default. So get the list of zones to get the first one, * instead of relying on malloc_default_zone. */ if (KERN_SUCCESS != malloc_get_all_zones(0, NULL, (vm_address_t**)&zones, &num_zones)) { /* * Reset the value in case the failure happened after it was * set. */ num_zones = 0; } if (num_zones) { return zones[0]; } return malloc_default_zone(); } /* As written, this function can only promote jemalloc_zone. */ static void zone_promote(void) { malloc_zone_t *zone; do { /* * Unregister and reregister the default zone. On OSX >= 10.6, * unregistering takes the last registered zone and places it * at the location of the specified zone. Unregistering the * default zone thus makes the last registered one the default. * On OSX < 10.6, unregistering shifts all registered zones. * The first registered zone then becomes the default. */ malloc_zone_unregister(default_zone); malloc_zone_register(default_zone); /* * On OSX 10.6, having the default purgeable zone appear before * the default zone makes some things crash because it thinks it * owns the default zone allocated pointers. We thus * unregister/re-register it in order to ensure it's always * after the default zone. On OSX < 10.6, there is no purgeable * zone, so this does nothing. On OSX >= 10.6, unregistering * replaces the purgeable zone with the last registered zone * above, i.e. the default zone. Registering it again then puts * it at the end, obviously after the default zone. */ if (purgeable_zone != NULL) { malloc_zone_unregister(purgeable_zone); malloc_zone_register(purgeable_zone); } zone = zone_default_get(); } while (zone != &jemalloc_zone); } JEMALLOC_ATTR(constructor) void zone_register(void) { /* * If something else replaced the system default zone allocator, don't * register jemalloc's. */ default_zone = zone_default_get(); if (!default_zone->zone_name || strcmp(default_zone->zone_name, "DefaultMallocZone") != 0) { return; } /* * The default purgeable zone is created lazily by OSX's libc. It uses * the default zone when it is created for "small" allocations * (< 15 KiB), but assumes the default zone is a scalable_zone. This * obviously fails when the default zone is the jemalloc zone, so * malloc_default_purgeable_zone() is called beforehand so that the * default purgeable zone is created when the default zone is still * a scalable_zone. As purgeable zones only exist on >= 10.6, we need * to check for the existence of malloc_default_purgeable_zone() at * run time. */ purgeable_zone = (malloc_default_purgeable_zone == NULL) ? NULL : malloc_default_purgeable_zone(); /* Register the custom zone. At this point it won't be the default. */ zone_init(); malloc_zone_register(&jemalloc_zone); /* Promote the custom zone to be default. */ zone_promote(); } redis-8.0.2/deps/jemalloc/test/000077500000000000000000000000001501533116600163305ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/analyze/000077500000000000000000000000001501533116600177735ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/analyze/prof_bias.c000066400000000000000000000034721501533116600221110ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * This is a helper utility, only meant to be run manually (and, for example, * doesn't check for failures, try to skip execution in non-prof modes, etc.). * It runs, allocates objects of two different sizes from the same stack trace, * and exits. * * The idea is that some human operator will run it like: * MALLOC_CONF="prof:true,prof_final:true" test/analyze/prof_bias * and manually inspect the results. * * The results should be: * jeprof --text test/analyze/prof_bias --inuse_space jeprof..0.f.heap: * around 1024 MB * jeprof --text test/analyze/prof_bias --inuse_objects jeprof..0.f.heap: * around 33554448 = 16 + 32 * 1024 * 1024 * * And, if prof_accum is on: * jeprof --text test/analyze/prof_bias --alloc_space jeprof..0.f.heap: * around 2048 MB * jeprof --text test/analyze/prof_bias --alloc_objects jeprof..0.f.heap: * around 67108896 = 2 * (16 + 32 * 1024 * 1024) */ static void mock_backtrace(void **vec, unsigned *len, unsigned max_len) { *len = 4; vec[0] = (void *)0x111; vec[1] = (void *)0x222; vec[2] = (void *)0x333; vec[3] = (void *)0x444; } static void do_allocs(size_t sz, size_t cnt, bool do_frees) { for (size_t i = 0; i < cnt; i++) { void *ptr = mallocx(sz, 0); assert_ptr_not_null(ptr, "Unexpected mallocx failure"); if (do_frees) { dallocx(ptr, 0); } } } int main(void) { size_t lg_prof_sample_local = 19; int err = mallctl("prof.reset", NULL, NULL, (void *)&lg_prof_sample_local, sizeof(lg_prof_sample_local)); assert(err == 0); prof_backtrace_hook_set(mock_backtrace); do_allocs(16, 32 * 1024 * 1024, /* do_frees */ true); do_allocs(32 * 1024* 1024, 16, /* do_frees */ true); do_allocs(16, 32 * 1024 * 1024, /* do_frees */ false); do_allocs(32 * 1024* 1024, 16, /* do_frees */ false); return 0; } redis-8.0.2/deps/jemalloc/test/analyze/rand.c000066400000000000000000000201671501533116600210710ustar00rootroot00000000000000#include "test/jemalloc_test.h" /******************************************************************************/ /* * General purpose tool for examining random number distributions. * * Input - * (a) a random number generator, and * (b) the buckets: * (1) number of buckets, * (2) width of each bucket, in log scale, * (3) expected mean and stddev of the count of random numbers in each * bucket, and * (c) number of iterations to invoke the generator. * * The program generates the specified amount of random numbers, and assess how * well they conform to the expectations: for each bucket, output - * (a) the (given) expected mean and stddev, * (b) the actual count and any interesting level of deviation: * (1) ~68% buckets should show no interesting deviation, meaning a * deviation less than stddev from the expectation; * (2) ~27% buckets should show '+' / '-', meaning a deviation in the range * of [stddev, 2 * stddev) from the expectation; * (3) ~4% buckets should show '++' / '--', meaning a deviation in the * range of [2 * stddev, 3 * stddev) from the expectation; and * (4) less than 0.3% buckets should show more than two '+'s / '-'s. * * Technical remarks: * (a) The generator is expected to output uint64_t numbers, so you might need * to define a wrapper. * (b) The buckets must be of equal width and the lowest bucket starts at * [0, 2^lg_bucket_width - 1). * (c) Any generated number >= n_bucket * 2^lg_bucket_width will be counted * towards the last bucket; the expected mean and stddev provided should * also reflect that. * (d) The number of iterations is advised to be determined so that the bucket * with the minimal expected proportion gets a sufficient count. */ static void fill(size_t a[], const size_t n, const size_t k) { for (size_t i = 0; i < n; ++i) { a[i] = k; } } static void collect_buckets(uint64_t (*gen)(void *), void *opaque, size_t buckets[], const size_t n_bucket, const size_t lg_bucket_width, const size_t n_iter) { for (size_t i = 0; i < n_iter; ++i) { uint64_t num = gen(opaque); uint64_t bucket_id = num >> lg_bucket_width; if (bucket_id >= n_bucket) { bucket_id = n_bucket - 1; } ++buckets[bucket_id]; } } static void print_buckets(const size_t buckets[], const size_t means[], const size_t stddevs[], const size_t n_bucket) { for (size_t i = 0; i < n_bucket; ++i) { malloc_printf("%zu:\tmean = %zu,\tstddev = %zu,\tbucket = %zu", i, means[i], stddevs[i], buckets[i]); /* Make sure there's no overflow. */ assert(buckets[i] + stddevs[i] >= stddevs[i]); assert(means[i] + stddevs[i] >= stddevs[i]); if (buckets[i] + stddevs[i] <= means[i]) { malloc_write(" "); for (size_t t = means[i] - buckets[i]; t >= stddevs[i]; t -= stddevs[i]) { malloc_write("-"); } } else if (buckets[i] >= means[i] + stddevs[i]) { malloc_write(" "); for (size_t t = buckets[i] - means[i]; t >= stddevs[i]; t -= stddevs[i]) { malloc_write("+"); } } malloc_write("\n"); } } static void bucket_analysis(uint64_t (*gen)(void *), void *opaque, size_t buckets[], const size_t means[], const size_t stddevs[], const size_t n_bucket, const size_t lg_bucket_width, const size_t n_iter) { for (size_t i = 1; i <= 3; ++i) { malloc_printf("round %zu\n", i); fill(buckets, n_bucket, 0); collect_buckets(gen, opaque, buckets, n_bucket, lg_bucket_width, n_iter); print_buckets(buckets, means, stddevs, n_bucket); } } /* (Recommended) minimal bucket mean. */ #define MIN_BUCKET_MEAN 10000 /******************************************************************************/ /* Uniform random number generator. */ typedef struct uniform_gen_arg_s uniform_gen_arg_t; struct uniform_gen_arg_s { uint64_t state; const unsigned lg_range; }; static uint64_t uniform_gen(void *opaque) { uniform_gen_arg_t *arg = (uniform_gen_arg_t *)opaque; return prng_lg_range_u64(&arg->state, arg->lg_range); } TEST_BEGIN(test_uniform) { #define LG_N_BUCKET 5 #define N_BUCKET (1 << LG_N_BUCKET) #define QUOTIENT_CEIL(n, d) (((n) - 1) / (d) + 1) const unsigned lg_range_test = 25; /* * Mathematical tricks to guarantee that both mean and stddev are * integers, and that the minimal bucket mean is at least * MIN_BUCKET_MEAN. */ const size_t q = 1 << QUOTIENT_CEIL(LG_CEIL(QUOTIENT_CEIL( MIN_BUCKET_MEAN, N_BUCKET * (N_BUCKET - 1))), 2); const size_t stddev = (N_BUCKET - 1) * q; const size_t mean = N_BUCKET * stddev * q; const size_t n_iter = N_BUCKET * mean; size_t means[N_BUCKET]; fill(means, N_BUCKET, mean); size_t stddevs[N_BUCKET]; fill(stddevs, N_BUCKET, stddev); uniform_gen_arg_t arg = {(uint64_t)(uintptr_t)&lg_range_test, lg_range_test}; size_t buckets[N_BUCKET]; assert_zu_ge(lg_range_test, LG_N_BUCKET, ""); const size_t lg_bucket_width = lg_range_test - LG_N_BUCKET; bucket_analysis(uniform_gen, &arg, buckets, means, stddevs, N_BUCKET, lg_bucket_width, n_iter); #undef LG_N_BUCKET #undef N_BUCKET #undef QUOTIENT_CEIL } TEST_END /******************************************************************************/ /* Geometric random number generator; compiled only when prof is on. */ #ifdef JEMALLOC_PROF /* * Fills geometric proportions and returns the minimal proportion. See * comments in test_prof_sample for explanations for n_divide. */ static double fill_geometric_proportions(double proportions[], const size_t n_bucket, const size_t n_divide) { assert(n_bucket > 0); assert(n_divide > 0); double x = 1.; for (size_t i = 0; i < n_bucket; ++i) { if (i == n_bucket - 1) { proportions[i] = x; } else { double y = x * exp(-1. / n_divide); proportions[i] = x - y; x = y; } } /* * The minimal proportion is the smaller one of the last two * proportions for geometric distribution. */ double min_proportion = proportions[n_bucket - 1]; if (n_bucket >= 2 && proportions[n_bucket - 2] < min_proportion) { min_proportion = proportions[n_bucket - 2]; } return min_proportion; } static size_t round_to_nearest(const double x) { return (size_t)(x + .5); } static void fill_references(size_t means[], size_t stddevs[], const double proportions[], const size_t n_bucket, const size_t n_iter) { for (size_t i = 0; i < n_bucket; ++i) { double x = n_iter * proportions[i]; means[i] = round_to_nearest(x); stddevs[i] = round_to_nearest(sqrt(x * (1. - proportions[i]))); } } static uint64_t prof_sample_gen(void *opaque) { return prof_sample_new_event_wait((tsd_t *)opaque) - 1; } #endif /* JEMALLOC_PROF */ TEST_BEGIN(test_prof_sample) { test_skip_if(!config_prof); #ifdef JEMALLOC_PROF /* Number of divisions within [0, mean). */ #define LG_N_DIVIDE 3 #define N_DIVIDE (1 << LG_N_DIVIDE) /* Coverage of buckets in terms of multiples of mean. */ #define LG_N_MULTIPLY 2 #define N_GEO_BUCKET (N_DIVIDE << LG_N_MULTIPLY) test_skip_if(!opt_prof); size_t lg_prof_sample_test = 25; size_t lg_prof_sample_orig = lg_prof_sample; assert_d_eq(mallctl("prof.reset", NULL, NULL, &lg_prof_sample_test, sizeof(size_t)), 0, ""); malloc_printf("lg_prof_sample = %zu\n", lg_prof_sample_test); double proportions[N_GEO_BUCKET + 1]; const double min_proportion = fill_geometric_proportions(proportions, N_GEO_BUCKET + 1, N_DIVIDE); const size_t n_iter = round_to_nearest(MIN_BUCKET_MEAN / min_proportion); size_t means[N_GEO_BUCKET + 1]; size_t stddevs[N_GEO_BUCKET + 1]; fill_references(means, stddevs, proportions, N_GEO_BUCKET + 1, n_iter); tsd_t *tsd = tsd_fetch(); assert_ptr_not_null(tsd, ""); size_t buckets[N_GEO_BUCKET + 1]; assert_zu_ge(lg_prof_sample, LG_N_DIVIDE, ""); const size_t lg_bucket_width = lg_prof_sample - LG_N_DIVIDE; bucket_analysis(prof_sample_gen, tsd, buckets, means, stddevs, N_GEO_BUCKET + 1, lg_bucket_width, n_iter); assert_d_eq(mallctl("prof.reset", NULL, NULL, &lg_prof_sample_orig, sizeof(size_t)), 0, ""); #undef LG_N_DIVIDE #undef N_DIVIDE #undef LG_N_MULTIPLY #undef N_GEO_BUCKET #endif /* JEMALLOC_PROF */ } TEST_END /******************************************************************************/ int main(void) { return test_no_reentrancy( test_uniform, test_prof_sample); } redis-8.0.2/deps/jemalloc/test/analyze/sizes.c000066400000000000000000000020661501533116600213000ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include /* * Print the sizes of various important core data structures. OK, I guess this * isn't really a "stress" test, but it does give useful information about * low-level performance characteristics, as the other things in this directory * do. */ static void do_print(const char *name, size_t sz_bytes) { const char *sizes[] = {"bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"}; size_t sizes_max = sizeof(sizes)/sizeof(sizes[0]); size_t ind = 0; double sz = sz_bytes; while (sz >= 1024 && ind < sizes_max - 1) { sz /= 1024; ind++; } if (ind == 0) { printf("%-20s: %zu bytes\n", name, sz_bytes); } else { printf("%-20s: %f %s\n", name, sz, sizes[ind]); } } int main() { #define P(type) \ do_print(#type, sizeof(type)) P(arena_t); P(arena_stats_t); P(base_t); P(decay_t); P(edata_t); P(ecache_t); P(eset_t); P(malloc_mutex_t); P(prof_tctx_t); P(prof_gctx_t); P(prof_tdata_t); P(rtree_t); P(rtree_leaf_elm_t); P(slab_data_t); P(tcache_t); P(tcache_slow_t); P(tsd_t); #undef P } redis-8.0.2/deps/jemalloc/test/include/000077500000000000000000000000001501533116600177535ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/include/test/000077500000000000000000000000001501533116600207325ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/include/test/SFMT-alti.h000066400000000000000000000134271501533116600226120ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ /** * @file SFMT-alti.h * * @brief SIMD oriented Fast Mersenne Twister(SFMT) * pseudorandom number generator * * @author Mutsuo Saito (Hiroshima University) * @author Makoto Matsumoto (Hiroshima University) * * Copyright (C) 2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * The new BSD License is applied to this software. * see LICENSE.txt */ #ifndef SFMT_ALTI_H #define SFMT_ALTI_H /** * This function represents the recursion formula in AltiVec and BIG ENDIAN. * @param a a 128-bit part of the interal state array * @param b a 128-bit part of the interal state array * @param c a 128-bit part of the interal state array * @param d a 128-bit part of the interal state array * @return output */ JEMALLOC_ALWAYS_INLINE vector unsigned int vec_recursion(vector unsigned int a, vector unsigned int b, vector unsigned int c, vector unsigned int d) { const vector unsigned int sl1 = ALTI_SL1; const vector unsigned int sr1 = ALTI_SR1; #ifdef ONLY64 const vector unsigned int mask = ALTI_MSK64; const vector unsigned char perm_sl = ALTI_SL2_PERM64; const vector unsigned char perm_sr = ALTI_SR2_PERM64; #else const vector unsigned int mask = ALTI_MSK; const vector unsigned char perm_sl = ALTI_SL2_PERM; const vector unsigned char perm_sr = ALTI_SR2_PERM; #endif vector unsigned int v, w, x, y, z; x = vec_perm(a, (vector unsigned int)perm_sl, perm_sl); v = a; y = vec_sr(b, sr1); z = vec_perm(c, (vector unsigned int)perm_sr, perm_sr); w = vec_sl(d, sl1); z = vec_xor(z, w); y = vec_and(y, mask); v = vec_xor(v, x); z = vec_xor(z, y); z = vec_xor(z, v); return z; } /** * This function fills the internal state array with pseudorandom * integers. */ static inline void gen_rand_all(sfmt_t *ctx) { int i; vector unsigned int r, r1, r2; r1 = ctx->sfmt[N - 2].s; r2 = ctx->sfmt[N - 1].s; for (i = 0; i < N - POS1; i++) { r = vec_recursion(ctx->sfmt[i].s, ctx->sfmt[i + POS1].s, r1, r2); ctx->sfmt[i].s = r; r1 = r2; r2 = r; } for (; i < N; i++) { r = vec_recursion(ctx->sfmt[i].s, ctx->sfmt[i + POS1 - N].s, r1, r2); ctx->sfmt[i].s = r; r1 = r2; r2 = r; } } /** * This function fills the user-specified array with pseudorandom * integers. * * @param array an 128-bit array to be filled by pseudorandom numbers. * @param size number of 128-bit pesudorandom numbers to be generated. */ static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) { int i, j; vector unsigned int r, r1, r2; r1 = ctx->sfmt[N - 2].s; r2 = ctx->sfmt[N - 1].s; for (i = 0; i < N - POS1; i++) { r = vec_recursion(ctx->sfmt[i].s, ctx->sfmt[i + POS1].s, r1, r2); array[i].s = r; r1 = r2; r2 = r; } for (; i < N; i++) { r = vec_recursion(ctx->sfmt[i].s, array[i + POS1 - N].s, r1, r2); array[i].s = r; r1 = r2; r2 = r; } /* main loop */ for (; i < size - N; i++) { r = vec_recursion(array[i - N].s, array[i + POS1 - N].s, r1, r2); array[i].s = r; r1 = r2; r2 = r; } for (j = 0; j < 2 * N - size; j++) { ctx->sfmt[j].s = array[j + size - N].s; } for (; i < size; i++) { r = vec_recursion(array[i - N].s, array[i + POS1 - N].s, r1, r2); array[i].s = r; ctx->sfmt[j++].s = r; r1 = r2; r2 = r; } } #ifndef ONLY64 #if defined(__APPLE__) #define ALTI_SWAP (vector unsigned char) \ (4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11) #else #define ALTI_SWAP {4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11} #endif /** * This function swaps high and low 32-bit of 64-bit integers in user * specified array. * * @param array an 128-bit array to be swaped. * @param size size of 128-bit array. */ static inline void swap(w128_t *array, int size) { int i; const vector unsigned char perm = ALTI_SWAP; for (i = 0; i < size; i++) { array[i].s = vec_perm(array[i].s, (vector unsigned int)perm, perm); } } #endif #endif redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params.h000066400000000000000000000102761501533116600231430ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS_H #define SFMT_PARAMS_H #if !defined(MEXP) #ifdef __GNUC__ #warning "MEXP is not defined. I assume MEXP is 19937." #endif #define MEXP 19937 #endif /*----------------- BASIC DEFINITIONS -----------------*/ /** Mersenne Exponent. The period of the sequence * is a multiple of 2^MEXP-1. * #define MEXP 19937 */ /** SFMT generator has an internal state array of 128-bit integers, * and N is its size. */ #define N (MEXP / 128 + 1) /** N32 is the size of internal state array when regarded as an array * of 32-bit integers.*/ #define N32 (N * 4) /** N64 is the size of internal state array when regarded as an array * of 64-bit integers.*/ #define N64 (N * 2) /*---------------------- the parameters of SFMT following definitions are in paramsXXXX.h file. ----------------------*/ /** the pick up position of the array. #define POS1 122 */ /** the parameter of shift left as four 32-bit registers. #define SL1 18 */ /** the parameter of shift left as one 128-bit register. * The 128-bit integer is shifted by (SL2 * 8) bits. #define SL2 1 */ /** the parameter of shift right as four 32-bit registers. #define SR1 11 */ /** the parameter of shift right as one 128-bit register. * The 128-bit integer is shifted by (SL2 * 8) bits. #define SR2 1 */ /** A bitmask, used in the recursion. These parameters are introduced * to break symmetry of SIMD. #define MSK1 0xdfffffefU #define MSK2 0xddfecb7fU #define MSK3 0xbffaffffU #define MSK4 0xbffffff6U */ /** These definitions are part of a 128-bit period certification vector. #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0x00000000U #define PARITY4 0xc98e126aU */ #if MEXP == 607 #include "test/SFMT-params607.h" #elif MEXP == 1279 #include "test/SFMT-params1279.h" #elif MEXP == 2281 #include "test/SFMT-params2281.h" #elif MEXP == 4253 #include "test/SFMT-params4253.h" #elif MEXP == 11213 #include "test/SFMT-params11213.h" #elif MEXP == 19937 #include "test/SFMT-params19937.h" #elif MEXP == 44497 #include "test/SFMT-params44497.h" #elif MEXP == 86243 #include "test/SFMT-params86243.h" #elif MEXP == 132049 #include "test/SFMT-params132049.h" #elif MEXP == 216091 #include "test/SFMT-params216091.h" #else #ifdef __GNUC__ #error "MEXP is not valid." #undef MEXP #else #undef MEXP #endif #endif #endif /* SFMT_PARAMS_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params11213.h000066400000000000000000000067561501533116600235430ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS11213_H #define SFMT_PARAMS11213_H #define POS1 68 #define SL1 14 #define SL2 3 #define SR1 7 #define SR2 3 #define MSK1 0xeffff7fbU #define MSK2 0xffffffefU #define MSK3 0xdfdfbfffU #define MSK4 0x7fffdbfdU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0xe8148000U #define PARITY4 0xd0c7afa3U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10) #define ALTI_SL2_PERM64 \ (vector unsigned char)(3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2) #define ALTI_SR2_PERM \ (vector unsigned char)(5,6,7,0,9,10,11,4,13,14,15,8,19,19,19,12) #define ALTI_SR2_PERM64 \ (vector unsigned char)(13,14,15,0,1,2,3,4,19,19,19,8,9,10,11,12) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10} #define ALTI_SL2_PERM64 {3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2} #define ALTI_SR2_PERM {5,6,7,0,9,10,11,4,13,14,15,8,19,19,19,12} #define ALTI_SR2_PERM64 {13,14,15,0,1,2,3,4,19,19,19,8,9,10,11,12} #endif /* For OSX */ #define IDSTR "SFMT-11213:68-14-3-7-3:effff7fb-ffffffef-dfdfbfff-7fffdbfd" #endif /* SFMT_PARAMS11213_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params1279.h000066400000000000000000000067401501533116600234670ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS1279_H #define SFMT_PARAMS1279_H #define POS1 7 #define SL1 14 #define SL2 3 #define SR1 5 #define SR2 1 #define MSK1 0xf7fefffdU #define MSK2 0x7fefcfffU #define MSK3 0xaff3ef3fU #define MSK4 0xb5ffff7fU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0x00000000U #define PARITY4 0x20000000U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10) #define ALTI_SL2_PERM64 \ (vector unsigned char)(3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10} #define ALTI_SL2_PERM64 {3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-1279:7-14-3-5-1:f7fefffd-7fefcfff-aff3ef3f-b5ffff7f" #endif /* SFMT_PARAMS1279_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params132049.h000066400000000000000000000067541501533116600236340ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS132049_H #define SFMT_PARAMS132049_H #define POS1 110 #define SL1 19 #define SL2 1 #define SR1 21 #define SR2 1 #define MSK1 0xffffbb5fU #define MSK2 0xfb6ebf95U #define MSK3 0xfffefffaU #define MSK4 0xcff77fffU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0xcb520000U #define PARITY4 0xc7e91c7dU /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8) #define ALTI_SL2_PERM64 \ (vector unsigned char)(1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8} #define ALTI_SL2_PERM64 {1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-132049:110-19-1-21-1:ffffbb5f-fb6ebf95-fffefffa-cff77fff" #endif /* SFMT_PARAMS132049_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params19937.h000066400000000000000000000067501501533116600235620ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS19937_H #define SFMT_PARAMS19937_H #define POS1 122 #define SL1 18 #define SL2 1 #define SR1 11 #define SR2 1 #define MSK1 0xdfffffefU #define MSK2 0xddfecb7fU #define MSK3 0xbffaffffU #define MSK4 0xbffffff6U #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0x00000000U #define PARITY4 0x13c9e684U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8) #define ALTI_SL2_PERM64 \ (vector unsigned char)(1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8} #define ALTI_SL2_PERM64 {1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-19937:122-18-1-11-1:dfffffef-ddfecb7f-bffaffff-bffffff6" #endif /* SFMT_PARAMS19937_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params216091.h000066400000000000000000000067561501533116600236360ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS216091_H #define SFMT_PARAMS216091_H #define POS1 627 #define SL1 11 #define SL2 3 #define SR1 10 #define SR2 1 #define MSK1 0xbff7bff7U #define MSK2 0xbfffffffU #define MSK3 0xbffffa7fU #define MSK4 0xffddfbfbU #define PARITY1 0xf8000001U #define PARITY2 0x89e80709U #define PARITY3 0x3bd2b64bU #define PARITY4 0x0c64b1e4U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10) #define ALTI_SL2_PERM64 \ (vector unsigned char)(3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10} #define ALTI_SL2_PERM64 {3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-216091:627-11-3-10-1:bff7bff7-bfffffff-bffffa7f-ffddfbfb" #endif /* SFMT_PARAMS216091_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params2281.h000066400000000000000000000067401501533116600234610ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS2281_H #define SFMT_PARAMS2281_H #define POS1 12 #define SL1 19 #define SL2 1 #define SR1 5 #define SR2 1 #define MSK1 0xbff7ffbfU #define MSK2 0xfdfffffeU #define MSK3 0xf7ffef7fU #define MSK4 0xf2f7cbbfU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0x00000000U #define PARITY4 0x41dfa600U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8) #define ALTI_SL2_PERM64 \ (vector unsigned char)(1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8} #define ALTI_SL2_PERM64 {1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-2281:12-19-1-5-1:bff7ffbf-fdfffffe-f7ffef7f-f2f7cbbf" #endif /* SFMT_PARAMS2281_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params4253.h000066400000000000000000000067401501533116600234620ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS4253_H #define SFMT_PARAMS4253_H #define POS1 17 #define SL1 20 #define SL2 1 #define SR1 7 #define SR2 1 #define MSK1 0x9f7bffffU #define MSK2 0x9fffff5fU #define MSK3 0x3efffffbU #define MSK4 0xfffff7bbU #define PARITY1 0xa8000001U #define PARITY2 0xaf5390a3U #define PARITY3 0xb740b3f8U #define PARITY4 0x6c11486dU /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8) #define ALTI_SL2_PERM64 \ (vector unsigned char)(1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8} #define ALTI_SL2_PERM64 {1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-4253:17-20-1-7-1:9f7bffff-9fffff5f-3efffffb-fffff7bb" #endif /* SFMT_PARAMS4253_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params44497.h000066400000000000000000000067561501533116600235670ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS44497_H #define SFMT_PARAMS44497_H #define POS1 330 #define SL1 5 #define SL2 3 #define SR1 9 #define SR2 3 #define MSK1 0xeffffffbU #define MSK2 0xdfbebfffU #define MSK3 0xbfbf7befU #define MSK4 0x9ffd7bffU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0xa3ac4000U #define PARITY4 0xecc1327aU /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10) #define ALTI_SL2_PERM64 \ (vector unsigned char)(3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2) #define ALTI_SR2_PERM \ (vector unsigned char)(5,6,7,0,9,10,11,4,13,14,15,8,19,19,19,12) #define ALTI_SR2_PERM64 \ (vector unsigned char)(13,14,15,0,1,2,3,4,19,19,19,8,9,10,11,12) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10} #define ALTI_SL2_PERM64 {3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2} #define ALTI_SR2_PERM {5,6,7,0,9,10,11,4,13,14,15,8,19,19,19,12} #define ALTI_SR2_PERM64 {13,14,15,0,1,2,3,4,19,19,19,8,9,10,11,12} #endif /* For OSX */ #define IDSTR "SFMT-44497:330-5-3-9-3:effffffb-dfbebfff-bfbf7bef-9ffd7bff" #endif /* SFMT_PARAMS44497_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params607.h000066400000000000000000000067461501533116600234070ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS607_H #define SFMT_PARAMS607_H #define POS1 2 #define SL1 15 #define SL2 3 #define SR1 13 #define SR2 3 #define MSK1 0xfdff37ffU #define MSK2 0xef7f3f7dU #define MSK3 0xff777b7dU #define MSK4 0x7ff7fb2fU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0x00000000U #define PARITY4 0x5986f054U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10) #define ALTI_SL2_PERM64 \ (vector unsigned char)(3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2) #define ALTI_SR2_PERM \ (vector unsigned char)(5,6,7,0,9,10,11,4,13,14,15,8,19,19,19,12) #define ALTI_SR2_PERM64 \ (vector unsigned char)(13,14,15,0,1,2,3,4,19,19,19,8,9,10,11,12) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {3,21,21,21,7,0,1,2,11,4,5,6,15,8,9,10} #define ALTI_SL2_PERM64 {3,4,5,6,7,29,29,29,11,12,13,14,15,0,1,2} #define ALTI_SR2_PERM {5,6,7,0,9,10,11,4,13,14,15,8,19,19,19,12} #define ALTI_SR2_PERM64 {13,14,15,0,1,2,3,4,19,19,19,8,9,10,11,12} #endif /* For OSX */ #define IDSTR "SFMT-607:2-15-3-13-3:fdff37ff-ef7f3f7d-ff777b7d-7ff7fb2f" #endif /* SFMT_PARAMS607_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-params86243.h000066400000000000000000000067541501533116600235600ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #ifndef SFMT_PARAMS86243_H #define SFMT_PARAMS86243_H #define POS1 366 #define SL1 6 #define SL2 7 #define SR1 19 #define SR2 1 #define MSK1 0xfdbffbffU #define MSK2 0xbff7ff3fU #define MSK3 0xfd77efffU #define MSK4 0xbf9ff3ffU #define PARITY1 0x00000001U #define PARITY2 0x00000000U #define PARITY3 0x00000000U #define PARITY4 0xe9528d85U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) #define ALTI_MSK64 \ (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) #define ALTI_SL2_PERM \ (vector unsigned char)(25,25,25,25,3,25,25,25,7,0,1,2,11,4,5,6) #define ALTI_SL2_PERM64 \ (vector unsigned char)(7,25,25,25,25,25,25,25,15,0,1,2,3,4,5,6) #define ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) #define ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ #define ALTI_SL1 {SL1, SL1, SL1, SL1} #define ALTI_SR1 {SR1, SR1, SR1, SR1} #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} #define ALTI_SL2_PERM {25,25,25,25,3,25,25,25,7,0,1,2,11,4,5,6} #define ALTI_SL2_PERM64 {7,25,25,25,25,25,25,25,15,0,1,2,3,4,5,6} #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ #define IDSTR "SFMT-86243:366-6-7-19-1:fdbffbff-bff7ff3f-fd77efff-bf9ff3ff" #endif /* SFMT_PARAMS86243_H */ redis-8.0.2/deps/jemalloc/test/include/test/SFMT-sse2.h000066400000000000000000000121231501533116600225250ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ /** * @file SFMT-sse2.h * @brief SIMD oriented Fast Mersenne Twister(SFMT) for Intel SSE2 * * @author Mutsuo Saito (Hiroshima University) * @author Makoto Matsumoto (Hiroshima University) * * @note We assume LITTLE ENDIAN in this file * * Copyright (C) 2006, 2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * The new BSD License is applied to this software, see LICENSE.txt */ #ifndef SFMT_SSE2_H #define SFMT_SSE2_H /** * This function represents the recursion formula. * @param a a 128-bit part of the interal state array * @param b a 128-bit part of the interal state array * @param c a 128-bit part of the interal state array * @param d a 128-bit part of the interal state array * @param mask 128-bit mask * @return output */ JEMALLOC_ALWAYS_INLINE __m128i mm_recursion(__m128i *a, __m128i *b, __m128i c, __m128i d, __m128i mask) { __m128i v, x, y, z; x = _mm_load_si128(a); y = _mm_srli_epi32(*b, SR1); z = _mm_srli_si128(c, SR2); v = _mm_slli_epi32(d, SL1); z = _mm_xor_si128(z, x); z = _mm_xor_si128(z, v); x = _mm_slli_si128(x, SL2); y = _mm_and_si128(y, mask); z = _mm_xor_si128(z, x); z = _mm_xor_si128(z, y); return z; } /** * This function fills the internal state array with pseudorandom * integers. */ static inline void gen_rand_all(sfmt_t *ctx) { int i; __m128i r, r1, r2, mask; mask = _mm_set_epi32(MSK4, MSK3, MSK2, MSK1); r1 = _mm_load_si128(&ctx->sfmt[N - 2].si); r2 = _mm_load_si128(&ctx->sfmt[N - 1].si); for (i = 0; i < N - POS1; i++) { r = mm_recursion(&ctx->sfmt[i].si, &ctx->sfmt[i + POS1].si, r1, r2, mask); _mm_store_si128(&ctx->sfmt[i].si, r); r1 = r2; r2 = r; } for (; i < N; i++) { r = mm_recursion(&ctx->sfmt[i].si, &ctx->sfmt[i + POS1 - N].si, r1, r2, mask); _mm_store_si128(&ctx->sfmt[i].si, r); r1 = r2; r2 = r; } } /** * This function fills the user-specified array with pseudorandom * integers. * * @param array an 128-bit array to be filled by pseudorandom numbers. * @param size number of 128-bit pesudorandom numbers to be generated. */ static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) { int i, j; __m128i r, r1, r2, mask; mask = _mm_set_epi32(MSK4, MSK3, MSK2, MSK1); r1 = _mm_load_si128(&ctx->sfmt[N - 2].si); r2 = _mm_load_si128(&ctx->sfmt[N - 1].si); for (i = 0; i < N - POS1; i++) { r = mm_recursion(&ctx->sfmt[i].si, &ctx->sfmt[i + POS1].si, r1, r2, mask); _mm_store_si128(&array[i].si, r); r1 = r2; r2 = r; } for (; i < N; i++) { r = mm_recursion(&ctx->sfmt[i].si, &array[i + POS1 - N].si, r1, r2, mask); _mm_store_si128(&array[i].si, r); r1 = r2; r2 = r; } /* main loop */ for (; i < size - N; i++) { r = mm_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask); _mm_store_si128(&array[i].si, r); r1 = r2; r2 = r; } for (j = 0; j < 2 * N - size; j++) { r = _mm_load_si128(&array[j + size - N].si); _mm_store_si128(&ctx->sfmt[j].si, r); } for (; i < size; i++) { r = mm_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask); _mm_store_si128(&array[i].si, r); _mm_store_si128(&ctx->sfmt[j++].si, r); r1 = r2; r2 = r; } } #endif redis-8.0.2/deps/jemalloc/test/include/test/SFMT.h000066400000000000000000000123231501533116600216550ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ /** * @file SFMT.h * * @brief SIMD oriented Fast Mersenne Twister(SFMT) pseudorandom * number generator * * @author Mutsuo Saito (Hiroshima University) * @author Makoto Matsumoto (Hiroshima University) * * Copyright (C) 2006, 2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * The new BSD License is applied to this software. * see LICENSE.txt * * @note We assume that your system has inttypes.h. If your system * doesn't have inttypes.h, you have to typedef uint32_t and uint64_t, * and you have to define PRIu64 and PRIx64 in this file as follows: * @verbatim typedef unsigned int uint32_t typedef unsigned long long uint64_t #define PRIu64 "llu" #define PRIx64 "llx" @endverbatim * uint32_t must be exactly 32-bit unsigned integer type (no more, no * less), and uint64_t must be exactly 64-bit unsigned integer type. * PRIu64 and PRIx64 are used for printf function to print 64-bit * unsigned int and 64-bit unsigned int in hexadecimal format. */ #ifndef SFMT_H #define SFMT_H typedef struct sfmt_s sfmt_t; uint32_t gen_rand32(sfmt_t *ctx); uint32_t gen_rand32_range(sfmt_t *ctx, uint32_t limit); uint64_t gen_rand64(sfmt_t *ctx); uint64_t gen_rand64_range(sfmt_t *ctx, uint64_t limit); void fill_array32(sfmt_t *ctx, uint32_t *array, int size); void fill_array64(sfmt_t *ctx, uint64_t *array, int size); sfmt_t *init_gen_rand(uint32_t seed); sfmt_t *init_by_array(uint32_t *init_key, int key_length); void fini_gen_rand(sfmt_t *ctx); const char *get_idstring(void); int get_min_array_size32(void); int get_min_array_size64(void); /* These real versions are due to Isaku Wada */ /** generates a random number on [0,1]-real-interval */ static inline double to_real1(uint32_t v) { return v * (1.0/4294967295.0); /* divided by 2^32-1 */ } /** generates a random number on [0,1]-real-interval */ static inline double genrand_real1(sfmt_t *ctx) { return to_real1(gen_rand32(ctx)); } /** generates a random number on [0,1)-real-interval */ static inline double to_real2(uint32_t v) { return v * (1.0/4294967296.0); /* divided by 2^32 */ } /** generates a random number on [0,1)-real-interval */ static inline double genrand_real2(sfmt_t *ctx) { return to_real2(gen_rand32(ctx)); } /** generates a random number on (0,1)-real-interval */ static inline double to_real3(uint32_t v) { return (((double)v) + 0.5)*(1.0/4294967296.0); /* divided by 2^32 */ } /** generates a random number on (0,1)-real-interval */ static inline double genrand_real3(sfmt_t *ctx) { return to_real3(gen_rand32(ctx)); } /** These real versions are due to Isaku Wada */ /** generates a random number on [0,1) with 53-bit resolution*/ static inline double to_res53(uint64_t v) { return v * (1.0/18446744073709551616.0L); } /** generates a random number on [0,1) with 53-bit resolution from two * 32 bit integers */ static inline double to_res53_mix(uint32_t x, uint32_t y) { return to_res53(x | ((uint64_t)y << 32)); } /** generates a random number on [0,1) with 53-bit resolution */ static inline double genrand_res53(sfmt_t *ctx) { return to_res53(gen_rand64(ctx)); } /** generates a random number on [0,1) with 53-bit resolution using 32bit integer. */ static inline double genrand_res53_mix(sfmt_t *ctx) { uint32_t x, y; x = gen_rand32(ctx); y = gen_rand32(ctx); return to_res53_mix(x, y); } #endif redis-8.0.2/deps/jemalloc/test/include/test/arena_util.h000066400000000000000000000114151501533116600232300ustar00rootroot00000000000000static inline unsigned do_arena_create(ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms) { unsigned arena_ind; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); size_t mib[3]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.dirty_decay_ms", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&dirty_decay_ms, sizeof(dirty_decay_ms)), 0, "Unexpected mallctlbymib() failure"); expect_d_eq(mallctlnametomib("arena.0.muzzy_decay_ms", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&muzzy_decay_ms, sizeof(muzzy_decay_ms)), 0, "Unexpected mallctlbymib() failure"); return arena_ind; } static inline void do_arena_destroy(unsigned arena_ind) { /* * For convenience, flush tcache in case there are cached items. * However not assert success since the tcache may be disabled. */ mallctl("thread.tcache.flush", NULL, NULL, NULL, 0); size_t mib[3]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } static inline void do_epoch(void) { uint64_t epoch = 1; expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); } static inline void do_purge(unsigned arena_ind) { size_t mib[3]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.purge", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } static inline void do_decay(unsigned arena_ind) { size_t mib[3]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.decay", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } static inline uint64_t get_arena_npurge_impl(const char *mibname, unsigned arena_ind) { size_t mib[4]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib(mibname, mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[2] = (size_t)arena_ind; uint64_t npurge = 0; size_t sz = sizeof(npurge); expect_d_eq(mallctlbymib(mib, miblen, (void *)&npurge, &sz, NULL, 0), config_stats ? 0 : ENOENT, "Unexpected mallctlbymib() failure"); return npurge; } static inline uint64_t get_arena_dirty_npurge(unsigned arena_ind) { do_epoch(); return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind); } static inline uint64_t get_arena_dirty_purged(unsigned arena_ind) { do_epoch(); return get_arena_npurge_impl("stats.arenas.0.dirty_purged", arena_ind); } static inline uint64_t get_arena_muzzy_npurge(unsigned arena_ind) { do_epoch(); return get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind); } static inline uint64_t get_arena_npurge(unsigned arena_ind) { do_epoch(); return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind) + get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind); } static inline size_t get_arena_pdirty(unsigned arena_ind) { do_epoch(); size_t mib[4]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("stats.arenas.0.pdirty", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[2] = (size_t)arena_ind; size_t pdirty; size_t sz = sizeof(pdirty); expect_d_eq(mallctlbymib(mib, miblen, (void *)&pdirty, &sz, NULL, 0), 0, "Unexpected mallctlbymib() failure"); return pdirty; } static inline size_t get_arena_pmuzzy(unsigned arena_ind) { do_epoch(); size_t mib[4]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("stats.arenas.0.pmuzzy", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[2] = (size_t)arena_ind; size_t pmuzzy; size_t sz = sizeof(pmuzzy); expect_d_eq(mallctlbymib(mib, miblen, (void *)&pmuzzy, &sz, NULL, 0), 0, "Unexpected mallctlbymib() failure"); return pmuzzy; } static inline void * do_mallocx(size_t size, int flags) { void *p = mallocx(size, flags); expect_ptr_not_null(p, "Unexpected mallocx() failure"); return p; } static inline void generate_dirty(unsigned arena_ind, size_t size) { int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; void *p = do_mallocx(size, flags); dallocx(p, flags); } redis-8.0.2/deps/jemalloc/test/include/test/bench.h000066400000000000000000000031721501533116600221650ustar00rootroot00000000000000static inline void time_func(timedelta_t *timer, uint64_t nwarmup, uint64_t niter, void (*func)(void)) { uint64_t i; for (i = 0; i < nwarmup; i++) { func(); } timer_start(timer); for (i = 0; i < niter; i++) { func(); } timer_stop(timer); } #define FMT_NSECS_BUF_SIZE 100 /* Print nanoseconds / iter into the buffer "buf". */ static inline void fmt_nsecs(uint64_t usec, uint64_t iters, char *buf) { uint64_t nsec = usec * 1000; /* We'll display 3 digits after the decimal point. */ uint64_t nsec1000 = nsec * 1000; uint64_t nsecs_per_iter1000 = nsec1000 / iters; uint64_t intpart = nsecs_per_iter1000 / 1000; uint64_t fracpart = nsecs_per_iter1000 % 1000; malloc_snprintf(buf, FMT_NSECS_BUF_SIZE, "%"FMTu64".%03"FMTu64, intpart, fracpart); } static inline void compare_funcs(uint64_t nwarmup, uint64_t niter, const char *name_a, void (*func_a), const char *name_b, void (*func_b)) { timedelta_t timer_a, timer_b; char ratio_buf[6]; void *p; p = mallocx(1, 0); if (p == NULL) { test_fail("Unexpected mallocx() failure"); return; } time_func(&timer_a, nwarmup, niter, func_a); time_func(&timer_b, nwarmup, niter, func_b); uint64_t usec_a = timer_usec(&timer_a); char buf_a[FMT_NSECS_BUF_SIZE]; fmt_nsecs(usec_a, niter, buf_a); uint64_t usec_b = timer_usec(&timer_b); char buf_b[FMT_NSECS_BUF_SIZE]; fmt_nsecs(usec_b, niter, buf_b); timer_ratio(&timer_a, &timer_b, ratio_buf, sizeof(ratio_buf)); malloc_printf("%"FMTu64" iterations, %s=%"FMTu64"us (%s ns/iter), " "%s=%"FMTu64"us (%s ns/iter), ratio=1:%s\n", niter, name_a, usec_a, buf_a, name_b, usec_b, buf_b, ratio_buf); dallocx(p, 0); } redis-8.0.2/deps/jemalloc/test/include/test/bgthd.h000066400000000000000000000006461501533116600222010ustar00rootroot00000000000000/* * Shared utility for checking if background_thread is enabled, which affects * the purging behavior and assumptions in some tests. */ static inline bool is_background_thread_enabled(void) { bool enabled; size_t sz = sizeof(bool); int ret = mallctl("background_thread", (void *)&enabled, &sz, NULL,0); if (ret == ENOENT) { return false; } assert_d_eq(ret, 0, "Unexpected mallctl error"); return enabled; } redis-8.0.2/deps/jemalloc/test/include/test/btalloc.h000066400000000000000000000014561501533116600225310ustar00rootroot00000000000000/* btalloc() provides a mechanism for allocating via permuted backtraces. */ void *btalloc(size_t size, unsigned bits); #define btalloc_n_proto(n) \ void *btalloc_##n(size_t size, unsigned bits); btalloc_n_proto(0) btalloc_n_proto(1) #define btalloc_n_gen(n) \ void * \ btalloc_##n(size_t size, unsigned bits) { \ void *p; \ \ if (bits == 0) { \ p = mallocx(size, 0); \ } else { \ switch (bits & 0x1U) { \ case 0: \ p = (btalloc_0(size, bits >> 1)); \ break; \ case 1: \ p = (btalloc_1(size, bits >> 1)); \ break; \ default: not_reached(); \ } \ } \ /* Intentionally sabotage tail call optimization. */ \ expect_ptr_not_null(p, "Unexpected mallocx() failure"); \ return p; \ } redis-8.0.2/deps/jemalloc/test/include/test/extent_hooks.h000066400000000000000000000230351501533116600236200ustar00rootroot00000000000000/* * Boilerplate code used for testing extent hooks via interception and * passthrough. */ static void *extent_alloc_hook(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_ind); static bool extent_dalloc_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind); static void extent_destroy_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind); static bool extent_commit_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind); static bool extent_decommit_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind); static bool extent_purge_lazy_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind); static bool extent_purge_forced_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind); static bool extent_split_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t size_a, size_t size_b, bool committed, unsigned arena_ind); static bool extent_merge_hook(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, void *addr_b, size_t size_b, bool committed, unsigned arena_ind); static extent_hooks_t *default_hooks; static extent_hooks_t hooks = { extent_alloc_hook, extent_dalloc_hook, extent_destroy_hook, extent_commit_hook, extent_decommit_hook, extent_purge_lazy_hook, extent_purge_forced_hook, extent_split_hook, extent_merge_hook }; /* Control whether hook functions pass calls through to default hooks. */ static bool try_alloc = true; static bool try_dalloc = true; static bool try_destroy = true; static bool try_commit = true; static bool try_decommit = true; static bool try_purge_lazy = true; static bool try_purge_forced = true; static bool try_split = true; static bool try_merge = true; /* Set to false prior to operations, then introspect after operations. */ static bool called_alloc; static bool called_dalloc; static bool called_destroy; static bool called_commit; static bool called_decommit; static bool called_purge_lazy; static bool called_purge_forced; static bool called_split; static bool called_merge; /* Set to false prior to operations, then introspect after operations. */ static bool did_alloc; static bool did_dalloc; static bool did_destroy; static bool did_commit; static bool did_decommit; static bool did_purge_lazy; static bool did_purge_forced; static bool did_split; static bool did_merge; #if 0 # define TRACE_HOOK(fmt, ...) malloc_printf(fmt, __VA_ARGS__) #else # define TRACE_HOOK(fmt, ...) #endif static void * extent_alloc_hook(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { void *ret; TRACE_HOOK("%s(extent_hooks=%p, new_addr=%p, size=%zu, alignment=%zu, " "*zero=%s, *commit=%s, arena_ind=%u)\n", __func__, extent_hooks, new_addr, size, alignment, *zero ? "true" : "false", *commit ? "true" : "false", arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->alloc, extent_alloc_hook, "Wrong hook function"); called_alloc = true; if (!try_alloc) { return NULL; } ret = default_hooks->alloc(default_hooks, new_addr, size, alignment, zero, commit, 0); did_alloc = (ret != NULL); return ret; } static bool extent_dalloc_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, committed=%s, " "arena_ind=%u)\n", __func__, extent_hooks, addr, size, committed ? "true" : "false", arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->dalloc, extent_dalloc_hook, "Wrong hook function"); called_dalloc = true; if (!try_dalloc) { return true; } err = default_hooks->dalloc(default_hooks, addr, size, committed, 0); did_dalloc = !err; return err; } static void extent_destroy_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind) { TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, committed=%s, " "arena_ind=%u)\n", __func__, extent_hooks, addr, size, committed ? "true" : "false", arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->destroy, extent_destroy_hook, "Wrong hook function"); called_destroy = true; if (!try_destroy) { return; } default_hooks->destroy(default_hooks, addr, size, committed, 0); did_destroy = true; } static bool extent_commit_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, " "length=%zu, arena_ind=%u)\n", __func__, extent_hooks, addr, size, offset, length, arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->commit, extent_commit_hook, "Wrong hook function"); called_commit = true; if (!try_commit) { return true; } err = default_hooks->commit(default_hooks, addr, size, offset, length, 0); did_commit = !err; return err; } static bool extent_decommit_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, " "length=%zu, arena_ind=%u)\n", __func__, extent_hooks, addr, size, offset, length, arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->decommit, extent_decommit_hook, "Wrong hook function"); called_decommit = true; if (!try_decommit) { return true; } err = default_hooks->decommit(default_hooks, addr, size, offset, length, 0); did_decommit = !err; return err; } static bool extent_purge_lazy_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, " "length=%zu arena_ind=%u)\n", __func__, extent_hooks, addr, size, offset, length, arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->purge_lazy, extent_purge_lazy_hook, "Wrong hook function"); called_purge_lazy = true; if (!try_purge_lazy) { return true; } err = default_hooks->purge_lazy == NULL || default_hooks->purge_lazy(default_hooks, addr, size, offset, length, 0); did_purge_lazy = !err; return err; } static bool extent_purge_forced_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, " "length=%zu arena_ind=%u)\n", __func__, extent_hooks, addr, size, offset, length, arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->purge_forced, extent_purge_forced_hook, "Wrong hook function"); called_purge_forced = true; if (!try_purge_forced) { return true; } err = default_hooks->purge_forced == NULL || default_hooks->purge_forced(default_hooks, addr, size, offset, length, 0); did_purge_forced = !err; return err; } static bool extent_split_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t size_a, size_t size_b, bool committed, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, size_a=%zu, " "size_b=%zu, committed=%s, arena_ind=%u)\n", __func__, extent_hooks, addr, size, size_a, size_b, committed ? "true" : "false", arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->split, extent_split_hook, "Wrong hook function"); called_split = true; if (!try_split) { return true; } err = (default_hooks->split == NULL || default_hooks->split(default_hooks, addr, size, size_a, size_b, committed, 0)); did_split = !err; return err; } static bool extent_merge_hook(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, void *addr_b, size_t size_b, bool committed, unsigned arena_ind) { bool err; TRACE_HOOK("%s(extent_hooks=%p, addr_a=%p, size_a=%zu, addr_b=%p " "size_b=%zu, committed=%s, arena_ind=%u)\n", __func__, extent_hooks, addr_a, size_a, addr_b, size_b, committed ? "true" : "false", arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->merge, extent_merge_hook, "Wrong hook function"); expect_ptr_eq((void *)((uintptr_t)addr_a + size_a), addr_b, "Extents not mergeable"); called_merge = true; if (!try_merge) { return true; } err = (default_hooks->merge == NULL || default_hooks->merge(default_hooks, addr_a, size_a, addr_b, size_b, committed, 0)); did_merge = !err; return err; } static void extent_hooks_prep(void) { size_t sz; sz = sizeof(default_hooks); expect_d_eq(mallctl("arena.0.extent_hooks", (void *)&default_hooks, &sz, NULL, 0), 0, "Unexpected mallctl() error"); } redis-8.0.2/deps/jemalloc/test/include/test/jemalloc_test.h.in000066400000000000000000000113471501533116600243430ustar00rootroot00000000000000#ifdef __cplusplus extern "C" { #endif #include #ifndef SIZE_T_MAX # define SIZE_T_MAX SIZE_MAX #endif #include #include #include #include #include #include #ifdef _WIN32 # include "msvc_compat/strings.h" #endif #ifdef _WIN32 # include # include "msvc_compat/windows_extra.h" #else # include #endif #include "test/jemalloc_test_defs.h" #if defined(JEMALLOC_OSATOMIC) # include #endif #if defined(HAVE_ALTIVEC) && !defined(__APPLE__) # include #endif #ifdef HAVE_SSE2 # include #endif /******************************************************************************/ /* * For unit tests and analytics tests, expose all public and private interfaces. */ #if defined(JEMALLOC_UNIT_TEST) || defined (JEMALLOC_ANALYZE_TEST) # define JEMALLOC_JET # define JEMALLOC_MANGLE # include "jemalloc/internal/jemalloc_preamble.h" # include "jemalloc/internal/jemalloc_internal_includes.h" /******************************************************************************/ /* * For integration tests, expose the public jemalloc interfaces, but only * expose the minimum necessary internal utility code (to avoid re-implementing * essentially identical code within the test infrastructure). */ #elif defined(JEMALLOC_INTEGRATION_TEST) || \ defined(JEMALLOC_INTEGRATION_CPP_TEST) # define JEMALLOC_MANGLE # include "jemalloc/jemalloc@install_suffix@.h" # include "jemalloc/internal/jemalloc_internal_defs.h" # include "jemalloc/internal/jemalloc_internal_macros.h" static const bool config_debug = #ifdef JEMALLOC_DEBUG true #else false #endif ; # define JEMALLOC_N(n) @private_namespace@##n # include "jemalloc/internal/private_namespace.h" # include "jemalloc/internal/test_hooks.h" /* Hermetic headers. */ # include "jemalloc/internal/assert.h" # include "jemalloc/internal/malloc_io.h" # include "jemalloc/internal/nstime.h" # include "jemalloc/internal/util.h" /* Non-hermetic headers. */ # include "jemalloc/internal/qr.h" # include "jemalloc/internal/ql.h" /******************************************************************************/ /* * For stress tests, expose the public jemalloc interfaces with name mangling * so that they can be tested as e.g. malloc() and free(). Also expose the * public jemalloc interfaces with jet_ prefixes, so that stress tests can use * a separate allocator for their internal data structures. */ #elif defined(JEMALLOC_STRESS_TEST) # include "jemalloc/jemalloc@install_suffix@.h" # include "jemalloc/jemalloc_protos_jet.h" # define JEMALLOC_JET # include "jemalloc/internal/jemalloc_preamble.h" # include "jemalloc/internal/jemalloc_internal_includes.h" # include "jemalloc/internal/public_unnamespace.h" # undef JEMALLOC_JET # include "jemalloc/jemalloc_rename.h" # define JEMALLOC_MANGLE # ifdef JEMALLOC_STRESS_TESTLIB # include "jemalloc/jemalloc_mangle_jet.h" # else # include "jemalloc/jemalloc_mangle.h" # endif /******************************************************************************/ /* * This header does dangerous things, the effects of which only test code * should be subject to. */ #else # error "This header cannot be included outside a testing context" #endif /******************************************************************************/ /* * Common test utilities. */ #include "test/btalloc.h" #include "test/math.h" #include "test/mtx.h" #include "test/mq.h" #include "test/sleep.h" #include "test/test.h" #include "test/timer.h" #include "test/thd.h" #include "test/bgthd.h" #define MEXP 19937 #include "test/SFMT.h" #ifndef JEMALLOC_HAVE_MALLOC_SIZE #define TEST_MALLOC_SIZE malloc_usable_size #else #define TEST_MALLOC_SIZE malloc_size #endif /******************************************************************************/ /* * Define always-enabled assertion macros, so that test assertions execute even * if assertions are disabled in the library code. */ #undef assert #undef not_reached #undef not_implemented #undef expect_not_implemented #define assert(e) do { \ if (!(e)) { \ malloc_printf( \ ": %s:%d: Failed assertion: \"%s\"\n", \ __FILE__, __LINE__, #e); \ abort(); \ } \ } while (0) #define not_reached() do { \ malloc_printf( \ ": %s:%d: Unreachable code reached\n", \ __FILE__, __LINE__); \ abort(); \ } while (0) #define not_implemented() do { \ malloc_printf(": %s:%d: Not implemented\n", \ __FILE__, __LINE__); \ abort(); \ } while (0) #define expect_not_implemented(e) do { \ if (!(e)) { \ not_implemented(); \ } \ } while (0) #ifdef __cplusplus } #endif redis-8.0.2/deps/jemalloc/test/include/test/jemalloc_test_defs.h.in000066400000000000000000000004421501533116600253360ustar00rootroot00000000000000#include "jemalloc/internal/jemalloc_internal_defs.h" #include "jemalloc/internal/jemalloc_internal_decls.h" /* * For use by SFMT. configure.ac doesn't actually define HAVE_SSE2 because its * dependencies are notoriously unportable in practice. */ #undef HAVE_SSE2 #undef HAVE_ALTIVEC redis-8.0.2/deps/jemalloc/test/include/test/math.h000066400000000000000000000172721501533116600220450ustar00rootroot00000000000000/* * Compute the natural log of Gamma(x), accurate to 10 decimal places. * * This implementation is based on: * * Pike, M.C., I.D. Hill (1966) Algorithm 291: Logarithm of Gamma function * [S14]. Communications of the ACM 9(9):684. */ static inline double ln_gamma(double x) { double f, z; assert(x > 0.0); if (x < 7.0) { f = 1.0; z = x; while (z < 7.0) { f *= z; z += 1.0; } x = z; f = -log(f); } else { f = 0.0; } z = 1.0 / (x * x); return f + (x-0.5) * log(x) - x + 0.918938533204673 + (((-0.000595238095238 * z + 0.000793650793651) * z - 0.002777777777778) * z + 0.083333333333333) / x; } /* * Compute the incomplete Gamma ratio for [0..x], where p is the shape * parameter, and ln_gamma_p is ln_gamma(p). * * This implementation is based on: * * Bhattacharjee, G.P. (1970) Algorithm AS 32: The incomplete Gamma integral. * Applied Statistics 19:285-287. */ static inline double i_gamma(double x, double p, double ln_gamma_p) { double acu, factor, oflo, gin, term, rn, a, b, an, dif; double pn[6]; unsigned i; assert(p > 0.0); assert(x >= 0.0); if (x == 0.0) { return 0.0; } acu = 1.0e-10; oflo = 1.0e30; gin = 0.0; factor = exp(p * log(x) - x - ln_gamma_p); if (x <= 1.0 || x < p) { /* Calculation by series expansion. */ gin = 1.0; term = 1.0; rn = p; while (true) { rn += 1.0; term *= x / rn; gin += term; if (term <= acu) { gin *= factor / p; return gin; } } } else { /* Calculation by continued fraction. */ a = 1.0 - p; b = a + x + 1.0; term = 0.0; pn[0] = 1.0; pn[1] = x; pn[2] = x + 1.0; pn[3] = x * b; gin = pn[2] / pn[3]; while (true) { a += 1.0; b += 2.0; term += 1.0; an = a * term; for (i = 0; i < 2; i++) { pn[i+4] = b * pn[i+2] - an * pn[i]; } if (pn[5] != 0.0) { rn = pn[4] / pn[5]; dif = fabs(gin - rn); if (dif <= acu && dif <= acu * rn) { gin = 1.0 - factor * gin; return gin; } gin = rn; } for (i = 0; i < 4; i++) { pn[i] = pn[i+2]; } if (fabs(pn[4]) >= oflo) { for (i = 0; i < 4; i++) { pn[i] /= oflo; } } } } } /* * Given a value p in [0..1] of the lower tail area of the normal distribution, * compute the limit on the definite integral from [-inf..z] that satisfies p, * accurate to 16 decimal places. * * This implementation is based on: * * Wichura, M.J. (1988) Algorithm AS 241: The percentage points of the normal * distribution. Applied Statistics 37(3):477-484. */ static inline double pt_norm(double p) { double q, r, ret; assert(p > 0.0 && p < 1.0); q = p - 0.5; if (fabs(q) <= 0.425) { /* p close to 1/2. */ r = 0.180625 - q * q; return q * (((((((2.5090809287301226727e3 * r + 3.3430575583588128105e4) * r + 6.7265770927008700853e4) * r + 4.5921953931549871457e4) * r + 1.3731693765509461125e4) * r + 1.9715909503065514427e3) * r + 1.3314166789178437745e2) * r + 3.3871328727963666080e0) / (((((((5.2264952788528545610e3 * r + 2.8729085735721942674e4) * r + 3.9307895800092710610e4) * r + 2.1213794301586595867e4) * r + 5.3941960214247511077e3) * r + 6.8718700749205790830e2) * r + 4.2313330701600911252e1) * r + 1.0); } else { if (q < 0.0) { r = p; } else { r = 1.0 - p; } assert(r > 0.0); r = sqrt(-log(r)); if (r <= 5.0) { /* p neither close to 1/2 nor 0 or 1. */ r -= 1.6; ret = ((((((((7.74545014278341407640e-4 * r + 2.27238449892691845833e-2) * r + 2.41780725177450611770e-1) * r + 1.27045825245236838258e0) * r + 3.64784832476320460504e0) * r + 5.76949722146069140550e0) * r + 4.63033784615654529590e0) * r + 1.42343711074968357734e0) / (((((((1.05075007164441684324e-9 * r + 5.47593808499534494600e-4) * r + 1.51986665636164571966e-2) * r + 1.48103976427480074590e-1) * r + 6.89767334985100004550e-1) * r + 1.67638483018380384940e0) * r + 2.05319162663775882187e0) * r + 1.0)); } else { /* p near 0 or 1. */ r -= 5.0; ret = ((((((((2.01033439929228813265e-7 * r + 2.71155556874348757815e-5) * r + 1.24266094738807843860e-3) * r + 2.65321895265761230930e-2) * r + 2.96560571828504891230e-1) * r + 1.78482653991729133580e0) * r + 5.46378491116411436990e0) * r + 6.65790464350110377720e0) / (((((((2.04426310338993978564e-15 * r + 1.42151175831644588870e-7) * r + 1.84631831751005468180e-5) * r + 7.86869131145613259100e-4) * r + 1.48753612908506148525e-2) * r + 1.36929880922735805310e-1) * r + 5.99832206555887937690e-1) * r + 1.0)); } if (q < 0.0) { ret = -ret; } return ret; } } /* * Given a value p in [0..1] of the lower tail area of the Chi^2 distribution * with df degrees of freedom, where ln_gamma_df_2 is ln_gamma(df/2.0), compute * the upper limit on the definite integral from [0..z] that satisfies p, * accurate to 12 decimal places. * * This implementation is based on: * * Best, D.J., D.E. Roberts (1975) Algorithm AS 91: The percentage points of * the Chi^2 distribution. Applied Statistics 24(3):385-388. * * Shea, B.L. (1991) Algorithm AS R85: A remark on AS 91: The percentage * points of the Chi^2 distribution. Applied Statistics 40(1):233-235. */ static inline double pt_chi2(double p, double df, double ln_gamma_df_2) { double e, aa, xx, c, ch, a, q, p1, p2, t, x, b, s1, s2, s3, s4, s5, s6; unsigned i; assert(p >= 0.0 && p < 1.0); assert(df > 0.0); e = 5.0e-7; aa = 0.6931471805; xx = 0.5 * df; c = xx - 1.0; if (df < -1.24 * log(p)) { /* Starting approximation for small Chi^2. */ ch = pow(p * xx * exp(ln_gamma_df_2 + xx * aa), 1.0 / xx); if (ch - e < 0.0) { return ch; } } else { if (df > 0.32) { x = pt_norm(p); /* * Starting approximation using Wilson and Hilferty * estimate. */ p1 = 0.222222 / df; ch = df * pow(x * sqrt(p1) + 1.0 - p1, 3.0); /* Starting approximation for p tending to 1. */ if (ch > 2.2 * df + 6.0) { ch = -2.0 * (log(1.0 - p) - c * log(0.5 * ch) + ln_gamma_df_2); } } else { ch = 0.4; a = log(1.0 - p); while (true) { q = ch; p1 = 1.0 + ch * (4.67 + ch); p2 = ch * (6.73 + ch * (6.66 + ch)); t = -0.5 + (4.67 + 2.0 * ch) / p1 - (6.73 + ch * (13.32 + 3.0 * ch)) / p2; ch -= (1.0 - exp(a + ln_gamma_df_2 + 0.5 * ch + c * aa) * p2 / p1) / t; if (fabs(q / ch - 1.0) - 0.01 <= 0.0) { break; } } } } for (i = 0; i < 20; i++) { /* Calculation of seven-term Taylor series. */ q = ch; p1 = 0.5 * ch; if (p1 < 0.0) { return -1.0; } p2 = p - i_gamma(p1, xx, ln_gamma_df_2); t = p2 * exp(xx * aa + ln_gamma_df_2 + p1 - c * log(ch)); b = t / ch; a = 0.5 * t - b * c; s1 = (210.0 + a * (140.0 + a * (105.0 + a * (84.0 + a * (70.0 + 60.0 * a))))) / 420.0; s2 = (420.0 + a * (735.0 + a * (966.0 + a * (1141.0 + 1278.0 * a)))) / 2520.0; s3 = (210.0 + a * (462.0 + a * (707.0 + 932.0 * a))) / 2520.0; s4 = (252.0 + a * (672.0 + 1182.0 * a) + c * (294.0 + a * (889.0 + 1740.0 * a))) / 5040.0; s5 = (84.0 + 264.0 * a + c * (175.0 + 606.0 * a)) / 2520.0; s6 = (120.0 + c * (346.0 + 127.0 * c)) / 5040.0; ch += t * (1.0 + 0.5 * t * s1 - b * c * (s1 - b * (s2 - b * (s3 - b * (s4 - b * (s5 - b * s6)))))); if (fabs(q / ch - 1.0) <= e) { break; } } return ch; } /* * Given a value p in [0..1] and Gamma distribution shape and scale parameters, * compute the upper limit on the definite integral from [0..z] that satisfies * p. */ static inline double pt_gamma(double p, double shape, double scale, double ln_gamma_shape) { return pt_chi2(p, shape * 2.0, ln_gamma_shape) * 0.5 * scale; } redis-8.0.2/deps/jemalloc/test/include/test/mq.h000066400000000000000000000054701501533116600215260ustar00rootroot00000000000000#include "test/sleep.h" /* * Simple templated message queue implementation that relies on only mutexes for * synchronization (which reduces portability issues). Given the following * setup: * * typedef struct mq_msg_s mq_msg_t; * struct mq_msg_s { * mq_msg(mq_msg_t) link; * [message data] * }; * mq_gen(, mq_, mq_t, mq_msg_t, link) * * The API is as follows: * * bool mq_init(mq_t *mq); * void mq_fini(mq_t *mq); * unsigned mq_count(mq_t *mq); * mq_msg_t *mq_tryget(mq_t *mq); * mq_msg_t *mq_get(mq_t *mq); * void mq_put(mq_t *mq, mq_msg_t *msg); * * The message queue linkage embedded in each message is to be treated as * externally opaque (no need to initialize or clean up externally). mq_fini() * does not perform any cleanup of messages, since it knows nothing of their * payloads. */ #define mq_msg(a_mq_msg_type) ql_elm(a_mq_msg_type) #define mq_gen(a_attr, a_prefix, a_mq_type, a_mq_msg_type, a_field) \ typedef struct { \ mtx_t lock; \ ql_head(a_mq_msg_type) msgs; \ unsigned count; \ } a_mq_type; \ a_attr bool \ a_prefix##init(a_mq_type *mq) { \ \ if (mtx_init(&mq->lock)) { \ return true; \ } \ ql_new(&mq->msgs); \ mq->count = 0; \ return false; \ } \ a_attr void \ a_prefix##fini(a_mq_type *mq) { \ mtx_fini(&mq->lock); \ } \ a_attr unsigned \ a_prefix##count(a_mq_type *mq) { \ unsigned count; \ \ mtx_lock(&mq->lock); \ count = mq->count; \ mtx_unlock(&mq->lock); \ return count; \ } \ a_attr a_mq_msg_type * \ a_prefix##tryget(a_mq_type *mq) { \ a_mq_msg_type *msg; \ \ mtx_lock(&mq->lock); \ msg = ql_first(&mq->msgs); \ if (msg != NULL) { \ ql_head_remove(&mq->msgs, a_mq_msg_type, a_field); \ mq->count--; \ } \ mtx_unlock(&mq->lock); \ return msg; \ } \ a_attr a_mq_msg_type * \ a_prefix##get(a_mq_type *mq) { \ a_mq_msg_type *msg; \ unsigned ns; \ \ msg = a_prefix##tryget(mq); \ if (msg != NULL) { \ return msg; \ } \ \ ns = 1; \ while (true) { \ sleep_ns(ns); \ msg = a_prefix##tryget(mq); \ if (msg != NULL) { \ return msg; \ } \ if (ns < 1000*1000*1000) { \ /* Double sleep time, up to max 1 second. */ \ ns <<= 1; \ if (ns > 1000*1000*1000) { \ ns = 1000*1000*1000; \ } \ } \ } \ } \ a_attr void \ a_prefix##put(a_mq_type *mq, a_mq_msg_type *msg) { \ \ mtx_lock(&mq->lock); \ ql_elm_new(msg, a_field); \ ql_tail_insert(&mq->msgs, msg, a_field); \ mq->count++; \ mtx_unlock(&mq->lock); \ } redis-8.0.2/deps/jemalloc/test/include/test/mtx.h000066400000000000000000000010241501533116600217100ustar00rootroot00000000000000/* * mtx is a slightly simplified version of malloc_mutex. This code duplication * is unfortunate, but there are allocator bootstrapping considerations that * would leak into the test infrastructure if malloc_mutex were used directly * in tests. */ typedef struct { #ifdef _WIN32 CRITICAL_SECTION lock; #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) os_unfair_lock lock; #else pthread_mutex_t lock; #endif } mtx_t; bool mtx_init(mtx_t *mtx); void mtx_fini(mtx_t *mtx); void mtx_lock(mtx_t *mtx); void mtx_unlock(mtx_t *mtx); redis-8.0.2/deps/jemalloc/test/include/test/nbits.h000066400000000000000000000026571501533116600222340ustar00rootroot00000000000000#ifndef TEST_NBITS_H #define TEST_NBITS_H /* Interesting bitmap counts to test. */ #define NBITS_TAB \ NB( 1) \ NB( 2) \ NB( 3) \ NB( 4) \ NB( 5) \ NB( 6) \ NB( 7) \ NB( 8) \ NB( 9) \ NB(10) \ NB(11) \ NB(12) \ NB(13) \ NB(14) \ NB(15) \ NB(16) \ NB(17) \ NB(18) \ NB(19) \ NB(20) \ NB(21) \ NB(22) \ NB(23) \ NB(24) \ NB(25) \ NB(26) \ NB(27) \ NB(28) \ NB(29) \ NB(30) \ NB(31) \ NB(32) \ \ NB(33) \ NB(34) \ NB(35) \ NB(36) \ NB(37) \ NB(38) \ NB(39) \ NB(40) \ NB(41) \ NB(42) \ NB(43) \ NB(44) \ NB(45) \ NB(46) \ NB(47) \ NB(48) \ NB(49) \ NB(50) \ NB(51) \ NB(52) \ NB(53) \ NB(54) \ NB(55) \ NB(56) \ NB(57) \ NB(58) \ NB(59) \ NB(60) \ NB(61) \ NB(62) \ NB(63) \ NB(64) \ NB(65) \ NB(66) \ NB(67) \ \ NB(126) \ NB(127) \ NB(128) \ NB(129) \ NB(130) \ \ NB(254) \ NB(255) \ NB(256) \ NB(257) \ NB(258) \ \ NB(510) \ NB(511) \ NB(512) \ NB(513) \ NB(514) \ \ NB(1022) \ NB(1023) \ NB(1024) \ NB(1025) \ NB(1026) \ \ NB(2048) \ \ NB(4094) \ NB(4095) \ NB(4096) \ NB(4097) \ NB(4098) \ \ NB(8192) \ NB(16384) #endif /* TEST_NBITS_H */ redis-8.0.2/deps/jemalloc/test/include/test/san.h000066400000000000000000000006721501533116600216710ustar00rootroot00000000000000#if defined(JEMALLOC_UAF_DETECTION) || defined(JEMALLOC_DEBUG) # define TEST_SAN_UAF_ALIGN_ENABLE "lg_san_uaf_align:12" # define TEST_SAN_UAF_ALIGN_DISABLE "lg_san_uaf_align:-1" #else # define TEST_SAN_UAF_ALIGN_ENABLE "" # define TEST_SAN_UAF_ALIGN_DISABLE "" #endif static inline bool extent_is_guarded(tsdn_t *tsdn, void *ptr) { edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); return edata_guarded_get(edata); } redis-8.0.2/deps/jemalloc/test/include/test/sleep.h000066400000000000000000000000341501533116600222100ustar00rootroot00000000000000void sleep_ns(unsigned ns); redis-8.0.2/deps/jemalloc/test/include/test/test.h000066400000000000000000000567321501533116600220770ustar00rootroot00000000000000#define ASSERT_BUFSIZE 256 #define verify_cmp(may_abort, t, a, b, cmp, neg_cmp, pri, ...) do { \ const t a_ = (a); \ const t b_ = (b); \ if (!(a_ cmp b_)) { \ char prefix[ASSERT_BUFSIZE]; \ char message[ASSERT_BUFSIZE]; \ malloc_snprintf(prefix, sizeof(prefix), \ "%s:%s:%d: Failed assertion: " \ "(%s) " #cmp " (%s) --> " \ "%" pri " " #neg_cmp " %" pri ": ", \ __func__, __FILE__, __LINE__, \ #a, #b, a_, b_); \ malloc_snprintf(message, sizeof(message), __VA_ARGS__); \ if (may_abort) { \ abort(); \ } else { \ p_test_fail(prefix, message); \ } \ } \ } while (0) #define expect_cmp(t, a, b, cmp, neg_cmp, pri, ...) verify_cmp(false, \ t, a, b, cmp, neg_cmp, pri, __VA_ARGS__) #define expect_ptr_eq(a, b, ...) expect_cmp(void *, a, b, ==, \ !=, "p", __VA_ARGS__) #define expect_ptr_ne(a, b, ...) expect_cmp(void *, a, b, !=, \ ==, "p", __VA_ARGS__) #define expect_ptr_null(a, ...) expect_cmp(void *, a, NULL, ==, \ !=, "p", __VA_ARGS__) #define expect_ptr_not_null(a, ...) expect_cmp(void *, a, NULL, !=, \ ==, "p", __VA_ARGS__) #define expect_c_eq(a, b, ...) expect_cmp(char, a, b, ==, !=, "c", __VA_ARGS__) #define expect_c_ne(a, b, ...) expect_cmp(char, a, b, !=, ==, "c", __VA_ARGS__) #define expect_c_lt(a, b, ...) expect_cmp(char, a, b, <, >=, "c", __VA_ARGS__) #define expect_c_le(a, b, ...) expect_cmp(char, a, b, <=, >, "c", __VA_ARGS__) #define expect_c_ge(a, b, ...) expect_cmp(char, a, b, >=, <, "c", __VA_ARGS__) #define expect_c_gt(a, b, ...) expect_cmp(char, a, b, >, <=, "c", __VA_ARGS__) #define expect_x_eq(a, b, ...) expect_cmp(int, a, b, ==, !=, "#x", __VA_ARGS__) #define expect_x_ne(a, b, ...) expect_cmp(int, a, b, !=, ==, "#x", __VA_ARGS__) #define expect_x_lt(a, b, ...) expect_cmp(int, a, b, <, >=, "#x", __VA_ARGS__) #define expect_x_le(a, b, ...) expect_cmp(int, a, b, <=, >, "#x", __VA_ARGS__) #define expect_x_ge(a, b, ...) expect_cmp(int, a, b, >=, <, "#x", __VA_ARGS__) #define expect_x_gt(a, b, ...) expect_cmp(int, a, b, >, <=, "#x", __VA_ARGS__) #define expect_d_eq(a, b, ...) expect_cmp(int, a, b, ==, !=, "d", __VA_ARGS__) #define expect_d_ne(a, b, ...) expect_cmp(int, a, b, !=, ==, "d", __VA_ARGS__) #define expect_d_lt(a, b, ...) expect_cmp(int, a, b, <, >=, "d", __VA_ARGS__) #define expect_d_le(a, b, ...) expect_cmp(int, a, b, <=, >, "d", __VA_ARGS__) #define expect_d_ge(a, b, ...) expect_cmp(int, a, b, >=, <, "d", __VA_ARGS__) #define expect_d_gt(a, b, ...) expect_cmp(int, a, b, >, <=, "d", __VA_ARGS__) #define expect_u_eq(a, b, ...) expect_cmp(int, a, b, ==, !=, "u", __VA_ARGS__) #define expect_u_ne(a, b, ...) expect_cmp(int, a, b, !=, ==, "u", __VA_ARGS__) #define expect_u_lt(a, b, ...) expect_cmp(int, a, b, <, >=, "u", __VA_ARGS__) #define expect_u_le(a, b, ...) expect_cmp(int, a, b, <=, >, "u", __VA_ARGS__) #define expect_u_ge(a, b, ...) expect_cmp(int, a, b, >=, <, "u", __VA_ARGS__) #define expect_u_gt(a, b, ...) expect_cmp(int, a, b, >, <=, "u", __VA_ARGS__) #define expect_ld_eq(a, b, ...) expect_cmp(long, a, b, ==, \ !=, "ld", __VA_ARGS__) #define expect_ld_ne(a, b, ...) expect_cmp(long, a, b, !=, \ ==, "ld", __VA_ARGS__) #define expect_ld_lt(a, b, ...) expect_cmp(long, a, b, <, \ >=, "ld", __VA_ARGS__) #define expect_ld_le(a, b, ...) expect_cmp(long, a, b, <=, \ >, "ld", __VA_ARGS__) #define expect_ld_ge(a, b, ...) expect_cmp(long, a, b, >=, \ <, "ld", __VA_ARGS__) #define expect_ld_gt(a, b, ...) expect_cmp(long, a, b, >, \ <=, "ld", __VA_ARGS__) #define expect_lu_eq(a, b, ...) expect_cmp(unsigned long, \ a, b, ==, !=, "lu", __VA_ARGS__) #define expect_lu_ne(a, b, ...) expect_cmp(unsigned long, \ a, b, !=, ==, "lu", __VA_ARGS__) #define expect_lu_lt(a, b, ...) expect_cmp(unsigned long, \ a, b, <, >=, "lu", __VA_ARGS__) #define expect_lu_le(a, b, ...) expect_cmp(unsigned long, \ a, b, <=, >, "lu", __VA_ARGS__) #define expect_lu_ge(a, b, ...) expect_cmp(unsigned long, \ a, b, >=, <, "lu", __VA_ARGS__) #define expect_lu_gt(a, b, ...) expect_cmp(unsigned long, \ a, b, >, <=, "lu", __VA_ARGS__) #define expect_qd_eq(a, b, ...) expect_cmp(long long, a, b, ==, \ !=, "qd", __VA_ARGS__) #define expect_qd_ne(a, b, ...) expect_cmp(long long, a, b, !=, \ ==, "qd", __VA_ARGS__) #define expect_qd_lt(a, b, ...) expect_cmp(long long, a, b, <, \ >=, "qd", __VA_ARGS__) #define expect_qd_le(a, b, ...) expect_cmp(long long, a, b, <=, \ >, "qd", __VA_ARGS__) #define expect_qd_ge(a, b, ...) expect_cmp(long long, a, b, >=, \ <, "qd", __VA_ARGS__) #define expect_qd_gt(a, b, ...) expect_cmp(long long, a, b, >, \ <=, "qd", __VA_ARGS__) #define expect_qu_eq(a, b, ...) expect_cmp(unsigned long long, \ a, b, ==, !=, "qu", __VA_ARGS__) #define expect_qu_ne(a, b, ...) expect_cmp(unsigned long long, \ a, b, !=, ==, "qu", __VA_ARGS__) #define expect_qu_lt(a, b, ...) expect_cmp(unsigned long long, \ a, b, <, >=, "qu", __VA_ARGS__) #define expect_qu_le(a, b, ...) expect_cmp(unsigned long long, \ a, b, <=, >, "qu", __VA_ARGS__) #define expect_qu_ge(a, b, ...) expect_cmp(unsigned long long, \ a, b, >=, <, "qu", __VA_ARGS__) #define expect_qu_gt(a, b, ...) expect_cmp(unsigned long long, \ a, b, >, <=, "qu", __VA_ARGS__) #define expect_jd_eq(a, b, ...) expect_cmp(intmax_t, a, b, ==, \ !=, "jd", __VA_ARGS__) #define expect_jd_ne(a, b, ...) expect_cmp(intmax_t, a, b, !=, \ ==, "jd", __VA_ARGS__) #define expect_jd_lt(a, b, ...) expect_cmp(intmax_t, a, b, <, \ >=, "jd", __VA_ARGS__) #define expect_jd_le(a, b, ...) expect_cmp(intmax_t, a, b, <=, \ >, "jd", __VA_ARGS__) #define expect_jd_ge(a, b, ...) expect_cmp(intmax_t, a, b, >=, \ <, "jd", __VA_ARGS__) #define expect_jd_gt(a, b, ...) expect_cmp(intmax_t, a, b, >, \ <=, "jd", __VA_ARGS__) #define expect_ju_eq(a, b, ...) expect_cmp(uintmax_t, a, b, ==, \ !=, "ju", __VA_ARGS__) #define expect_ju_ne(a, b, ...) expect_cmp(uintmax_t, a, b, !=, \ ==, "ju", __VA_ARGS__) #define expect_ju_lt(a, b, ...) expect_cmp(uintmax_t, a, b, <, \ >=, "ju", __VA_ARGS__) #define expect_ju_le(a, b, ...) expect_cmp(uintmax_t, a, b, <=, \ >, "ju", __VA_ARGS__) #define expect_ju_ge(a, b, ...) expect_cmp(uintmax_t, a, b, >=, \ <, "ju", __VA_ARGS__) #define expect_ju_gt(a, b, ...) expect_cmp(uintmax_t, a, b, >, \ <=, "ju", __VA_ARGS__) #define expect_zd_eq(a, b, ...) expect_cmp(ssize_t, a, b, ==, \ !=, "zd", __VA_ARGS__) #define expect_zd_ne(a, b, ...) expect_cmp(ssize_t, a, b, !=, \ ==, "zd", __VA_ARGS__) #define expect_zd_lt(a, b, ...) expect_cmp(ssize_t, a, b, <, \ >=, "zd", __VA_ARGS__) #define expect_zd_le(a, b, ...) expect_cmp(ssize_t, a, b, <=, \ >, "zd", __VA_ARGS__) #define expect_zd_ge(a, b, ...) expect_cmp(ssize_t, a, b, >=, \ <, "zd", __VA_ARGS__) #define expect_zd_gt(a, b, ...) expect_cmp(ssize_t, a, b, >, \ <=, "zd", __VA_ARGS__) #define expect_zu_eq(a, b, ...) expect_cmp(size_t, a, b, ==, \ !=, "zu", __VA_ARGS__) #define expect_zu_ne(a, b, ...) expect_cmp(size_t, a, b, !=, \ ==, "zu", __VA_ARGS__) #define expect_zu_lt(a, b, ...) expect_cmp(size_t, a, b, <, \ >=, "zu", __VA_ARGS__) #define expect_zu_le(a, b, ...) expect_cmp(size_t, a, b, <=, \ >, "zu", __VA_ARGS__) #define expect_zu_ge(a, b, ...) expect_cmp(size_t, a, b, >=, \ <, "zu", __VA_ARGS__) #define expect_zu_gt(a, b, ...) expect_cmp(size_t, a, b, >, \ <=, "zu", __VA_ARGS__) #define expect_d32_eq(a, b, ...) expect_cmp(int32_t, a, b, ==, \ !=, FMTd32, __VA_ARGS__) #define expect_d32_ne(a, b, ...) expect_cmp(int32_t, a, b, !=, \ ==, FMTd32, __VA_ARGS__) #define expect_d32_lt(a, b, ...) expect_cmp(int32_t, a, b, <, \ >=, FMTd32, __VA_ARGS__) #define expect_d32_le(a, b, ...) expect_cmp(int32_t, a, b, <=, \ >, FMTd32, __VA_ARGS__) #define expect_d32_ge(a, b, ...) expect_cmp(int32_t, a, b, >=, \ <, FMTd32, __VA_ARGS__) #define expect_d32_gt(a, b, ...) expect_cmp(int32_t, a, b, >, \ <=, FMTd32, __VA_ARGS__) #define expect_u32_eq(a, b, ...) expect_cmp(uint32_t, a, b, ==, \ !=, FMTu32, __VA_ARGS__) #define expect_u32_ne(a, b, ...) expect_cmp(uint32_t, a, b, !=, \ ==, FMTu32, __VA_ARGS__) #define expect_u32_lt(a, b, ...) expect_cmp(uint32_t, a, b, <, \ >=, FMTu32, __VA_ARGS__) #define expect_u32_le(a, b, ...) expect_cmp(uint32_t, a, b, <=, \ >, FMTu32, __VA_ARGS__) #define expect_u32_ge(a, b, ...) expect_cmp(uint32_t, a, b, >=, \ <, FMTu32, __VA_ARGS__) #define expect_u32_gt(a, b, ...) expect_cmp(uint32_t, a, b, >, \ <=, FMTu32, __VA_ARGS__) #define expect_d64_eq(a, b, ...) expect_cmp(int64_t, a, b, ==, \ !=, FMTd64, __VA_ARGS__) #define expect_d64_ne(a, b, ...) expect_cmp(int64_t, a, b, !=, \ ==, FMTd64, __VA_ARGS__) #define expect_d64_lt(a, b, ...) expect_cmp(int64_t, a, b, <, \ >=, FMTd64, __VA_ARGS__) #define expect_d64_le(a, b, ...) expect_cmp(int64_t, a, b, <=, \ >, FMTd64, __VA_ARGS__) #define expect_d64_ge(a, b, ...) expect_cmp(int64_t, a, b, >=, \ <, FMTd64, __VA_ARGS__) #define expect_d64_gt(a, b, ...) expect_cmp(int64_t, a, b, >, \ <=, FMTd64, __VA_ARGS__) #define expect_u64_eq(a, b, ...) expect_cmp(uint64_t, a, b, ==, \ !=, FMTu64, __VA_ARGS__) #define expect_u64_ne(a, b, ...) expect_cmp(uint64_t, a, b, !=, \ ==, FMTu64, __VA_ARGS__) #define expect_u64_lt(a, b, ...) expect_cmp(uint64_t, a, b, <, \ >=, FMTu64, __VA_ARGS__) #define expect_u64_le(a, b, ...) expect_cmp(uint64_t, a, b, <=, \ >, FMTu64, __VA_ARGS__) #define expect_u64_ge(a, b, ...) expect_cmp(uint64_t, a, b, >=, \ <, FMTu64, __VA_ARGS__) #define expect_u64_gt(a, b, ...) expect_cmp(uint64_t, a, b, >, \ <=, FMTu64, __VA_ARGS__) #define verify_b_eq(may_abort, a, b, ...) do { \ bool a_ = (a); \ bool b_ = (b); \ if (!(a_ == b_)) { \ char prefix[ASSERT_BUFSIZE]; \ char message[ASSERT_BUFSIZE]; \ malloc_snprintf(prefix, sizeof(prefix), \ "%s:%s:%d: Failed assertion: " \ "(%s) == (%s) --> %s != %s: ", \ __func__, __FILE__, __LINE__, \ #a, #b, a_ ? "true" : "false", \ b_ ? "true" : "false"); \ malloc_snprintf(message, sizeof(message), __VA_ARGS__); \ if (may_abort) { \ abort(); \ } else { \ p_test_fail(prefix, message); \ } \ } \ } while (0) #define verify_b_ne(may_abort, a, b, ...) do { \ bool a_ = (a); \ bool b_ = (b); \ if (!(a_ != b_)) { \ char prefix[ASSERT_BUFSIZE]; \ char message[ASSERT_BUFSIZE]; \ malloc_snprintf(prefix, sizeof(prefix), \ "%s:%s:%d: Failed assertion: " \ "(%s) != (%s) --> %s == %s: ", \ __func__, __FILE__, __LINE__, \ #a, #b, a_ ? "true" : "false", \ b_ ? "true" : "false"); \ malloc_snprintf(message, sizeof(message), __VA_ARGS__); \ if (may_abort) { \ abort(); \ } else { \ p_test_fail(prefix, message); \ } \ } \ } while (0) #define expect_b_eq(a, b, ...) verify_b_eq(false, a, b, __VA_ARGS__) #define expect_b_ne(a, b, ...) verify_b_ne(false, a, b, __VA_ARGS__) #define expect_true(a, ...) expect_b_eq(a, true, __VA_ARGS__) #define expect_false(a, ...) expect_b_eq(a, false, __VA_ARGS__) #define verify_str_eq(may_abort, a, b, ...) do { \ if (strcmp((a), (b))) { \ char prefix[ASSERT_BUFSIZE]; \ char message[ASSERT_BUFSIZE]; \ malloc_snprintf(prefix, sizeof(prefix), \ "%s:%s:%d: Failed assertion: " \ "(%s) same as (%s) --> " \ "\"%s\" differs from \"%s\": ", \ __func__, __FILE__, __LINE__, #a, #b, a, b); \ malloc_snprintf(message, sizeof(message), __VA_ARGS__); \ if (may_abort) { \ abort(); \ } else { \ p_test_fail(prefix, message); \ } \ } \ } while (0) #define verify_str_ne(may_abort, a, b, ...) do { \ if (!strcmp((a), (b))) { \ char prefix[ASSERT_BUFSIZE]; \ char message[ASSERT_BUFSIZE]; \ malloc_snprintf(prefix, sizeof(prefix), \ "%s:%s:%d: Failed assertion: " \ "(%s) differs from (%s) --> " \ "\"%s\" same as \"%s\": ", \ __func__, __FILE__, __LINE__, #a, #b, a, b); \ malloc_snprintf(message, sizeof(message), __VA_ARGS__); \ if (may_abort) { \ abort(); \ } else { \ p_test_fail(prefix, message); \ } \ } \ } while (0) #define expect_str_eq(a, b, ...) verify_str_eq(false, a, b, __VA_ARGS__) #define expect_str_ne(a, b, ...) verify_str_ne(false, a, b, __VA_ARGS__) #define verify_not_reached(may_abort, ...) do { \ char prefix[ASSERT_BUFSIZE]; \ char message[ASSERT_BUFSIZE]; \ malloc_snprintf(prefix, sizeof(prefix), \ "%s:%s:%d: Unreachable code reached: ", \ __func__, __FILE__, __LINE__); \ malloc_snprintf(message, sizeof(message), __VA_ARGS__); \ if (may_abort) { \ abort(); \ } else { \ p_test_fail(prefix, message); \ } \ } while (0) #define expect_not_reached(...) verify_not_reached(false, __VA_ARGS__) #define assert_cmp(t, a, b, cmp, neg_cmp, pri, ...) verify_cmp(true, \ t, a, b, cmp, neg_cmp, pri, __VA_ARGS__) #define assert_ptr_eq(a, b, ...) assert_cmp(void *, a, b, ==, \ !=, "p", __VA_ARGS__) #define assert_ptr_ne(a, b, ...) assert_cmp(void *, a, b, !=, \ ==, "p", __VA_ARGS__) #define assert_ptr_null(a, ...) assert_cmp(void *, a, NULL, ==, \ !=, "p", __VA_ARGS__) #define assert_ptr_not_null(a, ...) assert_cmp(void *, a, NULL, !=, \ ==, "p", __VA_ARGS__) #define assert_c_eq(a, b, ...) assert_cmp(char, a, b, ==, !=, "c", __VA_ARGS__) #define assert_c_ne(a, b, ...) assert_cmp(char, a, b, !=, ==, "c", __VA_ARGS__) #define assert_c_lt(a, b, ...) assert_cmp(char, a, b, <, >=, "c", __VA_ARGS__) #define assert_c_le(a, b, ...) assert_cmp(char, a, b, <=, >, "c", __VA_ARGS__) #define assert_c_ge(a, b, ...) assert_cmp(char, a, b, >=, <, "c", __VA_ARGS__) #define assert_c_gt(a, b, ...) assert_cmp(char, a, b, >, <=, "c", __VA_ARGS__) #define assert_x_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "#x", __VA_ARGS__) #define assert_x_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "#x", __VA_ARGS__) #define assert_x_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "#x", __VA_ARGS__) #define assert_x_le(a, b, ...) assert_cmp(int, a, b, <=, >, "#x", __VA_ARGS__) #define assert_x_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "#x", __VA_ARGS__) #define assert_x_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "#x", __VA_ARGS__) #define assert_d_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "d", __VA_ARGS__) #define assert_d_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "d", __VA_ARGS__) #define assert_d_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "d", __VA_ARGS__) #define assert_d_le(a, b, ...) assert_cmp(int, a, b, <=, >, "d", __VA_ARGS__) #define assert_d_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "d", __VA_ARGS__) #define assert_d_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "d", __VA_ARGS__) #define assert_u_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "u", __VA_ARGS__) #define assert_u_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "u", __VA_ARGS__) #define assert_u_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "u", __VA_ARGS__) #define assert_u_le(a, b, ...) assert_cmp(int, a, b, <=, >, "u", __VA_ARGS__) #define assert_u_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "u", __VA_ARGS__) #define assert_u_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "u", __VA_ARGS__) #define assert_ld_eq(a, b, ...) assert_cmp(long, a, b, ==, \ !=, "ld", __VA_ARGS__) #define assert_ld_ne(a, b, ...) assert_cmp(long, a, b, !=, \ ==, "ld", __VA_ARGS__) #define assert_ld_lt(a, b, ...) assert_cmp(long, a, b, <, \ >=, "ld", __VA_ARGS__) #define assert_ld_le(a, b, ...) assert_cmp(long, a, b, <=, \ >, "ld", __VA_ARGS__) #define assert_ld_ge(a, b, ...) assert_cmp(long, a, b, >=, \ <, "ld", __VA_ARGS__) #define assert_ld_gt(a, b, ...) assert_cmp(long, a, b, >, \ <=, "ld", __VA_ARGS__) #define assert_lu_eq(a, b, ...) assert_cmp(unsigned long, \ a, b, ==, !=, "lu", __VA_ARGS__) #define assert_lu_ne(a, b, ...) assert_cmp(unsigned long, \ a, b, !=, ==, "lu", __VA_ARGS__) #define assert_lu_lt(a, b, ...) assert_cmp(unsigned long, \ a, b, <, >=, "lu", __VA_ARGS__) #define assert_lu_le(a, b, ...) assert_cmp(unsigned long, \ a, b, <=, >, "lu", __VA_ARGS__) #define assert_lu_ge(a, b, ...) assert_cmp(unsigned long, \ a, b, >=, <, "lu", __VA_ARGS__) #define assert_lu_gt(a, b, ...) assert_cmp(unsigned long, \ a, b, >, <=, "lu", __VA_ARGS__) #define assert_qd_eq(a, b, ...) assert_cmp(long long, a, b, ==, \ !=, "qd", __VA_ARGS__) #define assert_qd_ne(a, b, ...) assert_cmp(long long, a, b, !=, \ ==, "qd", __VA_ARGS__) #define assert_qd_lt(a, b, ...) assert_cmp(long long, a, b, <, \ >=, "qd", __VA_ARGS__) #define assert_qd_le(a, b, ...) assert_cmp(long long, a, b, <=, \ >, "qd", __VA_ARGS__) #define assert_qd_ge(a, b, ...) assert_cmp(long long, a, b, >=, \ <, "qd", __VA_ARGS__) #define assert_qd_gt(a, b, ...) assert_cmp(long long, a, b, >, \ <=, "qd", __VA_ARGS__) #define assert_qu_eq(a, b, ...) assert_cmp(unsigned long long, \ a, b, ==, !=, "qu", __VA_ARGS__) #define assert_qu_ne(a, b, ...) assert_cmp(unsigned long long, \ a, b, !=, ==, "qu", __VA_ARGS__) #define assert_qu_lt(a, b, ...) assert_cmp(unsigned long long, \ a, b, <, >=, "qu", __VA_ARGS__) #define assert_qu_le(a, b, ...) assert_cmp(unsigned long long, \ a, b, <=, >, "qu", __VA_ARGS__) #define assert_qu_ge(a, b, ...) assert_cmp(unsigned long long, \ a, b, >=, <, "qu", __VA_ARGS__) #define assert_qu_gt(a, b, ...) assert_cmp(unsigned long long, \ a, b, >, <=, "qu", __VA_ARGS__) #define assert_jd_eq(a, b, ...) assert_cmp(intmax_t, a, b, ==, \ !=, "jd", __VA_ARGS__) #define assert_jd_ne(a, b, ...) assert_cmp(intmax_t, a, b, !=, \ ==, "jd", __VA_ARGS__) #define assert_jd_lt(a, b, ...) assert_cmp(intmax_t, a, b, <, \ >=, "jd", __VA_ARGS__) #define assert_jd_le(a, b, ...) assert_cmp(intmax_t, a, b, <=, \ >, "jd", __VA_ARGS__) #define assert_jd_ge(a, b, ...) assert_cmp(intmax_t, a, b, >=, \ <, "jd", __VA_ARGS__) #define assert_jd_gt(a, b, ...) assert_cmp(intmax_t, a, b, >, \ <=, "jd", __VA_ARGS__) #define assert_ju_eq(a, b, ...) assert_cmp(uintmax_t, a, b, ==, \ !=, "ju", __VA_ARGS__) #define assert_ju_ne(a, b, ...) assert_cmp(uintmax_t, a, b, !=, \ ==, "ju", __VA_ARGS__) #define assert_ju_lt(a, b, ...) assert_cmp(uintmax_t, a, b, <, \ >=, "ju", __VA_ARGS__) #define assert_ju_le(a, b, ...) assert_cmp(uintmax_t, a, b, <=, \ >, "ju", __VA_ARGS__) #define assert_ju_ge(a, b, ...) assert_cmp(uintmax_t, a, b, >=, \ <, "ju", __VA_ARGS__) #define assert_ju_gt(a, b, ...) assert_cmp(uintmax_t, a, b, >, \ <=, "ju", __VA_ARGS__) #define assert_zd_eq(a, b, ...) assert_cmp(ssize_t, a, b, ==, \ !=, "zd", __VA_ARGS__) #define assert_zd_ne(a, b, ...) assert_cmp(ssize_t, a, b, !=, \ ==, "zd", __VA_ARGS__) #define assert_zd_lt(a, b, ...) assert_cmp(ssize_t, a, b, <, \ >=, "zd", __VA_ARGS__) #define assert_zd_le(a, b, ...) assert_cmp(ssize_t, a, b, <=, \ >, "zd", __VA_ARGS__) #define assert_zd_ge(a, b, ...) assert_cmp(ssize_t, a, b, >=, \ <, "zd", __VA_ARGS__) #define assert_zd_gt(a, b, ...) assert_cmp(ssize_t, a, b, >, \ <=, "zd", __VA_ARGS__) #define assert_zu_eq(a, b, ...) assert_cmp(size_t, a, b, ==, \ !=, "zu", __VA_ARGS__) #define assert_zu_ne(a, b, ...) assert_cmp(size_t, a, b, !=, \ ==, "zu", __VA_ARGS__) #define assert_zu_lt(a, b, ...) assert_cmp(size_t, a, b, <, \ >=, "zu", __VA_ARGS__) #define assert_zu_le(a, b, ...) assert_cmp(size_t, a, b, <=, \ >, "zu", __VA_ARGS__) #define assert_zu_ge(a, b, ...) assert_cmp(size_t, a, b, >=, \ <, "zu", __VA_ARGS__) #define assert_zu_gt(a, b, ...) assert_cmp(size_t, a, b, >, \ <=, "zu", __VA_ARGS__) #define assert_d32_eq(a, b, ...) assert_cmp(int32_t, a, b, ==, \ !=, FMTd32, __VA_ARGS__) #define assert_d32_ne(a, b, ...) assert_cmp(int32_t, a, b, !=, \ ==, FMTd32, __VA_ARGS__) #define assert_d32_lt(a, b, ...) assert_cmp(int32_t, a, b, <, \ >=, FMTd32, __VA_ARGS__) #define assert_d32_le(a, b, ...) assert_cmp(int32_t, a, b, <=, \ >, FMTd32, __VA_ARGS__) #define assert_d32_ge(a, b, ...) assert_cmp(int32_t, a, b, >=, \ <, FMTd32, __VA_ARGS__) #define assert_d32_gt(a, b, ...) assert_cmp(int32_t, a, b, >, \ <=, FMTd32, __VA_ARGS__) #define assert_u32_eq(a, b, ...) assert_cmp(uint32_t, a, b, ==, \ !=, FMTu32, __VA_ARGS__) #define assert_u32_ne(a, b, ...) assert_cmp(uint32_t, a, b, !=, \ ==, FMTu32, __VA_ARGS__) #define assert_u32_lt(a, b, ...) assert_cmp(uint32_t, a, b, <, \ >=, FMTu32, __VA_ARGS__) #define assert_u32_le(a, b, ...) assert_cmp(uint32_t, a, b, <=, \ >, FMTu32, __VA_ARGS__) #define assert_u32_ge(a, b, ...) assert_cmp(uint32_t, a, b, >=, \ <, FMTu32, __VA_ARGS__) #define assert_u32_gt(a, b, ...) assert_cmp(uint32_t, a, b, >, \ <=, FMTu32, __VA_ARGS__) #define assert_d64_eq(a, b, ...) assert_cmp(int64_t, a, b, ==, \ !=, FMTd64, __VA_ARGS__) #define assert_d64_ne(a, b, ...) assert_cmp(int64_t, a, b, !=, \ ==, FMTd64, __VA_ARGS__) #define assert_d64_lt(a, b, ...) assert_cmp(int64_t, a, b, <, \ >=, FMTd64, __VA_ARGS__) #define assert_d64_le(a, b, ...) assert_cmp(int64_t, a, b, <=, \ >, FMTd64, __VA_ARGS__) #define assert_d64_ge(a, b, ...) assert_cmp(int64_t, a, b, >=, \ <, FMTd64, __VA_ARGS__) #define assert_d64_gt(a, b, ...) assert_cmp(int64_t, a, b, >, \ <=, FMTd64, __VA_ARGS__) #define assert_u64_eq(a, b, ...) assert_cmp(uint64_t, a, b, ==, \ !=, FMTu64, __VA_ARGS__) #define assert_u64_ne(a, b, ...) assert_cmp(uint64_t, a, b, !=, \ ==, FMTu64, __VA_ARGS__) #define assert_u64_lt(a, b, ...) assert_cmp(uint64_t, a, b, <, \ >=, FMTu64, __VA_ARGS__) #define assert_u64_le(a, b, ...) assert_cmp(uint64_t, a, b, <=, \ >, FMTu64, __VA_ARGS__) #define assert_u64_ge(a, b, ...) assert_cmp(uint64_t, a, b, >=, \ <, FMTu64, __VA_ARGS__) #define assert_u64_gt(a, b, ...) assert_cmp(uint64_t, a, b, >, \ <=, FMTu64, __VA_ARGS__) #define assert_b_eq(a, b, ...) verify_b_eq(true, a, b, __VA_ARGS__) #define assert_b_ne(a, b, ...) verify_b_ne(true, a, b, __VA_ARGS__) #define assert_true(a, ...) assert_b_eq(a, true, __VA_ARGS__) #define assert_false(a, ...) assert_b_eq(a, false, __VA_ARGS__) #define assert_str_eq(a, b, ...) verify_str_eq(true, a, b, __VA_ARGS__) #define assert_str_ne(a, b, ...) verify_str_ne(true, a, b, __VA_ARGS__) #define assert_not_reached(...) verify_not_reached(true, __VA_ARGS__) /* * If this enum changes, corresponding changes in test/test.sh.in are also * necessary. */ typedef enum { test_status_pass = 0, test_status_skip = 1, test_status_fail = 2, test_status_count = 3 } test_status_t; typedef void (test_t)(void); #define TEST_BEGIN(f) \ static void \ f(void) { \ p_test_init(#f); #define TEST_END \ goto label_test_end; \ label_test_end: \ p_test_fini(); \ } #define test(...) \ p_test(__VA_ARGS__, NULL) #define test_no_reentrancy(...) \ p_test_no_reentrancy(__VA_ARGS__, NULL) #define test_no_malloc_init(...) \ p_test_no_malloc_init(__VA_ARGS__, NULL) #define test_skip_if(e) do { \ if (e) { \ test_skip("%s:%s:%d: Test skipped: (%s)", \ __func__, __FILE__, __LINE__, #e); \ goto label_test_end; \ } \ } while (0) bool test_is_reentrant(); void test_skip(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2); void test_fail(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2); /* For private use by macros. */ test_status_t p_test(test_t *t, ...); test_status_t p_test_no_reentrancy(test_t *t, ...); test_status_t p_test_no_malloc_init(test_t *t, ...); void p_test_init(const char *name); void p_test_fini(void); void p_test_fail(const char *prefix, const char *message); redis-8.0.2/deps/jemalloc/test/include/test/thd.h000066400000000000000000000003401501533116600216570ustar00rootroot00000000000000/* Abstraction layer for threading in tests. */ #ifdef _WIN32 typedef HANDLE thd_t; #else typedef pthread_t thd_t; #endif void thd_create(thd_t *thd, void *(*proc)(void *), void *arg); void thd_join(thd_t thd, void **ret); redis-8.0.2/deps/jemalloc/test/include/test/timer.h000066400000000000000000000004701501533116600222240ustar00rootroot00000000000000/* Simple timer, for use in benchmark reporting. */ typedef struct { nstime_t t0; nstime_t t1; } timedelta_t; void timer_start(timedelta_t *timer); void timer_stop(timedelta_t *timer); uint64_t timer_usec(const timedelta_t *timer); void timer_ratio(timedelta_t *a, timedelta_t *b, char *buf, size_t buflen); redis-8.0.2/deps/jemalloc/test/integration/000077500000000000000000000000001501533116600206535ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/integration/MALLOCX_ARENA.c000066400000000000000000000026411501533116600230270ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define NTHREADS 10 static bool have_dss = #ifdef JEMALLOC_DSS true #else false #endif ; void * thd_start(void *arg) { unsigned thread_ind = (unsigned)(uintptr_t)arg; unsigned arena_ind; void *p; size_t sz; sz = sizeof(arena_ind); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Error in arenas.create"); if (thread_ind % 4 != 3) { size_t mib[3]; size_t miblen = sizeof(mib) / sizeof(size_t); const char *dss_precs[] = {"disabled", "primary", "secondary"}; unsigned prec_ind = thread_ind % (sizeof(dss_precs)/sizeof(char*)); const char *dss = dss_precs[prec_ind]; int expected_err = (have_dss || prec_ind == 0) ? 0 : EFAULT; expect_d_eq(mallctlnametomib("arena.0.dss", mib, &miblen), 0, "Error in mallctlnametomib()"); mib[1] = arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&dss, sizeof(const char *)), expected_err, "Error in mallctlbymib()"); } p = mallocx(1, MALLOCX_ARENA(arena_ind)); expect_ptr_not_null(p, "Unexpected mallocx() error"); dallocx(p, 0); return NULL; } TEST_BEGIN(test_MALLOCX_ARENA) { thd_t thds[NTHREADS]; unsigned i; for (i = 0; i < NTHREADS; i++) { thd_create(&thds[i], thd_start, (void *)(uintptr_t)i); } for (i = 0; i < NTHREADS; i++) { thd_join(thds[i], NULL); } } TEST_END int main(void) { return test( test_MALLOCX_ARENA); } redis-8.0.2/deps/jemalloc/test/integration/aligned_alloc.c000066400000000000000000000072461501533116600236050ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define MAXALIGN (((size_t)1) << 23) /* * On systems which can't merge extents, tests that call this function generate * a lot of dirty memory very quickly. Purging between cycles mitigates * potential OOM on e.g. 32-bit Windows. */ static void purge(void) { expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl error"); } TEST_BEGIN(test_alignment_errors) { size_t alignment; void *p; alignment = 0; set_errno(0); p = aligned_alloc(alignment, 1); expect_false(p != NULL || get_errno() != EINVAL, "Expected error for invalid alignment %zu", alignment); for (alignment = sizeof(size_t); alignment < MAXALIGN; alignment <<= 1) { set_errno(0); p = aligned_alloc(alignment + 1, 1); expect_false(p != NULL || get_errno() != EINVAL, "Expected error for invalid alignment %zu", alignment + 1); } } TEST_END /* * GCC "-Walloc-size-larger-than" warning detects when one of the memory * allocation functions is called with a size larger than the maximum size that * they support. Here we want to explicitly test that the allocation functions * do indeed fail properly when this is the case, which triggers the warning. * Therefore we disable the warning for these tests. */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN TEST_BEGIN(test_oom_errors) { size_t alignment, size; void *p; #if LG_SIZEOF_PTR == 3 alignment = UINT64_C(0x8000000000000000); size = UINT64_C(0x8000000000000000); #else alignment = 0x80000000LU; size = 0x80000000LU; #endif set_errno(0); p = aligned_alloc(alignment, size); expect_false(p != NULL || get_errno() != ENOMEM, "Expected error for aligned_alloc(%zu, %zu)", alignment, size); #if LG_SIZEOF_PTR == 3 alignment = UINT64_C(0x4000000000000000); size = UINT64_C(0xc000000000000001); #else alignment = 0x40000000LU; size = 0xc0000001LU; #endif set_errno(0); p = aligned_alloc(alignment, size); expect_false(p != NULL || get_errno() != ENOMEM, "Expected error for aligned_alloc(%zu, %zu)", alignment, size); alignment = 0x10LU; #if LG_SIZEOF_PTR == 3 size = UINT64_C(0xfffffffffffffff0); #else size = 0xfffffff0LU; #endif set_errno(0); p = aligned_alloc(alignment, size); expect_false(p != NULL || get_errno() != ENOMEM, "Expected error for aligned_alloc(&p, %zu, %zu)", alignment, size); } TEST_END /* Re-enable the "-Walloc-size-larger-than=" warning */ JEMALLOC_DIAGNOSTIC_POP TEST_BEGIN(test_alignment_and_size) { #define NITER 4 size_t alignment, size, total; unsigned i; void *ps[NITER]; for (i = 0; i < NITER; i++) { ps[i] = NULL; } for (alignment = 8; alignment <= MAXALIGN; alignment <<= 1) { total = 0; for (size = 1; size < 3 * alignment && size < (1U << 31); size += (alignment >> (LG_SIZEOF_PTR-1)) - 1) { for (i = 0; i < NITER; i++) { ps[i] = aligned_alloc(alignment, size); if (ps[i] == NULL) { char buf[BUFERROR_BUF]; buferror(get_errno(), buf, sizeof(buf)); test_fail( "Error for alignment=%zu, " "size=%zu (%#zx): %s", alignment, size, size, buf); } total += TEST_MALLOC_SIZE(ps[i]); if (total >= (MAXALIGN << 1)) { break; } } for (i = 0; i < NITER; i++) { if (ps[i] != NULL) { free(ps[i]); ps[i] = NULL; } } } purge(); } #undef NITER } TEST_END TEST_BEGIN(test_zero_alloc) { void *res = aligned_alloc(8, 0); assert(res); size_t usable = TEST_MALLOC_SIZE(res); assert(usable > 0); free(res); } TEST_END int main(void) { return test( test_alignment_errors, test_oom_errors, test_alignment_and_size, test_zero_alloc); } redis-8.0.2/deps/jemalloc/test/integration/allocated.c000066400000000000000000000060001501533116600227430ustar00rootroot00000000000000#include "test/jemalloc_test.h" static const bool config_stats = #ifdef JEMALLOC_STATS true #else false #endif ; void * thd_start(void *arg) { int err; void *p; uint64_t a0, a1, d0, d1; uint64_t *ap0, *ap1, *dp0, *dp1; size_t sz, usize; sz = sizeof(a0); if ((err = mallctl("thread.allocated", (void *)&a0, &sz, NULL, 0))) { if (err == ENOENT) { goto label_ENOENT; } test_fail("%s(): Error in mallctl(): %s", __func__, strerror(err)); } sz = sizeof(ap0); if ((err = mallctl("thread.allocatedp", (void *)&ap0, &sz, NULL, 0))) { if (err == ENOENT) { goto label_ENOENT; } test_fail("%s(): Error in mallctl(): %s", __func__, strerror(err)); } expect_u64_eq(*ap0, a0, "\"thread.allocatedp\" should provide a pointer to internal " "storage"); sz = sizeof(d0); if ((err = mallctl("thread.deallocated", (void *)&d0, &sz, NULL, 0))) { if (err == ENOENT) { goto label_ENOENT; } test_fail("%s(): Error in mallctl(): %s", __func__, strerror(err)); } sz = sizeof(dp0); if ((err = mallctl("thread.deallocatedp", (void *)&dp0, &sz, NULL, 0))) { if (err == ENOENT) { goto label_ENOENT; } test_fail("%s(): Error in mallctl(): %s", __func__, strerror(err)); } expect_u64_eq(*dp0, d0, "\"thread.deallocatedp\" should provide a pointer to internal " "storage"); p = malloc(1); expect_ptr_not_null(p, "Unexpected malloc() error"); sz = sizeof(a1); mallctl("thread.allocated", (void *)&a1, &sz, NULL, 0); sz = sizeof(ap1); mallctl("thread.allocatedp", (void *)&ap1, &sz, NULL, 0); expect_u64_eq(*ap1, a1, "Dereferenced \"thread.allocatedp\" value should equal " "\"thread.allocated\" value"); expect_ptr_eq(ap0, ap1, "Pointer returned by \"thread.allocatedp\" should not change"); usize = TEST_MALLOC_SIZE(p); expect_u64_le(a0 + usize, a1, "Allocated memory counter should increase by at least the amount " "explicitly allocated"); free(p); sz = sizeof(d1); mallctl("thread.deallocated", (void *)&d1, &sz, NULL, 0); sz = sizeof(dp1); mallctl("thread.deallocatedp", (void *)&dp1, &sz, NULL, 0); expect_u64_eq(*dp1, d1, "Dereferenced \"thread.deallocatedp\" value should equal " "\"thread.deallocated\" value"); expect_ptr_eq(dp0, dp1, "Pointer returned by \"thread.deallocatedp\" should not change"); expect_u64_le(d0 + usize, d1, "Deallocated memory counter should increase by at least the amount " "explicitly deallocated"); return NULL; label_ENOENT: expect_false(config_stats, "ENOENT should only be returned if stats are disabled"); test_skip("\"thread.allocated\" mallctl not available"); return NULL; } TEST_BEGIN(test_main_thread) { thd_start(NULL); } TEST_END TEST_BEGIN(test_subthread) { thd_t thd; thd_create(&thd, thd_start, NULL); thd_join(thd, NULL); } TEST_END int main(void) { /* Run tests multiple times to check for bad interactions. */ return test( test_main_thread, test_subthread, test_main_thread, test_subthread, test_main_thread); } redis-8.0.2/deps/jemalloc/test/integration/cpp/000077500000000000000000000000001501533116600214355ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/integration/cpp/basic.cpp000066400000000000000000000006331501533116600232240ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_basic) { auto foo = new long(4); expect_ptr_not_null(foo, "Unexpected new[] failure"); delete foo; // Test nullptr handling. foo = nullptr; delete foo; auto bar = new long; expect_ptr_not_null(bar, "Unexpected new failure"); delete bar; // Test nullptr handling. bar = nullptr; delete bar; } TEST_END int main() { return test( test_basic); } redis-8.0.2/deps/jemalloc/test/integration/cpp/infallible_new_false.cpp000066400000000000000000000006171501533116600262710ustar00rootroot00000000000000#include #include "test/jemalloc_test.h" TEST_BEGIN(test_failing_alloc) { bool saw_exception = false; try { /* Too big of an allocation to succeed. */ void *volatile ptr = ::operator new((size_t)-1); (void)ptr; } catch (...) { saw_exception = true; } expect_true(saw_exception, "Didn't get a failure"); } TEST_END int main(void) { return test( test_failing_alloc); } redis-8.0.2/deps/jemalloc/test/integration/cpp/infallible_new_false.sh000066400000000000000000000002541501533116600261160ustar00rootroot00000000000000#!/bin/sh XMALLOC_STR="" if [ "x${enable_xmalloc}" = "x1" ] ; then XMALLOC_STR="xmalloc:false," fi export MALLOC_CONF="${XMALLOC_STR}experimental_infallible_new:false" redis-8.0.2/deps/jemalloc/test/integration/cpp/infallible_new_true.cpp000066400000000000000000000035321501533116600261550ustar00rootroot00000000000000#include #include "test/jemalloc_test.h" /* * We can't test C++ in unit tests. In order to intercept abort, use a secret * safety check abort hook in integration tests. */ typedef void (*abort_hook_t)(const char *message); bool fake_abort_called; void fake_abort(const char *message) { if (strcmp(message, ": Allocation failed and " "opt.experimental_infallible_new is true. Aborting.\n") != 0) { abort(); } fake_abort_called = true; } static bool own_operator_new(void) { uint64_t before, after; size_t sz = sizeof(before); /* thread.allocated is always available, even w/o config_stats. */ expect_d_eq(mallctl("thread.allocated", (void *)&before, &sz, NULL, 0), 0, "Unexpected mallctl failure reading stats"); void *volatile ptr = ::operator new((size_t)8); expect_ptr_not_null(ptr, "Unexpected allocation failure"); expect_d_eq(mallctl("thread.allocated", (void *)&after, &sz, NULL, 0), 0, "Unexpected mallctl failure reading stats"); return (after != before); } TEST_BEGIN(test_failing_alloc) { abort_hook_t abort_hook = &fake_abort; expect_d_eq(mallctl("experimental.hooks.safety_check_abort", NULL, NULL, (void *)&abort_hook, sizeof(abort_hook)), 0, "Unexpected mallctl failure setting abort hook"); /* * Not owning operator new is only expected to happen on MinGW which * does not support operator new / delete replacement. */ #ifdef _WIN32 test_skip_if(!own_operator_new()); #else expect_true(own_operator_new(), "No operator new overload"); #endif void *volatile ptr = (void *)1; try { /* Too big of an allocation to succeed. */ ptr = ::operator new((size_t)-1); } catch (...) { abort(); } expect_ptr_null(ptr, "Allocation should have failed"); expect_b_eq(fake_abort_called, true, "Abort hook not invoked"); } TEST_END int main(void) { return test( test_failing_alloc); } redis-8.0.2/deps/jemalloc/test/integration/cpp/infallible_new_true.sh000066400000000000000000000002531501533116600260020ustar00rootroot00000000000000#!/bin/sh XMALLOC_STR="" if [ "x${enable_xmalloc}" = "x1" ] ; then XMALLOC_STR="xmalloc:false," fi export MALLOC_CONF="${XMALLOC_STR}experimental_infallible_new:true" redis-8.0.2/deps/jemalloc/test/integration/extent.c000066400000000000000000000221401501533116600223250ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/extent_hooks.h" #include "jemalloc/internal/arena_types.h" static void test_extent_body(unsigned arena_ind) { void *p; size_t large0, large1, large2, sz; size_t purge_mib[3]; size_t purge_miblen; int flags; bool xallocx_success_a, xallocx_success_b, xallocx_success_c; flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; /* Get large size classes. */ sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL, 0), 0, "Unexpected arenas.lextent.0.size failure"); expect_d_eq(mallctl("arenas.lextent.1.size", (void *)&large1, &sz, NULL, 0), 0, "Unexpected arenas.lextent.1.size failure"); expect_d_eq(mallctl("arenas.lextent.2.size", (void *)&large2, &sz, NULL, 0), 0, "Unexpected arenas.lextent.2.size failure"); /* Test dalloc/decommit/purge cascade. */ purge_miblen = sizeof(purge_mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.purge", purge_mib, &purge_miblen), 0, "Unexpected mallctlnametomib() failure"); purge_mib[1] = (size_t)arena_ind; called_alloc = false; try_alloc = true; try_dalloc = false; try_decommit = false; p = mallocx(large0 * 2, flags); expect_ptr_not_null(p, "Unexpected mallocx() error"); expect_true(called_alloc, "Expected alloc call"); called_dalloc = false; called_decommit = false; did_purge_lazy = false; did_purge_forced = false; called_split = false; xallocx_success_a = (xallocx(p, large0, 0, flags) == large0); expect_d_eq(mallctlbymib(purge_mib, purge_miblen, NULL, NULL, NULL, 0), 0, "Unexpected arena.%u.purge error", arena_ind); if (xallocx_success_a) { expect_true(called_dalloc, "Expected dalloc call"); expect_true(called_decommit, "Expected decommit call"); expect_true(did_purge_lazy || did_purge_forced, "Expected purge"); expect_true(called_split, "Expected split call"); } dallocx(p, flags); try_dalloc = true; /* Test decommit/commit and observe split/merge. */ try_dalloc = false; try_decommit = true; p = mallocx(large0 * 2, flags); expect_ptr_not_null(p, "Unexpected mallocx() error"); did_decommit = false; did_commit = false; called_split = false; did_split = false; did_merge = false; xallocx_success_b = (xallocx(p, large0, 0, flags) == large0); expect_d_eq(mallctlbymib(purge_mib, purge_miblen, NULL, NULL, NULL, 0), 0, "Unexpected arena.%u.purge error", arena_ind); if (xallocx_success_b) { expect_true(did_split, "Expected split"); } xallocx_success_c = (xallocx(p, large0 * 2, 0, flags) == large0 * 2); if (did_split) { expect_b_eq(did_decommit, did_commit, "Expected decommit/commit match"); } if (xallocx_success_b && xallocx_success_c) { expect_true(did_merge, "Expected merge"); } dallocx(p, flags); try_dalloc = true; try_decommit = false; /* Make sure non-large allocation succeeds. */ p = mallocx(42, flags); expect_ptr_not_null(p, "Unexpected mallocx() error"); dallocx(p, flags); } static void test_manual_hook_auto_arena(void) { unsigned narenas; size_t old_size, new_size, sz; size_t hooks_mib[3]; size_t hooks_miblen; extent_hooks_t *new_hooks, *old_hooks; extent_hooks_prep(); sz = sizeof(unsigned); /* Get number of auto arenas. */ expect_d_eq(mallctl("opt.narenas", (void *)&narenas, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); if (narenas == 1) { return; } /* Install custom extent hooks on arena 1 (might not be initialized). */ hooks_miblen = sizeof(hooks_mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.extent_hooks", hooks_mib, &hooks_miblen), 0, "Unexpected mallctlnametomib() failure"); hooks_mib[1] = 1; old_size = sizeof(extent_hooks_t *); new_hooks = &hooks; new_size = sizeof(extent_hooks_t *); expect_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks, &old_size, (void *)&new_hooks, new_size), 0, "Unexpected extent_hooks error"); static bool auto_arena_created = false; if (old_hooks != &hooks) { expect_b_eq(auto_arena_created, false, "Expected auto arena 1 created only once."); auto_arena_created = true; } } static void test_manual_hook_body(void) { unsigned arena_ind; size_t old_size, new_size, sz; size_t hooks_mib[3]; size_t hooks_miblen; extent_hooks_t *new_hooks, *old_hooks; extent_hooks_prep(); sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); /* Install custom extent hooks. */ hooks_miblen = sizeof(hooks_mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.extent_hooks", hooks_mib, &hooks_miblen), 0, "Unexpected mallctlnametomib() failure"); hooks_mib[1] = (size_t)arena_ind; old_size = sizeof(extent_hooks_t *); new_hooks = &hooks; new_size = sizeof(extent_hooks_t *); expect_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks, &old_size, (void *)&new_hooks, new_size), 0, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->alloc, extent_alloc_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->dalloc, extent_dalloc_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->commit, extent_commit_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->decommit, extent_decommit_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->purge_lazy, extent_purge_lazy_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->purge_forced, extent_purge_forced_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->split, extent_split_hook, "Unexpected extent_hooks error"); expect_ptr_ne(old_hooks->merge, extent_merge_hook, "Unexpected extent_hooks error"); if (!is_background_thread_enabled()) { test_extent_body(arena_ind); } /* Restore extent hooks. */ expect_d_eq(mallctlbymib(hooks_mib, hooks_miblen, NULL, NULL, (void *)&old_hooks, new_size), 0, "Unexpected extent_hooks error"); expect_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks, &old_size, NULL, 0), 0, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks, default_hooks, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->alloc, default_hooks->alloc, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->dalloc, default_hooks->dalloc, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->commit, default_hooks->commit, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->decommit, default_hooks->decommit, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->purge_lazy, default_hooks->purge_lazy, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->purge_forced, default_hooks->purge_forced, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->split, default_hooks->split, "Unexpected extent_hooks error"); expect_ptr_eq(old_hooks->merge, default_hooks->merge, "Unexpected extent_hooks error"); } TEST_BEGIN(test_extent_manual_hook) { test_manual_hook_auto_arena(); test_manual_hook_body(); /* Test failure paths. */ try_split = false; test_manual_hook_body(); try_merge = false; test_manual_hook_body(); try_purge_lazy = false; try_purge_forced = false; test_manual_hook_body(); try_split = try_merge = try_purge_lazy = try_purge_forced = true; } TEST_END TEST_BEGIN(test_extent_auto_hook) { unsigned arena_ind; size_t new_size, sz; extent_hooks_t *new_hooks; extent_hooks_prep(); sz = sizeof(unsigned); new_hooks = &hooks; new_size = sizeof(extent_hooks_t *); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, (void *)&new_hooks, new_size), 0, "Unexpected mallctl() failure"); test_skip_if(is_background_thread_enabled()); test_extent_body(arena_ind); } TEST_END static void test_arenas_create_ext_base(arena_config_t config, bool expect_hook_data, bool expect_hook_metadata) { unsigned arena, arena1; void *ptr; size_t sz = sizeof(unsigned); extent_hooks_prep(); called_alloc = false; expect_d_eq(mallctl("experimental.arenas_create_ext", (void *)&arena, &sz, &config, sizeof(arena_config_t)), 0, "Unexpected mallctl() failure"); expect_b_eq(called_alloc, expect_hook_metadata, "expected hook metadata alloc mismatch"); called_alloc = false; ptr = mallocx(42, MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE); expect_b_eq(called_alloc, expect_hook_data, "expected hook data alloc mismatch"); expect_ptr_not_null(ptr, "Unexpected mallocx() failure"); expect_d_eq(mallctl("arenas.lookup", &arena1, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_eq(arena, arena1, "Unexpected arena index"); dallocx(ptr, 0); } TEST_BEGIN(test_arenas_create_ext_with_ehooks_no_metadata) { arena_config_t config; config.extent_hooks = &hooks; config.metadata_use_hooks = false; test_arenas_create_ext_base(config, true, false); } TEST_END TEST_BEGIN(test_arenas_create_ext_with_ehooks_with_metadata) { arena_config_t config; config.extent_hooks = &hooks; config.metadata_use_hooks = true; test_arenas_create_ext_base(config, true, true); } TEST_END int main(void) { return test( test_extent_manual_hook, test_extent_auto_hook, test_arenas_create_ext_with_ehooks_no_metadata, test_arenas_create_ext_with_ehooks_with_metadata); } redis-8.0.2/deps/jemalloc/test/integration/extent.sh000066400000000000000000000001271501533116600225160ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="junk:false" fi redis-8.0.2/deps/jemalloc/test/integration/malloc.c000066400000000000000000000003621501533116600222670ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_zero_alloc) { void *res = malloc(0); assert(res); size_t usable = TEST_MALLOC_SIZE(res); assert(usable > 0); free(res); } TEST_END int main(void) { return test( test_zero_alloc); } redis-8.0.2/deps/jemalloc/test/integration/mallocx.c000066400000000000000000000157711501533116600224710ustar00rootroot00000000000000#include "test/jemalloc_test.h" static unsigned get_nsizes_impl(const char *cmd) { unsigned ret; size_t z; z = sizeof(unsigned); expect_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctl(\"%s\", ...) failure", cmd); return ret; } static unsigned get_nlarge(void) { return get_nsizes_impl("arenas.nlextents"); } static size_t get_size_impl(const char *cmd, size_t ind) { size_t ret; size_t z; size_t mib[4]; size_t miblen = 4; z = sizeof(size_t); expect_d_eq(mallctlnametomib(cmd, mib, &miblen), 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd); mib[2] = ind; z = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind); return ret; } static size_t get_large_size(size_t ind) { return get_size_impl("arenas.lextent.0.size", ind); } /* * On systems which can't merge extents, tests that call this function generate * a lot of dirty memory very quickly. Purging between cycles mitigates * potential OOM on e.g. 32-bit Windows. */ static void purge(void) { expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl error"); } /* * GCC "-Walloc-size-larger-than" warning detects when one of the memory * allocation functions is called with a size larger than the maximum size that * they support. Here we want to explicitly test that the allocation functions * do indeed fail properly when this is the case, which triggers the warning. * Therefore we disable the warning for these tests. */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN TEST_BEGIN(test_overflow) { size_t largemax; largemax = get_large_size(get_nlarge()-1); expect_ptr_null(mallocx(largemax+1, 0), "Expected OOM for mallocx(size=%#zx, 0)", largemax+1); expect_ptr_null(mallocx(ZU(PTRDIFF_MAX)+1, 0), "Expected OOM for mallocx(size=%#zx, 0)", ZU(PTRDIFF_MAX)+1); expect_ptr_null(mallocx(SIZE_T_MAX, 0), "Expected OOM for mallocx(size=%#zx, 0)", SIZE_T_MAX); expect_ptr_null(mallocx(1, MALLOCX_ALIGN(ZU(PTRDIFF_MAX)+1)), "Expected OOM for mallocx(size=1, MALLOCX_ALIGN(%#zx))", ZU(PTRDIFF_MAX)+1); } TEST_END static void * remote_alloc(void *arg) { unsigned arena; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); size_t large_sz; sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&large_sz, &sz, NULL, 0), 0, "Unexpected mallctl failure"); void *ptr = mallocx(large_sz, MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE); void **ret = (void **)arg; *ret = ptr; return NULL; } TEST_BEGIN(test_remote_free) { thd_t thd; void *ret; thd_create(&thd, remote_alloc, (void *)&ret); thd_join(thd, NULL); expect_ptr_not_null(ret, "Unexpected mallocx failure"); /* Avoid TCACHE_NONE to explicitly test tcache_flush(). */ dallocx(ret, 0); mallctl("thread.tcache.flush", NULL, NULL, NULL, 0); } TEST_END TEST_BEGIN(test_oom) { size_t largemax; bool oom; void *ptrs[3]; unsigned i; /* * It should be impossible to allocate three objects that each consume * nearly half the virtual address space. */ largemax = get_large_size(get_nlarge()-1); oom = false; for (i = 0; i < sizeof(ptrs) / sizeof(void *); i++) { ptrs[i] = mallocx(largemax, MALLOCX_ARENA(0)); if (ptrs[i] == NULL) { oom = true; } } expect_true(oom, "Expected OOM during series of calls to mallocx(size=%zu, 0)", largemax); for (i = 0; i < sizeof(ptrs) / sizeof(void *); i++) { if (ptrs[i] != NULL) { dallocx(ptrs[i], 0); } } purge(); #if LG_SIZEOF_PTR == 3 expect_ptr_null(mallocx(0x8000000000000000ULL, MALLOCX_ALIGN(0x8000000000000000ULL)), "Expected OOM for mallocx()"); expect_ptr_null(mallocx(0x8000000000000000ULL, MALLOCX_ALIGN(0x80000000)), "Expected OOM for mallocx()"); #else expect_ptr_null(mallocx(0x80000000UL, MALLOCX_ALIGN(0x80000000UL)), "Expected OOM for mallocx()"); #endif } TEST_END /* Re-enable the "-Walloc-size-larger-than=" warning */ JEMALLOC_DIAGNOSTIC_POP TEST_BEGIN(test_basic) { #define MAXSZ (((size_t)1) << 23) size_t sz; for (sz = 1; sz < MAXSZ; sz = nallocx(sz, 0) + 1) { size_t nsz, rsz; void *p; nsz = nallocx(sz, 0); expect_zu_ne(nsz, 0, "Unexpected nallocx() error"); p = mallocx(sz, 0); expect_ptr_not_null(p, "Unexpected mallocx(size=%zx, flags=0) error", sz); rsz = sallocx(p, 0); expect_zu_ge(rsz, sz, "Real size smaller than expected"); expect_zu_eq(nsz, rsz, "nallocx()/sallocx() size mismatch"); dallocx(p, 0); p = mallocx(sz, 0); expect_ptr_not_null(p, "Unexpected mallocx(size=%zx, flags=0) error", sz); dallocx(p, 0); nsz = nallocx(sz, MALLOCX_ZERO); expect_zu_ne(nsz, 0, "Unexpected nallocx() error"); p = mallocx(sz, MALLOCX_ZERO); expect_ptr_not_null(p, "Unexpected mallocx(size=%zx, flags=MALLOCX_ZERO) error", nsz); rsz = sallocx(p, 0); expect_zu_eq(nsz, rsz, "nallocx()/sallocx() rsize mismatch"); dallocx(p, 0); purge(); } #undef MAXSZ } TEST_END TEST_BEGIN(test_alignment_and_size) { const char *percpu_arena; size_t sz = sizeof(percpu_arena); if(mallctl("opt.percpu_arena", (void *)&percpu_arena, &sz, NULL, 0) || strcmp(percpu_arena, "disabled") != 0) { test_skip("test_alignment_and_size skipped: " "not working with percpu arena."); }; #define MAXALIGN (((size_t)1) << 23) #define NITER 4 size_t nsz, rsz, alignment, total; unsigned i; void *ps[NITER]; for (i = 0; i < NITER; i++) { ps[i] = NULL; } for (alignment = 8; alignment <= MAXALIGN; alignment <<= 1) { total = 0; for (sz = 1; sz < 3 * alignment && sz < (1U << 31); sz += (alignment >> (LG_SIZEOF_PTR-1)) - 1) { for (i = 0; i < NITER; i++) { nsz = nallocx(sz, MALLOCX_ALIGN(alignment) | MALLOCX_ZERO | MALLOCX_ARENA(0)); expect_zu_ne(nsz, 0, "nallocx() error for alignment=%zu, " "size=%zu (%#zx)", alignment, sz, sz); ps[i] = mallocx(sz, MALLOCX_ALIGN(alignment) | MALLOCX_ZERO | MALLOCX_ARENA(0)); expect_ptr_not_null(ps[i], "mallocx() error for alignment=%zu, " "size=%zu (%#zx)", alignment, sz, sz); rsz = sallocx(ps[i], 0); expect_zu_ge(rsz, sz, "Real size smaller than expected for " "alignment=%zu, size=%zu", alignment, sz); expect_zu_eq(nsz, rsz, "nallocx()/sallocx() size mismatch for " "alignment=%zu, size=%zu", alignment, sz); expect_ptr_null( (void *)((uintptr_t)ps[i] & (alignment-1)), "%p inadequately aligned for" " alignment=%zu, size=%zu", ps[i], alignment, sz); total += rsz; if (total >= (MAXALIGN << 1)) { break; } } for (i = 0; i < NITER; i++) { if (ps[i] != NULL) { dallocx(ps[i], 0); ps[i] = NULL; } } } purge(); } #undef MAXALIGN #undef NITER } TEST_END int main(void) { return test( test_overflow, test_oom, test_remote_free, test_basic, test_alignment_and_size); } redis-8.0.2/deps/jemalloc/test/integration/mallocx.sh000066400000000000000000000001271501533116600226460ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="junk:false" fi redis-8.0.2/deps/jemalloc/test/integration/overflow.c000066400000000000000000000035561501533116600226730ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * GCC "-Walloc-size-larger-than" warning detects when one of the memory * allocation functions is called with a size larger than the maximum size that * they support. Here we want to explicitly test that the allocation functions * do indeed fail properly when this is the case, which triggers the warning. * Therefore we disable the warning for these tests. */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN TEST_BEGIN(test_overflow) { unsigned nlextents; size_t mib[4]; size_t sz, miblen, max_size_class; void *p; sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.nlextents", (void *)&nlextents, &sz, NULL, 0), 0, "Unexpected mallctl() error"); miblen = sizeof(mib) / sizeof(size_t); expect_d_eq(mallctlnametomib("arenas.lextent.0.size", mib, &miblen), 0, "Unexpected mallctlnametomib() error"); mib[2] = nlextents - 1; sz = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&max_size_class, &sz, NULL, 0), 0, "Unexpected mallctlbymib() error"); expect_ptr_null(malloc(max_size_class + 1), "Expected OOM due to over-sized allocation request"); expect_ptr_null(malloc(SIZE_T_MAX), "Expected OOM due to over-sized allocation request"); expect_ptr_null(calloc(1, max_size_class + 1), "Expected OOM due to over-sized allocation request"); expect_ptr_null(calloc(1, SIZE_T_MAX), "Expected OOM due to over-sized allocation request"); p = malloc(1); expect_ptr_not_null(p, "Unexpected malloc() OOM"); expect_ptr_null(realloc(p, max_size_class + 1), "Expected OOM due to over-sized allocation request"); expect_ptr_null(realloc(p, SIZE_T_MAX), "Expected OOM due to over-sized allocation request"); free(p); } TEST_END /* Re-enable the "-Walloc-size-larger-than=" warning */ JEMALLOC_DIAGNOSTIC_POP int main(void) { return test( test_overflow); } redis-8.0.2/deps/jemalloc/test/integration/posix_memalign.c000066400000000000000000000055321501533116600240370ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define MAXALIGN (((size_t)1) << 23) /* * On systems which can't merge extents, tests that call this function generate * a lot of dirty memory very quickly. Purging between cycles mitigates * potential OOM on e.g. 32-bit Windows. */ static void purge(void) { expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl error"); } TEST_BEGIN(test_alignment_errors) { size_t alignment; void *p; for (alignment = 0; alignment < sizeof(void *); alignment++) { expect_d_eq(posix_memalign(&p, alignment, 1), EINVAL, "Expected error for invalid alignment %zu", alignment); } for (alignment = sizeof(size_t); alignment < MAXALIGN; alignment <<= 1) { expect_d_ne(posix_memalign(&p, alignment + 1, 1), 0, "Expected error for invalid alignment %zu", alignment + 1); } } TEST_END TEST_BEGIN(test_oom_errors) { size_t alignment, size; void *p; #if LG_SIZEOF_PTR == 3 alignment = UINT64_C(0x8000000000000000); size = UINT64_C(0x8000000000000000); #else alignment = 0x80000000LU; size = 0x80000000LU; #endif expect_d_ne(posix_memalign(&p, alignment, size), 0, "Expected error for posix_memalign(&p, %zu, %zu)", alignment, size); #if LG_SIZEOF_PTR == 3 alignment = UINT64_C(0x4000000000000000); size = UINT64_C(0xc000000000000001); #else alignment = 0x40000000LU; size = 0xc0000001LU; #endif expect_d_ne(posix_memalign(&p, alignment, size), 0, "Expected error for posix_memalign(&p, %zu, %zu)", alignment, size); alignment = 0x10LU; #if LG_SIZEOF_PTR == 3 size = UINT64_C(0xfffffffffffffff0); #else size = 0xfffffff0LU; #endif expect_d_ne(posix_memalign(&p, alignment, size), 0, "Expected error for posix_memalign(&p, %zu, %zu)", alignment, size); } TEST_END TEST_BEGIN(test_alignment_and_size) { #define NITER 4 size_t alignment, size, total; unsigned i; int err; void *ps[NITER]; for (i = 0; i < NITER; i++) { ps[i] = NULL; } for (alignment = 8; alignment <= MAXALIGN; alignment <<= 1) { total = 0; for (size = 0; size < 3 * alignment && size < (1U << 31); size += ((size == 0) ? 1 : (alignment >> (LG_SIZEOF_PTR-1)) - 1)) { for (i = 0; i < NITER; i++) { err = posix_memalign(&ps[i], alignment, size); if (err) { char buf[BUFERROR_BUF]; buferror(get_errno(), buf, sizeof(buf)); test_fail( "Error for alignment=%zu, " "size=%zu (%#zx): %s", alignment, size, size, buf); } total += TEST_MALLOC_SIZE(ps[i]); if (total >= (MAXALIGN << 1)) { break; } } for (i = 0; i < NITER; i++) { if (ps[i] != NULL) { free(ps[i]); ps[i] = NULL; } } } purge(); } #undef NITER } TEST_END int main(void) { return test( test_alignment_errors, test_oom_errors, test_alignment_and_size); } redis-8.0.2/deps/jemalloc/test/integration/rallocx.c000066400000000000000000000173501501533116600224710ustar00rootroot00000000000000#include "test/jemalloc_test.h" static unsigned get_nsizes_impl(const char *cmd) { unsigned ret; size_t z; z = sizeof(unsigned); expect_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctl(\"%s\", ...) failure", cmd); return ret; } static unsigned get_nlarge(void) { return get_nsizes_impl("arenas.nlextents"); } static size_t get_size_impl(const char *cmd, size_t ind) { size_t ret; size_t z; size_t mib[4]; size_t miblen = 4; z = sizeof(size_t); expect_d_eq(mallctlnametomib(cmd, mib, &miblen), 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd); mib[2] = ind; z = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind); return ret; } static size_t get_large_size(size_t ind) { return get_size_impl("arenas.lextent.0.size", ind); } TEST_BEGIN(test_grow_and_shrink) { /* * Use volatile to workaround buffer overflow false positives * (-D_FORTIFY_SOURCE=3). */ void *volatile p, *volatile q; size_t tsz; #define NCYCLES 3 unsigned i, j; #define NSZS 1024 size_t szs[NSZS]; #define MAXSZ ZU(12 * 1024 * 1024) p = mallocx(1, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); szs[0] = sallocx(p, 0); for (i = 0; i < NCYCLES; i++) { for (j = 1; j < NSZS && szs[j-1] < MAXSZ; j++) { q = rallocx(p, szs[j-1]+1, 0); expect_ptr_not_null(q, "Unexpected rallocx() error for size=%zu-->%zu", szs[j-1], szs[j-1]+1); szs[j] = sallocx(q, 0); expect_zu_ne(szs[j], szs[j-1]+1, "Expected size to be at least: %zu", szs[j-1]+1); p = q; } for (j--; j > 0; j--) { q = rallocx(p, szs[j-1], 0); expect_ptr_not_null(q, "Unexpected rallocx() error for size=%zu-->%zu", szs[j], szs[j-1]); tsz = sallocx(q, 0); expect_zu_eq(tsz, szs[j-1], "Expected size=%zu, got size=%zu", szs[j-1], tsz); p = q; } } dallocx(p, 0); #undef MAXSZ #undef NSZS #undef NCYCLES } TEST_END static bool validate_fill(void *p, uint8_t c, size_t offset, size_t len) { bool ret = false; /* * Use volatile to workaround buffer overflow false positives * (-D_FORTIFY_SOURCE=3). */ uint8_t *volatile buf = (uint8_t *)p; size_t i; for (i = 0; i < len; i++) { uint8_t b = buf[offset+i]; if (b != c) { test_fail("Allocation at %p (len=%zu) contains %#x " "rather than %#x at offset %zu", p, len, b, c, offset+i); ret = true; } } return ret; } TEST_BEGIN(test_zero) { /* * Use volatile to workaround buffer overflow false positives * (-D_FORTIFY_SOURCE=3). */ void *volatile p, *volatile q; size_t psz, qsz, i, j; size_t start_sizes[] = {1, 3*1024, 63*1024, 4095*1024}; #define FILL_BYTE 0xaaU #define RANGE 2048 for (i = 0; i < sizeof(start_sizes)/sizeof(size_t); i++) { size_t start_size = start_sizes[i]; p = mallocx(start_size, MALLOCX_ZERO); expect_ptr_not_null(p, "Unexpected mallocx() error"); psz = sallocx(p, 0); expect_false(validate_fill(p, 0, 0, psz), "Expected zeroed memory"); memset(p, FILL_BYTE, psz); expect_false(validate_fill(p, FILL_BYTE, 0, psz), "Expected filled memory"); for (j = 1; j < RANGE; j++) { q = rallocx(p, start_size+j, MALLOCX_ZERO); expect_ptr_not_null(q, "Unexpected rallocx() error"); qsz = sallocx(q, 0); if (q != p || qsz != psz) { expect_false(validate_fill(q, FILL_BYTE, 0, psz), "Expected filled memory"); expect_false(validate_fill(q, 0, psz, qsz-psz), "Expected zeroed memory"); } if (psz != qsz) { memset((void *)((uintptr_t)q+psz), FILL_BYTE, qsz-psz); psz = qsz; } p = q; } expect_false(validate_fill(p, FILL_BYTE, 0, psz), "Expected filled memory"); dallocx(p, 0); } #undef FILL_BYTE } TEST_END TEST_BEGIN(test_align) { void *p, *q; size_t align; #define MAX_ALIGN (ZU(1) << 25) align = ZU(1); p = mallocx(1, MALLOCX_ALIGN(align)); expect_ptr_not_null(p, "Unexpected mallocx() error"); for (align <<= 1; align <= MAX_ALIGN; align <<= 1) { q = rallocx(p, 1, MALLOCX_ALIGN(align)); expect_ptr_not_null(q, "Unexpected rallocx() error for align=%zu", align); expect_ptr_null( (void *)((uintptr_t)q & (align-1)), "%p inadequately aligned for align=%zu", q, align); p = q; } dallocx(p, 0); #undef MAX_ALIGN } TEST_END TEST_BEGIN(test_align_enum) { /* Span both small sizes and large sizes. */ #define LG_MIN 12 #define LG_MAX 15 for (size_t lg_align = LG_MIN; lg_align <= LG_MAX; ++lg_align) { for (size_t lg_size = LG_MIN; lg_size <= LG_MAX; ++lg_size) { size_t size = 1 << lg_size; for (size_t lg_align_next = LG_MIN; lg_align_next <= LG_MAX; ++lg_align_next) { int flags = MALLOCX_LG_ALIGN(lg_align); void *p = mallocx(1, flags); assert_ptr_not_null(p, "Unexpected mallocx() error"); assert_zu_eq(nallocx(1, flags), TEST_MALLOC_SIZE(p), "Wrong mallocx() usable size"); int flags_next = MALLOCX_LG_ALIGN(lg_align_next); p = rallocx(p, size, flags_next); assert_ptr_not_null(p, "Unexpected rallocx() error"); expect_zu_eq(nallocx(size, flags_next), TEST_MALLOC_SIZE(p), "Wrong rallocx() usable size"); free(p); } } } #undef LG_MAX #undef LG_MIN } TEST_END TEST_BEGIN(test_lg_align_and_zero) { /* * Use volatile to workaround buffer overflow false positives * (-D_FORTIFY_SOURCE=3). */ void *volatile p, *volatile q; unsigned lg_align; size_t sz; #define MAX_LG_ALIGN 25 #define MAX_VALIDATE (ZU(1) << 22) lg_align = 0; p = mallocx(1, MALLOCX_LG_ALIGN(lg_align)|MALLOCX_ZERO); expect_ptr_not_null(p, "Unexpected mallocx() error"); for (lg_align++; lg_align <= MAX_LG_ALIGN; lg_align++) { q = rallocx(p, 1, MALLOCX_LG_ALIGN(lg_align)|MALLOCX_ZERO); expect_ptr_not_null(q, "Unexpected rallocx() error for lg_align=%u", lg_align); expect_ptr_null( (void *)((uintptr_t)q & ((ZU(1) << lg_align)-1)), "%p inadequately aligned for lg_align=%u", q, lg_align); sz = sallocx(q, 0); if ((sz << 1) <= MAX_VALIDATE) { expect_false(validate_fill(q, 0, 0, sz), "Expected zeroed memory"); } else { expect_false(validate_fill(q, 0, 0, MAX_VALIDATE), "Expected zeroed memory"); expect_false(validate_fill( (void *)((uintptr_t)q+sz-MAX_VALIDATE), 0, 0, MAX_VALIDATE), "Expected zeroed memory"); } p = q; } dallocx(p, 0); #undef MAX_VALIDATE #undef MAX_LG_ALIGN } TEST_END /* * GCC "-Walloc-size-larger-than" warning detects when one of the memory * allocation functions is called with a size larger than the maximum size that * they support. Here we want to explicitly test that the allocation functions * do indeed fail properly when this is the case, which triggers the warning. * Therefore we disable the warning for these tests. */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN TEST_BEGIN(test_overflow) { size_t largemax; void *p; largemax = get_large_size(get_nlarge()-1); p = mallocx(1, 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_ptr_null(rallocx(p, largemax+1, 0), "Expected OOM for rallocx(p, size=%#zx, 0)", largemax+1); expect_ptr_null(rallocx(p, ZU(PTRDIFF_MAX)+1, 0), "Expected OOM for rallocx(p, size=%#zx, 0)", ZU(PTRDIFF_MAX)+1); expect_ptr_null(rallocx(p, SIZE_T_MAX, 0), "Expected OOM for rallocx(p, size=%#zx, 0)", SIZE_T_MAX); expect_ptr_null(rallocx(p, 1, MALLOCX_ALIGN(ZU(PTRDIFF_MAX)+1)), "Expected OOM for rallocx(p, size=1, MALLOCX_ALIGN(%#zx))", ZU(PTRDIFF_MAX)+1); dallocx(p, 0); } TEST_END /* Re-enable the "-Walloc-size-larger-than=" warning */ JEMALLOC_DIAGNOSTIC_POP int main(void) { return test( test_grow_and_shrink, test_zero, test_align, test_align_enum, test_lg_align_and_zero, test_overflow); } redis-8.0.2/deps/jemalloc/test/integration/sdallocx.c000066400000000000000000000020311501533116600226240ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define MAXALIGN (((size_t)1) << 22) #define NITER 3 TEST_BEGIN(test_basic) { void *ptr = mallocx(64, 0); sdallocx(ptr, 64, 0); } TEST_END TEST_BEGIN(test_alignment_and_size) { size_t nsz, sz, alignment, total; unsigned i; void *ps[NITER]; for (i = 0; i < NITER; i++) { ps[i] = NULL; } for (alignment = 8; alignment <= MAXALIGN; alignment <<= 1) { total = 0; for (sz = 1; sz < 3 * alignment && sz < (1U << 31); sz += (alignment >> (LG_SIZEOF_PTR-1)) - 1) { for (i = 0; i < NITER; i++) { nsz = nallocx(sz, MALLOCX_ALIGN(alignment) | MALLOCX_ZERO); ps[i] = mallocx(sz, MALLOCX_ALIGN(alignment) | MALLOCX_ZERO); total += nsz; if (total >= (MAXALIGN << 1)) { break; } } for (i = 0; i < NITER; i++) { if (ps[i] != NULL) { sdallocx(ps[i], sz, MALLOCX_ALIGN(alignment)); ps[i] = NULL; } } } } } TEST_END int main(void) { return test_no_reentrancy( test_basic, test_alignment_and_size); } redis-8.0.2/deps/jemalloc/test/integration/slab_sizes.c000066400000000000000000000043271501533116600231630ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* Note that this test relies on the unusual slab sizes set in slab_sizes.sh. */ TEST_BEGIN(test_slab_sizes) { unsigned nbins; size_t page; size_t sizemib[4]; size_t slabmib[4]; size_t len; len = sizeof(nbins); expect_d_eq(mallctl("arenas.nbins", &nbins, &len, NULL, 0), 0, "nbins mallctl failure"); len = sizeof(page); expect_d_eq(mallctl("arenas.page", &page, &len, NULL, 0), 0, "page mallctl failure"); len = 4; expect_d_eq(mallctlnametomib("arenas.bin.0.size", sizemib, &len), 0, "bin size mallctlnametomib failure"); len = 4; expect_d_eq(mallctlnametomib("arenas.bin.0.slab_size", slabmib, &len), 0, "slab size mallctlnametomib failure"); size_t biggest_slab_seen = 0; for (unsigned i = 0; i < nbins; i++) { size_t bin_size; size_t slab_size; len = sizeof(size_t); sizemib[2] = i; slabmib[2] = i; expect_d_eq(mallctlbymib(sizemib, 4, (void *)&bin_size, &len, NULL, 0), 0, "bin size mallctlbymib failure"); len = sizeof(size_t); expect_d_eq(mallctlbymib(slabmib, 4, (void *)&slab_size, &len, NULL, 0), 0, "slab size mallctlbymib failure"); if (bin_size < 100) { /* * Then we should be as close to 17 as possible. Since * not all page sizes are valid (because of bitmap * limitations on the number of items in a slab), we * should at least make sure that the number of pages * goes up. */ expect_zu_ge(slab_size, biggest_slab_seen, "Slab sizes should go up"); biggest_slab_seen = slab_size; } else if ( (100 <= bin_size && bin_size < 128) || (128 < bin_size && bin_size <= 200)) { expect_zu_eq(slab_size, page, "Forced-small slabs should be small"); } else if (bin_size == 128) { expect_zu_eq(slab_size, 2 * page, "Forced-2-page slab should be 2 pages"); } else if (200 < bin_size && bin_size <= 4096) { expect_zu_ge(slab_size, biggest_slab_seen, "Slab sizes should go up"); biggest_slab_seen = slab_size; } } /* * For any reasonable configuration, 17 pages should be a valid slab * size for 4096-byte items. */ expect_zu_eq(biggest_slab_seen, 17 * page, "Didn't hit page target"); } TEST_END int main(void) { return test( test_slab_sizes); } redis-8.0.2/deps/jemalloc/test/integration/slab_sizes.sh000066400000000000000000000001531501533116600233440ustar00rootroot00000000000000#!/bin/sh # Some screwy-looking slab sizes. export MALLOC_CONF="slab_sizes:1-4096:17|100-200:1|128-128:2" redis-8.0.2/deps/jemalloc/test/integration/smallocx.c000066400000000000000000000201211501533116600226350ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/jemalloc_macros.h" #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #ifndef JEMALLOC_VERSION_GID_IDENT #error "JEMALLOC_VERSION_GID_IDENT not defined" #endif #define JOIN(x, y) x ## y #define JOIN2(x, y) JOIN(x, y) #define smallocx JOIN2(smallocx_, JEMALLOC_VERSION_GID_IDENT) typedef struct { void *ptr; size_t size; } smallocx_return_t; extern smallocx_return_t smallocx(size_t size, int flags); static unsigned get_nsizes_impl(const char *cmd) { unsigned ret; size_t z; z = sizeof(unsigned); expect_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctl(\"%s\", ...) failure", cmd); return ret; } static unsigned get_nlarge(void) { return get_nsizes_impl("arenas.nlextents"); } static size_t get_size_impl(const char *cmd, size_t ind) { size_t ret; size_t z; size_t mib[4]; size_t miblen = 4; z = sizeof(size_t); expect_d_eq(mallctlnametomib(cmd, mib, &miblen), 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd); mib[2] = ind; z = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind); return ret; } static size_t get_large_size(size_t ind) { return get_size_impl("arenas.lextent.0.size", ind); } /* * On systems which can't merge extents, tests that call this function generate * a lot of dirty memory very quickly. Purging between cycles mitigates * potential OOM on e.g. 32-bit Windows. */ static void purge(void) { expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl error"); } /* * GCC "-Walloc-size-larger-than" warning detects when one of the memory * allocation functions is called with a size larger than the maximum size that * they support. Here we want to explicitly test that the allocation functions * do indeed fail properly when this is the case, which triggers the warning. * Therefore we disable the warning for these tests. */ JEMALLOC_DIAGNOSTIC_PUSH JEMALLOC_DIAGNOSTIC_IGNORE_ALLOC_SIZE_LARGER_THAN TEST_BEGIN(test_overflow) { size_t largemax; largemax = get_large_size(get_nlarge()-1); expect_ptr_null(smallocx(largemax+1, 0).ptr, "Expected OOM for smallocx(size=%#zx, 0)", largemax+1); expect_ptr_null(smallocx(ZU(PTRDIFF_MAX)+1, 0).ptr, "Expected OOM for smallocx(size=%#zx, 0)", ZU(PTRDIFF_MAX)+1); expect_ptr_null(smallocx(SIZE_T_MAX, 0).ptr, "Expected OOM for smallocx(size=%#zx, 0)", SIZE_T_MAX); expect_ptr_null(smallocx(1, MALLOCX_ALIGN(ZU(PTRDIFF_MAX)+1)).ptr, "Expected OOM for smallocx(size=1, MALLOCX_ALIGN(%#zx))", ZU(PTRDIFF_MAX)+1); } TEST_END static void * remote_alloc(void *arg) { unsigned arena; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); size_t large_sz; sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&large_sz, &sz, NULL, 0), 0, "Unexpected mallctl failure"); smallocx_return_t r = smallocx(large_sz, MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE); void *ptr = r.ptr; expect_zu_eq(r.size, nallocx(large_sz, MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE), "Expected smalloc(size,flags).size == nallocx(size,flags)"); void **ret = (void **)arg; *ret = ptr; return NULL; } TEST_BEGIN(test_remote_free) { thd_t thd; void *ret; thd_create(&thd, remote_alloc, (void *)&ret); thd_join(thd, NULL); expect_ptr_not_null(ret, "Unexpected smallocx failure"); /* Avoid TCACHE_NONE to explicitly test tcache_flush(). */ dallocx(ret, 0); mallctl("thread.tcache.flush", NULL, NULL, NULL, 0); } TEST_END TEST_BEGIN(test_oom) { size_t largemax; bool oom; void *ptrs[3]; unsigned i; /* * It should be impossible to allocate three objects that each consume * nearly half the virtual address space. */ largemax = get_large_size(get_nlarge()-1); oom = false; for (i = 0; i < sizeof(ptrs) / sizeof(void *); i++) { ptrs[i] = smallocx(largemax, 0).ptr; if (ptrs[i] == NULL) { oom = true; } } expect_true(oom, "Expected OOM during series of calls to smallocx(size=%zu, 0)", largemax); for (i = 0; i < sizeof(ptrs) / sizeof(void *); i++) { if (ptrs[i] != NULL) { dallocx(ptrs[i], 0); } } purge(); #if LG_SIZEOF_PTR == 3 expect_ptr_null(smallocx(0x8000000000000000ULL, MALLOCX_ALIGN(0x8000000000000000ULL)).ptr, "Expected OOM for smallocx()"); expect_ptr_null(smallocx(0x8000000000000000ULL, MALLOCX_ALIGN(0x80000000)).ptr, "Expected OOM for smallocx()"); #else expect_ptr_null(smallocx(0x80000000UL, MALLOCX_ALIGN(0x80000000UL)).ptr, "Expected OOM for smallocx()"); #endif } TEST_END /* Re-enable the "-Walloc-size-larger-than=" warning */ JEMALLOC_DIAGNOSTIC_POP TEST_BEGIN(test_basic) { #define MAXSZ (((size_t)1) << 23) size_t sz; for (sz = 1; sz < MAXSZ; sz = nallocx(sz, 0) + 1) { smallocx_return_t ret; size_t nsz, rsz, smz; void *p; nsz = nallocx(sz, 0); expect_zu_ne(nsz, 0, "Unexpected nallocx() error"); ret = smallocx(sz, 0); p = ret.ptr; smz = ret.size; expect_ptr_not_null(p, "Unexpected smallocx(size=%zx, flags=0) error", sz); rsz = sallocx(p, 0); expect_zu_ge(rsz, sz, "Real size smaller than expected"); expect_zu_eq(nsz, rsz, "nallocx()/sallocx() size mismatch"); expect_zu_eq(nsz, smz, "nallocx()/smallocx() size mismatch"); dallocx(p, 0); ret = smallocx(sz, 0); p = ret.ptr; smz = ret.size; expect_ptr_not_null(p, "Unexpected smallocx(size=%zx, flags=0) error", sz); dallocx(p, 0); nsz = nallocx(sz, MALLOCX_ZERO); expect_zu_ne(nsz, 0, "Unexpected nallocx() error"); expect_zu_ne(smz, 0, "Unexpected smallocx() error"); ret = smallocx(sz, MALLOCX_ZERO); p = ret.ptr; expect_ptr_not_null(p, "Unexpected smallocx(size=%zx, flags=MALLOCX_ZERO) error", nsz); rsz = sallocx(p, 0); expect_zu_eq(nsz, rsz, "nallocx()/sallocx() rsize mismatch"); expect_zu_eq(nsz, smz, "nallocx()/smallocx() size mismatch"); dallocx(p, 0); purge(); } #undef MAXSZ } TEST_END TEST_BEGIN(test_alignment_and_size) { const char *percpu_arena; size_t sz = sizeof(percpu_arena); if(mallctl("opt.percpu_arena", (void *)&percpu_arena, &sz, NULL, 0) || strcmp(percpu_arena, "disabled") != 0) { test_skip("test_alignment_and_size skipped: " "not working with percpu arena."); }; #define MAXALIGN (((size_t)1) << 23) #define NITER 4 size_t nsz, rsz, smz, alignment, total; unsigned i; void *ps[NITER]; for (i = 0; i < NITER; i++) { ps[i] = NULL; } for (alignment = 8; alignment <= MAXALIGN; alignment <<= 1) { total = 0; for (sz = 1; sz < 3 * alignment && sz < (1U << 31); sz += (alignment >> (LG_SIZEOF_PTR-1)) - 1) { for (i = 0; i < NITER; i++) { nsz = nallocx(sz, MALLOCX_ALIGN(alignment) | MALLOCX_ZERO); expect_zu_ne(nsz, 0, "nallocx() error for alignment=%zu, " "size=%zu (%#zx)", alignment, sz, sz); smallocx_return_t ret = smallocx(sz, MALLOCX_ALIGN(alignment) | MALLOCX_ZERO); ps[i] = ret.ptr; expect_ptr_not_null(ps[i], "smallocx() error for alignment=%zu, " "size=%zu (%#zx)", alignment, sz, sz); rsz = sallocx(ps[i], 0); smz = ret.size; expect_zu_ge(rsz, sz, "Real size smaller than expected for " "alignment=%zu, size=%zu", alignment, sz); expect_zu_eq(nsz, rsz, "nallocx()/sallocx() size mismatch for " "alignment=%zu, size=%zu", alignment, sz); expect_zu_eq(nsz, smz, "nallocx()/smallocx() size mismatch for " "alignment=%zu, size=%zu", alignment, sz); expect_ptr_null( (void *)((uintptr_t)ps[i] & (alignment-1)), "%p inadequately aligned for" " alignment=%zu, size=%zu", ps[i], alignment, sz); total += rsz; if (total >= (MAXALIGN << 1)) { break; } } for (i = 0; i < NITER; i++) { if (ps[i] != NULL) { dallocx(ps[i], 0); ps[i] = NULL; } } } purge(); } #undef MAXALIGN #undef NITER } TEST_END int main(void) { return test( test_overflow, test_oom, test_remote_free, test_basic, test_alignment_and_size); } redis-8.0.2/deps/jemalloc/test/integration/smallocx.sh000066400000000000000000000001311501533116600230240ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="junk:false" fi redis-8.0.2/deps/jemalloc/test/integration/thread_arena.c000066400000000000000000000034571501533116600234450ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define NTHREADS 10 void * thd_start(void *arg) { unsigned main_arena_ind = *(unsigned *)arg; void *p; unsigned arena_ind; size_t size; int err; p = malloc(1); expect_ptr_not_null(p, "Error in malloc()"); free(p); size = sizeof(arena_ind); if ((err = mallctl("thread.arena", (void *)&arena_ind, &size, (void *)&main_arena_ind, sizeof(main_arena_ind)))) { char buf[BUFERROR_BUF]; buferror(err, buf, sizeof(buf)); test_fail("Error in mallctl(): %s", buf); } size = sizeof(arena_ind); if ((err = mallctl("thread.arena", (void *)&arena_ind, &size, NULL, 0))) { char buf[BUFERROR_BUF]; buferror(err, buf, sizeof(buf)); test_fail("Error in mallctl(): %s", buf); } expect_u_eq(arena_ind, main_arena_ind, "Arena index should be same as for main thread"); return NULL; } static void mallctl_failure(int err) { char buf[BUFERROR_BUF]; buferror(err, buf, sizeof(buf)); test_fail("Error in mallctl(): %s", buf); } TEST_BEGIN(test_thread_arena) { void *p; int err; thd_t thds[NTHREADS]; unsigned i; p = malloc(1); expect_ptr_not_null(p, "Error in malloc()"); unsigned arena_ind, old_arena_ind; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Arena creation failure"); size_t size = sizeof(arena_ind); if ((err = mallctl("thread.arena", (void *)&old_arena_ind, &size, (void *)&arena_ind, sizeof(arena_ind))) != 0) { mallctl_failure(err); } for (i = 0; i < NTHREADS; i++) { thd_create(&thds[i], thd_start, (void *)&arena_ind); } for (i = 0; i < NTHREADS; i++) { intptr_t join_ret; thd_join(thds[i], (void *)&join_ret); expect_zd_eq(join_ret, 0, "Unexpected thread join error"); } free(p); } TEST_END int main(void) { return test( test_thread_arena); } redis-8.0.2/deps/jemalloc/test/integration/thread_tcache_enabled.c000066400000000000000000000044651501533116600252600ustar00rootroot00000000000000#include "test/jemalloc_test.h" void * thd_start(void *arg) { bool e0, e1; size_t sz = sizeof(bool); expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, NULL, 0), 0, "Unexpected mallctl failure"); if (e0) { e1 = false; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_true(e0, "tcache should be enabled"); } e1 = true; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_false(e0, "tcache should be disabled"); e1 = true; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_true(e0, "tcache should be enabled"); e1 = false; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_true(e0, "tcache should be enabled"); e1 = false; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_false(e0, "tcache should be disabled"); free(malloc(1)); e1 = true; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_false(e0, "tcache should be disabled"); free(malloc(1)); e1 = true; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_true(e0, "tcache should be enabled"); free(malloc(1)); e1 = false; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_true(e0, "tcache should be enabled"); free(malloc(1)); e1 = false; expect_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, (void *)&e1, sz), 0, "Unexpected mallctl() error"); expect_false(e0, "tcache should be disabled"); free(malloc(1)); return NULL; } TEST_BEGIN(test_main_thread) { thd_start(NULL); } TEST_END TEST_BEGIN(test_subthread) { thd_t thd; thd_create(&thd, thd_start, NULL); thd_join(thd, NULL); } TEST_END int main(void) { /* Run tests multiple times to check for bad interactions. */ return test( test_main_thread, test_subthread, test_main_thread, test_subthread, test_main_thread); } redis-8.0.2/deps/jemalloc/test/integration/xallocx.c000066400000000000000000000234241501533116600224760ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * Use a separate arena for xallocx() extension/contraction tests so that * internal allocation e.g. by heap profiling can't interpose allocations where * xallocx() would ordinarily be able to extend. */ static unsigned arena_ind(void) { static unsigned ind = 0; if (ind == 0) { size_t sz = sizeof(ind); expect_d_eq(mallctl("arenas.create", (void *)&ind, &sz, NULL, 0), 0, "Unexpected mallctl failure creating arena"); } return ind; } TEST_BEGIN(test_same_size) { void *p; size_t sz, tsz; p = mallocx(42, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); sz = sallocx(p, 0); tsz = xallocx(p, sz, 0, 0); expect_zu_eq(tsz, sz, "Unexpected size change: %zu --> %zu", sz, tsz); dallocx(p, 0); } TEST_END TEST_BEGIN(test_extra_no_move) { void *p; size_t sz, tsz; p = mallocx(42, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); sz = sallocx(p, 0); tsz = xallocx(p, sz, sz-42, 0); expect_zu_eq(tsz, sz, "Unexpected size change: %zu --> %zu", sz, tsz); dallocx(p, 0); } TEST_END TEST_BEGIN(test_no_move_fail) { void *p; size_t sz, tsz; p = mallocx(42, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); sz = sallocx(p, 0); tsz = xallocx(p, sz + 5, 0, 0); expect_zu_eq(tsz, sz, "Unexpected size change: %zu --> %zu", sz, tsz); dallocx(p, 0); } TEST_END static unsigned get_nsizes_impl(const char *cmd) { unsigned ret; size_t z; z = sizeof(unsigned); expect_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctl(\"%s\", ...) failure", cmd); return ret; } static unsigned get_nsmall(void) { return get_nsizes_impl("arenas.nbins"); } static unsigned get_nlarge(void) { return get_nsizes_impl("arenas.nlextents"); } static size_t get_size_impl(const char *cmd, size_t ind) { size_t ret; size_t z; size_t mib[4]; size_t miblen = 4; z = sizeof(size_t); expect_d_eq(mallctlnametomib(cmd, mib, &miblen), 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd); mib[2] = ind; z = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind); return ret; } static size_t get_small_size(size_t ind) { return get_size_impl("arenas.bin.0.size", ind); } static size_t get_large_size(size_t ind) { return get_size_impl("arenas.lextent.0.size", ind); } TEST_BEGIN(test_size) { size_t small0, largemax; void *p; /* Get size classes. */ small0 = get_small_size(0); largemax = get_large_size(get_nlarge()-1); p = mallocx(small0, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); /* Test smallest supported size. */ expect_zu_eq(xallocx(p, 1, 0, 0), small0, "Unexpected xallocx() behavior"); /* Test largest supported size. */ expect_zu_le(xallocx(p, largemax, 0, 0), largemax, "Unexpected xallocx() behavior"); /* Test size overflow. */ expect_zu_le(xallocx(p, largemax+1, 0, 0), largemax, "Unexpected xallocx() behavior"); expect_zu_le(xallocx(p, SIZE_T_MAX, 0, 0), largemax, "Unexpected xallocx() behavior"); dallocx(p, 0); } TEST_END TEST_BEGIN(test_size_extra_overflow) { size_t small0, largemax; void *p; /* Get size classes. */ small0 = get_small_size(0); largemax = get_large_size(get_nlarge()-1); p = mallocx(small0, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); /* Test overflows that can be resolved by clamping extra. */ expect_zu_le(xallocx(p, largemax-1, 2, 0), largemax, "Unexpected xallocx() behavior"); expect_zu_le(xallocx(p, largemax, 1, 0), largemax, "Unexpected xallocx() behavior"); /* Test overflow such that largemax-size underflows. */ expect_zu_le(xallocx(p, largemax+1, 2, 0), largemax, "Unexpected xallocx() behavior"); expect_zu_le(xallocx(p, largemax+2, 3, 0), largemax, "Unexpected xallocx() behavior"); expect_zu_le(xallocx(p, SIZE_T_MAX-2, 2, 0), largemax, "Unexpected xallocx() behavior"); expect_zu_le(xallocx(p, SIZE_T_MAX-1, 1, 0), largemax, "Unexpected xallocx() behavior"); dallocx(p, 0); } TEST_END TEST_BEGIN(test_extra_small) { size_t small0, small1, largemax; void *p; /* Get size classes. */ small0 = get_small_size(0); small1 = get_small_size(1); largemax = get_large_size(get_nlarge()-1); p = mallocx(small0, 0); expect_ptr_not_null(p, "Unexpected mallocx() error"); expect_zu_eq(xallocx(p, small1, 0, 0), small0, "Unexpected xallocx() behavior"); expect_zu_eq(xallocx(p, small1, 0, 0), small0, "Unexpected xallocx() behavior"); expect_zu_eq(xallocx(p, small0, small1 - small0, 0), small0, "Unexpected xallocx() behavior"); /* Test size+extra overflow. */ expect_zu_eq(xallocx(p, small0, largemax - small0 + 1, 0), small0, "Unexpected xallocx() behavior"); expect_zu_eq(xallocx(p, small0, SIZE_T_MAX - small0, 0), small0, "Unexpected xallocx() behavior"); dallocx(p, 0); } TEST_END TEST_BEGIN(test_extra_large) { int flags = MALLOCX_ARENA(arena_ind()); size_t smallmax, large1, large2, large3, largemax; void *p; /* Get size classes. */ smallmax = get_small_size(get_nsmall()-1); large1 = get_large_size(1); large2 = get_large_size(2); large3 = get_large_size(3); largemax = get_large_size(get_nlarge()-1); p = mallocx(large3, flags); expect_ptr_not_null(p, "Unexpected mallocx() error"); expect_zu_eq(xallocx(p, large3, 0, flags), large3, "Unexpected xallocx() behavior"); /* Test size decrease with zero extra. */ expect_zu_ge(xallocx(p, large1, 0, flags), large1, "Unexpected xallocx() behavior"); expect_zu_ge(xallocx(p, smallmax, 0, flags), large1, "Unexpected xallocx() behavior"); if (xallocx(p, large3, 0, flags) != large3) { p = rallocx(p, large3, flags); expect_ptr_not_null(p, "Unexpected rallocx() failure"); } /* Test size decrease with non-zero extra. */ expect_zu_eq(xallocx(p, large1, large3 - large1, flags), large3, "Unexpected xallocx() behavior"); expect_zu_eq(xallocx(p, large2, large3 - large2, flags), large3, "Unexpected xallocx() behavior"); expect_zu_ge(xallocx(p, large1, large2 - large1, flags), large2, "Unexpected xallocx() behavior"); expect_zu_ge(xallocx(p, smallmax, large1 - smallmax, flags), large1, "Unexpected xallocx() behavior"); expect_zu_ge(xallocx(p, large1, 0, flags), large1, "Unexpected xallocx() behavior"); /* Test size increase with zero extra. */ expect_zu_le(xallocx(p, large3, 0, flags), large3, "Unexpected xallocx() behavior"); expect_zu_le(xallocx(p, largemax+1, 0, flags), large3, "Unexpected xallocx() behavior"); expect_zu_ge(xallocx(p, large1, 0, flags), large1, "Unexpected xallocx() behavior"); /* Test size increase with non-zero extra. */ expect_zu_le(xallocx(p, large1, SIZE_T_MAX - large1, flags), largemax, "Unexpected xallocx() behavior"); expect_zu_ge(xallocx(p, large1, 0, flags), large1, "Unexpected xallocx() behavior"); /* Test size increase with non-zero extra. */ expect_zu_le(xallocx(p, large1, large3 - large1, flags), large3, "Unexpected xallocx() behavior"); if (xallocx(p, large3, 0, flags) != large3) { p = rallocx(p, large3, flags); expect_ptr_not_null(p, "Unexpected rallocx() failure"); } /* Test size+extra overflow. */ expect_zu_le(xallocx(p, large3, largemax - large3 + 1, flags), largemax, "Unexpected xallocx() behavior"); dallocx(p, flags); } TEST_END static void print_filled_extents(const void *p, uint8_t c, size_t len) { const uint8_t *pc = (const uint8_t *)p; size_t i, range0; uint8_t c0; malloc_printf(" p=%p, c=%#x, len=%zu:", p, c, len); range0 = 0; c0 = pc[0]; for (i = 0; i < len; i++) { if (pc[i] != c0) { malloc_printf(" %#x[%zu..%zu)", c0, range0, i); range0 = i; c0 = pc[i]; } } malloc_printf(" %#x[%zu..%zu)\n", c0, range0, i); } static bool validate_fill(const void *p, uint8_t c, size_t offset, size_t len) { const uint8_t *pc = (const uint8_t *)p; bool err; size_t i; for (i = offset, err = false; i < offset+len; i++) { if (pc[i] != c) { err = true; } } if (err) { print_filled_extents(p, c, offset + len); } return err; } static void test_zero(size_t szmin, size_t szmax) { int flags = MALLOCX_ARENA(arena_ind()) | MALLOCX_ZERO; size_t sz, nsz; void *p; #define FILL_BYTE 0x7aU sz = szmax; p = mallocx(sz, flags); expect_ptr_not_null(p, "Unexpected mallocx() error"); expect_false(validate_fill(p, 0x00, 0, sz), "Memory not filled: sz=%zu", sz); /* * Fill with non-zero so that non-debug builds are more likely to detect * errors. */ memset(p, FILL_BYTE, sz); expect_false(validate_fill(p, FILL_BYTE, 0, sz), "Memory not filled: sz=%zu", sz); /* Shrink in place so that we can expect growing in place to succeed. */ sz = szmin; if (xallocx(p, sz, 0, flags) != sz) { p = rallocx(p, sz, flags); expect_ptr_not_null(p, "Unexpected rallocx() failure"); } expect_false(validate_fill(p, FILL_BYTE, 0, sz), "Memory not filled: sz=%zu", sz); for (sz = szmin; sz < szmax; sz = nsz) { nsz = nallocx(sz+1, flags); if (xallocx(p, sz+1, 0, flags) != nsz) { p = rallocx(p, sz+1, flags); expect_ptr_not_null(p, "Unexpected rallocx() failure"); } expect_false(validate_fill(p, FILL_BYTE, 0, sz), "Memory not filled: sz=%zu", sz); expect_false(validate_fill(p, 0x00, sz, nsz-sz), "Memory not filled: sz=%zu, nsz-sz=%zu", sz, nsz-sz); memset((void *)((uintptr_t)p + sz), FILL_BYTE, nsz-sz); expect_false(validate_fill(p, FILL_BYTE, 0, nsz), "Memory not filled: nsz=%zu", nsz); } dallocx(p, flags); } TEST_BEGIN(test_zero_large) { size_t large0, large1; /* Get size classes. */ large0 = get_large_size(0); large1 = get_large_size(1); test_zero(large1, large0 * 2); } TEST_END int main(void) { return test( test_same_size, test_extra_no_move, test_no_move_fail, test_size, test_size_extra_overflow, test_extra_small, test_extra_large, test_zero_large); } redis-8.0.2/deps/jemalloc/test/integration/xallocx.sh000066400000000000000000000001271501533116600226610ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="junk:false" fi redis-8.0.2/deps/jemalloc/test/src/000077500000000000000000000000001501533116600171175ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/src/SFMT.c000066400000000000000000000501701501533116600200370ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ /** * @file SFMT.c * @brief SIMD oriented Fast Mersenne Twister(SFMT) * * @author Mutsuo Saito (Hiroshima University) * @author Makoto Matsumoto (Hiroshima University) * * Copyright (C) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * The new BSD License is applied to this software, see LICENSE.txt */ #define SFMT_C_ #include "test/jemalloc_test.h" #include "test/SFMT-params.h" #if defined(JEMALLOC_BIG_ENDIAN) && !defined(BIG_ENDIAN64) #define BIG_ENDIAN64 1 #endif #if defined(__BIG_ENDIAN__) && !defined(__amd64) && !defined(BIG_ENDIAN64) #define BIG_ENDIAN64 1 #endif #if defined(HAVE_ALTIVEC) && !defined(BIG_ENDIAN64) #define BIG_ENDIAN64 1 #endif #if defined(ONLY64) && !defined(BIG_ENDIAN64) #if defined(__GNUC__) #error "-DONLY64 must be specified with -DBIG_ENDIAN64" #endif #undef ONLY64 #endif /*------------------------------------------------------ 128-bit SIMD data type for Altivec, SSE2 or standard C ------------------------------------------------------*/ #if defined(HAVE_ALTIVEC) /** 128-bit data structure */ union W128_T { vector unsigned int s; uint32_t u[4]; }; /** 128-bit data type */ typedef union W128_T w128_t; #elif defined(HAVE_SSE2) /** 128-bit data structure */ union W128_T { __m128i si; uint32_t u[4]; }; /** 128-bit data type */ typedef union W128_T w128_t; #else /** 128-bit data structure */ struct W128_T { uint32_t u[4]; }; /** 128-bit data type */ typedef struct W128_T w128_t; #endif struct sfmt_s { /** the 128-bit internal state array */ w128_t sfmt[N]; /** index counter to the 32-bit internal state array */ int idx; /** a flag: it is 0 if and only if the internal state is not yet * initialized. */ int initialized; }; /*-------------------------------------- FILE GLOBAL VARIABLES internal state, index counter and flag --------------------------------------*/ /** a parity check vector which certificate the period of 2^{MEXP} */ static uint32_t parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4}; /*---------------- STATIC FUNCTIONS ----------------*/ static inline int idxof(int i); #if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) static inline void rshift128(w128_t *out, w128_t const *in, int shift); static inline void lshift128(w128_t *out, w128_t const *in, int shift); #endif static inline void gen_rand_all(sfmt_t *ctx); static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size); static inline uint32_t func1(uint32_t x); static inline uint32_t func2(uint32_t x); static void period_certification(sfmt_t *ctx); #if defined(BIG_ENDIAN64) && !defined(ONLY64) static inline void swap(w128_t *array, int size); #endif #if defined(HAVE_ALTIVEC) #include "test/SFMT-alti.h" #elif defined(HAVE_SSE2) #include "test/SFMT-sse2.h" #endif /** * This function simulate a 64-bit index of LITTLE ENDIAN * in BIG ENDIAN machine. */ #ifdef ONLY64 static inline int idxof(int i) { return i ^ 1; } #else static inline int idxof(int i) { return i; } #endif /** * This function simulates SIMD 128-bit right shift by the standard C. * The 128-bit integer given in in is shifted by (shift * 8) bits. * This function simulates the LITTLE ENDIAN SIMD. * @param out the output of this function * @param in the 128-bit data to be shifted * @param shift the shift value */ #if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) #ifdef ONLY64 static inline void rshift128(w128_t *out, w128_t const *in, int shift) { uint64_t th, tl, oh, ol; th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]); tl = ((uint64_t)in->u[0] << 32) | ((uint64_t)in->u[1]); oh = th >> (shift * 8); ol = tl >> (shift * 8); ol |= th << (64 - shift * 8); out->u[0] = (uint32_t)(ol >> 32); out->u[1] = (uint32_t)ol; out->u[2] = (uint32_t)(oh >> 32); out->u[3] = (uint32_t)oh; } #else static inline void rshift128(w128_t *out, w128_t const *in, int shift) { uint64_t th, tl, oh, ol; th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]); tl = ((uint64_t)in->u[1] << 32) | ((uint64_t)in->u[0]); oh = th >> (shift * 8); ol = tl >> (shift * 8); ol |= th << (64 - shift * 8); out->u[1] = (uint32_t)(ol >> 32); out->u[0] = (uint32_t)ol; out->u[3] = (uint32_t)(oh >> 32); out->u[2] = (uint32_t)oh; } #endif /** * This function simulates SIMD 128-bit left shift by the standard C. * The 128-bit integer given in in is shifted by (shift * 8) bits. * This function simulates the LITTLE ENDIAN SIMD. * @param out the output of this function * @param in the 128-bit data to be shifted * @param shift the shift value */ #ifdef ONLY64 static inline void lshift128(w128_t *out, w128_t const *in, int shift) { uint64_t th, tl, oh, ol; th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]); tl = ((uint64_t)in->u[0] << 32) | ((uint64_t)in->u[1]); oh = th << (shift * 8); ol = tl << (shift * 8); oh |= tl >> (64 - shift * 8); out->u[0] = (uint32_t)(ol >> 32); out->u[1] = (uint32_t)ol; out->u[2] = (uint32_t)(oh >> 32); out->u[3] = (uint32_t)oh; } #else static inline void lshift128(w128_t *out, w128_t const *in, int shift) { uint64_t th, tl, oh, ol; th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]); tl = ((uint64_t)in->u[1] << 32) | ((uint64_t)in->u[0]); oh = th << (shift * 8); ol = tl << (shift * 8); oh |= tl >> (64 - shift * 8); out->u[1] = (uint32_t)(ol >> 32); out->u[0] = (uint32_t)ol; out->u[3] = (uint32_t)(oh >> 32); out->u[2] = (uint32_t)oh; } #endif #endif /** * This function represents the recursion formula. * @param r output * @param a a 128-bit part of the internal state array * @param b a 128-bit part of the internal state array * @param c a 128-bit part of the internal state array * @param d a 128-bit part of the internal state array */ #if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) #ifdef ONLY64 static inline void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c, w128_t *d) { w128_t x; w128_t y; lshift128(&x, a, SL2); rshift128(&y, c, SR2); r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0] ^ (d->u[0] << SL1); r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1] ^ (d->u[1] << SL1); r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2] ^ (d->u[2] << SL1); r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3] ^ (d->u[3] << SL1); } #else static inline void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c, w128_t *d) { w128_t x; w128_t y; lshift128(&x, a, SL2); rshift128(&y, c, SR2); r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0] ^ (d->u[0] << SL1); r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1] ^ (d->u[1] << SL1); r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2] ^ (d->u[2] << SL1); r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3] ^ (d->u[3] << SL1); } #endif #endif #if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) /** * This function fills the internal state array with pseudorandom * integers. */ static inline void gen_rand_all(sfmt_t *ctx) { int i; w128_t *r1, *r2; r1 = &ctx->sfmt[N - 2]; r2 = &ctx->sfmt[N - 1]; for (i = 0; i < N - POS1; i++) { do_recursion(&ctx->sfmt[i], &ctx->sfmt[i], &ctx->sfmt[i + POS1], r1, r2); r1 = r2; r2 = &ctx->sfmt[i]; } for (; i < N; i++) { do_recursion(&ctx->sfmt[i], &ctx->sfmt[i], &ctx->sfmt[i + POS1 - N], r1, r2); r1 = r2; r2 = &ctx->sfmt[i]; } } /** * This function fills the user-specified array with pseudorandom * integers. * * @param array an 128-bit array to be filled by pseudorandom numbers. * @param size number of 128-bit pseudorandom numbers to be generated. */ static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) { int i, j; w128_t *r1, *r2; r1 = &ctx->sfmt[N - 2]; r2 = &ctx->sfmt[N - 1]; for (i = 0; i < N - POS1; i++) { do_recursion(&array[i], &ctx->sfmt[i], &ctx->sfmt[i + POS1], r1, r2); r1 = r2; r2 = &array[i]; } for (; i < N; i++) { do_recursion(&array[i], &ctx->sfmt[i], &array[i + POS1 - N], r1, r2); r1 = r2; r2 = &array[i]; } for (; i < size - N; i++) { do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); r1 = r2; r2 = &array[i]; } for (j = 0; j < 2 * N - size; j++) { ctx->sfmt[j] = array[j + size - N]; } for (; i < size; i++, j++) { do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); r1 = r2; r2 = &array[i]; ctx->sfmt[j] = array[i]; } } #endif #if defined(BIG_ENDIAN64) && !defined(ONLY64) && !defined(HAVE_ALTIVEC) static inline void swap(w128_t *array, int size) { int i; uint32_t x, y; for (i = 0; i < size; i++) { x = array[i].u[0]; y = array[i].u[2]; array[i].u[0] = array[i].u[1]; array[i].u[2] = array[i].u[3]; array[i].u[1] = x; array[i].u[3] = y; } } #endif /** * This function represents a function used in the initialization * by init_by_array * @param x 32-bit integer * @return 32-bit integer */ static uint32_t func1(uint32_t x) { return (x ^ (x >> 27)) * (uint32_t)1664525UL; } /** * This function represents a function used in the initialization * by init_by_array * @param x 32-bit integer * @return 32-bit integer */ static uint32_t func2(uint32_t x) { return (x ^ (x >> 27)) * (uint32_t)1566083941UL; } /** * This function certificate the period of 2^{MEXP} */ static void period_certification(sfmt_t *ctx) { int inner = 0; int i, j; uint32_t work; uint32_t *psfmt32 = &ctx->sfmt[0].u[0]; for (i = 0; i < 4; i++) inner ^= psfmt32[idxof(i)] & parity[i]; for (i = 16; i > 0; i >>= 1) inner ^= inner >> i; inner &= 1; /* check OK */ if (inner == 1) { return; } /* check NG, and modification */ for (i = 0; i < 4; i++) { work = 1; for (j = 0; j < 32; j++) { if ((work & parity[i]) != 0) { psfmt32[idxof(i)] ^= work; return; } work = work << 1; } } } /*---------------- PUBLIC FUNCTIONS ----------------*/ /** * This function returns the identification string. * The string shows the word size, the Mersenne exponent, * and all parameters of this generator. */ const char *get_idstring(void) { return IDSTR; } /** * This function returns the minimum size of array used for \b * fill_array32() function. * @return minimum size of array used for fill_array32() function. */ int get_min_array_size32(void) { return N32; } /** * This function returns the minimum size of array used for \b * fill_array64() function. * @return minimum size of array used for fill_array64() function. */ int get_min_array_size64(void) { return N64; } #ifndef ONLY64 /** * This function generates and returns 32-bit pseudorandom number. * init_gen_rand or init_by_array must be called before this function. * @return 32-bit pseudorandom number */ uint32_t gen_rand32(sfmt_t *ctx) { uint32_t r; uint32_t *psfmt32 = &ctx->sfmt[0].u[0]; assert(ctx->initialized); if (ctx->idx >= N32) { gen_rand_all(ctx); ctx->idx = 0; } r = psfmt32[ctx->idx++]; return r; } /* Generate a random integer in [0..limit). */ uint32_t gen_rand32_range(sfmt_t *ctx, uint32_t limit) { uint32_t ret, above; above = 0xffffffffU - (0xffffffffU % limit); while (1) { ret = gen_rand32(ctx); if (ret < above) { ret %= limit; break; } } return ret; } #endif /** * This function generates and returns 64-bit pseudorandom number. * init_gen_rand or init_by_array must be called before this function. * The function gen_rand64 should not be called after gen_rand32, * unless an initialization is again executed. * @return 64-bit pseudorandom number */ uint64_t gen_rand64(sfmt_t *ctx) { #if defined(BIG_ENDIAN64) && !defined(ONLY64) uint32_t r1, r2; uint32_t *psfmt32 = &ctx->sfmt[0].u[0]; #else uint64_t r; uint64_t *psfmt64 = (uint64_t *)&ctx->sfmt[0].u[0]; #endif assert(ctx->initialized); assert(ctx->idx % 2 == 0); if (ctx->idx >= N32) { gen_rand_all(ctx); ctx->idx = 0; } #if defined(BIG_ENDIAN64) && !defined(ONLY64) r1 = psfmt32[ctx->idx]; r2 = psfmt32[ctx->idx + 1]; ctx->idx += 2; return ((uint64_t)r2 << 32) | r1; #else r = psfmt64[ctx->idx / 2]; ctx->idx += 2; return r; #endif } /* Generate a random integer in [0..limit). */ uint64_t gen_rand64_range(sfmt_t *ctx, uint64_t limit) { uint64_t ret, above; above = KQU(0xffffffffffffffff) - (KQU(0xffffffffffffffff) % limit); while (1) { ret = gen_rand64(ctx); if (ret < above) { ret %= limit; break; } } return ret; } #ifndef ONLY64 /** * This function generates pseudorandom 32-bit integers in the * specified array[] by one call. The number of pseudorandom integers * is specified by the argument size, which must be at least 624 and a * multiple of four. The generation by this function is much faster * than the following gen_rand function. * * For initialization, init_gen_rand or init_by_array must be called * before the first call of this function. This function can not be * used after calling gen_rand function, without initialization. * * @param array an array where pseudorandom 32-bit integers are filled * by this function. The pointer to the array must be \b "aligned" * (namely, must be a multiple of 16) in the SIMD version, since it * refers to the address of a 128-bit integer. In the standard C * version, the pointer is arbitrary. * * @param size the number of 32-bit pseudorandom integers to be * generated. size must be a multiple of 4, and greater than or equal * to (MEXP / 128 + 1) * 4. * * @note \b memalign or \b posix_memalign is available to get aligned * memory. Mac OSX doesn't have these functions, but \b malloc of OSX * returns the pointer to the aligned memory block. */ void fill_array32(sfmt_t *ctx, uint32_t *array, int size) { assert(ctx->initialized); assert(ctx->idx == N32); assert(size % 4 == 0); assert(size >= N32); gen_rand_array(ctx, (w128_t *)array, size / 4); ctx->idx = N32; } #endif /** * This function generates pseudorandom 64-bit integers in the * specified array[] by one call. The number of pseudorandom integers * is specified by the argument size, which must be at least 312 and a * multiple of two. The generation by this function is much faster * than the following gen_rand function. * * For initialization, init_gen_rand or init_by_array must be called * before the first call of this function. This function can not be * used after calling gen_rand function, without initialization. * * @param array an array where pseudorandom 64-bit integers are filled * by this function. The pointer to the array must be "aligned" * (namely, must be a multiple of 16) in the SIMD version, since it * refers to the address of a 128-bit integer. In the standard C * version, the pointer is arbitrary. * * @param size the number of 64-bit pseudorandom integers to be * generated. size must be a multiple of 2, and greater than or equal * to (MEXP / 128 + 1) * 2 * * @note \b memalign or \b posix_memalign is available to get aligned * memory. Mac OSX doesn't have these functions, but \b malloc of OSX * returns the pointer to the aligned memory block. */ void fill_array64(sfmt_t *ctx, uint64_t *array, int size) { assert(ctx->initialized); assert(ctx->idx == N32); assert(size % 2 == 0); assert(size >= N64); gen_rand_array(ctx, (w128_t *)array, size / 2); ctx->idx = N32; #if defined(BIG_ENDIAN64) && !defined(ONLY64) swap((w128_t *)array, size /2); #endif } /** * This function initializes the internal state array with a 32-bit * integer seed. * * @param seed a 32-bit integer used as the seed. */ sfmt_t *init_gen_rand(uint32_t seed) { void *p; sfmt_t *ctx; int i; uint32_t *psfmt32; if (posix_memalign(&p, sizeof(w128_t), sizeof(sfmt_t)) != 0) { return NULL; } ctx = (sfmt_t *)p; psfmt32 = &ctx->sfmt[0].u[0]; psfmt32[idxof(0)] = seed; for (i = 1; i < N32; i++) { psfmt32[idxof(i)] = 1812433253UL * (psfmt32[idxof(i - 1)] ^ (psfmt32[idxof(i - 1)] >> 30)) + i; } ctx->idx = N32; period_certification(ctx); ctx->initialized = 1; return ctx; } /** * This function initializes the internal state array, * with an array of 32-bit integers used as the seeds * @param init_key the array of 32-bit integers, used as a seed. * @param key_length the length of init_key. */ sfmt_t *init_by_array(uint32_t *init_key, int key_length) { void *p; sfmt_t *ctx; int i, j, count; uint32_t r; int lag; int mid; int size = N * 4; uint32_t *psfmt32; if (posix_memalign(&p, sizeof(w128_t), sizeof(sfmt_t)) != 0) { return NULL; } ctx = (sfmt_t *)p; psfmt32 = &ctx->sfmt[0].u[0]; if (size >= 623) { lag = 11; } else if (size >= 68) { lag = 7; } else if (size >= 39) { lag = 5; } else { lag = 3; } mid = (size - lag) / 2; memset(ctx->sfmt, 0x8b, sizeof(ctx->sfmt)); if (key_length + 1 > N32) { count = key_length + 1; } else { count = N32; } r = func1(psfmt32[idxof(0)] ^ psfmt32[idxof(mid)] ^ psfmt32[idxof(N32 - 1)]); psfmt32[idxof(mid)] += r; r += key_length; psfmt32[idxof(mid + lag)] += r; psfmt32[idxof(0)] = r; count--; for (i = 1, j = 0; (j < count) && (j < key_length); j++) { r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)] ^ psfmt32[idxof((i + N32 - 1) % N32)]); psfmt32[idxof((i + mid) % N32)] += r; r += init_key[j] + i; psfmt32[idxof((i + mid + lag) % N32)] += r; psfmt32[idxof(i)] = r; i = (i + 1) % N32; } for (; j < count; j++) { r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)] ^ psfmt32[idxof((i + N32 - 1) % N32)]); psfmt32[idxof((i + mid) % N32)] += r; r += i; psfmt32[idxof((i + mid + lag) % N32)] += r; psfmt32[idxof(i)] = r; i = (i + 1) % N32; } for (j = 0; j < N32; j++) { r = func2(psfmt32[idxof(i)] + psfmt32[idxof((i + mid) % N32)] + psfmt32[idxof((i + N32 - 1) % N32)]); psfmt32[idxof((i + mid) % N32)] ^= r; r -= i; psfmt32[idxof((i + mid + lag) % N32)] ^= r; psfmt32[idxof(i)] = r; i = (i + 1) % N32; } ctx->idx = N32; period_certification(ctx); ctx->initialized = 1; return ctx; } void fini_gen_rand(sfmt_t *ctx) { assert(ctx != NULL); ctx->initialized = 0; free(ctx); } redis-8.0.2/deps/jemalloc/test/src/btalloc.c000066400000000000000000000001571501533116600207060ustar00rootroot00000000000000#include "test/jemalloc_test.h" void * btalloc(size_t size, unsigned bits) { return btalloc_0(size, bits); } redis-8.0.2/deps/jemalloc/test/src/btalloc_0.c000066400000000000000000000000621501533116600211200ustar00rootroot00000000000000#include "test/jemalloc_test.h" btalloc_n_gen(0) redis-8.0.2/deps/jemalloc/test/src/btalloc_1.c000066400000000000000000000000621501533116600211210ustar00rootroot00000000000000#include "test/jemalloc_test.h" btalloc_n_gen(1) redis-8.0.2/deps/jemalloc/test/src/math.c000066400000000000000000000000601501533116600202100ustar00rootroot00000000000000#define MATH_C_ #include "test/jemalloc_test.h" redis-8.0.2/deps/jemalloc/test/src/mtx.c000066400000000000000000000022171501533116600200750ustar00rootroot00000000000000#include "test/jemalloc_test.h" #ifndef _CRT_SPINCOUNT #define _CRT_SPINCOUNT 4000 #endif bool mtx_init(mtx_t *mtx) { #ifdef _WIN32 if (!InitializeCriticalSectionAndSpinCount(&mtx->lock, _CRT_SPINCOUNT)) { return true; } #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) mtx->lock = OS_UNFAIR_LOCK_INIT; #else pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr) != 0) { return true; } pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); if (pthread_mutex_init(&mtx->lock, &attr) != 0) { pthread_mutexattr_destroy(&attr); return true; } pthread_mutexattr_destroy(&attr); #endif return false; } void mtx_fini(mtx_t *mtx) { #ifdef _WIN32 #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) #else pthread_mutex_destroy(&mtx->lock); #endif } void mtx_lock(mtx_t *mtx) { #ifdef _WIN32 EnterCriticalSection(&mtx->lock); #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) os_unfair_lock_lock(&mtx->lock); #else pthread_mutex_lock(&mtx->lock); #endif } void mtx_unlock(mtx_t *mtx) { #ifdef _WIN32 LeaveCriticalSection(&mtx->lock); #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) os_unfair_lock_unlock(&mtx->lock); #else pthread_mutex_unlock(&mtx->lock); #endif } redis-8.0.2/deps/jemalloc/test/src/sleep.c000066400000000000000000000007151501533116600203760ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * Sleep for approximately ns nanoseconds. No lower *nor* upper bound on sleep * time is guaranteed. */ void sleep_ns(unsigned ns) { assert(ns <= 1000*1000*1000); #ifdef _WIN32 Sleep(ns / 1000 / 1000); #else { struct timespec timeout; if (ns < 1000*1000*1000) { timeout.tv_sec = 0; timeout.tv_nsec = ns; } else { timeout.tv_sec = 1; timeout.tv_nsec = 0; } nanosleep(&timeout, NULL); } #endif } redis-8.0.2/deps/jemalloc/test/src/test.c000066400000000000000000000116521501533116600202470ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* Test status state. */ static unsigned test_count = 0; static test_status_t test_counts[test_status_count] = {0, 0, 0}; static test_status_t test_status = test_status_pass; static const char * test_name = ""; /* Reentrancy testing helpers. */ #define NUM_REENTRANT_ALLOCS 20 typedef enum { non_reentrant = 0, libc_reentrant = 1, arena_new_reentrant = 2 } reentrancy_t; static reentrancy_t reentrancy; static bool libc_hook_ran = false; static bool arena_new_hook_ran = false; static const char * reentrancy_t_str(reentrancy_t r) { switch (r) { case non_reentrant: return "non-reentrant"; case libc_reentrant: return "libc-reentrant"; case arena_new_reentrant: return "arena_new-reentrant"; default: unreachable(); } } static void do_hook(bool *hook_ran, void (**hook)()) { *hook_ran = true; *hook = NULL; size_t alloc_size = 1; for (int i = 0; i < NUM_REENTRANT_ALLOCS; i++) { free(malloc(alloc_size)); alloc_size *= 2; } } static void libc_reentrancy_hook() { do_hook(&libc_hook_ran, &test_hooks_libc_hook); } static void arena_new_reentrancy_hook() { do_hook(&arena_new_hook_ran, &test_hooks_arena_new_hook); } /* Actual test infrastructure. */ bool test_is_reentrant() { return reentrancy != non_reentrant; } JEMALLOC_FORMAT_PRINTF(1, 2) void test_skip(const char *format, ...) { va_list ap; va_start(ap, format); malloc_vcprintf(NULL, NULL, format, ap); va_end(ap); malloc_printf("\n"); test_status = test_status_skip; } JEMALLOC_FORMAT_PRINTF(1, 2) void test_fail(const char *format, ...) { va_list ap; va_start(ap, format); malloc_vcprintf(NULL, NULL, format, ap); va_end(ap); malloc_printf("\n"); test_status = test_status_fail; } static const char * test_status_string(test_status_t current_status) { switch (current_status) { case test_status_pass: return "pass"; case test_status_skip: return "skip"; case test_status_fail: return "fail"; default: not_reached(); } } void p_test_init(const char *name) { test_count++; test_status = test_status_pass; test_name = name; } void p_test_fini(void) { test_counts[test_status]++; malloc_printf("%s (%s): %s\n", test_name, reentrancy_t_str(reentrancy), test_status_string(test_status)); } static void check_global_slow(test_status_t *status) { #ifdef JEMALLOC_UNIT_TEST /* * This check needs to peek into tsd internals, which is why it's only * exposed in unit tests. */ if (tsd_global_slow()) { malloc_printf("Testing increased global slow count\n"); *status = test_status_fail; } #endif } static test_status_t p_test_impl(bool do_malloc_init, bool do_reentrant, test_t *t, va_list ap) { test_status_t ret; if (do_malloc_init) { /* * Make sure initialization occurs prior to running tests. * Tests are special because they may use internal facilities * prior to triggering initialization as a side effect of * calling into the public API. */ if (nallocx(1, 0) == 0) { malloc_printf("Initialization error"); return test_status_fail; } } ret = test_status_pass; for (; t != NULL; t = va_arg(ap, test_t *)) { /* Non-reentrant run. */ reentrancy = non_reentrant; test_hooks_arena_new_hook = test_hooks_libc_hook = NULL; t(); if (test_status > ret) { ret = test_status; } check_global_slow(&ret); /* Reentrant run. */ if (do_reentrant) { reentrancy = libc_reentrant; test_hooks_arena_new_hook = NULL; test_hooks_libc_hook = &libc_reentrancy_hook; t(); if (test_status > ret) { ret = test_status; } check_global_slow(&ret); reentrancy = arena_new_reentrant; test_hooks_libc_hook = NULL; test_hooks_arena_new_hook = &arena_new_reentrancy_hook; t(); if (test_status > ret) { ret = test_status; } check_global_slow(&ret); } } malloc_printf("--- %s: %u/%u, %s: %u/%u, %s: %u/%u ---\n", test_status_string(test_status_pass), test_counts[test_status_pass], test_count, test_status_string(test_status_skip), test_counts[test_status_skip], test_count, test_status_string(test_status_fail), test_counts[test_status_fail], test_count); return ret; } test_status_t p_test(test_t *t, ...) { test_status_t ret; va_list ap; ret = test_status_pass; va_start(ap, t); ret = p_test_impl(true, true, t, ap); va_end(ap); return ret; } test_status_t p_test_no_reentrancy(test_t *t, ...) { test_status_t ret; va_list ap; ret = test_status_pass; va_start(ap, t); ret = p_test_impl(true, false, t, ap); va_end(ap); return ret; } test_status_t p_test_no_malloc_init(test_t *t, ...) { test_status_t ret; va_list ap; ret = test_status_pass; va_start(ap, t); /* * We also omit reentrancy from bootstrapping tests, since we don't * (yet) care about general reentrancy during bootstrapping. */ ret = p_test_impl(false, false, t, ap); va_end(ap); return ret; } void p_test_fail(const char *prefix, const char *message) { malloc_cprintf(NULL, NULL, "%s%s\n", prefix, message); test_status = test_status_fail; } redis-8.0.2/deps/jemalloc/test/src/thd.c000066400000000000000000000013671501533116600200510ustar00rootroot00000000000000#include "test/jemalloc_test.h" #ifdef _WIN32 void thd_create(thd_t *thd, void *(*proc)(void *), void *arg) { LPTHREAD_START_ROUTINE routine = (LPTHREAD_START_ROUTINE)proc; *thd = CreateThread(NULL, 0, routine, arg, 0, NULL); if (*thd == NULL) { test_fail("Error in CreateThread()\n"); } } void thd_join(thd_t thd, void **ret) { if (WaitForSingleObject(thd, INFINITE) == WAIT_OBJECT_0 && ret) { DWORD exit_code; GetExitCodeThread(thd, (LPDWORD) &exit_code); *ret = (void *)(uintptr_t)exit_code; } } #else void thd_create(thd_t *thd, void *(*proc)(void *), void *arg) { if (pthread_create(thd, NULL, proc, arg) != 0) { test_fail("Error in pthread_create()\n"); } } void thd_join(thd_t thd, void **ret) { pthread_join(thd, ret); } #endif redis-8.0.2/deps/jemalloc/test/src/timer.c000066400000000000000000000020531501533116600204030ustar00rootroot00000000000000#include "test/jemalloc_test.h" void timer_start(timedelta_t *timer) { nstime_init_update(&timer->t0); } void timer_stop(timedelta_t *timer) { nstime_copy(&timer->t1, &timer->t0); nstime_update(&timer->t1); } uint64_t timer_usec(const timedelta_t *timer) { nstime_t delta; nstime_copy(&delta, &timer->t1); nstime_subtract(&delta, &timer->t0); return nstime_ns(&delta) / 1000; } void timer_ratio(timedelta_t *a, timedelta_t *b, char *buf, size_t buflen) { uint64_t t0 = timer_usec(a); uint64_t t1 = timer_usec(b); uint64_t mult; size_t i = 0; size_t j, n; /* Whole. */ n = malloc_snprintf(&buf[i], buflen-i, "%"FMTu64, t0 / t1); i += n; if (i >= buflen) { return; } mult = 1; for (j = 0; j < n; j++) { mult *= 10; } /* Decimal. */ n = malloc_snprintf(&buf[i], buflen-i, "."); i += n; /* Fraction. */ while (i < buflen-1) { uint64_t round = (i+1 == buflen-1 && ((t0 * mult * 10 / t1) % 10 >= 5)) ? 1 : 0; n = malloc_snprintf(&buf[i], buflen-i, "%"FMTu64, (t0 * mult / t1) % 10 + round); i += n; mult *= 10; } } redis-8.0.2/deps/jemalloc/test/stress/000077500000000000000000000000001501533116600176535ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/stress/batch_alloc.c000066400000000000000000000112511501533116600222520ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/bench.h" #define MIBLEN 8 static size_t mib[MIBLEN]; static size_t miblen = MIBLEN; #define TINY_BATCH 10 #define TINY_BATCH_ITER (10 * 1000 * 1000) #define HUGE_BATCH (1000 * 1000) #define HUGE_BATCH_ITER 100 #define LEN (100 * 1000 * 1000) static void *batch_ptrs[LEN]; static size_t batch_ptrs_next = 0; static void *item_ptrs[LEN]; static size_t item_ptrs_next = 0; #define SIZE 7 typedef struct batch_alloc_packet_s batch_alloc_packet_t; struct batch_alloc_packet_s { void **ptrs; size_t num; size_t size; int flags; }; static void batch_alloc_wrapper(size_t batch) { batch_alloc_packet_t batch_alloc_packet = {batch_ptrs + batch_ptrs_next, batch, SIZE, 0}; size_t filled; size_t len = sizeof(size_t); assert_d_eq(mallctlbymib(mib, miblen, &filled, &len, &batch_alloc_packet, sizeof(batch_alloc_packet)), 0, ""); assert_zu_eq(filled, batch, ""); } static void item_alloc_wrapper(size_t batch) { for (size_t i = item_ptrs_next, end = i + batch; i < end; ++i) { item_ptrs[i] = malloc(SIZE); } } static void release_and_clear(void **ptrs, size_t len) { for (size_t i = 0; i < len; ++i) { void *p = ptrs[i]; assert_ptr_not_null(p, "allocation failed"); sdallocx(p, SIZE, 0); ptrs[i] = NULL; } } static void batch_alloc_without_free(size_t batch) { batch_alloc_wrapper(batch); batch_ptrs_next += batch; } static void item_alloc_without_free(size_t batch) { item_alloc_wrapper(batch); item_ptrs_next += batch; } static void batch_alloc_with_free(size_t batch) { batch_alloc_wrapper(batch); release_and_clear(batch_ptrs + batch_ptrs_next, batch); batch_ptrs_next += batch; } static void item_alloc_with_free(size_t batch) { item_alloc_wrapper(batch); release_and_clear(item_ptrs + item_ptrs_next, batch); item_ptrs_next += batch; } static void compare_without_free(size_t batch, size_t iter, void (*batch_alloc_without_free_func)(void), void (*item_alloc_without_free_func)(void)) { assert(batch_ptrs_next == 0); assert(item_ptrs_next == 0); assert(batch * iter <= LEN); for (size_t i = 0; i < iter; ++i) { batch_alloc_without_free_func(); item_alloc_without_free_func(); } release_and_clear(batch_ptrs, batch_ptrs_next); batch_ptrs_next = 0; release_and_clear(item_ptrs, item_ptrs_next); item_ptrs_next = 0; compare_funcs(0, iter, "batch allocation", batch_alloc_without_free_func, "item allocation", item_alloc_without_free_func); release_and_clear(batch_ptrs, batch_ptrs_next); batch_ptrs_next = 0; release_and_clear(item_ptrs, item_ptrs_next); item_ptrs_next = 0; } static void compare_with_free(size_t batch, size_t iter, void (*batch_alloc_with_free_func)(void), void (*item_alloc_with_free_func)(void)) { assert(batch_ptrs_next == 0); assert(item_ptrs_next == 0); assert(batch * iter <= LEN); for (size_t i = 0; i < iter; ++i) { batch_alloc_with_free_func(); item_alloc_with_free_func(); } batch_ptrs_next = 0; item_ptrs_next = 0; compare_funcs(0, iter, "batch allocation", batch_alloc_with_free_func, "item allocation", item_alloc_with_free_func); batch_ptrs_next = 0; item_ptrs_next = 0; } static void batch_alloc_without_free_tiny() { batch_alloc_without_free(TINY_BATCH); } static void item_alloc_without_free_tiny() { item_alloc_without_free(TINY_BATCH); } TEST_BEGIN(test_tiny_batch_without_free) { compare_without_free(TINY_BATCH, TINY_BATCH_ITER, batch_alloc_without_free_tiny, item_alloc_without_free_tiny); } TEST_END static void batch_alloc_with_free_tiny() { batch_alloc_with_free(TINY_BATCH); } static void item_alloc_with_free_tiny() { item_alloc_with_free(TINY_BATCH); } TEST_BEGIN(test_tiny_batch_with_free) { compare_with_free(TINY_BATCH, TINY_BATCH_ITER, batch_alloc_with_free_tiny, item_alloc_with_free_tiny); } TEST_END static void batch_alloc_without_free_huge() { batch_alloc_without_free(HUGE_BATCH); } static void item_alloc_without_free_huge() { item_alloc_without_free(HUGE_BATCH); } TEST_BEGIN(test_huge_batch_without_free) { compare_without_free(HUGE_BATCH, HUGE_BATCH_ITER, batch_alloc_without_free_huge, item_alloc_without_free_huge); } TEST_END static void batch_alloc_with_free_huge() { batch_alloc_with_free(HUGE_BATCH); } static void item_alloc_with_free_huge() { item_alloc_with_free(HUGE_BATCH); } TEST_BEGIN(test_huge_batch_with_free) { compare_with_free(HUGE_BATCH, HUGE_BATCH_ITER, batch_alloc_with_free_huge, item_alloc_with_free_huge); } TEST_END int main(void) { assert_d_eq(mallctlnametomib("experimental.batch_alloc", mib, &miblen), 0, ""); return test_no_reentrancy( test_tiny_batch_without_free, test_tiny_batch_with_free, test_huge_batch_without_free, test_huge_batch_with_free); } redis-8.0.2/deps/jemalloc/test/stress/fill_flush.c000066400000000000000000000035451501533116600221550ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/bench.h" #define SMALL_ALLOC_SIZE 128 #define LARGE_ALLOC_SIZE SC_LARGE_MINCLASS #define NALLOCS 1000 /* * We make this volatile so the 1-at-a-time variants can't leave the allocation * in a register, just to try to get the cache behavior closer. */ void *volatile allocs[NALLOCS]; static void array_alloc_dalloc_small(void) { for (int i = 0; i < NALLOCS; i++) { void *p = mallocx(SMALL_ALLOC_SIZE, 0); assert_ptr_not_null(p, "mallocx shouldn't fail"); allocs[i] = p; } for (int i = 0; i < NALLOCS; i++) { sdallocx(allocs[i], SMALL_ALLOC_SIZE, 0); } } static void item_alloc_dalloc_small(void) { for (int i = 0; i < NALLOCS; i++) { void *p = mallocx(SMALL_ALLOC_SIZE, 0); assert_ptr_not_null(p, "mallocx shouldn't fail"); allocs[i] = p; sdallocx(allocs[i], SMALL_ALLOC_SIZE, 0); } } TEST_BEGIN(test_array_vs_item_small) { compare_funcs(1 * 1000, 10 * 1000, "array of small allocations", array_alloc_dalloc_small, "small item allocation", item_alloc_dalloc_small); } TEST_END static void array_alloc_dalloc_large(void) { for (int i = 0; i < NALLOCS; i++) { void *p = mallocx(LARGE_ALLOC_SIZE, 0); assert_ptr_not_null(p, "mallocx shouldn't fail"); allocs[i] = p; } for (int i = 0; i < NALLOCS; i++) { sdallocx(allocs[i], LARGE_ALLOC_SIZE, 0); } } static void item_alloc_dalloc_large(void) { for (int i = 0; i < NALLOCS; i++) { void *p = mallocx(LARGE_ALLOC_SIZE, 0); assert_ptr_not_null(p, "mallocx shouldn't fail"); allocs[i] = p; sdallocx(allocs[i], LARGE_ALLOC_SIZE, 0); } } TEST_BEGIN(test_array_vs_item_large) { compare_funcs(100, 1000, "array of large allocations", array_alloc_dalloc_large, "large item allocation", item_alloc_dalloc_large); } TEST_END int main(void) { return test_no_reentrancy( test_array_vs_item_small, test_array_vs_item_large); } redis-8.0.2/deps/jemalloc/test/stress/hookbench.c000066400000000000000000000032061501533116600217600ustar00rootroot00000000000000#include "test/jemalloc_test.h" static void noop_alloc_hook(void *extra, hook_alloc_t type, void *result, uintptr_t result_raw, uintptr_t args_raw[3]) { } static void noop_dalloc_hook(void *extra, hook_dalloc_t type, void *address, uintptr_t args_raw[3]) { } static void noop_expand_hook(void *extra, hook_expand_t type, void *address, size_t old_usize, size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]) { } static void malloc_free_loop(int iters) { for (int i = 0; i < iters; i++) { void *p = mallocx(1, 0); free(p); } } static void test_hooked(int iters) { hooks_t hooks = {&noop_alloc_hook, &noop_dalloc_hook, &noop_expand_hook, NULL}; int err; void *handles[HOOK_MAX]; size_t sz = sizeof(handles[0]); for (int i = 0; i < HOOK_MAX; i++) { err = mallctl("experimental.hooks.install", &handles[i], &sz, &hooks, sizeof(hooks)); assert(err == 0); timedelta_t timer; timer_start(&timer); malloc_free_loop(iters); timer_stop(&timer); malloc_printf("With %d hook%s: %"FMTu64"us\n", i + 1, i + 1 == 1 ? "" : "s", timer_usec(&timer)); } for (int i = 0; i < HOOK_MAX; i++) { err = mallctl("experimental.hooks.remove", NULL, NULL, &handles[i], sizeof(handles[i])); assert(err == 0); } } static void test_unhooked(int iters) { timedelta_t timer; timer_start(&timer); malloc_free_loop(iters); timer_stop(&timer); malloc_printf("Without hooks: %"FMTu64"us\n", timer_usec(&timer)); } int main(void) { /* Initialize */ free(mallocx(1, 0)); int iters = 10 * 1000 * 1000; malloc_printf("Benchmarking hooks with %d iterations:\n", iters); test_hooked(iters); test_unhooked(iters); } redis-8.0.2/deps/jemalloc/test/stress/large_microbench.c000066400000000000000000000013051501533116600233010ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/bench.h" static void large_mallocx_free(void) { /* * We go a bit larger than the large minclass on its own to better * expose costs from things like zeroing. */ void *p = mallocx(SC_LARGE_MINCLASS, MALLOCX_TCACHE_NONE); assert_ptr_not_null(p, "mallocx shouldn't fail"); free(p); } static void small_mallocx_free(void) { void *p = mallocx(16, 0); assert_ptr_not_null(p, "mallocx shouldn't fail"); free(p); } TEST_BEGIN(test_large_vs_small) { compare_funcs(100*1000, 1*1000*1000, "large mallocx", large_mallocx_free, "small mallocx", small_mallocx_free); } TEST_END int main(void) { return test_no_reentrancy( test_large_vs_small); } redis-8.0.2/deps/jemalloc/test/stress/mallctl.c000066400000000000000000000037171501533116600214570ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/bench.h" static void mallctl_short(void) { const char *version; size_t sz = sizeof(version); int err = mallctl("version", &version, &sz, NULL, 0); assert_d_eq(err, 0, "mallctl failure"); } size_t mib_short[1]; static void mallctlbymib_short(void) { size_t miblen = sizeof(mib_short)/sizeof(mib_short[0]); const char *version; size_t sz = sizeof(version); int err = mallctlbymib(mib_short, miblen, &version, &sz, NULL, 0); assert_d_eq(err, 0, "mallctlbymib failure"); } TEST_BEGIN(test_mallctl_vs_mallctlbymib_short) { size_t miblen = sizeof(mib_short)/sizeof(mib_short[0]); int err = mallctlnametomib("version", mib_short, &miblen); assert_d_eq(err, 0, "mallctlnametomib failure"); compare_funcs(10*1000*1000, 10*1000*1000, "mallctl_short", mallctl_short, "mallctlbymib_short", mallctlbymib_short); } TEST_END static void mallctl_long(void) { uint64_t nmalloc; size_t sz = sizeof(nmalloc); int err = mallctl("stats.arenas.0.bins.0.nmalloc", &nmalloc, &sz, NULL, 0); assert_d_eq(err, 0, "mallctl failure"); } size_t mib_long[6]; static void mallctlbymib_long(void) { size_t miblen = sizeof(mib_long)/sizeof(mib_long[0]); uint64_t nmalloc; size_t sz = sizeof(nmalloc); int err = mallctlbymib(mib_long, miblen, &nmalloc, &sz, NULL, 0); assert_d_eq(err, 0, "mallctlbymib failure"); } TEST_BEGIN(test_mallctl_vs_mallctlbymib_long) { /* * We want to use the longest mallctl we have; that needs stats support * to be allowed. */ test_skip_if(!config_stats); size_t miblen = sizeof(mib_long)/sizeof(mib_long[0]); int err = mallctlnametomib("stats.arenas.0.bins.0.nmalloc", mib_long, &miblen); assert_d_eq(err, 0, "mallctlnametomib failure"); compare_funcs(10*1000*1000, 10*1000*1000, "mallctl_long", mallctl_long, "mallctlbymib_long", mallctlbymib_long); } TEST_END int main(void) { return test_no_reentrancy( test_mallctl_vs_mallctlbymib_short, test_mallctl_vs_mallctlbymib_long); } redis-8.0.2/deps/jemalloc/test/stress/microbench.c000066400000000000000000000044071501533116600221350ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/bench.h" static void malloc_free(void) { /* The compiler can optimize away free(malloc(1))! */ void *p = malloc(1); if (p == NULL) { test_fail("Unexpected malloc() failure"); return; } free(p); } static void mallocx_free(void) { void *p = mallocx(1, 0); if (p == NULL) { test_fail("Unexpected mallocx() failure"); return; } free(p); } TEST_BEGIN(test_malloc_vs_mallocx) { compare_funcs(10*1000*1000, 100*1000*1000, "malloc", malloc_free, "mallocx", mallocx_free); } TEST_END static void malloc_dallocx(void) { void *p = malloc(1); if (p == NULL) { test_fail("Unexpected malloc() failure"); return; } dallocx(p, 0); } static void malloc_sdallocx(void) { void *p = malloc(1); if (p == NULL) { test_fail("Unexpected malloc() failure"); return; } sdallocx(p, 1, 0); } TEST_BEGIN(test_free_vs_dallocx) { compare_funcs(10*1000*1000, 100*1000*1000, "free", malloc_free, "dallocx", malloc_dallocx); } TEST_END TEST_BEGIN(test_dallocx_vs_sdallocx) { compare_funcs(10*1000*1000, 100*1000*1000, "dallocx", malloc_dallocx, "sdallocx", malloc_sdallocx); } TEST_END static void malloc_mus_free(void) { void *p; p = malloc(1); if (p == NULL) { test_fail("Unexpected malloc() failure"); return; } TEST_MALLOC_SIZE(p); free(p); } static void malloc_sallocx_free(void) { void *p; p = malloc(1); if (p == NULL) { test_fail("Unexpected malloc() failure"); return; } if (sallocx(p, 0) < 1) { test_fail("Unexpected sallocx() failure"); } free(p); } TEST_BEGIN(test_mus_vs_sallocx) { compare_funcs(10*1000*1000, 100*1000*1000, "malloc_usable_size", malloc_mus_free, "sallocx", malloc_sallocx_free); } TEST_END static void malloc_nallocx_free(void) { void *p; p = malloc(1); if (p == NULL) { test_fail("Unexpected malloc() failure"); return; } if (nallocx(1, 0) < 1) { test_fail("Unexpected nallocx() failure"); } free(p); } TEST_BEGIN(test_sallocx_vs_nallocx) { compare_funcs(10*1000*1000, 100*1000*1000, "sallocx", malloc_sallocx_free, "nallocx", malloc_nallocx_free); } TEST_END int main(void) { return test_no_reentrancy( test_malloc_vs_mallocx, test_free_vs_dallocx, test_dallocx_vs_sdallocx, test_mus_vs_sallocx, test_sallocx_vs_nallocx); } redis-8.0.2/deps/jemalloc/test/test.sh.in000066400000000000000000000043621501533116600202550ustar00rootroot00000000000000#!/bin/sh case @abi@ in macho) export DYLD_FALLBACK_LIBRARY_PATH="@objroot@lib" ;; pecoff) export PATH="${PATH}:@objroot@lib" ;; *) ;; esac # Make a copy of the @JEMALLOC_CPREFIX@MALLOC_CONF passed in to this script, so # it can be repeatedly concatenated with per test settings. export MALLOC_CONF_ALL=${@JEMALLOC_CPREFIX@MALLOC_CONF} # Concatenate the individual test's MALLOC_CONF and MALLOC_CONF_ALL. export_malloc_conf() { if [ "x${MALLOC_CONF}" != "x" -a "x${MALLOC_CONF_ALL}" != "x" ] ; then export @JEMALLOC_CPREFIX@MALLOC_CONF="${MALLOC_CONF},${MALLOC_CONF_ALL}" else export @JEMALLOC_CPREFIX@MALLOC_CONF="${MALLOC_CONF}${MALLOC_CONF_ALL}" fi } # Corresponds to test_status_t. pass_code=0 skip_code=1 fail_code=2 pass_count=0 skip_count=0 fail_count=0 for t in $@; do if [ $pass_count -ne 0 -o $skip_count -ne 0 -o $fail_count != 0 ] ; then echo fi echo "=== ${t} ===" if [ -e "@srcroot@${t}.sh" ] ; then # Source the shell script corresponding to the test in a subshell and # execute the test. This allows the shell script to set MALLOC_CONF, which # is then used to set @JEMALLOC_CPREFIX@MALLOC_CONF (thus allowing the # per test shell script to ignore the @JEMALLOC_CPREFIX@ detail). enable_fill=@enable_fill@ \ enable_prof=@enable_prof@ \ . @srcroot@${t}.sh && \ export_malloc_conf && \ $JEMALLOC_TEST_PREFIX ${t}@exe@ @abs_srcroot@ @abs_objroot@ else export MALLOC_CONF= && \ export_malloc_conf && \ $JEMALLOC_TEST_PREFIX ${t}@exe@ @abs_srcroot@ @abs_objroot@ fi result_code=$? case ${result_code} in ${pass_code}) pass_count=$((pass_count+1)) ;; ${skip_code}) skip_count=$((skip_count+1)) ;; ${fail_code}) fail_count=$((fail_count+1)) ;; *) echo "Test harness error: ${t} w/ MALLOC_CONF=\"${MALLOC_CONF}\"" 1>&2 echo "Use prefix to debug, e.g. JEMALLOC_TEST_PREFIX=\"gdb --args\" sh test/test.sh ${t}" 1>&2 exit 1 esac done total_count=`expr ${pass_count} + ${skip_count} + ${fail_count}` echo echo "Test suite summary: pass: ${pass_count}/${total_count}, skip: ${skip_count}/${total_count}, fail: ${fail_count}/${total_count}" if [ ${fail_count} -eq 0 ] ; then exit 0 else exit 1 fi redis-8.0.2/deps/jemalloc/test/unit/000077500000000000000000000000001501533116600173075ustar00rootroot00000000000000redis-8.0.2/deps/jemalloc/test/unit/SFMT.c000066400000000000000000002530701501533116600202330ustar00rootroot00000000000000/* * This file derives from SFMT 1.3.3 * (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html), which was * released under the terms of the following license: * * Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima * University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the Hiroshima University nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * OWNER 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. */ #include "test/jemalloc_test.h" #define BLOCK_SIZE 10000 #define BLOCK_SIZE64 (BLOCK_SIZE / 2) #define COUNT_1 1000 #define COUNT_2 700 static const uint32_t init_gen_rand_32_expected[] = { 3440181298U, 1564997079U, 1510669302U, 2930277156U, 1452439940U, 3796268453U, 423124208U, 2143818589U, 3827219408U, 2987036003U, 2674978610U, 1536842514U, 2027035537U, 2534897563U, 1686527725U, 545368292U, 1489013321U, 1370534252U, 4231012796U, 3994803019U, 1764869045U, 824597505U, 862581900U, 2469764249U, 812862514U, 359318673U, 116957936U, 3367389672U, 2327178354U, 1898245200U, 3206507879U, 2378925033U, 1040214787U, 2524778605U, 3088428700U, 1417665896U, 964324147U, 2282797708U, 2456269299U, 313400376U, 2245093271U, 1015729427U, 2694465011U, 3246975184U, 1992793635U, 463679346U, 3721104591U, 3475064196U, 856141236U, 1499559719U, 3522818941U, 3721533109U, 1954826617U, 1282044024U, 1543279136U, 1301863085U, 2669145051U, 4221477354U, 3896016841U, 3392740262U, 462466863U, 1037679449U, 1228140306U, 922298197U, 1205109853U, 1872938061U, 3102547608U, 2742766808U, 1888626088U, 4028039414U, 157593879U, 1136901695U, 4038377686U, 3572517236U, 4231706728U, 2997311961U, 1189931652U, 3981543765U, 2826166703U, 87159245U, 1721379072U, 3897926942U, 1790395498U, 2569178939U, 1047368729U, 2340259131U, 3144212906U, 2301169789U, 2442885464U, 3034046771U, 3667880593U, 3935928400U, 2372805237U, 1666397115U, 2460584504U, 513866770U, 3810869743U, 2147400037U, 2792078025U, 2941761810U, 3212265810U, 984692259U, 346590253U, 1804179199U, 3298543443U, 750108141U, 2880257022U, 243310542U, 1869036465U, 1588062513U, 2983949551U, 1931450364U, 4034505847U, 2735030199U, 1628461061U, 2539522841U, 127965585U, 3992448871U, 913388237U, 559130076U, 1202933193U, 4087643167U, 2590021067U, 2256240196U, 1746697293U, 1013913783U, 1155864921U, 2715773730U, 915061862U, 1948766573U, 2322882854U, 3761119102U, 1343405684U, 3078711943U, 3067431651U, 3245156316U, 3588354584U, 3484623306U, 3899621563U, 4156689741U, 3237090058U, 3880063844U, 862416318U, 4039923869U, 2303788317U, 3073590536U, 701653667U, 2131530884U, 3169309950U, 2028486980U, 747196777U, 3620218225U, 432016035U, 1449580595U, 2772266392U, 444224948U, 1662832057U, 3184055582U, 3028331792U, 1861686254U, 1104864179U, 342430307U, 1350510923U, 3024656237U, 1028417492U, 2870772950U, 290847558U, 3675663500U, 508431529U, 4264340390U, 2263569913U, 1669302976U, 519511383U, 2706411211U, 3764615828U, 3883162495U, 4051445305U, 2412729798U, 3299405164U, 3991911166U, 2348767304U, 2664054906U, 3763609282U, 593943581U, 3757090046U, 2075338894U, 2020550814U, 4287452920U, 4290140003U, 1422957317U, 2512716667U, 2003485045U, 2307520103U, 2288472169U, 3940751663U, 4204638664U, 2892583423U, 1710068300U, 3904755993U, 2363243951U, 3038334120U, 547099465U, 771105860U, 3199983734U, 4282046461U, 2298388363U, 934810218U, 2837827901U, 3952500708U, 2095130248U, 3083335297U, 26885281U, 3932155283U, 1531751116U, 1425227133U, 495654159U, 3279634176U, 3855562207U, 3957195338U, 4159985527U, 893375062U, 1875515536U, 1327247422U, 3754140693U, 1028923197U, 1729880440U, 805571298U, 448971099U, 2726757106U, 2749436461U, 2485987104U, 175337042U, 3235477922U, 3882114302U, 2020970972U, 943926109U, 2762587195U, 1904195558U, 3452650564U, 108432281U, 3893463573U, 3977583081U, 2636504348U, 1110673525U, 3548479841U, 4258854744U, 980047703U, 4057175418U, 3890008292U, 145653646U, 3141868989U, 3293216228U, 1194331837U, 1254570642U, 3049934521U, 2868313360U, 2886032750U, 1110873820U, 279553524U, 3007258565U, 1104807822U, 3186961098U, 315764646U, 2163680838U, 3574508994U, 3099755655U, 191957684U, 3642656737U, 3317946149U, 3522087636U, 444526410U, 779157624U, 1088229627U, 1092460223U, 1856013765U, 3659877367U, 368270451U, 503570716U, 3000984671U, 2742789647U, 928097709U, 2914109539U, 308843566U, 2816161253U, 3667192079U, 2762679057U, 3395240989U, 2928925038U, 1491465914U, 3458702834U, 3787782576U, 2894104823U, 1296880455U, 1253636503U, 989959407U, 2291560361U, 2776790436U, 1913178042U, 1584677829U, 689637520U, 1898406878U, 688391508U, 3385234998U, 845493284U, 1943591856U, 2720472050U, 222695101U, 1653320868U, 2904632120U, 4084936008U, 1080720688U, 3938032556U, 387896427U, 2650839632U, 99042991U, 1720913794U, 1047186003U, 1877048040U, 2090457659U, 517087501U, 4172014665U, 2129713163U, 2413533132U, 2760285054U, 4129272496U, 1317737175U, 2309566414U, 2228873332U, 3889671280U, 1110864630U, 3576797776U, 2074552772U, 832002644U, 3097122623U, 2464859298U, 2679603822U, 1667489885U, 3237652716U, 1478413938U, 1719340335U, 2306631119U, 639727358U, 3369698270U, 226902796U, 2099920751U, 1892289957U, 2201594097U, 3508197013U, 3495811856U, 3900381493U, 841660320U, 3974501451U, 3360949056U, 1676829340U, 728899254U, 2047809627U, 2390948962U, 670165943U, 3412951831U, 4189320049U, 1911595255U, 2055363086U, 507170575U, 418219594U, 4141495280U, 2692088692U, 4203630654U, 3540093932U, 791986533U, 2237921051U, 2526864324U, 2956616642U, 1394958700U, 1983768223U, 1893373266U, 591653646U, 228432437U, 1611046598U, 3007736357U, 1040040725U, 2726180733U, 2789804360U, 4263568405U, 829098158U, 3847722805U, 1123578029U, 1804276347U, 997971319U, 4203797076U, 4185199713U, 2811733626U, 2343642194U, 2985262313U, 1417930827U, 3759587724U, 1967077982U, 1585223204U, 1097475516U, 1903944948U, 740382444U, 1114142065U, 1541796065U, 1718384172U, 1544076191U, 1134682254U, 3519754455U, 2866243923U, 341865437U, 645498576U, 2690735853U, 1046963033U, 2493178460U, 1187604696U, 1619577821U, 488503634U, 3255768161U, 2306666149U, 1630514044U, 2377698367U, 2751503746U, 3794467088U, 1796415981U, 3657173746U, 409136296U, 1387122342U, 1297726519U, 219544855U, 4270285558U, 437578827U, 1444698679U, 2258519491U, 963109892U, 3982244073U, 3351535275U, 385328496U, 1804784013U, 698059346U, 3920535147U, 708331212U, 784338163U, 785678147U, 1238376158U, 1557298846U, 2037809321U, 271576218U, 4145155269U, 1913481602U, 2763691931U, 588981080U, 1201098051U, 3717640232U, 1509206239U, 662536967U, 3180523616U, 1133105435U, 2963500837U, 2253971215U, 3153642623U, 1066925709U, 2582781958U, 3034720222U, 1090798544U, 2942170004U, 4036187520U, 686972531U, 2610990302U, 2641437026U, 1837562420U, 722096247U, 1315333033U, 2102231203U, 3402389208U, 3403698140U, 1312402831U, 2898426558U, 814384596U, 385649582U, 1916643285U, 1924625106U, 2512905582U, 2501170304U, 4275223366U, 2841225246U, 1467663688U, 3563567847U, 2969208552U, 884750901U, 102992576U, 227844301U, 3681442994U, 3502881894U, 4034693299U, 1166727018U, 1697460687U, 1737778332U, 1787161139U, 1053003655U, 1215024478U, 2791616766U, 2525841204U, 1629323443U, 3233815U, 2003823032U, 3083834263U, 2379264872U, 3752392312U, 1287475550U, 3770904171U, 3004244617U, 1502117784U, 918698423U, 2419857538U, 3864502062U, 1751322107U, 2188775056U, 4018728324U, 983712955U, 440071928U, 3710838677U, 2001027698U, 3994702151U, 22493119U, 3584400918U, 3446253670U, 4254789085U, 1405447860U, 1240245579U, 1800644159U, 1661363424U, 3278326132U, 3403623451U, 67092802U, 2609352193U, 3914150340U, 1814842761U, 3610830847U, 591531412U, 3880232807U, 1673505890U, 2585326991U, 1678544474U, 3148435887U, 3457217359U, 1193226330U, 2816576908U, 154025329U, 121678860U, 1164915738U, 973873761U, 269116100U, 52087970U, 744015362U, 498556057U, 94298882U, 1563271621U, 2383059628U, 4197367290U, 3958472990U, 2592083636U, 2906408439U, 1097742433U, 3924840517U, 264557272U, 2292287003U, 3203307984U, 4047038857U, 3820609705U, 2333416067U, 1839206046U, 3600944252U, 3412254904U, 583538222U, 2390557166U, 4140459427U, 2810357445U, 226777499U, 2496151295U, 2207301712U, 3283683112U, 611630281U, 1933218215U, 3315610954U, 3889441987U, 3719454256U, 3957190521U, 1313998161U, 2365383016U, 3146941060U, 1801206260U, 796124080U, 2076248581U, 1747472464U, 3254365145U, 595543130U, 3573909503U, 3758250204U, 2020768540U, 2439254210U, 93368951U, 3155792250U, 2600232980U, 3709198295U, 3894900440U, 2971850836U, 1578909644U, 1443493395U, 2581621665U, 3086506297U, 2443465861U, 558107211U, 1519367835U, 249149686U, 908102264U, 2588765675U, 1232743965U, 1001330373U, 3561331654U, 2259301289U, 1564977624U, 3835077093U, 727244906U, 4255738067U, 1214133513U, 2570786021U, 3899704621U, 1633861986U, 1636979509U, 1438500431U, 58463278U, 2823485629U, 2297430187U, 2926781924U, 3371352948U, 1864009023U, 2722267973U, 1444292075U, 437703973U, 1060414512U, 189705863U, 910018135U, 4077357964U, 884213423U, 2644986052U, 3973488374U, 1187906116U, 2331207875U, 780463700U, 3713351662U, 3854611290U, 412805574U, 2978462572U, 2176222820U, 829424696U, 2790788332U, 2750819108U, 1594611657U, 3899878394U, 3032870364U, 1702887682U, 1948167778U, 14130042U, 192292500U, 947227076U, 90719497U, 3854230320U, 784028434U, 2142399787U, 1563449646U, 2844400217U, 819143172U, 2883302356U, 2328055304U, 1328532246U, 2603885363U, 3375188924U, 933941291U, 3627039714U, 2129697284U, 2167253953U, 2506905438U, 1412424497U, 2981395985U, 1418359660U, 2925902456U, 52752784U, 3713667988U, 3924669405U, 648975707U, 1145520213U, 4018650664U, 3805915440U, 2380542088U, 2013260958U, 3262572197U, 2465078101U, 1114540067U, 3728768081U, 2396958768U, 590672271U, 904818725U, 4263660715U, 700754408U, 1042601829U, 4094111823U, 4274838909U, 2512692617U, 2774300207U, 2057306915U, 3470942453U, 99333088U, 1142661026U, 2889931380U, 14316674U, 2201179167U, 415289459U, 448265759U, 3515142743U, 3254903683U, 246633281U, 1184307224U, 2418347830U, 2092967314U, 2682072314U, 2558750234U, 2000352263U, 1544150531U, 399010405U, 1513946097U, 499682937U, 461167460U, 3045570638U, 1633669705U, 851492362U, 4052801922U, 2055266765U, 635556996U, 368266356U, 2385737383U, 3218202352U, 2603772408U, 349178792U, 226482567U, 3102426060U, 3575998268U, 2103001871U, 3243137071U, 225500688U, 1634718593U, 4283311431U, 4292122923U, 3842802787U, 811735523U, 105712518U, 663434053U, 1855889273U, 2847972595U, 1196355421U, 2552150115U, 4254510614U, 3752181265U, 3430721819U, 3828705396U, 3436287905U, 3441964937U, 4123670631U, 353001539U, 459496439U, 3799690868U, 1293777660U, 2761079737U, 498096339U, 3398433374U, 4080378380U, 2304691596U, 2995729055U, 4134660419U, 3903444024U, 3576494993U, 203682175U, 3321164857U, 2747963611U, 79749085U, 2992890370U, 1240278549U, 1772175713U, 2111331972U, 2655023449U, 1683896345U, 2836027212U, 3482868021U, 2489884874U, 756853961U, 2298874501U, 4013448667U, 4143996022U, 2948306858U, 4132920035U, 1283299272U, 995592228U, 3450508595U, 1027845759U, 1766942720U, 3861411826U, 1446861231U, 95974993U, 3502263554U, 1487532194U, 601502472U, 4129619129U, 250131773U, 2050079547U, 3198903947U, 3105589778U, 4066481316U, 3026383978U, 2276901713U, 365637751U, 2260718426U, 1394775634U, 1791172338U, 2690503163U, 2952737846U, 1568710462U, 732623190U, 2980358000U, 1053631832U, 1432426951U, 3229149635U, 1854113985U, 3719733532U, 3204031934U, 735775531U, 107468620U, 3734611984U, 631009402U, 3083622457U, 4109580626U, 159373458U, 1301970201U, 4132389302U, 1293255004U, 847182752U, 4170022737U, 96712900U, 2641406755U, 1381727755U, 405608287U, 4287919625U, 1703554290U, 3589580244U, 2911403488U, 2166565U, 2647306451U, 2330535117U, 1200815358U, 1165916754U, 245060911U, 4040679071U, 3684908771U, 2452834126U, 2486872773U, 2318678365U, 2940627908U, 1837837240U, 3447897409U, 4270484676U, 1495388728U, 3754288477U, 4204167884U, 1386977705U, 2692224733U, 3076249689U, 4109568048U, 4170955115U, 4167531356U, 4020189950U, 4261855038U, 3036907575U, 3410399885U, 3076395737U, 1046178638U, 144496770U, 230725846U, 3349637149U, 17065717U, 2809932048U, 2054581785U, 3608424964U, 3259628808U, 134897388U, 3743067463U, 257685904U, 3795656590U, 1562468719U, 3589103904U, 3120404710U, 254684547U, 2653661580U, 3663904795U, 2631942758U, 1063234347U, 2609732900U, 2332080715U, 3521125233U, 1180599599U, 1935868586U, 4110970440U, 296706371U, 2128666368U, 1319875791U, 1570900197U, 3096025483U, 1799882517U, 1928302007U, 1163707758U, 1244491489U, 3533770203U, 567496053U, 2757924305U, 2781639343U, 2818420107U, 560404889U, 2619609724U, 4176035430U, 2511289753U, 2521842019U, 3910553502U, 2926149387U, 3302078172U, 4237118867U, 330725126U, 367400677U, 888239854U, 545570454U, 4259590525U, 134343617U, 1102169784U, 1647463719U, 3260979784U, 1518840883U, 3631537963U, 3342671457U, 1301549147U, 2083739356U, 146593792U, 3217959080U, 652755743U, 2032187193U, 3898758414U, 1021358093U, 4037409230U, 2176407931U, 3427391950U, 2883553603U, 985613827U, 3105265092U, 3423168427U, 3387507672U, 467170288U, 2141266163U, 3723870208U, 916410914U, 1293987799U, 2652584950U, 769160137U, 3205292896U, 1561287359U, 1684510084U, 3136055621U, 3765171391U, 639683232U, 2639569327U, 1218546948U, 4263586685U, 3058215773U, 2352279820U, 401870217U, 2625822463U, 1529125296U, 2981801895U, 1191285226U, 4027725437U, 3432700217U, 4098835661U, 971182783U, 2443861173U, 3881457123U, 3874386651U, 457276199U, 2638294160U, 4002809368U, 421169044U, 1112642589U, 3076213779U, 3387033971U, 2499610950U, 3057240914U, 1662679783U, 461224431U, 1168395933U }; static const uint32_t init_by_array_32_expected[] = { 2920711183U, 3885745737U, 3501893680U, 856470934U, 1421864068U, 277361036U, 1518638004U, 2328404353U, 3355513634U, 64329189U, 1624587673U, 3508467182U, 2481792141U, 3706480799U, 1925859037U, 2913275699U, 882658412U, 384641219U, 422202002U, 1873384891U, 2006084383U, 3924929912U, 1636718106U, 3108838742U, 1245465724U, 4195470535U, 779207191U, 1577721373U, 1390469554U, 2928648150U, 121399709U, 3170839019U, 4044347501U, 953953814U, 3821710850U, 3085591323U, 3666535579U, 3577837737U, 2012008410U, 3565417471U, 4044408017U, 433600965U, 1637785608U, 1798509764U, 860770589U, 3081466273U, 3982393409U, 2451928325U, 3437124742U, 4093828739U, 3357389386U, 2154596123U, 496568176U, 2650035164U, 2472361850U, 3438299U, 2150366101U, 1577256676U, 3802546413U, 1787774626U, 4078331588U, 3706103141U, 170391138U, 3806085154U, 1680970100U, 1961637521U, 3316029766U, 890610272U, 1453751581U, 1430283664U, 3051057411U, 3597003186U, 542563954U, 3796490244U, 1690016688U, 3448752238U, 440702173U, 347290497U, 1121336647U, 2540588620U, 280881896U, 2495136428U, 213707396U, 15104824U, 2946180358U, 659000016U, 566379385U, 2614030979U, 2855760170U, 334526548U, 2315569495U, 2729518615U, 564745877U, 1263517638U, 3157185798U, 1604852056U, 1011639885U, 2950579535U, 2524219188U, 312951012U, 1528896652U, 1327861054U, 2846910138U, 3966855905U, 2536721582U, 855353911U, 1685434729U, 3303978929U, 1624872055U, 4020329649U, 3164802143U, 1642802700U, 1957727869U, 1792352426U, 3334618929U, 2631577923U, 3027156164U, 842334259U, 3353446843U, 1226432104U, 1742801369U, 3552852535U, 3471698828U, 1653910186U, 3380330939U, 2313782701U, 3351007196U, 2129839995U, 1800682418U, 4085884420U, 1625156629U, 3669701987U, 615211810U, 3294791649U, 4131143784U, 2590843588U, 3207422808U, 3275066464U, 561592872U, 3957205738U, 3396578098U, 48410678U, 3505556445U, 1005764855U, 3920606528U, 2936980473U, 2378918600U, 2404449845U, 1649515163U, 701203563U, 3705256349U, 83714199U, 3586854132U, 922978446U, 2863406304U, 3523398907U, 2606864832U, 2385399361U, 3171757816U, 4262841009U, 3645837721U, 1169579486U, 3666433897U, 3174689479U, 1457866976U, 3803895110U, 3346639145U, 1907224409U, 1978473712U, 1036712794U, 980754888U, 1302782359U, 1765252468U, 459245755U, 3728923860U, 1512894209U, 2046491914U, 207860527U, 514188684U, 2288713615U, 1597354672U, 3349636117U, 2357291114U, 3995796221U, 945364213U, 1893326518U, 3770814016U, 1691552714U, 2397527410U, 967486361U, 776416472U, 4197661421U, 951150819U, 1852770983U, 4044624181U, 1399439738U, 4194455275U, 2284037669U, 1550734958U, 3321078108U, 1865235926U, 2912129961U, 2664980877U, 1357572033U, 2600196436U, 2486728200U, 2372668724U, 1567316966U, 2374111491U, 1839843570U, 20815612U, 3727008608U, 3871996229U, 824061249U, 1932503978U, 3404541726U, 758428924U, 2609331364U, 1223966026U, 1299179808U, 648499352U, 2180134401U, 880821170U, 3781130950U, 113491270U, 1032413764U, 4185884695U, 2490396037U, 1201932817U, 4060951446U, 4165586898U, 1629813212U, 2887821158U, 415045333U, 628926856U, 2193466079U, 3391843445U, 2227540681U, 1907099846U, 2848448395U, 1717828221U, 1372704537U, 1707549841U, 2294058813U, 2101214437U, 2052479531U, 1695809164U, 3176587306U, 2632770465U, 81634404U, 1603220563U, 644238487U, 302857763U, 897352968U, 2613146653U, 1391730149U, 4245717312U, 4191828749U, 1948492526U, 2618174230U, 3992984522U, 2178852787U, 3596044509U, 3445573503U, 2026614616U, 915763564U, 3415689334U, 2532153403U, 3879661562U, 2215027417U, 3111154986U, 2929478371U, 668346391U, 1152241381U, 2632029711U, 3004150659U, 2135025926U, 948690501U, 2799119116U, 4228829406U, 1981197489U, 4209064138U, 684318751U, 3459397845U, 201790843U, 4022541136U, 3043635877U, 492509624U, 3263466772U, 1509148086U, 921459029U, 3198857146U, 705479721U, 3835966910U, 3603356465U, 576159741U, 1742849431U, 594214882U, 2055294343U, 3634861861U, 449571793U, 3246390646U, 3868232151U, 1479156585U, 2900125656U, 2464815318U, 3960178104U, 1784261920U, 18311476U, 3627135050U, 644609697U, 424968996U, 919890700U, 2986824110U, 816423214U, 4003562844U, 1392714305U, 1757384428U, 2569030598U, 995949559U, 3875659880U, 2933807823U, 2752536860U, 2993858466U, 4030558899U, 2770783427U, 2775406005U, 2777781742U, 1931292655U, 472147933U, 3865853827U, 2726470545U, 2668412860U, 2887008249U, 408979190U, 3578063323U, 3242082049U, 1778193530U, 27981909U, 2362826515U, 389875677U, 1043878156U, 581653903U, 3830568952U, 389535942U, 3713523185U, 2768373359U, 2526101582U, 1998618197U, 1160859704U, 3951172488U, 1098005003U, 906275699U, 3446228002U, 2220677963U, 2059306445U, 132199571U, 476838790U, 1868039399U, 3097344807U, 857300945U, 396345050U, 2835919916U, 1782168828U, 1419519470U, 4288137521U, 819087232U, 596301494U, 872823172U, 1526888217U, 805161465U, 1116186205U, 2829002754U, 2352620120U, 620121516U, 354159268U, 3601949785U, 209568138U, 1352371732U, 2145977349U, 4236871834U, 1539414078U, 3558126206U, 3224857093U, 4164166682U, 3817553440U, 3301780278U, 2682696837U, 3734994768U, 1370950260U, 1477421202U, 2521315749U, 1330148125U, 1261554731U, 2769143688U, 3554756293U, 4235882678U, 3254686059U, 3530579953U, 1215452615U, 3574970923U, 4057131421U, 589224178U, 1000098193U, 171190718U, 2521852045U, 2351447494U, 2284441580U, 2646685513U, 3486933563U, 3789864960U, 1190528160U, 1702536782U, 1534105589U, 4262946827U, 2726686826U, 3584544841U, 2348270128U, 2145092281U, 2502718509U, 1027832411U, 3571171153U, 1287361161U, 4011474411U, 3241215351U, 2419700818U, 971242709U, 1361975763U, 1096842482U, 3271045537U, 81165449U, 612438025U, 3912966678U, 1356929810U, 733545735U, 537003843U, 1282953084U, 884458241U, 588930090U, 3930269801U, 2961472450U, 1219535534U, 3632251943U, 268183903U, 1441240533U, 3653903360U, 3854473319U, 2259087390U, 2548293048U, 2022641195U, 2105543911U, 1764085217U, 3246183186U, 482438805U, 888317895U, 2628314765U, 2466219854U, 717546004U, 2322237039U, 416725234U, 1544049923U, 1797944973U, 3398652364U, 3111909456U, 485742908U, 2277491072U, 1056355088U, 3181001278U, 129695079U, 2693624550U, 1764438564U, 3797785470U, 195503713U, 3266519725U, 2053389444U, 1961527818U, 3400226523U, 3777903038U, 2597274307U, 4235851091U, 4094406648U, 2171410785U, 1781151386U, 1378577117U, 654643266U, 3424024173U, 3385813322U, 679385799U, 479380913U, 681715441U, 3096225905U, 276813409U, 3854398070U, 2721105350U, 831263315U, 3276280337U, 2628301522U, 3984868494U, 1466099834U, 2104922114U, 1412672743U, 820330404U, 3491501010U, 942735832U, 710652807U, 3972652090U, 679881088U, 40577009U, 3705286397U, 2815423480U, 3566262429U, 663396513U, 3777887429U, 4016670678U, 404539370U, 1142712925U, 1140173408U, 2913248352U, 2872321286U, 263751841U, 3175196073U, 3162557581U, 2878996619U, 75498548U, 3836833140U, 3284664959U, 1157523805U, 112847376U, 207855609U, 1337979698U, 1222578451U, 157107174U, 901174378U, 3883717063U, 1618632639U, 1767889440U, 4264698824U, 1582999313U, 884471997U, 2508825098U, 3756370771U, 2457213553U, 3565776881U, 3709583214U, 915609601U, 460833524U, 1091049576U, 85522880U, 2553251U, 132102809U, 2429882442U, 2562084610U, 1386507633U, 4112471229U, 21965213U, 1981516006U, 2418435617U, 3054872091U, 4251511224U, 2025783543U, 1916911512U, 2454491136U, 3938440891U, 3825869115U, 1121698605U, 3463052265U, 802340101U, 1912886800U, 4031997367U, 3550640406U, 1596096923U, 610150600U, 431464457U, 2541325046U, 486478003U, 739704936U, 2862696430U, 3037903166U, 1129749694U, 2611481261U, 1228993498U, 510075548U, 3424962587U, 2458689681U, 818934833U, 4233309125U, 1608196251U, 3419476016U, 1858543939U, 2682166524U, 3317854285U, 631986188U, 3008214764U, 613826412U, 3567358221U, 3512343882U, 1552467474U, 3316162670U, 1275841024U, 4142173454U, 565267881U, 768644821U, 198310105U, 2396688616U, 1837659011U, 203429334U, 854539004U, 4235811518U, 3338304926U, 3730418692U, 3852254981U, 3032046452U, 2329811860U, 2303590566U, 2696092212U, 3894665932U, 145835667U, 249563655U, 1932210840U, 2431696407U, 3312636759U, 214962629U, 2092026914U, 3020145527U, 4073039873U, 2739105705U, 1308336752U, 855104522U, 2391715321U, 67448785U, 547989482U, 854411802U, 3608633740U, 431731530U, 537375589U, 3888005760U, 696099141U, 397343236U, 1864511780U, 44029739U, 1729526891U, 1993398655U, 2010173426U, 2591546756U, 275223291U, 1503900299U, 4217765081U, 2185635252U, 1122436015U, 3550155364U, 681707194U, 3260479338U, 933579397U, 2983029282U, 2505504587U, 2667410393U, 2962684490U, 4139721708U, 2658172284U, 2452602383U, 2607631612U, 1344296217U, 3075398709U, 2949785295U, 1049956168U, 3917185129U, 2155660174U, 3280524475U, 1503827867U, 674380765U, 1918468193U, 3843983676U, 634358221U, 2538335643U, 1873351298U, 3368723763U, 2129144130U, 3203528633U, 3087174986U, 2691698871U, 2516284287U, 24437745U, 1118381474U, 2816314867U, 2448576035U, 4281989654U, 217287825U, 165872888U, 2628995722U, 3533525116U, 2721669106U, 872340568U, 3429930655U, 3309047304U, 3916704967U, 3270160355U, 1348884255U, 1634797670U, 881214967U, 4259633554U, 174613027U, 1103974314U, 1625224232U, 2678368291U, 1133866707U, 3853082619U, 4073196549U, 1189620777U, 637238656U, 930241537U, 4042750792U, 3842136042U, 2417007212U, 2524907510U, 1243036827U, 1282059441U, 3764588774U, 1394459615U, 2323620015U, 1166152231U, 3307479609U, 3849322257U, 3507445699U, 4247696636U, 758393720U, 967665141U, 1095244571U, 1319812152U, 407678762U, 2640605208U, 2170766134U, 3663594275U, 4039329364U, 2512175520U, 725523154U, 2249807004U, 3312617979U, 2414634172U, 1278482215U, 349206484U, 1573063308U, 1196429124U, 3873264116U, 2400067801U, 268795167U, 226175489U, 2961367263U, 1968719665U, 42656370U, 1010790699U, 561600615U, 2422453992U, 3082197735U, 1636700484U, 3977715296U, 3125350482U, 3478021514U, 2227819446U, 1540868045U, 3061908980U, 1087362407U, 3625200291U, 361937537U, 580441897U, 1520043666U, 2270875402U, 1009161260U, 2502355842U, 4278769785U, 473902412U, 1057239083U, 1905829039U, 1483781177U, 2080011417U, 1207494246U, 1806991954U, 2194674403U, 3455972205U, 807207678U, 3655655687U, 674112918U, 195425752U, 3917890095U, 1874364234U, 1837892715U, 3663478166U, 1548892014U, 2570748714U, 2049929836U, 2167029704U, 697543767U, 3499545023U, 3342496315U, 1725251190U, 3561387469U, 2905606616U, 1580182447U, 3934525927U, 4103172792U, 1365672522U, 1534795737U, 3308667416U, 2841911405U, 3943182730U, 4072020313U, 3494770452U, 3332626671U, 55327267U, 478030603U, 411080625U, 3419529010U, 1604767823U, 3513468014U, 570668510U, 913790824U, 2283967995U, 695159462U, 3825542932U, 4150698144U, 1829758699U, 202895590U, 1609122645U, 1267651008U, 2910315509U, 2511475445U, 2477423819U, 3932081579U, 900879979U, 2145588390U, 2670007504U, 580819444U, 1864996828U, 2526325979U, 1019124258U, 815508628U, 2765933989U, 1277301341U, 3006021786U, 855540956U, 288025710U, 1919594237U, 2331223864U, 177452412U, 2475870369U, 2689291749U, 865194284U, 253432152U, 2628531804U, 2861208555U, 2361597573U, 1653952120U, 1039661024U, 2159959078U, 3709040440U, 3564718533U, 2596878672U, 2041442161U, 31164696U, 2662962485U, 3665637339U, 1678115244U, 2699839832U, 3651968520U, 3521595541U, 458433303U, 2423096824U, 21831741U, 380011703U, 2498168716U, 861806087U, 1673574843U, 4188794405U, 2520563651U, 2632279153U, 2170465525U, 4171949898U, 3886039621U, 1661344005U, 3424285243U, 992588372U, 2500984144U, 2993248497U, 3590193895U, 1535327365U, 515645636U, 131633450U, 3729760261U, 1613045101U, 3254194278U, 15889678U, 1493590689U, 244148718U, 2991472662U, 1401629333U, 777349878U, 2501401703U, 4285518317U, 3794656178U, 955526526U, 3442142820U, 3970298374U, 736025417U, 2737370764U, 1271509744U, 440570731U, 136141826U, 1596189518U, 923399175U, 257541519U, 3505774281U, 2194358432U, 2518162991U, 1379893637U, 2667767062U, 3748146247U, 1821712620U, 3923161384U, 1947811444U, 2392527197U, 4127419685U, 1423694998U, 4156576871U, 1382885582U, 3420127279U, 3617499534U, 2994377493U, 4038063986U, 1918458672U, 2983166794U, 4200449033U, 353294540U, 1609232588U, 243926648U, 2332803291U, 507996832U, 2392838793U, 4075145196U, 2060984340U, 4287475136U, 88232602U, 2491531140U, 4159725633U, 2272075455U, 759298618U, 201384554U, 838356250U, 1416268324U, 674476934U, 90795364U, 141672229U, 3660399588U, 4196417251U, 3249270244U, 3774530247U, 59587265U, 3683164208U, 19392575U, 1463123697U, 1882205379U, 293780489U, 2553160622U, 2933904694U, 675638239U, 2851336944U, 1435238743U, 2448730183U, 804436302U, 2119845972U, 322560608U, 4097732704U, 2987802540U, 641492617U, 2575442710U, 4217822703U, 3271835300U, 2836418300U, 3739921620U, 2138378768U, 2879771855U, 4294903423U, 3121097946U, 2603440486U, 2560820391U, 1012930944U, 2313499967U, 584489368U, 3431165766U, 897384869U, 2062537737U, 2847889234U, 3742362450U, 2951174585U, 4204621084U, 1109373893U, 3668075775U, 2750138839U, 3518055702U, 733072558U, 4169325400U, 788493625U }; static const uint64_t init_gen_rand_64_expected[] = { KQU(16924766246869039260), KQU( 8201438687333352714), KQU( 2265290287015001750), KQU(18397264611805473832), KQU( 3375255223302384358), KQU( 6345559975416828796), KQU(18229739242790328073), KQU( 7596792742098800905), KQU( 255338647169685981), KQU( 2052747240048610300), KQU(18328151576097299343), KQU(12472905421133796567), KQU(11315245349717600863), KQU(16594110197775871209), KQU(15708751964632456450), KQU(10452031272054632535), KQU(11097646720811454386), KQU( 4556090668445745441), KQU(17116187693090663106), KQU(14931526836144510645), KQU( 9190752218020552591), KQU( 9625800285771901401), KQU(13995141077659972832), KQU( 5194209094927829625), KQU( 4156788379151063303), KQU( 8523452593770139494), KQU(14082382103049296727), KQU( 2462601863986088483), KQU( 3030583461592840678), KQU( 5221622077872827681), KQU( 3084210671228981236), KQU(13956758381389953823), KQU(13503889856213423831), KQU(15696904024189836170), KQU( 4612584152877036206), KQU( 6231135538447867881), KQU(10172457294158869468), KQU( 6452258628466708150), KQU(14044432824917330221), KQU( 370168364480044279), KQU(10102144686427193359), KQU( 667870489994776076), KQU( 2732271956925885858), KQU(18027788905977284151), KQU(15009842788582923859), KQU( 7136357960180199542), KQU(15901736243475578127), KQU(16951293785352615701), KQU(10551492125243691632), KQU(17668869969146434804), KQU(13646002971174390445), KQU( 9804471050759613248), KQU( 5511670439655935493), KQU(18103342091070400926), KQU(17224512747665137533), KQU(15534627482992618168), KQU( 1423813266186582647), KQU(15821176807932930024), KQU( 30323369733607156), KQU(11599382494723479403), KQU( 653856076586810062), KQU( 3176437395144899659), KQU(14028076268147963917), KQU(16156398271809666195), KQU( 3166955484848201676), KQU( 5746805620136919390), KQU(17297845208891256593), KQU(11691653183226428483), KQU(17900026146506981577), KQU(15387382115755971042), KQU(16923567681040845943), KQU( 8039057517199388606), KQU(11748409241468629263), KQU( 794358245539076095), KQU(13438501964693401242), KQU(14036803236515618962), KQU( 5252311215205424721), KQU(17806589612915509081), KQU( 6802767092397596006), KQU(14212120431184557140), KQU( 1072951366761385712), KQU(13098491780722836296), KQU( 9466676828710797353), KQU(12673056849042830081), KQU(12763726623645357580), KQU(16468961652999309493), KQU(15305979875636438926), KQU(17444713151223449734), KQU( 5692214267627883674), KQU(13049589139196151505), KQU( 880115207831670745), KQU( 1776529075789695498), KQU(16695225897801466485), KQU(10666901778795346845), KQU( 6164389346722833869), KQU( 2863817793264300475), KQU( 9464049921886304754), KQU( 3993566636740015468), KQU( 9983749692528514136), KQU(16375286075057755211), KQU(16042643417005440820), KQU(11445419662923489877), KQU( 7999038846885158836), KQU( 6721913661721511535), KQU( 5363052654139357320), KQU( 1817788761173584205), KQU(13290974386445856444), KQU( 4650350818937984680), KQU( 8219183528102484836), KQU( 1569862923500819899), KQU( 4189359732136641860), KQU(14202822961683148583), KQU( 4457498315309429058), KQU(13089067387019074834), KQU(11075517153328927293), KQU(10277016248336668389), KQU( 7070509725324401122), KQU(17808892017780289380), KQU(13143367339909287349), KQU( 1377743745360085151), KQU( 5749341807421286485), KQU(14832814616770931325), KQU( 7688820635324359492), KQU(10960474011539770045), KQU( 81970066653179790), KQU(12619476072607878022), KQU( 4419566616271201744), KQU(15147917311750568503), KQU( 5549739182852706345), KQU( 7308198397975204770), KQU(13580425496671289278), KQU(17070764785210130301), KQU( 8202832846285604405), KQU( 6873046287640887249), KQU( 6927424434308206114), KQU( 6139014645937224874), KQU(10290373645978487639), KQU(15904261291701523804), KQU( 9628743442057826883), KQU(18383429096255546714), KQU( 4977413265753686967), KQU( 7714317492425012869), KQU( 9025232586309926193), KQU(14627338359776709107), KQU(14759849896467790763), KQU(10931129435864423252), KQU( 4588456988775014359), KQU(10699388531797056724), KQU( 468652268869238792), KQU( 5755943035328078086), KQU( 2102437379988580216), KQU( 9986312786506674028), KQU( 2654207180040945604), KQU( 8726634790559960062), KQU( 100497234871808137), KQU( 2800137176951425819), KQU( 6076627612918553487), KQU( 5780186919186152796), KQU( 8179183595769929098), KQU( 6009426283716221169), KQU( 2796662551397449358), KQU( 1756961367041986764), KQU( 6972897917355606205), KQU(14524774345368968243), KQU( 2773529684745706940), KQU( 4853632376213075959), KQU( 4198177923731358102), KQU( 8271224913084139776), KQU( 2741753121611092226), KQU(16782366145996731181), KQU(15426125238972640790), KQU(13595497100671260342), KQU( 3173531022836259898), KQU( 6573264560319511662), KQU(18041111951511157441), KQU( 2351433581833135952), KQU( 3113255578908173487), KQU( 1739371330877858784), KQU(16046126562789165480), KQU( 8072101652214192925), KQU(15267091584090664910), KQU( 9309579200403648940), KQU( 5218892439752408722), KQU(14492477246004337115), KQU(17431037586679770619), KQU( 7385248135963250480), KQU( 9580144956565560660), KQU( 4919546228040008720), KQU(15261542469145035584), KQU(18233297270822253102), KQU( 5453248417992302857), KQU( 9309519155931460285), KQU(10342813012345291756), KQU(15676085186784762381), KQU(15912092950691300645), KQU( 9371053121499003195), KQU( 9897186478226866746), KQU(14061858287188196327), KQU( 122575971620788119), KQU(12146750969116317754), KQU( 4438317272813245201), KQU( 8332576791009527119), KQU(13907785691786542057), KQU(10374194887283287467), KQU( 2098798755649059566), KQU( 3416235197748288894), KQU( 8688269957320773484), KQU( 7503964602397371571), KQU(16724977015147478236), KQU( 9461512855439858184), KQU(13259049744534534727), KQU( 3583094952542899294), KQU( 8764245731305528292), KQU(13240823595462088985), KQU(13716141617617910448), KQU(18114969519935960955), KQU( 2297553615798302206), KQU( 4585521442944663362), KQU(17776858680630198686), KQU( 4685873229192163363), KQU( 152558080671135627), KQU(15424900540842670088), KQU(13229630297130024108), KQU(17530268788245718717), KQU(16675633913065714144), KQU( 3158912717897568068), KQU(15399132185380087288), KQU( 7401418744515677872), KQU(13135412922344398535), KQU( 6385314346100509511), KQU(13962867001134161139), KQU(10272780155442671999), KQU(12894856086597769142), KQU(13340877795287554994), KQU(12913630602094607396), KQU(12543167911119793857), KQU(17343570372251873096), KQU(10959487764494150545), KQU( 6966737953093821128), KQU(13780699135496988601), KQU( 4405070719380142046), KQU(14923788365607284982), KQU( 2869487678905148380), KQU( 6416272754197188403), KQU(15017380475943612591), KQU( 1995636220918429487), KQU( 3402016804620122716), KQU(15800188663407057080), KQU(11362369990390932882), KQU(15262183501637986147), KQU(10239175385387371494), KQU( 9352042420365748334), KQU( 1682457034285119875), KQU( 1724710651376289644), KQU( 2038157098893817966), KQU( 9897825558324608773), KQU( 1477666236519164736), KQU(16835397314511233640), KQU(10370866327005346508), KQU(10157504370660621982), KQU(12113904045335882069), KQU(13326444439742783008), KQU(11302769043000765804), KQU(13594979923955228484), KQU(11779351762613475968), KQU( 3786101619539298383), KQU( 8021122969180846063), KQU(15745904401162500495), KQU(10762168465993897267), KQU(13552058957896319026), KQU(11200228655252462013), KQU( 5035370357337441226), KQU( 7593918984545500013), KQU( 5418554918361528700), KQU( 4858270799405446371), KQU( 9974659566876282544), KQU(18227595922273957859), KQU( 2772778443635656220), KQU(14285143053182085385), KQU( 9939700992429600469), KQU(12756185904545598068), KQU( 2020783375367345262), KQU( 57026775058331227), KQU( 950827867930065454), KQU( 6602279670145371217), KQU( 2291171535443566929), KQU( 5832380724425010313), KQU( 1220343904715982285), KQU(17045542598598037633), KQU(15460481779702820971), KQU(13948388779949365130), KQU(13975040175430829518), KQU(17477538238425541763), KQU(11104663041851745725), KQU(15860992957141157587), KQU(14529434633012950138), KQU( 2504838019075394203), KQU( 7512113882611121886), KQU( 4859973559980886617), KQU( 1258601555703250219), KQU(15594548157514316394), KQU( 4516730171963773048), KQU(11380103193905031983), KQU( 6809282239982353344), KQU(18045256930420065002), KQU( 2453702683108791859), KQU( 977214582986981460), KQU( 2006410402232713466), KQU( 6192236267216378358), KQU( 3429468402195675253), KQU(18146933153017348921), KQU(17369978576367231139), KQU( 1246940717230386603), KQU(11335758870083327110), KQU(14166488801730353682), KQU( 9008573127269635732), KQU(10776025389820643815), KQU(15087605441903942962), KQU( 1359542462712147922), KQU(13898874411226454206), KQU(17911176066536804411), KQU( 9435590428600085274), KQU( 294488509967864007), KQU( 8890111397567922046), KQU( 7987823476034328778), KQU(13263827582440967651), KQU( 7503774813106751573), KQU(14974747296185646837), KQU( 8504765037032103375), KQU(17340303357444536213), KQU( 7704610912964485743), KQU( 8107533670327205061), KQU( 9062969835083315985), KQU(16968963142126734184), KQU(12958041214190810180), KQU( 2720170147759570200), KQU( 2986358963942189566), KQU(14884226322219356580), KQU( 286224325144368520), KQU(11313800433154279797), KQU(18366849528439673248), KQU(17899725929482368789), KQU( 3730004284609106799), KQU( 1654474302052767205), KQU( 5006698007047077032), KQU( 8196893913601182838), KQU(15214541774425211640), KQU(17391346045606626073), KQU( 8369003584076969089), KQU( 3939046733368550293), KQU(10178639720308707785), KQU( 2180248669304388697), KQU( 62894391300126322), KQU( 9205708961736223191), KQU( 6837431058165360438), KQU( 3150743890848308214), KQU(17849330658111464583), KQU(12214815643135450865), KQU(13410713840519603402), KQU( 3200778126692046802), KQU(13354780043041779313), KQU( 800850022756886036), KQU(15660052933953067433), KQU( 6572823544154375676), KQU(11030281857015819266), KQU(12682241941471433835), KQU(11654136407300274693), KQU( 4517795492388641109), KQU( 9757017371504524244), KQU(17833043400781889277), KQU(12685085201747792227), KQU(10408057728835019573), KQU( 98370418513455221), KQU( 6732663555696848598), KQU(13248530959948529780), KQU( 3530441401230622826), KQU(18188251992895660615), KQU( 1847918354186383756), KQU( 1127392190402660921), KQU(11293734643143819463), KQU( 3015506344578682982), KQU(13852645444071153329), KQU( 2121359659091349142), KQU( 1294604376116677694), KQU( 5616576231286352318), KQU( 7112502442954235625), KQU(11676228199551561689), KQU(12925182803007305359), KQU( 7852375518160493082), KQU( 1136513130539296154), KQU( 5636923900916593195), KQU( 3221077517612607747), KQU(17784790465798152513), KQU( 3554210049056995938), KQU(17476839685878225874), KQU( 3206836372585575732), KQU( 2765333945644823430), KQU(10080070903718799528), KQU( 5412370818878286353), KQU( 9689685887726257728), KQU( 8236117509123533998), KQU( 1951139137165040214), KQU( 4492205209227980349), KQU(16541291230861602967), KQU( 1424371548301437940), KQU( 9117562079669206794), KQU(14374681563251691625), KQU(13873164030199921303), KQU( 6680317946770936731), KQU(15586334026918276214), KQU(10896213950976109802), KQU( 9506261949596413689), KQU( 9903949574308040616), KQU( 6038397344557204470), KQU( 174601465422373648), KQU(15946141191338238030), KQU(17142225620992044937), KQU( 7552030283784477064), KQU( 2947372384532947997), KQU( 510797021688197711), KQU( 4962499439249363461), KQU( 23770320158385357), KQU( 959774499105138124), KQU( 1468396011518788276), KQU( 2015698006852312308), KQU( 4149400718489980136), KQU( 5992916099522371188), KQU(10819182935265531076), KQU(16189787999192351131), KQU( 342833961790261950), KQU(12470830319550495336), KQU(18128495041912812501), KQU( 1193600899723524337), KQU( 9056793666590079770), KQU( 2154021227041669041), KQU( 4963570213951235735), KQU( 4865075960209211409), KQU( 2097724599039942963), KQU( 2024080278583179845), KQU(11527054549196576736), KQU(10650256084182390252), KQU( 4808408648695766755), KQU( 1642839215013788844), KQU(10607187948250398390), KQU( 7076868166085913508), KQU( 730522571106887032), KQU(12500579240208524895), KQU( 4484390097311355324), KQU(15145801330700623870), KQU( 8055827661392944028), KQU( 5865092976832712268), KQU(15159212508053625143), KQU( 3560964582876483341), KQU( 4070052741344438280), KQU( 6032585709886855634), KQU(15643262320904604873), KQU( 2565119772293371111), KQU( 318314293065348260), KQU(15047458749141511872), KQU( 7772788389811528730), KQU( 7081187494343801976), KQU( 6465136009467253947), KQU(10425940692543362069), KQU( 554608190318339115), KQU(14796699860302125214), KQU( 1638153134431111443), KQU(10336967447052276248), KQU( 8412308070396592958), KQU( 4004557277152051226), KQU( 8143598997278774834), KQU(16413323996508783221), KQU(13139418758033994949), KQU( 9772709138335006667), KQU( 2818167159287157659), KQU(17091740573832523669), KQU(14629199013130751608), KQU(18268322711500338185), KQU( 8290963415675493063), KQU( 8830864907452542588), KQU( 1614839084637494849), KQU(14855358500870422231), KQU( 3472996748392519937), KQU(15317151166268877716), KQU( 5825895018698400362), KQU(16730208429367544129), KQU(10481156578141202800), KQU( 4746166512382823750), KQU(12720876014472464998), KQU( 8825177124486735972), KQU(13733447296837467838), KQU( 6412293741681359625), KQU( 8313213138756135033), KQU(11421481194803712517), KQU( 7997007691544174032), KQU( 6812963847917605930), KQU( 9683091901227558641), KQU(14703594165860324713), KQU( 1775476144519618309), KQU( 2724283288516469519), KQU( 717642555185856868), KQU( 8736402192215092346), KQU(11878800336431381021), KQU( 4348816066017061293), KQU( 6115112756583631307), KQU( 9176597239667142976), KQU(12615622714894259204), KQU(10283406711301385987), KQU( 5111762509485379420), KQU( 3118290051198688449), KQU( 7345123071632232145), KQU( 9176423451688682359), KQU( 4843865456157868971), KQU(12008036363752566088), KQU(12058837181919397720), KQU( 2145073958457347366), KQU( 1526504881672818067), KQU( 3488830105567134848), KQU(13208362960674805143), KQU( 4077549672899572192), KQU( 7770995684693818365), KQU( 1398532341546313593), KQU(12711859908703927840), KQU( 1417561172594446813), KQU(17045191024194170604), KQU( 4101933177604931713), KQU(14708428834203480320), KQU(17447509264469407724), KQU(14314821973983434255), KQU(17990472271061617265), KQU( 5087756685841673942), KQU(12797820586893859939), KQU( 1778128952671092879), KQU( 3535918530508665898), KQU( 9035729701042481301), KQU(14808661568277079962), KQU(14587345077537747914), KQU(11920080002323122708), KQU( 6426515805197278753), KQU( 3295612216725984831), KQU(11040722532100876120), KQU(12305952936387598754), KQU(16097391899742004253), KQU( 4908537335606182208), KQU(12446674552196795504), KQU(16010497855816895177), KQU( 9194378874788615551), KQU( 3382957529567613384), KQU( 5154647600754974077), KQU( 9801822865328396141), KQU( 9023662173919288143), KQU(17623115353825147868), KQU( 8238115767443015816), KQU(15811444159859002560), KQU( 9085612528904059661), KQU( 6888601089398614254), KQU( 258252992894160189), KQU( 6704363880792428622), KQU( 6114966032147235763), KQU(11075393882690261875), KQU( 8797664238933620407), KQU( 5901892006476726920), KQU( 5309780159285518958), KQU(14940808387240817367), KQU(14642032021449656698), KQU( 9808256672068504139), KQU( 3670135111380607658), KQU(11211211097845960152), KQU( 1474304506716695808), KQU(15843166204506876239), KQU( 7661051252471780561), KQU(10170905502249418476), KQU( 7801416045582028589), KQU( 2763981484737053050), KQU( 9491377905499253054), KQU(16201395896336915095), KQU( 9256513756442782198), KQU( 5411283157972456034), KQU( 5059433122288321676), KQU( 4327408006721123357), KQU( 9278544078834433377), KQU( 7601527110882281612), KQU(11848295896975505251), KQU(12096998801094735560), KQU(14773480339823506413), KQU(15586227433895802149), KQU(12786541257830242872), KQU( 6904692985140503067), KQU( 5309011515263103959), KQU(12105257191179371066), KQU(14654380212442225037), KQU( 2556774974190695009), KQU( 4461297399927600261), KQU(14888225660915118646), KQU(14915459341148291824), KQU( 2738802166252327631), KQU( 6047155789239131512), KQU(12920545353217010338), KQU(10697617257007840205), KQU( 2751585253158203504), KQU(13252729159780047496), KQU(14700326134672815469), KQU(14082527904374600529), KQU(16852962273496542070), KQU(17446675504235853907), KQU(15019600398527572311), KQU(12312781346344081551), KQU(14524667935039810450), KQU( 5634005663377195738), KQU(11375574739525000569), KQU( 2423665396433260040), KQU( 5222836914796015410), KQU( 4397666386492647387), KQU( 4619294441691707638), KQU( 665088602354770716), KQU(13246495665281593610), KQU( 6564144270549729409), KQU(10223216188145661688), KQU( 3961556907299230585), KQU(11543262515492439914), KQU(16118031437285993790), KQU( 7143417964520166465), KQU(13295053515909486772), KQU( 40434666004899675), KQU(17127804194038347164), KQU( 8599165966560586269), KQU( 8214016749011284903), KQU(13725130352140465239), KQU( 5467254474431726291), KQU( 7748584297438219877), KQU(16933551114829772472), KQU( 2169618439506799400), KQU( 2169787627665113463), KQU(17314493571267943764), KQU(18053575102911354912), KQU(11928303275378476973), KQU(11593850925061715550), KQU(17782269923473589362), KQU( 3280235307704747039), KQU( 6145343578598685149), KQU(17080117031114086090), KQU(18066839902983594755), KQU( 6517508430331020706), KQU( 8092908893950411541), KQU(12558378233386153732), KQU( 4476532167973132976), KQU(16081642430367025016), KQU( 4233154094369139361), KQU( 8693630486693161027), KQU(11244959343027742285), KQU(12273503967768513508), KQU(14108978636385284876), KQU( 7242414665378826984), KQU( 6561316938846562432), KQU( 8601038474994665795), KQU(17532942353612365904), KQU(17940076637020912186), KQU( 7340260368823171304), KQU( 7061807613916067905), KQU(10561734935039519326), KQU(17990796503724650862), KQU( 6208732943911827159), KQU( 359077562804090617), KQU(14177751537784403113), KQU(10659599444915362902), KQU(15081727220615085833), KQU(13417573895659757486), KQU(15513842342017811524), KQU(11814141516204288231), KQU( 1827312513875101814), KQU( 2804611699894603103), KQU(17116500469975602763), KQU(12270191815211952087), KQU(12256358467786024988), KQU(18435021722453971267), KQU( 671330264390865618), KQU( 476504300460286050), KQU(16465470901027093441), KQU( 4047724406247136402), KQU( 1322305451411883346), KQU( 1388308688834322280), KQU( 7303989085269758176), KQU( 9323792664765233642), KQU( 4542762575316368936), KQU(17342696132794337618), KQU( 4588025054768498379), KQU(13415475057390330804), KQU(17880279491733405570), KQU(10610553400618620353), KQU( 3180842072658960139), KQU(13002966655454270120), KQU( 1665301181064982826), KQU( 7083673946791258979), KQU( 190522247122496820), KQU(17388280237250677740), KQU( 8430770379923642945), KQU(12987180971921668584), KQU( 2311086108365390642), KQU( 2870984383579822345), KQU(14014682609164653318), KQU(14467187293062251484), KQU( 192186361147413298), KQU(15171951713531796524), KQU( 9900305495015948728), KQU(17958004775615466344), KQU(14346380954498606514), KQU(18040047357617407096), KQU( 5035237584833424532), KQU(15089555460613972287), KQU( 4131411873749729831), KQU( 1329013581168250330), KQU(10095353333051193949), KQU(10749518561022462716), KQU( 9050611429810755847), KQU(15022028840236655649), KQU( 8775554279239748298), KQU(13105754025489230502), KQU(15471300118574167585), KQU( 89864764002355628), KQU( 8776416323420466637), KQU( 5280258630612040891), KQU( 2719174488591862912), KQU( 7599309137399661994), KQU(15012887256778039979), KQU(14062981725630928925), KQU(12038536286991689603), KQU( 7089756544681775245), KQU(10376661532744718039), KQU( 1265198725901533130), KQU(13807996727081142408), KQU( 2935019626765036403), KQU( 7651672460680700141), KQU( 3644093016200370795), KQU( 2840982578090080674), KQU(17956262740157449201), KQU(18267979450492880548), KQU(11799503659796848070), KQU( 9942537025669672388), KQU(11886606816406990297), KQU( 5488594946437447576), KQU( 7226714353282744302), KQU( 3784851653123877043), KQU( 878018453244803041), KQU(12110022586268616085), KQU( 734072179404675123), KQU(11869573627998248542), KQU( 469150421297783998), KQU( 260151124912803804), KQU(11639179410120968649), KQU( 9318165193840846253), KQU(12795671722734758075), KQU(15318410297267253933), KQU( 691524703570062620), KQU( 5837129010576994601), KQU(15045963859726941052), KQU( 5850056944932238169), KQU(12017434144750943807), KQU( 7447139064928956574), KQU( 3101711812658245019), KQU(16052940704474982954), KQU(18195745945986994042), KQU( 8932252132785575659), KQU(13390817488106794834), KQU(11582771836502517453), KQU( 4964411326683611686), KQU( 2195093981702694011), KQU(14145229538389675669), KQU(16459605532062271798), KQU( 866316924816482864), KQU( 4593041209937286377), KQU( 8415491391910972138), KQU( 4171236715600528969), KQU(16637569303336782889), KQU( 2002011073439212680), KQU(17695124661097601411), KQU( 4627687053598611702), KQU( 7895831936020190403), KQU( 8455951300917267802), KQU( 2923861649108534854), KQU( 8344557563927786255), KQU( 6408671940373352556), KQU(12210227354536675772), KQU(14294804157294222295), KQU(10103022425071085127), KQU(10092959489504123771), KQU( 6554774405376736268), KQU(12629917718410641774), KQU( 6260933257596067126), KQU( 2460827021439369673), KQU( 2541962996717103668), KQU( 597377203127351475), KQU( 5316984203117315309), KQU( 4811211393563241961), KQU(13119698597255811641), KQU( 8048691512862388981), KQU(10216818971194073842), KQU( 4612229970165291764), KQU(10000980798419974770), KQU( 6877640812402540687), KQU( 1488727563290436992), KQU( 2227774069895697318), KQU(11237754507523316593), KQU(13478948605382290972), KQU( 1963583846976858124), KQU( 5512309205269276457), KQU( 3972770164717652347), KQU( 3841751276198975037), KQU(10283343042181903117), KQU( 8564001259792872199), KQU(16472187244722489221), KQU( 8953493499268945921), KQU( 3518747340357279580), KQU( 4003157546223963073), KQU( 3270305958289814590), KQU( 3966704458129482496), KQU( 8122141865926661939), KQU(14627734748099506653), KQU(13064426990862560568), KQU( 2414079187889870829), KQU( 5378461209354225306), KQU(10841985740128255566), KQU( 538582442885401738), KQU( 7535089183482905946), KQU(16117559957598879095), KQU( 8477890721414539741), KQU( 1459127491209533386), KQU(17035126360733620462), KQU( 8517668552872379126), KQU(10292151468337355014), KQU(17081267732745344157), KQU(13751455337946087178), KQU(14026945459523832966), KQU( 6653278775061723516), KQU(10619085543856390441), KQU( 2196343631481122885), KQU(10045966074702826136), KQU(10082317330452718282), KQU( 5920859259504831242), KQU( 9951879073426540617), KQU( 7074696649151414158), KQU(15808193543879464318), KQU( 7385247772746953374), KQU( 3192003544283864292), KQU(18153684490917593847), KQU(12423498260668568905), KQU(10957758099756378169), KQU(11488762179911016040), KQU( 2099931186465333782), KQU(11180979581250294432), KQU( 8098916250668367933), KQU( 3529200436790763465), KQU(12988418908674681745), KQU( 6147567275954808580), KQU( 3207503344604030989), KQU(10761592604898615360), KQU( 229854861031893504), KQU( 8809853962667144291), KQU(13957364469005693860), KQU( 7634287665224495886), KQU(12353487366976556874), KQU( 1134423796317152034), KQU( 2088992471334107068), KQU( 7393372127190799698), KQU( 1845367839871058391), KQU( 207922563987322884), KQU(11960870813159944976), KQU(12182120053317317363), KQU(17307358132571709283), KQU(13871081155552824936), KQU(18304446751741566262), KQU( 7178705220184302849), KQU(10929605677758824425), KQU(16446976977835806844), KQU(13723874412159769044), KQU( 6942854352100915216), KQU( 1726308474365729390), KQU( 2150078766445323155), KQU(15345558947919656626), KQU(12145453828874527201), KQU( 2054448620739726849), KQU( 2740102003352628137), KQU(11294462163577610655), KQU( 756164283387413743), KQU(17841144758438810880), KQU(10802406021185415861), KQU( 8716455530476737846), KQU( 6321788834517649606), KQU(14681322910577468426), KQU(17330043563884336387), KQU(12701802180050071614), KQU(14695105111079727151), KQU( 5112098511654172830), KQU( 4957505496794139973), KQU( 8270979451952045982), KQU(12307685939199120969), KQU(12425799408953443032), KQU( 8376410143634796588), KQU(16621778679680060464), KQU( 3580497854566660073), KQU( 1122515747803382416), KQU( 857664980960597599), KQU( 6343640119895925918), KQU(12878473260854462891), KQU(10036813920765722626), KQU(14451335468363173812), KQU( 5476809692401102807), KQU(16442255173514366342), KQU(13060203194757167104), KQU(14354124071243177715), KQU(15961249405696125227), KQU(13703893649690872584), KQU( 363907326340340064), KQU( 6247455540491754842), KQU(12242249332757832361), KQU( 156065475679796717), KQU( 9351116235749732355), KQU( 4590350628677701405), KQU( 1671195940982350389), KQU(13501398458898451905), KQU( 6526341991225002255), KQU( 1689782913778157592), KQU( 7439222350869010334), KQU(13975150263226478308), KQU(11411961169932682710), KQU(17204271834833847277), KQU( 541534742544435367), KQU( 6591191931218949684), KQU( 2645454775478232486), KQU( 4322857481256485321), KQU( 8477416487553065110), KQU(12902505428548435048), KQU( 971445777981341415), KQU(14995104682744976712), KQU( 4243341648807158063), KQU( 8695061252721927661), KQU( 5028202003270177222), KQU( 2289257340915567840), KQU(13870416345121866007), KQU(13994481698072092233), KQU( 6912785400753196481), KQU( 2278309315841980139), KQU( 4329765449648304839), KQU( 5963108095785485298), KQU( 4880024847478722478), KQU(16015608779890240947), KQU( 1866679034261393544), KQU( 914821179919731519), KQU( 9643404035648760131), KQU( 2418114953615593915), KQU( 944756836073702374), KQU(15186388048737296834), KQU( 7723355336128442206), KQU( 7500747479679599691), KQU(18013961306453293634), KQU( 2315274808095756456), KQU(13655308255424029566), KQU(17203800273561677098), KQU( 1382158694422087756), KQU( 5090390250309588976), KQU( 517170818384213989), KQU( 1612709252627729621), KQU( 1330118955572449606), KQU( 300922478056709885), KQU(18115693291289091987), KQU(13491407109725238321), KQU(15293714633593827320), KQU( 5151539373053314504), KQU( 5951523243743139207), KQU(14459112015249527975), KQU( 5456113959000700739), KQU( 3877918438464873016), KQU(12534071654260163555), KQU(15871678376893555041), KQU(11005484805712025549), KQU(16353066973143374252), KQU( 4358331472063256685), KQU( 8268349332210859288), KQU(12485161590939658075), KQU(13955993592854471343), KQU( 5911446886848367039), KQU(14925834086813706974), KQU( 6590362597857994805), KQU( 1280544923533661875), KQU( 1637756018947988164), KQU( 4734090064512686329), KQU(16693705263131485912), KQU( 6834882340494360958), KQU( 8120732176159658505), KQU( 2244371958905329346), KQU(10447499707729734021), KQU( 7318742361446942194), KQU( 8032857516355555296), KQU(14023605983059313116), KQU( 1032336061815461376), KQU( 9840995337876562612), KQU( 9869256223029203587), KQU(12227975697177267636), KQU(12728115115844186033), KQU( 7752058479783205470), KQU( 729733219713393087), KQU(12954017801239007622) }; static const uint64_t init_by_array_64_expected[] = { KQU( 2100341266307895239), KQU( 8344256300489757943), KQU(15687933285484243894), KQU( 8268620370277076319), KQU(12371852309826545459), KQU( 8800491541730110238), KQU(18113268950100835773), KQU( 2886823658884438119), KQU( 3293667307248180724), KQU( 9307928143300172731), KQU( 7688082017574293629), KQU( 900986224735166665), KQU( 9977972710722265039), KQU( 6008205004994830552), KQU( 546909104521689292), KQU( 7428471521869107594), KQU(14777563419314721179), KQU(16116143076567350053), KQU( 5322685342003142329), KQU( 4200427048445863473), KQU( 4693092150132559146), KQU(13671425863759338582), KQU( 6747117460737639916), KQU( 4732666080236551150), KQU( 5912839950611941263), KQU( 3903717554504704909), KQU( 2615667650256786818), KQU(10844129913887006352), KQU(13786467861810997820), KQU(14267853002994021570), KQU(13767807302847237439), KQU(16407963253707224617), KQU( 4802498363698583497), KQU( 2523802839317209764), KQU( 3822579397797475589), KQU( 8950320572212130610), KQU( 3745623504978342534), KQU(16092609066068482806), KQU( 9817016950274642398), KQU(10591660660323829098), KQU(11751606650792815920), KQU( 5122873818577122211), KQU(17209553764913936624), KQU( 6249057709284380343), KQU(15088791264695071830), KQU(15344673071709851930), KQU( 4345751415293646084), KQU( 2542865750703067928), KQU(13520525127852368784), KQU(18294188662880997241), KQU( 3871781938044881523), KQU( 2873487268122812184), KQU(15099676759482679005), KQU(15442599127239350490), KQU( 6311893274367710888), KQU( 3286118760484672933), KQU( 4146067961333542189), KQU(13303942567897208770), KQU( 8196013722255630418), KQU( 4437815439340979989), KQU(15433791533450605135), KQU( 4254828956815687049), KQU( 1310903207708286015), KQU(10529182764462398549), KQU(14900231311660638810), KQU( 9727017277104609793), KQU( 1821308310948199033), KQU(11628861435066772084), KQU( 9469019138491546924), KQU( 3145812670532604988), KQU( 9938468915045491919), KQU( 1562447430672662142), KQU(13963995266697989134), KQU( 3356884357625028695), KQU( 4499850304584309747), KQU( 8456825817023658122), KQU(10859039922814285279), KQU( 8099512337972526555), KQU( 348006375109672149), KQU(11919893998241688603), KQU( 1104199577402948826), KQU(16689191854356060289), KQU(10992552041730168078), KQU( 7243733172705465836), KQU( 5668075606180319560), KQU(18182847037333286970), KQU( 4290215357664631322), KQU( 4061414220791828613), KQU(13006291061652989604), KQU( 7140491178917128798), KQU(12703446217663283481), KQU( 5500220597564558267), KQU(10330551509971296358), KQU(15958554768648714492), KQU( 5174555954515360045), KQU( 1731318837687577735), KQU( 3557700801048354857), KQU(13764012341928616198), KQU(13115166194379119043), KQU( 7989321021560255519), KQU( 2103584280905877040), KQU( 9230788662155228488), KQU(16396629323325547654), KQU( 657926409811318051), KQU(15046700264391400727), KQU( 5120132858771880830), KQU( 7934160097989028561), KQU( 6963121488531976245), KQU(17412329602621742089), KQU(15144843053931774092), KQU(17204176651763054532), KQU(13166595387554065870), KQU( 8590377810513960213), KQU( 5834365135373991938), KQU( 7640913007182226243), KQU( 3479394703859418425), KQU(16402784452644521040), KQU( 4993979809687083980), KQU(13254522168097688865), KQU(15643659095244365219), KQU( 5881437660538424982), KQU(11174892200618987379), KQU( 254409966159711077), KQU(17158413043140549909), KQU( 3638048789290376272), KQU( 1376816930299489190), KQU( 4622462095217761923), KQU(15086407973010263515), KQU(13253971772784692238), KQU( 5270549043541649236), KQU(11182714186805411604), KQU(12283846437495577140), KQU( 5297647149908953219), KQU(10047451738316836654), KQU( 4938228100367874746), KQU(12328523025304077923), KQU( 3601049438595312361), KQU( 9313624118352733770), KQU(13322966086117661798), KQU(16660005705644029394), KQU(11337677526988872373), KQU(13869299102574417795), KQU(15642043183045645437), KQU( 3021755569085880019), KQU( 4979741767761188161), KQU(13679979092079279587), KQU( 3344685842861071743), KQU(13947960059899588104), KQU( 305806934293368007), KQU( 5749173929201650029), KQU(11123724852118844098), KQU(15128987688788879802), KQU(15251651211024665009), KQU( 7689925933816577776), KQU(16732804392695859449), KQU(17087345401014078468), KQU(14315108589159048871), KQU( 4820700266619778917), KQU(16709637539357958441), KQU( 4936227875177351374), KQU( 2137907697912987247), KQU(11628565601408395420), KQU( 2333250549241556786), KQU( 5711200379577778637), KQU( 5170680131529031729), KQU(12620392043061335164), KQU( 95363390101096078), KQU( 5487981914081709462), KQU( 1763109823981838620), KQU( 3395861271473224396), KQU( 1300496844282213595), KQU( 6894316212820232902), KQU(10673859651135576674), KQU( 5911839658857903252), KQU(17407110743387299102), KQU( 8257427154623140385), KQU(11389003026741800267), KQU( 4070043211095013717), KQU(11663806997145259025), KQU(15265598950648798210), KQU( 630585789434030934), KQU( 3524446529213587334), KQU( 7186424168495184211), KQU(10806585451386379021), KQU(11120017753500499273), KQU( 1586837651387701301), KQU(17530454400954415544), KQU( 9991670045077880430), KQU( 7550997268990730180), KQU( 8640249196597379304), KQU( 3522203892786893823), KQU(10401116549878854788), KQU(13690285544733124852), KQU( 8295785675455774586), KQU(15535716172155117603), KQU( 3112108583723722511), KQU(17633179955339271113), KQU(18154208056063759375), KQU( 1866409236285815666), KQU(13326075895396412882), KQU( 8756261842948020025), KQU( 6281852999868439131), KQU(15087653361275292858), KQU(10333923911152949397), KQU( 5265567645757408500), KQU(12728041843210352184), KQU( 6347959327507828759), KQU( 154112802625564758), KQU(18235228308679780218), KQU( 3253805274673352418), KQU( 4849171610689031197), KQU(17948529398340432518), KQU(13803510475637409167), KQU(13506570190409883095), KQU(15870801273282960805), KQU( 8451286481299170773), KQU( 9562190620034457541), KQU( 8518905387449138364), KQU(12681306401363385655), KQU( 3788073690559762558), KQU( 5256820289573487769), KQU( 2752021372314875467), KQU( 6354035166862520716), KQU( 4328956378309739069), KQU( 449087441228269600), KQU( 5533508742653090868), KQU( 1260389420404746988), KQU(18175394473289055097), KQU( 1535467109660399420), KQU( 8818894282874061442), KQU(12140873243824811213), KQU(15031386653823014946), KQU( 1286028221456149232), KQU( 6329608889367858784), KQU( 9419654354945132725), KQU( 6094576547061672379), KQU(17706217251847450255), KQU( 1733495073065878126), KQU(16918923754607552663), KQU( 8881949849954945044), KQU(12938977706896313891), KQU(14043628638299793407), KQU(18393874581723718233), KQU( 6886318534846892044), KQU(14577870878038334081), KQU(13541558383439414119), KQU(13570472158807588273), KQU(18300760537910283361), KQU( 818368572800609205), KQU( 1417000585112573219), KQU(12337533143867683655), KQU(12433180994702314480), KQU( 778190005829189083), KQU(13667356216206524711), KQU( 9866149895295225230), KQU(11043240490417111999), KQU( 1123933826541378598), KQU( 6469631933605123610), KQU(14508554074431980040), KQU(13918931242962026714), KQU( 2870785929342348285), KQU(14786362626740736974), KQU(13176680060902695786), KQU( 9591778613541679456), KQU( 9097662885117436706), KQU( 749262234240924947), KQU( 1944844067793307093), KQU( 4339214904577487742), KQU( 8009584152961946551), KQU(16073159501225501777), KQU( 3335870590499306217), KQU(17088312653151202847), KQU( 3108893142681931848), KQU(16636841767202792021), KQU(10423316431118400637), KQU( 8008357368674443506), KQU(11340015231914677875), KQU(17687896501594936090), KQU(15173627921763199958), KQU( 542569482243721959), KQU(15071714982769812975), KQU( 4466624872151386956), KQU( 1901780715602332461), KQU( 9822227742154351098), KQU( 1479332892928648780), KQU( 6981611948382474400), KQU( 7620824924456077376), KQU(14095973329429406782), KQU( 7902744005696185404), KQU(15830577219375036920), KQU(10287076667317764416), KQU(12334872764071724025), KQU( 4419302088133544331), KQU(14455842851266090520), KQU(12488077416504654222), KQU( 7953892017701886766), KQU( 6331484925529519007), KQU( 4902145853785030022), KQU(17010159216096443073), KQU(11945354668653886087), KQU(15112022728645230829), KQU(17363484484522986742), KQU( 4423497825896692887), KQU( 8155489510809067471), KQU( 258966605622576285), KQU( 5462958075742020534), KQU( 6763710214913276228), KQU( 2368935183451109054), KQU(14209506165246453811), KQU( 2646257040978514881), KQU( 3776001911922207672), KQU( 1419304601390147631), KQU(14987366598022458284), KQU( 3977770701065815721), KQU( 730820417451838898), KQU( 3982991703612885327), KQU( 2803544519671388477), KQU(17067667221114424649), KQU( 2922555119737867166), KQU( 1989477584121460932), KQU(15020387605892337354), KQU( 9293277796427533547), KQU(10722181424063557247), KQU(16704542332047511651), KQU( 5008286236142089514), KQU(16174732308747382540), KQU(17597019485798338402), KQU(13081745199110622093), KQU( 8850305883842258115), KQU(12723629125624589005), KQU( 8140566453402805978), KQU(15356684607680935061), KQU(14222190387342648650), KQU(11134610460665975178), KQU( 1259799058620984266), KQU(13281656268025610041), KQU( 298262561068153992), KQU(12277871700239212922), KQU(13911297774719779438), KQU(16556727962761474934), KQU(17903010316654728010), KQU( 9682617699648434744), KQU(14757681836838592850), KQU( 1327242446558524473), KQU(11126645098780572792), KQU( 1883602329313221774), KQU( 2543897783922776873), KQU(15029168513767772842), KQU(12710270651039129878), KQU(16118202956069604504), KQU(15010759372168680524), KQU( 2296827082251923948), KQU(10793729742623518101), KQU(13829764151845413046), KQU(17769301223184451213), KQU( 3118268169210783372), KQU(17626204544105123127), KQU( 7416718488974352644), KQU(10450751996212925994), KQU( 9352529519128770586), KQU( 259347569641110140), KQU( 8048588892269692697), KQU( 1774414152306494058), KQU(10669548347214355622), KQU(13061992253816795081), KQU(18432677803063861659), KQU( 8879191055593984333), KQU(12433753195199268041), KQU(14919392415439730602), KQU( 6612848378595332963), KQU( 6320986812036143628), KQU(10465592420226092859), KQU( 4196009278962570808), KQU( 3747816564473572224), KQU(17941203486133732898), KQU( 2350310037040505198), KQU( 5811779859134370113), KQU(10492109599506195126), KQU( 7699650690179541274), KQU( 1954338494306022961), KQU(14095816969027231152), KQU( 5841346919964852061), KQU(14945969510148214735), KQU( 3680200305887550992), KQU( 6218047466131695792), KQU( 8242165745175775096), KQU(11021371934053307357), KQU( 1265099502753169797), KQU( 4644347436111321718), KQU( 3609296916782832859), KQU( 8109807992218521571), KQU(18387884215648662020), KQU(14656324896296392902), KQU(17386819091238216751), KQU(17788300878582317152), KQU( 7919446259742399591), KQU( 4466613134576358004), KQU(12928181023667938509), KQU(13147446154454932030), KQU(16552129038252734620), KQU( 8395299403738822450), KQU(11313817655275361164), KQU( 434258809499511718), KQU( 2074882104954788676), KQU( 7929892178759395518), KQU( 9006461629105745388), KQU( 5176475650000323086), KQU(11128357033468341069), KQU(12026158851559118955), KQU(14699716249471156500), KQU( 448982497120206757), KQU( 4156475356685519900), KQU( 6063816103417215727), KQU(10073289387954971479), KQU( 8174466846138590962), KQU( 2675777452363449006), KQU( 9090685420572474281), KQU( 6659652652765562060), KQU(12923120304018106621), KQU(11117480560334526775), KQU( 937910473424587511), KQU( 1838692113502346645), KQU(11133914074648726180), KQU( 7922600945143884053), KQU(13435287702700959550), KQU( 5287964921251123332), KQU(11354875374575318947), KQU(17955724760748238133), KQU(13728617396297106512), KQU( 4107449660118101255), KQU( 1210269794886589623), KQU(11408687205733456282), KQU( 4538354710392677887), KQU(13566803319341319267), KQU(17870798107734050771), KQU( 3354318982568089135), KQU( 9034450839405133651), KQU(13087431795753424314), KQU( 950333102820688239), KQU( 1968360654535604116), KQU(16840551645563314995), KQU( 8867501803892924995), KQU(11395388644490626845), KQU( 1529815836300732204), KQU(13330848522996608842), KQU( 1813432878817504265), KQU( 2336867432693429560), KQU(15192805445973385902), KQU( 2528593071076407877), KQU( 128459777936689248), KQU( 9976345382867214866), KQU( 6208885766767996043), KQU(14982349522273141706), KQU( 3099654362410737822), KQU(13776700761947297661), KQU( 8806185470684925550), KQU( 8151717890410585321), KQU( 640860591588072925), KQU(14592096303937307465), KQU( 9056472419613564846), KQU(14861544647742266352), KQU(12703771500398470216), KQU( 3142372800384138465), KQU( 6201105606917248196), KQU(18337516409359270184), KQU(15042268695665115339), KQU(15188246541383283846), KQU(12800028693090114519), KQU( 5992859621101493472), KQU(18278043971816803521), KQU( 9002773075219424560), KQU( 7325707116943598353), KQU( 7930571931248040822), KQU( 5645275869617023448), KQU( 7266107455295958487), KQU( 4363664528273524411), KQU(14313875763787479809), KQU(17059695613553486802), KQU( 9247761425889940932), KQU(13704726459237593128), KQU( 2701312427328909832), KQU(17235532008287243115), KQU(14093147761491729538), KQU( 6247352273768386516), KQU( 8268710048153268415), KQU( 7985295214477182083), KQU(15624495190888896807), KQU( 3772753430045262788), KQU( 9133991620474991698), KQU( 5665791943316256028), KQU( 7551996832462193473), KQU(13163729206798953877), KQU( 9263532074153846374), KQU( 1015460703698618353), KQU(17929874696989519390), KQU(18257884721466153847), KQU(16271867543011222991), KQU( 3905971519021791941), KQU(16814488397137052085), KQU( 1321197685504621613), KQU( 2870359191894002181), KQU(14317282970323395450), KQU(13663920845511074366), KQU( 2052463995796539594), KQU(14126345686431444337), KQU( 1727572121947022534), KQU(17793552254485594241), KQU( 6738857418849205750), KQU( 1282987123157442952), KQU(16655480021581159251), KQU( 6784587032080183866), KQU(14726758805359965162), KQU( 7577995933961987349), KQU(12539609320311114036), KQU(10789773033385439494), KQU( 8517001497411158227), KQU(10075543932136339710), KQU(14838152340938811081), KQU( 9560840631794044194), KQU(17445736541454117475), KQU(10633026464336393186), KQU(15705729708242246293), KQU( 1117517596891411098), KQU( 4305657943415886942), KQU( 4948856840533979263), KQU(16071681989041789593), KQU(13723031429272486527), KQU( 7639567622306509462), KQU(12670424537483090390), KQU( 9715223453097197134), KQU( 5457173389992686394), KQU( 289857129276135145), KQU(17048610270521972512), KQU( 692768013309835485), KQU(14823232360546632057), KQU(18218002361317895936), KQU( 3281724260212650204), KQU(16453957266549513795), KQU( 8592711109774511881), KQU( 929825123473369579), KQU(15966784769764367791), KQU( 9627344291450607588), KQU(10849555504977813287), KQU( 9234566913936339275), KQU( 6413807690366911210), KQU(10862389016184219267), KQU(13842504799335374048), KQU( 1531994113376881174), KQU( 2081314867544364459), KQU(16430628791616959932), KQU( 8314714038654394368), KQU( 9155473892098431813), KQU(12577843786670475704), KQU( 4399161106452401017), KQU( 1668083091682623186), KQU( 1741383777203714216), KQU( 2162597285417794374), KQU(15841980159165218736), KQU( 1971354603551467079), KQU( 1206714764913205968), KQU( 4790860439591272330), KQU(14699375615594055799), KQU( 8374423871657449988), KQU(10950685736472937738), KQU( 697344331343267176), KQU(10084998763118059810), KQU(12897369539795983124), KQU(12351260292144383605), KQU( 1268810970176811234), KQU( 7406287800414582768), KQU( 516169557043807831), KQU( 5077568278710520380), KQU( 3828791738309039304), KQU( 7721974069946943610), KQU( 3534670260981096460), KQU( 4865792189600584891), KQU(16892578493734337298), KQU( 9161499464278042590), KQU(11976149624067055931), KQU(13219479887277343990), KQU(14161556738111500680), KQU(14670715255011223056), KQU( 4671205678403576558), KQU(12633022931454259781), KQU(14821376219869187646), KQU( 751181776484317028), KQU( 2192211308839047070), KQU(11787306362361245189), KQU(10672375120744095707), KQU( 4601972328345244467), KQU(15457217788831125879), KQU( 8464345256775460809), KQU(10191938789487159478), KQU( 6184348739615197613), KQU(11425436778806882100), KQU( 2739227089124319793), KQU( 461464518456000551), KQU( 4689850170029177442), KQU( 6120307814374078625), KQU(11153579230681708671), KQU( 7891721473905347926), KQU(10281646937824872400), KQU( 3026099648191332248), KQU( 8666750296953273818), KQU(14978499698844363232), KQU(13303395102890132065), KQU( 8182358205292864080), KQU(10560547713972971291), KQU(11981635489418959093), KQU( 3134621354935288409), KQU(11580681977404383968), KQU(14205530317404088650), KQU( 5997789011854923157), KQU(13659151593432238041), KQU(11664332114338865086), KQU( 7490351383220929386), KQU( 7189290499881530378), KQU(15039262734271020220), KQU( 2057217285976980055), KQU( 555570804905355739), KQU(11235311968348555110), KQU(13824557146269603217), KQU(16906788840653099693), KQU( 7222878245455661677), KQU( 5245139444332423756), KQU( 4723748462805674292), KQU(12216509815698568612), KQU(17402362976648951187), KQU(17389614836810366768), KQU( 4880936484146667711), KQU( 9085007839292639880), KQU(13837353458498535449), KQU(11914419854360366677), KQU(16595890135313864103), KQU( 6313969847197627222), KQU(18296909792163910431), KQU(10041780113382084042), KQU( 2499478551172884794), KQU(11057894246241189489), KQU( 9742243032389068555), KQU(12838934582673196228), KQU(13437023235248490367), KQU(13372420669446163240), KQU( 6752564244716909224), KQU( 7157333073400313737), KQU(12230281516370654308), KQU( 1182884552219419117), KQU( 2955125381312499218), KQU(10308827097079443249), KQU( 1337648572986534958), KQU(16378788590020343939), KQU( 108619126514420935), KQU( 3990981009621629188), KQU( 5460953070230946410), KQU( 9703328329366531883), KQU(13166631489188077236), KQU( 1104768831213675170), KQU( 3447930458553877908), KQU( 8067172487769945676), KQU( 5445802098190775347), KQU( 3244840981648973873), KQU(17314668322981950060), KQU( 5006812527827763807), KQU(18158695070225526260), KQU( 2824536478852417853), KQU(13974775809127519886), KQU( 9814362769074067392), KQU(17276205156374862128), KQU(11361680725379306967), KQU( 3422581970382012542), KQU(11003189603753241266), KQU(11194292945277862261), KQU( 6839623313908521348), KQU(11935326462707324634), KQU( 1611456788685878444), KQU(13112620989475558907), KQU( 517659108904450427), KQU(13558114318574407624), KQU(15699089742731633077), KQU( 4988979278862685458), KQU( 8111373583056521297), KQU( 3891258746615399627), KQU( 8137298251469718086), KQU(12748663295624701649), KQU( 4389835683495292062), KQU( 5775217872128831729), KQU( 9462091896405534927), KQU( 8498124108820263989), KQU( 8059131278842839525), KQU(10503167994254090892), KQU(11613153541070396656), KQU(18069248738504647790), KQU( 570657419109768508), KQU( 3950574167771159665), KQU( 5514655599604313077), KQU( 2908460854428484165), KQU(10777722615935663114), KQU(12007363304839279486), KQU( 9800646187569484767), KQU( 8795423564889864287), KQU(14257396680131028419), KQU( 6405465117315096498), KQU( 7939411072208774878), KQU(17577572378528990006), KQU(14785873806715994850), KQU(16770572680854747390), KQU(18127549474419396481), KQU(11637013449455757750), KQU(14371851933996761086), KQU( 3601181063650110280), KQU( 4126442845019316144), KQU(10198287239244320669), KQU(18000169628555379659), KQU(18392482400739978269), KQU( 6219919037686919957), KQU( 3610085377719446052), KQU( 2513925039981776336), KQU(16679413537926716955), KQU(12903302131714909434), KQU( 5581145789762985009), KQU(12325955044293303233), KQU(17216111180742141204), KQU( 6321919595276545740), KQU( 3507521147216174501), KQU( 9659194593319481840), KQU(11473976005975358326), KQU(14742730101435987026), KQU( 492845897709954780), KQU(16976371186162599676), KQU(17712703422837648655), KQU( 9881254778587061697), KQU( 8413223156302299551), KQU( 1563841828254089168), KQU( 9996032758786671975), KQU( 138877700583772667), KQU(13003043368574995989), KQU( 4390573668650456587), KQU( 8610287390568126755), KQU(15126904974266642199), KQU( 6703637238986057662), KQU( 2873075592956810157), KQU( 6035080933946049418), KQU(13382846581202353014), KQU( 7303971031814642463), KQU(18418024405307444267), KQU( 5847096731675404647), KQU( 4035880699639842500), KQU(11525348625112218478), KQU( 3041162365459574102), KQU( 2604734487727986558), KQU(15526341771636983145), KQU(14556052310697370254), KQU(12997787077930808155), KQU( 9601806501755554499), KQU(11349677952521423389), KQU(14956777807644899350), KQU(16559736957742852721), KQU(12360828274778140726), KQU( 6685373272009662513), KQU(16932258748055324130), KQU(15918051131954158508), KQU( 1692312913140790144), KQU( 546653826801637367), KQU( 5341587076045986652), KQU(14975057236342585662), KQU(12374976357340622412), KQU(10328833995181940552), KQU(12831807101710443149), KQU(10548514914382545716), KQU( 2217806727199715993), KQU(12627067369242845138), KQU( 4598965364035438158), KQU( 150923352751318171), KQU(14274109544442257283), KQU( 4696661475093863031), KQU( 1505764114384654516), KQU(10699185831891495147), KQU( 2392353847713620519), KQU( 3652870166711788383), KQU( 8640653276221911108), KQU( 3894077592275889704), KQU( 4918592872135964845), KQU(16379121273281400789), KQU(12058465483591683656), KQU(11250106829302924945), KQU( 1147537556296983005), KQU( 6376342756004613268), KQU(14967128191709280506), KQU(18007449949790627628), KQU( 9497178279316537841), KQU( 7920174844809394893), KQU(10037752595255719907), KQU(15875342784985217697), KQU(15311615921712850696), KQU( 9552902652110992950), KQU(14054979450099721140), KQU( 5998709773566417349), KQU(18027910339276320187), KQU( 8223099053868585554), KQU( 7842270354824999767), KQU( 4896315688770080292), KQU(12969320296569787895), KQU( 2674321489185759961), KQU( 4053615936864718439), KQU(11349775270588617578), KQU( 4743019256284553975), KQU( 5602100217469723769), KQU(14398995691411527813), KQU( 7412170493796825470), KQU( 836262406131744846), KQU( 8231086633845153022), KQU( 5161377920438552287), KQU( 8828731196169924949), KQU(16211142246465502680), KQU( 3307990879253687818), KQU( 5193405406899782022), KQU( 8510842117467566693), KQU( 6070955181022405365), KQU(14482950231361409799), KQU(12585159371331138077), KQU( 3511537678933588148), KQU( 2041849474531116417), KQU(10944936685095345792), KQU(18303116923079107729), KQU( 2720566371239725320), KQU( 4958672473562397622), KQU( 3032326668253243412), KQU(13689418691726908338), KQU( 1895205511728843996), KQU( 8146303515271990527), KQU(16507343500056113480), KQU( 473996939105902919), KQU( 9897686885246881481), KQU(14606433762712790575), KQU( 6732796251605566368), KQU( 1399778120855368916), KQU( 935023885182833777), KQU(16066282816186753477), KQU( 7291270991820612055), KQU(17530230393129853844), KQU(10223493623477451366), KQU(15841725630495676683), KQU(17379567246435515824), KQU( 8588251429375561971), KQU(18339511210887206423), KQU(17349587430725976100), KQU(12244876521394838088), KQU( 6382187714147161259), KQU(12335807181848950831), KQU(16948885622305460665), KQU(13755097796371520506), KQU(14806740373324947801), KQU( 4828699633859287703), KQU( 8209879281452301604), KQU(12435716669553736437), KQU(13970976859588452131), KQU( 6233960842566773148), KQU(12507096267900505759), KQU( 1198713114381279421), KQU(14989862731124149015), KQU(15932189508707978949), KQU( 2526406641432708722), KQU( 29187427817271982), KQU( 1499802773054556353), KQU(10816638187021897173), KQU( 5436139270839738132), KQU( 6659882287036010082), KQU( 2154048955317173697), KQU(10887317019333757642), KQU(16281091802634424955), KQU(10754549879915384901), KQU(10760611745769249815), KQU( 2161505946972504002), KQU( 5243132808986265107), KQU(10129852179873415416), KQU( 710339480008649081), KQU( 7802129453068808528), KQU(17967213567178907213), KQU(15730859124668605599), KQU(13058356168962376502), KQU( 3701224985413645909), KQU(14464065869149109264), KQU( 9959272418844311646), KQU(10157426099515958752), KQU(14013736814538268528), KQU(17797456992065653951), KQU(17418878140257344806), KQU(15457429073540561521), KQU( 2184426881360949378), KQU( 2062193041154712416), KQU( 8553463347406931661), KQU( 4913057625202871854), KQU( 2668943682126618425), KQU(17064444737891172288), KQU( 4997115903913298637), KQU(12019402608892327416), KQU(17603584559765897352), KQU(11367529582073647975), KQU( 8211476043518436050), KQU( 8676849804070323674), KQU(18431829230394475730), KQU(10490177861361247904), KQU( 9508720602025651349), KQU( 7409627448555722700), KQU( 5804047018862729008), KQU(11943858176893142594), KQU(11908095418933847092), KQU( 5415449345715887652), KQU( 1554022699166156407), KQU( 9073322106406017161), KQU( 7080630967969047082), KQU(18049736940860732943), KQU(12748714242594196794), KQU( 1226992415735156741), KQU(17900981019609531193), KQU(11720739744008710999), KQU( 3006400683394775434), KQU(11347974011751996028), KQU( 3316999628257954608), KQU( 8384484563557639101), KQU(18117794685961729767), KQU( 1900145025596618194), KQU(17459527840632892676), KQU( 5634784101865710994), KQU( 7918619300292897158), KQU( 3146577625026301350), KQU( 9955212856499068767), KQU( 1873995843681746975), KQU( 1561487759967972194), KQU( 8322718804375878474), KQU(11300284215327028366), KQU( 4667391032508998982), KQU( 9820104494306625580), KQU(17922397968599970610), KQU( 1784690461886786712), KQU(14940365084341346821), KQU( 5348719575594186181), KQU(10720419084507855261), KQU(14210394354145143274), KQU( 2426468692164000131), KQU(16271062114607059202), KQU(14851904092357070247), KQU( 6524493015693121897), KQU( 9825473835127138531), KQU(14222500616268569578), KQU(15521484052007487468), KQU(14462579404124614699), KQU(11012375590820665520), KQU(11625327350536084927), KQU(14452017765243785417), KQU( 9989342263518766305), KQU( 3640105471101803790), KQU( 4749866455897513242), KQU(13963064946736312044), KQU(10007416591973223791), KQU(18314132234717431115), KQU( 3286596588617483450), KQU( 7726163455370818765), KQU( 7575454721115379328), KQU( 5308331576437663422), KQU(18288821894903530934), KQU( 8028405805410554106), KQU(15744019832103296628), KQU( 149765559630932100), KQU( 6137705557200071977), KQU(14513416315434803615), KQU(11665702820128984473), KQU( 218926670505601386), KQU( 6868675028717769519), KQU(15282016569441512302), KQU( 5707000497782960236), KQU( 6671120586555079567), KQU( 2194098052618985448), KQU(16849577895477330978), KQU(12957148471017466283), KQU( 1997805535404859393), KQU( 1180721060263860490), KQU(13206391310193756958), KQU(12980208674461861797), KQU( 3825967775058875366), KQU(17543433670782042631), KQU( 1518339070120322730), KQU(16344584340890991669), KQU( 2611327165318529819), KQU(11265022723283422529), KQU( 4001552800373196817), KQU(14509595890079346161), KQU( 3528717165416234562), KQU(18153222571501914072), KQU( 9387182977209744425), KQU(10064342315985580021), KQU(11373678413215253977), KQU( 2308457853228798099), KQU( 9729042942839545302), KQU( 7833785471140127746), KQU( 6351049900319844436), KQU(14454610627133496067), KQU(12533175683634819111), KQU(15570163926716513029), KQU(13356980519185762498) }; TEST_BEGIN(test_gen_rand_32) { uint32_t array32[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16)); uint32_t array32_2[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16)); int i; uint32_t r32; sfmt_t *ctx; expect_d_le(get_min_array_size32(), BLOCK_SIZE, "Array size too small"); ctx = init_gen_rand(1234); fill_array32(ctx, array32, BLOCK_SIZE); fill_array32(ctx, array32_2, BLOCK_SIZE); fini_gen_rand(ctx); ctx = init_gen_rand(1234); for (i = 0; i < BLOCK_SIZE; i++) { if (i < COUNT_1) { expect_u32_eq(array32[i], init_gen_rand_32_expected[i], "Output mismatch for i=%d", i); } r32 = gen_rand32(ctx); expect_u32_eq(r32, array32[i], "Mismatch at array32[%d]=%x, gen=%x", i, array32[i], r32); } for (i = 0; i < COUNT_2; i++) { r32 = gen_rand32(ctx); expect_u32_eq(r32, array32_2[i], "Mismatch at array32_2[%d]=%x, gen=%x", i, array32_2[i], r32); } fini_gen_rand(ctx); } TEST_END TEST_BEGIN(test_by_array_32) { uint32_t array32[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16)); uint32_t array32_2[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16)); int i; uint32_t ini[4] = {0x1234, 0x5678, 0x9abc, 0xdef0}; uint32_t r32; sfmt_t *ctx; expect_d_le(get_min_array_size32(), BLOCK_SIZE, "Array size too small"); ctx = init_by_array(ini, 4); fill_array32(ctx, array32, BLOCK_SIZE); fill_array32(ctx, array32_2, BLOCK_SIZE); fini_gen_rand(ctx); ctx = init_by_array(ini, 4); for (i = 0; i < BLOCK_SIZE; i++) { if (i < COUNT_1) { expect_u32_eq(array32[i], init_by_array_32_expected[i], "Output mismatch for i=%d", i); } r32 = gen_rand32(ctx); expect_u32_eq(r32, array32[i], "Mismatch at array32[%d]=%x, gen=%x", i, array32[i], r32); } for (i = 0; i < COUNT_2; i++) { r32 = gen_rand32(ctx); expect_u32_eq(r32, array32_2[i], "Mismatch at array32_2[%d]=%x, gen=%x", i, array32_2[i], r32); } fini_gen_rand(ctx); } TEST_END TEST_BEGIN(test_gen_rand_64) { uint64_t array64[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16)); uint64_t array64_2[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16)); int i; uint64_t r; sfmt_t *ctx; expect_d_le(get_min_array_size64(), BLOCK_SIZE64, "Array size too small"); ctx = init_gen_rand(4321); fill_array64(ctx, array64, BLOCK_SIZE64); fill_array64(ctx, array64_2, BLOCK_SIZE64); fini_gen_rand(ctx); ctx = init_gen_rand(4321); for (i = 0; i < BLOCK_SIZE64; i++) { if (i < COUNT_1) { expect_u64_eq(array64[i], init_gen_rand_64_expected[i], "Output mismatch for i=%d", i); } r = gen_rand64(ctx); expect_u64_eq(r, array64[i], "Mismatch at array64[%d]=%"FMTx64", gen=%"FMTx64, i, array64[i], r); } for (i = 0; i < COUNT_2; i++) { r = gen_rand64(ctx); expect_u64_eq(r, array64_2[i], "Mismatch at array64_2[%d]=%"FMTx64" gen=%"FMTx64"", i, array64_2[i], r); } fini_gen_rand(ctx); } TEST_END TEST_BEGIN(test_by_array_64) { uint64_t array64[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16)); uint64_t array64_2[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16)); int i; uint64_t r; uint32_t ini[] = {5, 4, 3, 2, 1}; sfmt_t *ctx; expect_d_le(get_min_array_size64(), BLOCK_SIZE64, "Array size too small"); ctx = init_by_array(ini, 5); fill_array64(ctx, array64, BLOCK_SIZE64); fill_array64(ctx, array64_2, BLOCK_SIZE64); fini_gen_rand(ctx); ctx = init_by_array(ini, 5); for (i = 0; i < BLOCK_SIZE64; i++) { if (i < COUNT_1) { expect_u64_eq(array64[i], init_by_array_64_expected[i], "Output mismatch for i=%d", i); } r = gen_rand64(ctx); expect_u64_eq(r, array64[i], "Mismatch at array64[%d]=%"FMTx64" gen=%"FMTx64, i, array64[i], r); } for (i = 0; i < COUNT_2; i++) { r = gen_rand64(ctx); expect_u64_eq(r, array64_2[i], "Mismatch at array64_2[%d]=%"FMTx64" gen=%"FMTx64, i, array64_2[i], r); } fini_gen_rand(ctx); } TEST_END int main(void) { return test( test_gen_rand_32, test_by_array_32, test_gen_rand_64, test_by_array_64); } redis-8.0.2/deps/jemalloc/test/unit/a0.c000066400000000000000000000003451501533116600177550ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_a0) { void *p; p = a0malloc(1); expect_ptr_not_null(p, "Unexpected a0malloc() error"); a0dalloc(p); } TEST_END int main(void) { return test_no_malloc_init( test_a0); } redis-8.0.2/deps/jemalloc/test/unit/arena_decay.c000066400000000000000000000324201501533116600217070ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/arena_util.h" #include "jemalloc/internal/ticker.h" static nstime_monotonic_t *nstime_monotonic_orig; static nstime_update_t *nstime_update_orig; static unsigned nupdates_mock; static nstime_t time_mock; static bool monotonic_mock; static bool nstime_monotonic_mock(void) { return monotonic_mock; } static void nstime_update_mock(nstime_t *time) { nupdates_mock++; if (monotonic_mock) { nstime_copy(time, &time_mock); } } TEST_BEGIN(test_decay_ticks) { test_skip_if(is_background_thread_enabled()); test_skip_if(opt_hpa); ticker_geom_t *decay_ticker; unsigned tick0, tick1, arena_ind; size_t sz, large0; void *p; sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL, 0), 0, "Unexpected mallctl failure"); /* Set up a manually managed arena for test. */ arena_ind = do_arena_create(0, 0); /* Migrate to the new arena, and get the ticker. */ unsigned old_arena_ind; size_t sz_arena_ind = sizeof(old_arena_ind); expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz_arena_ind, (void *)&arena_ind, sizeof(arena_ind)), 0, "Unexpected mallctl() failure"); decay_ticker = tsd_arena_decay_tickerp_get(tsd_fetch()); expect_ptr_not_null(decay_ticker, "Unexpected failure getting decay ticker"); /* * Test the standard APIs using a large size class, since we can't * control tcache interactions for small size classes (except by * completely disabling tcache for the entire test program). */ /* malloc(). */ tick0 = ticker_geom_read(decay_ticker); p = malloc(large0); expect_ptr_not_null(p, "Unexpected malloc() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during malloc()"); /* free(). */ tick0 = ticker_geom_read(decay_ticker); free(p); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during free()"); /* calloc(). */ tick0 = ticker_geom_read(decay_ticker); p = calloc(1, large0); expect_ptr_not_null(p, "Unexpected calloc() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during calloc()"); free(p); /* posix_memalign(). */ tick0 = ticker_geom_read(decay_ticker); expect_d_eq(posix_memalign(&p, sizeof(size_t), large0), 0, "Unexpected posix_memalign() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during posix_memalign()"); free(p); /* aligned_alloc(). */ tick0 = ticker_geom_read(decay_ticker); p = aligned_alloc(sizeof(size_t), large0); expect_ptr_not_null(p, "Unexpected aligned_alloc() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during aligned_alloc()"); free(p); /* realloc(). */ /* Allocate. */ tick0 = ticker_geom_read(decay_ticker); p = realloc(NULL, large0); expect_ptr_not_null(p, "Unexpected realloc() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()"); /* Reallocate. */ tick0 = ticker_geom_read(decay_ticker); p = realloc(p, large0); expect_ptr_not_null(p, "Unexpected realloc() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()"); /* Deallocate. */ tick0 = ticker_geom_read(decay_ticker); realloc(p, 0); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()"); /* * Test the *allocx() APIs using large and small size classes, with * tcache explicitly disabled. */ { unsigned i; size_t allocx_sizes[2]; allocx_sizes[0] = large0; allocx_sizes[1] = 1; for (i = 0; i < sizeof(allocx_sizes) / sizeof(size_t); i++) { sz = allocx_sizes[i]; /* mallocx(). */ tick0 = ticker_geom_read(decay_ticker); p = mallocx(sz, MALLOCX_TCACHE_NONE); expect_ptr_not_null(p, "Unexpected mallocx() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during mallocx() (sz=%zu)", sz); /* rallocx(). */ tick0 = ticker_geom_read(decay_ticker); p = rallocx(p, sz, MALLOCX_TCACHE_NONE); expect_ptr_not_null(p, "Unexpected rallocx() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during rallocx() (sz=%zu)", sz); /* xallocx(). */ tick0 = ticker_geom_read(decay_ticker); xallocx(p, sz, 0, MALLOCX_TCACHE_NONE); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during xallocx() (sz=%zu)", sz); /* dallocx(). */ tick0 = ticker_geom_read(decay_ticker); dallocx(p, MALLOCX_TCACHE_NONE); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during dallocx() (sz=%zu)", sz); /* sdallocx(). */ p = mallocx(sz, MALLOCX_TCACHE_NONE); expect_ptr_not_null(p, "Unexpected mallocx() failure"); tick0 = ticker_geom_read(decay_ticker); sdallocx(p, sz, MALLOCX_TCACHE_NONE); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during sdallocx() " "(sz=%zu)", sz); } } /* * Test tcache fill/flush interactions for large and small size classes, * using an explicit tcache. */ unsigned tcache_ind, i; size_t tcache_sizes[2]; tcache_sizes[0] = large0; tcache_sizes[1] = 1; size_t tcache_max, sz_tcache_max; sz_tcache_max = sizeof(tcache_max); expect_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max, &sz_tcache_max, NULL, 0), 0, "Unexpected mallctl() failure"); sz = sizeof(unsigned); expect_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz, NULL, 0), 0, "Unexpected mallctl failure"); for (i = 0; i < sizeof(tcache_sizes) / sizeof(size_t); i++) { sz = tcache_sizes[i]; /* tcache fill. */ tick0 = ticker_geom_read(decay_ticker); p = mallocx(sz, MALLOCX_TCACHE(tcache_ind)); expect_ptr_not_null(p, "Unexpected mallocx() failure"); tick1 = ticker_geom_read(decay_ticker); expect_u32_ne(tick1, tick0, "Expected ticker to tick during tcache fill " "(sz=%zu)", sz); /* tcache flush. */ dallocx(p, MALLOCX_TCACHE(tcache_ind)); tick0 = ticker_geom_read(decay_ticker); expect_d_eq(mallctl("tcache.flush", NULL, NULL, (void *)&tcache_ind, sizeof(unsigned)), 0, "Unexpected mallctl failure"); tick1 = ticker_geom_read(decay_ticker); /* Will only tick if it's in tcache. */ expect_u32_ne(tick1, tick0, "Expected ticker to tick during tcache flush (sz=%zu)", sz); } } TEST_END static void decay_ticker_helper(unsigned arena_ind, int flags, bool dirty, ssize_t dt, uint64_t dirty_npurge0, uint64_t muzzy_npurge0, bool terminate_asap) { #define NINTERVALS 101 nstime_t time, update_interval, decay_ms, deadline; nstime_init_update(&time); nstime_init2(&decay_ms, dt, 0); nstime_copy(&deadline, &time); nstime_add(&deadline, &decay_ms); nstime_init2(&update_interval, dt, 0); nstime_idivide(&update_interval, NINTERVALS); /* * Keep q's slab from being deallocated during the looping below. If a * cached slab were to repeatedly come and go during looping, it could * prevent the decay backlog ever becoming empty. */ void *p = do_mallocx(1, flags); uint64_t dirty_npurge1, muzzy_npurge1; do { for (unsigned i = 0; i < ARENA_DECAY_NTICKS_PER_UPDATE / 2; i++) { void *q = do_mallocx(1, flags); dallocx(q, flags); } dirty_npurge1 = get_arena_dirty_npurge(arena_ind); muzzy_npurge1 = get_arena_muzzy_npurge(arena_ind); nstime_add(&time_mock, &update_interval); nstime_update(&time); } while (nstime_compare(&time, &deadline) <= 0 && ((dirty_npurge1 == dirty_npurge0 && muzzy_npurge1 == muzzy_npurge0) || !terminate_asap)); dallocx(p, flags); if (config_stats) { expect_u64_gt(dirty_npurge1 + muzzy_npurge1, dirty_npurge0 + muzzy_npurge0, "Expected purging to occur"); } #undef NINTERVALS } TEST_BEGIN(test_decay_ticker) { test_skip_if(is_background_thread_enabled()); test_skip_if(opt_hpa); #define NPS 2048 ssize_t ddt = opt_dirty_decay_ms; ssize_t mdt = opt_muzzy_decay_ms; unsigned arena_ind = do_arena_create(ddt, mdt); int flags = (MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE); void *ps[NPS]; /* * Allocate a bunch of large objects, pause the clock, deallocate every * other object (to fragment virtual memory), restore the clock, then * [md]allocx() in a tight loop while advancing time rapidly to verify * the ticker triggers purging. */ size_t large; size_t sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&large, &sz, NULL, 0), 0, "Unexpected mallctl failure"); do_purge(arena_ind); uint64_t dirty_npurge0 = get_arena_dirty_npurge(arena_ind); uint64_t muzzy_npurge0 = get_arena_muzzy_npurge(arena_ind); for (unsigned i = 0; i < NPS; i++) { ps[i] = do_mallocx(large, flags); } nupdates_mock = 0; nstime_init_update(&time_mock); monotonic_mock = true; nstime_monotonic_orig = nstime_monotonic; nstime_update_orig = nstime_update; nstime_monotonic = nstime_monotonic_mock; nstime_update = nstime_update_mock; for (unsigned i = 0; i < NPS; i += 2) { dallocx(ps[i], flags); unsigned nupdates0 = nupdates_mock; do_decay(arena_ind); expect_u_gt(nupdates_mock, nupdates0, "Expected nstime_update() to be called"); } decay_ticker_helper(arena_ind, flags, true, ddt, dirty_npurge0, muzzy_npurge0, true); decay_ticker_helper(arena_ind, flags, false, ddt+mdt, dirty_npurge0, muzzy_npurge0, false); do_arena_destroy(arena_ind); nstime_monotonic = nstime_monotonic_orig; nstime_update = nstime_update_orig; #undef NPS } TEST_END TEST_BEGIN(test_decay_nonmonotonic) { test_skip_if(is_background_thread_enabled()); test_skip_if(opt_hpa); #define NPS (SMOOTHSTEP_NSTEPS + 1) int flags = (MALLOCX_ARENA(0) | MALLOCX_TCACHE_NONE); void *ps[NPS]; uint64_t npurge0 = 0; uint64_t npurge1 = 0; size_t sz, large0; unsigned i, nupdates0; sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL, 0), 0, "Unexpected mallctl failure"); expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure"); do_epoch(); sz = sizeof(uint64_t); npurge0 = get_arena_npurge(0); nupdates_mock = 0; nstime_init_update(&time_mock); monotonic_mock = false; nstime_monotonic_orig = nstime_monotonic; nstime_update_orig = nstime_update; nstime_monotonic = nstime_monotonic_mock; nstime_update = nstime_update_mock; for (i = 0; i < NPS; i++) { ps[i] = mallocx(large0, flags); expect_ptr_not_null(ps[i], "Unexpected mallocx() failure"); } for (i = 0; i < NPS; i++) { dallocx(ps[i], flags); nupdates0 = nupdates_mock; expect_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0, "Unexpected arena.0.decay failure"); expect_u_gt(nupdates_mock, nupdates0, "Expected nstime_update() to be called"); } do_epoch(); sz = sizeof(uint64_t); npurge1 = get_arena_npurge(0); if (config_stats) { expect_u64_eq(npurge0, npurge1, "Unexpected purging occurred"); } nstime_monotonic = nstime_monotonic_orig; nstime_update = nstime_update_orig; #undef NPS } TEST_END TEST_BEGIN(test_decay_now) { test_skip_if(is_background_thread_enabled()); test_skip_if(opt_hpa); unsigned arena_ind = do_arena_create(0, 0); expect_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages"); expect_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages"); size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2}; /* Verify that dirty/muzzy pages never linger after deallocation. */ for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) { size_t size = sizes[i]; generate_dirty(arena_ind, size); expect_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages"); expect_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages"); } do_arena_destroy(arena_ind); } TEST_END TEST_BEGIN(test_decay_never) { test_skip_if(is_background_thread_enabled() || !config_stats); test_skip_if(opt_hpa); unsigned arena_ind = do_arena_create(-1, -1); int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; expect_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages"); expect_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages"); size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2}; void *ptrs[sizeof(sizes)/sizeof(size_t)]; for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) { ptrs[i] = do_mallocx(sizes[i], flags); } /* Verify that each deallocation generates additional dirty pages. */ size_t pdirty_prev = get_arena_pdirty(arena_ind); size_t pmuzzy_prev = get_arena_pmuzzy(arena_ind); expect_zu_eq(pdirty_prev, 0, "Unexpected dirty pages"); expect_zu_eq(pmuzzy_prev, 0, "Unexpected muzzy pages"); for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) { dallocx(ptrs[i], flags); size_t pdirty = get_arena_pdirty(arena_ind); size_t pmuzzy = get_arena_pmuzzy(arena_ind); expect_zu_gt(pdirty + (size_t)get_arena_dirty_purged(arena_ind), pdirty_prev, "Expected dirty pages to increase."); expect_zu_eq(pmuzzy, 0, "Unexpected muzzy pages"); pdirty_prev = pdirty; } do_arena_destroy(arena_ind); } TEST_END int main(void) { return test( test_decay_ticks, test_decay_ticker, test_decay_nonmonotonic, test_decay_now, test_decay_never); } redis-8.0.2/deps/jemalloc/test/unit/arena_decay.sh000066400000000000000000000001301501533116600220700ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="dirty_decay_ms:1000,muzzy_decay_ms:1000,tcache_max:1024" redis-8.0.2/deps/jemalloc/test/unit/arena_reset.c000066400000000000000000000223061501533116600217460ustar00rootroot00000000000000#ifndef ARENA_RESET_PROF_C_ #include "test/jemalloc_test.h" #endif #include "jemalloc/internal/extent_mmap.h" #include "jemalloc/internal/rtree.h" #include "test/extent_hooks.h" static unsigned get_nsizes_impl(const char *cmd) { unsigned ret; size_t z; z = sizeof(unsigned); expect_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctl(\"%s\", ...) failure", cmd); return ret; } static unsigned get_nsmall(void) { return get_nsizes_impl("arenas.nbins"); } static unsigned get_nlarge(void) { return get_nsizes_impl("arenas.nlextents"); } static size_t get_size_impl(const char *cmd, size_t ind) { size_t ret; size_t z; size_t mib[4]; size_t miblen = 4; z = sizeof(size_t); expect_d_eq(mallctlnametomib(cmd, mib, &miblen), 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd); mib[2] = ind; z = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0), 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind); return ret; } static size_t get_small_size(size_t ind) { return get_size_impl("arenas.bin.0.size", ind); } static size_t get_large_size(size_t ind) { return get_size_impl("arenas.lextent.0.size", ind); } /* Like ivsalloc(), but safe to call on discarded allocations. */ static size_t vsalloc(tsdn_t *tsdn, const void *ptr) { emap_full_alloc_ctx_t full_alloc_ctx; bool missing = emap_full_alloc_ctx_try_lookup(tsdn, &arena_emap_global, ptr, &full_alloc_ctx); if (missing) { return 0; } if (full_alloc_ctx.edata == NULL) { return 0; } if (edata_state_get(full_alloc_ctx.edata) != extent_state_active) { return 0; } if (full_alloc_ctx.szind == SC_NSIZES) { return 0; } return sz_index2size(full_alloc_ctx.szind); } static unsigned do_arena_create(extent_hooks_t *h) { unsigned arena_ind; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, (void *)(h != NULL ? &h : NULL), (h != NULL ? sizeof(h) : 0)), 0, "Unexpected mallctl() failure"); return arena_ind; } static void do_arena_reset_pre(unsigned arena_ind, void ***ptrs, unsigned *nptrs) { #define NLARGE 32 unsigned nsmall, nlarge, i; size_t sz; int flags; tsdn_t *tsdn; flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; nsmall = get_nsmall(); nlarge = get_nlarge() > NLARGE ? NLARGE : get_nlarge(); *nptrs = nsmall + nlarge; *ptrs = (void **)malloc(*nptrs * sizeof(void *)); expect_ptr_not_null(*ptrs, "Unexpected malloc() failure"); /* Allocate objects with a wide range of sizes. */ for (i = 0; i < nsmall; i++) { sz = get_small_size(i); (*ptrs)[i] = mallocx(sz, flags); expect_ptr_not_null((*ptrs)[i], "Unexpected mallocx(%zu, %#x) failure", sz, flags); } for (i = 0; i < nlarge; i++) { sz = get_large_size(i); (*ptrs)[nsmall + i] = mallocx(sz, flags); expect_ptr_not_null((*ptrs)[i], "Unexpected mallocx(%zu, %#x) failure", sz, flags); } tsdn = tsdn_fetch(); /* Verify allocations. */ for (i = 0; i < *nptrs; i++) { expect_zu_gt(ivsalloc(tsdn, (*ptrs)[i]), 0, "Allocation should have queryable size"); } } static void do_arena_reset_post(void **ptrs, unsigned nptrs, unsigned arena_ind) { tsdn_t *tsdn; unsigned i; tsdn = tsdn_fetch(); if (have_background_thread) { malloc_mutex_lock(tsdn, &background_thread_info_get(arena_ind)->mtx); } /* Verify allocations no longer exist. */ for (i = 0; i < nptrs; i++) { expect_zu_eq(vsalloc(tsdn, ptrs[i]), 0, "Allocation should no longer exist"); } if (have_background_thread) { malloc_mutex_unlock(tsdn, &background_thread_info_get(arena_ind)->mtx); } free(ptrs); } static void do_arena_reset_destroy(const char *name, unsigned arena_ind) { size_t mib[3]; size_t miblen; miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib(name, mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } static void do_arena_reset(unsigned arena_ind) { do_arena_reset_destroy("arena.0.reset", arena_ind); } static void do_arena_destroy(unsigned arena_ind) { do_arena_reset_destroy("arena.0.destroy", arena_ind); } TEST_BEGIN(test_arena_reset) { unsigned arena_ind; void **ptrs; unsigned nptrs; arena_ind = do_arena_create(NULL); do_arena_reset_pre(arena_ind, &ptrs, &nptrs); do_arena_reset(arena_ind); do_arena_reset_post(ptrs, nptrs, arena_ind); } TEST_END static bool arena_i_initialized(unsigned arena_ind, bool refresh) { bool initialized; size_t mib[3]; size_t miblen, sz; if (refresh) { uint64_t epoch = 1; expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); } miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.initialized", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; sz = sizeof(initialized); expect_d_eq(mallctlbymib(mib, miblen, (void *)&initialized, &sz, NULL, 0), 0, "Unexpected mallctlbymib() failure"); return initialized; } TEST_BEGIN(test_arena_destroy_initial) { expect_false(arena_i_initialized(MALLCTL_ARENAS_DESTROYED, false), "Destroyed arena stats should not be initialized"); } TEST_END TEST_BEGIN(test_arena_destroy_hooks_default) { unsigned arena_ind, arena_ind_another, arena_ind_prev; void **ptrs; unsigned nptrs; arena_ind = do_arena_create(NULL); do_arena_reset_pre(arena_ind, &ptrs, &nptrs); expect_false(arena_i_initialized(arena_ind, false), "Arena stats should not be initialized"); expect_true(arena_i_initialized(arena_ind, true), "Arena stats should be initialized"); /* * Create another arena before destroying one, to better verify arena * index reuse. */ arena_ind_another = do_arena_create(NULL); do_arena_destroy(arena_ind); expect_false(arena_i_initialized(arena_ind, true), "Arena stats should not be initialized"); expect_true(arena_i_initialized(MALLCTL_ARENAS_DESTROYED, false), "Destroyed arena stats should be initialized"); do_arena_reset_post(ptrs, nptrs, arena_ind); arena_ind_prev = arena_ind; arena_ind = do_arena_create(NULL); do_arena_reset_pre(arena_ind, &ptrs, &nptrs); expect_u_eq(arena_ind, arena_ind_prev, "Arena index should have been recycled"); do_arena_destroy(arena_ind); do_arena_reset_post(ptrs, nptrs, arena_ind); do_arena_destroy(arena_ind_another); /* Try arena.create with custom hooks. */ size_t sz = sizeof(extent_hooks_t *); extent_hooks_t *a0_default_hooks; expect_d_eq(mallctl("arena.0.extent_hooks", (void *)&a0_default_hooks, &sz, NULL, 0), 0, "Unexpected mallctlnametomib() failure"); /* Default impl; but wrapped as "customized". */ extent_hooks_t new_hooks = *a0_default_hooks; extent_hooks_t *hook = &new_hooks; sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, (void *)&hook, sizeof(void *)), 0, "Unexpected mallctl() failure"); do_arena_destroy(arena_ind); } TEST_END /* * Actually unmap extents, regardless of opt_retain, so that attempts to access * a destroyed arena's memory will segfault. */ static bool extent_dalloc_unmap(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind) { TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, committed=%s, " "arena_ind=%u)\n", __func__, extent_hooks, addr, size, committed ? "true" : "false", arena_ind); expect_ptr_eq(extent_hooks, &hooks, "extent_hooks should be same as pointer used to set hooks"); expect_ptr_eq(extent_hooks->dalloc, extent_dalloc_unmap, "Wrong hook function"); called_dalloc = true; if (!try_dalloc) { return true; } did_dalloc = true; if (!maps_coalesce && opt_retain) { return true; } pages_unmap(addr, size); return false; } static extent_hooks_t hooks_orig; static extent_hooks_t hooks_unmap = { extent_alloc_hook, extent_dalloc_unmap, /* dalloc */ extent_destroy_hook, extent_commit_hook, extent_decommit_hook, extent_purge_lazy_hook, extent_purge_forced_hook, extent_split_hook, extent_merge_hook }; TEST_BEGIN(test_arena_destroy_hooks_unmap) { unsigned arena_ind; void **ptrs; unsigned nptrs; extent_hooks_prep(); if (maps_coalesce) { try_decommit = false; } memcpy(&hooks_orig, &hooks, sizeof(extent_hooks_t)); memcpy(&hooks, &hooks_unmap, sizeof(extent_hooks_t)); did_alloc = false; arena_ind = do_arena_create(&hooks); do_arena_reset_pre(arena_ind, &ptrs, &nptrs); expect_true(did_alloc, "Expected alloc"); expect_false(arena_i_initialized(arena_ind, false), "Arena stats should not be initialized"); expect_true(arena_i_initialized(arena_ind, true), "Arena stats should be initialized"); did_dalloc = false; do_arena_destroy(arena_ind); expect_true(did_dalloc, "Expected dalloc"); expect_false(arena_i_initialized(arena_ind, true), "Arena stats should not be initialized"); expect_true(arena_i_initialized(MALLCTL_ARENAS_DESTROYED, false), "Destroyed arena stats should be initialized"); do_arena_reset_post(ptrs, nptrs, arena_ind); memcpy(&hooks, &hooks_orig, sizeof(extent_hooks_t)); } TEST_END int main(void) { return test( test_arena_reset, test_arena_destroy_initial, test_arena_destroy_hooks_default, test_arena_destroy_hooks_unmap); } redis-8.0.2/deps/jemalloc/test/unit/arena_reset_prof.c000066400000000000000000000001261501533116600227700ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define ARENA_RESET_PROF_C_ #include "arena_reset.c" redis-8.0.2/deps/jemalloc/test/unit/arena_reset_prof.sh000066400000000000000000000000731501533116600231610ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="prof:true,lg_prof_sample:0" redis-8.0.2/deps/jemalloc/test/unit/atomic.c000066400000000000000000000156711501533116600207410ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * We *almost* have consistent short names (e.g. "u32" for uint32_t, "b" for * bool, etc. The one exception is that the short name for void * is "p" in * some places and "ptr" in others. In the long run it would be nice to unify * these, but in the short run we'll use this shim. */ #define expect_p_eq expect_ptr_eq /* * t: the non-atomic type, like "uint32_t". * ta: the short name for the type, like "u32". * val[1,2,3]: Values of the given type. The CAS tests use val2 for expected, * and val3 for desired. */ #define DO_TESTS(t, ta, val1, val2, val3) do { \ t val; \ t expected; \ bool success; \ /* This (along with the load below) also tests ATOMIC_LOAD. */ \ atomic_##ta##_t atom = ATOMIC_INIT(val1); \ \ /* ATOMIC_INIT and load. */ \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, "Load or init failed"); \ \ /* Store. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ atomic_store_##ta(&atom, val2, ATOMIC_RELAXED); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val2, val, "Store failed"); \ \ /* Exchange. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_exchange_##ta(&atom, val2, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, "Exchange returned invalid value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val2, val, "Exchange store invalid value"); \ \ /* \ * Weak CAS. Spurious failures are allowed, so we loop a few \ * times. \ */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ success = false; \ for (int retry = 0; retry < 10 && !success; retry++) { \ expected = val2; \ success = atomic_compare_exchange_weak_##ta(&atom, \ &expected, val3, ATOMIC_RELAXED, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, expected, \ "CAS should update expected"); \ } \ expect_b_eq(val1 == val2, success, \ "Weak CAS did the wrong state update"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ if (success) { \ expect_##ta##_eq(val3, val, \ "Successful CAS should update atomic"); \ } else { \ expect_##ta##_eq(val1, val, \ "Unsuccessful CAS should not update atomic"); \ } \ \ /* Strong CAS. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ expected = val2; \ success = atomic_compare_exchange_strong_##ta(&atom, &expected, \ val3, ATOMIC_RELAXED, ATOMIC_RELAXED); \ expect_b_eq(val1 == val2, success, \ "Strong CAS did the wrong state update"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ if (success) { \ expect_##ta##_eq(val3, val, \ "Successful CAS should update atomic"); \ } else { \ expect_##ta##_eq(val1, val, \ "Unsuccessful CAS should not update atomic"); \ } \ \ \ } while (0) #define DO_INTEGER_TESTS(t, ta, val1, val2) do { \ atomic_##ta##_t atom; \ t val; \ \ /* Fetch-add. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_add_##ta(&atom, val2, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, \ "Fetch-add should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val1 + val2, val, \ "Fetch-add should update atomic"); \ \ /* Fetch-sub. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_sub_##ta(&atom, val2, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, \ "Fetch-sub should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val1 - val2, val, \ "Fetch-sub should update atomic"); \ \ /* Fetch-and. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_and_##ta(&atom, val2, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, \ "Fetch-and should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val1 & val2, val, \ "Fetch-and should update atomic"); \ \ /* Fetch-or. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_or_##ta(&atom, val2, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, \ "Fetch-or should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val1 | val2, val, \ "Fetch-or should update atomic"); \ \ /* Fetch-xor. */ \ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \ val = atomic_fetch_xor_##ta(&atom, val2, ATOMIC_RELAXED); \ expect_##ta##_eq(val1, val, \ "Fetch-xor should return previous value"); \ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \ expect_##ta##_eq(val1 ^ val2, val, \ "Fetch-xor should update atomic"); \ } while (0) #define TEST_STRUCT(t, ta) \ typedef struct { \ t val1; \ t val2; \ t val3; \ } ta##_test_t; #define TEST_CASES(t) { \ {(t)-1, (t)-1, (t)-2}, \ {(t)-1, (t) 0, (t)-2}, \ {(t)-1, (t) 1, (t)-2}, \ \ {(t) 0, (t)-1, (t)-2}, \ {(t) 0, (t) 0, (t)-2}, \ {(t) 0, (t) 1, (t)-2}, \ \ {(t) 1, (t)-1, (t)-2}, \ {(t) 1, (t) 0, (t)-2}, \ {(t) 1, (t) 1, (t)-2}, \ \ {(t)0, (t)-(1 << 22), (t)-2}, \ {(t)0, (t)(1 << 22), (t)-2}, \ {(t)(1 << 22), (t)-(1 << 22), (t)-2}, \ {(t)(1 << 22), (t)(1 << 22), (t)-2} \ } #define TEST_BODY(t, ta) do { \ const ta##_test_t tests[] = TEST_CASES(t); \ for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { \ ta##_test_t test = tests[i]; \ DO_TESTS(t, ta, test.val1, test.val2, test.val3); \ } \ } while (0) #define INTEGER_TEST_BODY(t, ta) do { \ const ta##_test_t tests[] = TEST_CASES(t); \ for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { \ ta##_test_t test = tests[i]; \ DO_TESTS(t, ta, test.val1, test.val2, test.val3); \ DO_INTEGER_TESTS(t, ta, test.val1, test.val2); \ } \ } while (0) TEST_STRUCT(uint64_t, u64); TEST_BEGIN(test_atomic_u64) { #if !(LG_SIZEOF_PTR == 3 || LG_SIZEOF_INT == 3) test_skip("64-bit atomic operations not supported"); #else INTEGER_TEST_BODY(uint64_t, u64); #endif } TEST_END TEST_STRUCT(uint32_t, u32); TEST_BEGIN(test_atomic_u32) { INTEGER_TEST_BODY(uint32_t, u32); } TEST_END TEST_STRUCT(void *, p); TEST_BEGIN(test_atomic_p) { TEST_BODY(void *, p); } TEST_END TEST_STRUCT(size_t, zu); TEST_BEGIN(test_atomic_zu) { INTEGER_TEST_BODY(size_t, zu); } TEST_END TEST_STRUCT(ssize_t, zd); TEST_BEGIN(test_atomic_zd) { INTEGER_TEST_BODY(ssize_t, zd); } TEST_END TEST_STRUCT(unsigned, u); TEST_BEGIN(test_atomic_u) { INTEGER_TEST_BODY(unsigned, u); } TEST_END int main(void) { return test( test_atomic_u64, test_atomic_u32, test_atomic_p, test_atomic_zu, test_atomic_zd, test_atomic_u); } redis-8.0.2/deps/jemalloc/test/unit/background_thread.c000066400000000000000000000061121501533116600231210ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/util.h" static void test_switch_background_thread_ctl(bool new_val) { bool e0, e1; size_t sz = sizeof(bool); e1 = new_val; expect_d_eq(mallctl("background_thread", (void *)&e0, &sz, &e1, sz), 0, "Unexpected mallctl() failure"); expect_b_eq(e0, !e1, "background_thread should be %d before.\n", !e1); if (e1) { expect_zu_gt(n_background_threads, 0, "Number of background threads should be non zero.\n"); } else { expect_zu_eq(n_background_threads, 0, "Number of background threads should be zero.\n"); } } static void test_repeat_background_thread_ctl(bool before) { bool e0, e1; size_t sz = sizeof(bool); e1 = before; expect_d_eq(mallctl("background_thread", (void *)&e0, &sz, &e1, sz), 0, "Unexpected mallctl() failure"); expect_b_eq(e0, before, "background_thread should be %d.\n", before); if (e1) { expect_zu_gt(n_background_threads, 0, "Number of background threads should be non zero.\n"); } else { expect_zu_eq(n_background_threads, 0, "Number of background threads should be zero.\n"); } } TEST_BEGIN(test_background_thread_ctl) { test_skip_if(!have_background_thread); bool e0, e1; size_t sz = sizeof(bool); expect_d_eq(mallctl("opt.background_thread", (void *)&e0, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctl("background_thread", (void *)&e1, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_b_eq(e0, e1, "Default and opt.background_thread does not match.\n"); if (e0) { test_switch_background_thread_ctl(false); } expect_zu_eq(n_background_threads, 0, "Number of background threads should be 0.\n"); for (unsigned i = 0; i < 4; i++) { test_switch_background_thread_ctl(true); test_repeat_background_thread_ctl(true); test_repeat_background_thread_ctl(true); test_switch_background_thread_ctl(false); test_repeat_background_thread_ctl(false); test_repeat_background_thread_ctl(false); } } TEST_END TEST_BEGIN(test_background_thread_running) { test_skip_if(!have_background_thread); test_skip_if(!config_stats); #if defined(JEMALLOC_BACKGROUND_THREAD) tsd_t *tsd = tsd_fetch(); background_thread_info_t *info = &background_thread_info[0]; test_repeat_background_thread_ctl(false); test_switch_background_thread_ctl(true); expect_b_eq(info->state, background_thread_started, "Background_thread did not start.\n"); nstime_t start; nstime_init_update(&start); bool ran = false; while (true) { malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); if (info->tot_n_runs > 0) { ran = true; } malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); if (ran) { break; } nstime_t now; nstime_init_update(&now); nstime_subtract(&now, &start); expect_u64_lt(nstime_sec(&now), 1000, "Background threads did not run for 1000 seconds."); sleep(1); } test_switch_background_thread_ctl(false); #endif } TEST_END int main(void) { /* Background_thread creation tests reentrancy naturally. */ return test_no_reentrancy( test_background_thread_ctl, test_background_thread_running); } redis-8.0.2/deps/jemalloc/test/unit/background_thread_enable.c000066400000000000000000000057341501533116600244400ustar00rootroot00000000000000#include "test/jemalloc_test.h" const char *malloc_conf = "background_thread:false,narenas:1,max_background_threads:20"; static unsigned max_test_narenas(void) { /* * 10 here is somewhat arbitrary, except insofar as we want to ensure * that the number of background threads is smaller than the number of * arenas. I'll ragequit long before we have to spin up 10 threads per * cpu to handle background purging, so this is a conservative * approximation. */ unsigned ret = 10 * ncpus; /* Limit the max to avoid VM exhaustion on 32-bit . */ if (ret > 512) { ret = 512; } return ret; } TEST_BEGIN(test_deferred) { test_skip_if(!have_background_thread); unsigned id; size_t sz_u = sizeof(unsigned); for (unsigned i = 0; i < max_test_narenas(); i++) { expect_d_eq(mallctl("arenas.create", &id, &sz_u, NULL, 0), 0, "Failed to create arena"); } bool enable = true; size_t sz_b = sizeof(bool); expect_d_eq(mallctl("background_thread", NULL, NULL, &enable, sz_b), 0, "Failed to enable background threads"); enable = false; expect_d_eq(mallctl("background_thread", NULL, NULL, &enable, sz_b), 0, "Failed to disable background threads"); } TEST_END TEST_BEGIN(test_max_background_threads) { test_skip_if(!have_background_thread); size_t max_n_thds; size_t opt_max_n_thds; size_t sz_m = sizeof(max_n_thds); expect_d_eq(mallctl("opt.max_background_threads", &opt_max_n_thds, &sz_m, NULL, 0), 0, "Failed to get opt.max_background_threads"); expect_d_eq(mallctl("max_background_threads", &max_n_thds, &sz_m, NULL, 0), 0, "Failed to get max background threads"); expect_zu_eq(opt_max_n_thds, max_n_thds, "max_background_threads and " "opt.max_background_threads should match"); expect_d_eq(mallctl("max_background_threads", NULL, NULL, &max_n_thds, sz_m), 0, "Failed to set max background threads"); unsigned id; size_t sz_u = sizeof(unsigned); for (unsigned i = 0; i < max_test_narenas(); i++) { expect_d_eq(mallctl("arenas.create", &id, &sz_u, NULL, 0), 0, "Failed to create arena"); } bool enable = true; size_t sz_b = sizeof(bool); expect_d_eq(mallctl("background_thread", NULL, NULL, &enable, sz_b), 0, "Failed to enable background threads"); expect_zu_eq(n_background_threads, max_n_thds, "Number of background threads should not change.\n"); size_t new_max_thds = max_n_thds - 1; if (new_max_thds > 0) { expect_d_eq(mallctl("max_background_threads", NULL, NULL, &new_max_thds, sz_m), 0, "Failed to set max background threads"); expect_zu_eq(n_background_threads, new_max_thds, "Number of background threads should decrease by 1.\n"); } new_max_thds = 1; expect_d_eq(mallctl("max_background_threads", NULL, NULL, &new_max_thds, sz_m), 0, "Failed to set max background threads"); expect_zu_eq(n_background_threads, new_max_thds, "Number of background threads should be 1.\n"); } TEST_END int main(void) { return test_no_reentrancy( test_deferred, test_max_background_threads); } redis-8.0.2/deps/jemalloc/test/unit/base.c000066400000000000000000000172141501533116600203720ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/extent_hooks.h" static extent_hooks_t hooks_null = { extent_alloc_hook, NULL, /* dalloc */ NULL, /* destroy */ NULL, /* commit */ NULL, /* decommit */ NULL, /* purge_lazy */ NULL, /* purge_forced */ NULL, /* split */ NULL /* merge */ }; static extent_hooks_t hooks_not_null = { extent_alloc_hook, extent_dalloc_hook, extent_destroy_hook, NULL, /* commit */ extent_decommit_hook, extent_purge_lazy_hook, extent_purge_forced_hook, NULL, /* split */ NULL /* merge */ }; TEST_BEGIN(test_base_hooks_default) { base_t *base; size_t allocated0, allocated1, resident, mapped, n_thp; tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); base = base_new(tsdn, 0, (extent_hooks_t *)&ehooks_default_extent_hooks, /* metadata_use_hooks */ true); if (config_stats) { base_stats_get(tsdn, base, &allocated0, &resident, &mapped, &n_thp); expect_zu_ge(allocated0, sizeof(base_t), "Base header should count as allocated"); if (opt_metadata_thp == metadata_thp_always) { expect_zu_gt(n_thp, 0, "Base should have 1 THP at least."); } } expect_ptr_not_null(base_alloc(tsdn, base, 42, 1), "Unexpected base_alloc() failure"); if (config_stats) { base_stats_get(tsdn, base, &allocated1, &resident, &mapped, &n_thp); expect_zu_ge(allocated1 - allocated0, 42, "At least 42 bytes were allocated by base_alloc()"); } base_delete(tsdn, base); } TEST_END TEST_BEGIN(test_base_hooks_null) { extent_hooks_t hooks_orig; base_t *base; size_t allocated0, allocated1, resident, mapped, n_thp; extent_hooks_prep(); try_dalloc = false; try_destroy = true; try_decommit = false; try_purge_lazy = false; try_purge_forced = false; memcpy(&hooks_orig, &hooks, sizeof(extent_hooks_t)); memcpy(&hooks, &hooks_null, sizeof(extent_hooks_t)); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); base = base_new(tsdn, 0, &hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new() failure"); if (config_stats) { base_stats_get(tsdn, base, &allocated0, &resident, &mapped, &n_thp); expect_zu_ge(allocated0, sizeof(base_t), "Base header should count as allocated"); if (opt_metadata_thp == metadata_thp_always) { expect_zu_gt(n_thp, 0, "Base should have 1 THP at least."); } } expect_ptr_not_null(base_alloc(tsdn, base, 42, 1), "Unexpected base_alloc() failure"); if (config_stats) { base_stats_get(tsdn, base, &allocated1, &resident, &mapped, &n_thp); expect_zu_ge(allocated1 - allocated0, 42, "At least 42 bytes were allocated by base_alloc()"); } base_delete(tsdn, base); memcpy(&hooks, &hooks_orig, sizeof(extent_hooks_t)); } TEST_END TEST_BEGIN(test_base_hooks_not_null) { extent_hooks_t hooks_orig; base_t *base; void *p, *q, *r, *r_exp; extent_hooks_prep(); try_dalloc = false; try_destroy = true; try_decommit = false; try_purge_lazy = false; try_purge_forced = false; memcpy(&hooks_orig, &hooks, sizeof(extent_hooks_t)); memcpy(&hooks, &hooks_not_null, sizeof(extent_hooks_t)); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); did_alloc = false; base = base_new(tsdn, 0, &hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new() failure"); expect_true(did_alloc, "Expected alloc"); /* * Check for tight packing at specified alignment under simple * conditions. */ { const size_t alignments[] = { 1, QUANTUM, QUANTUM << 1, CACHELINE, CACHELINE << 1, }; unsigned i; for (i = 0; i < sizeof(alignments) / sizeof(size_t); i++) { size_t alignment = alignments[i]; size_t align_ceil = ALIGNMENT_CEILING(alignment, QUANTUM); p = base_alloc(tsdn, base, 1, alignment); expect_ptr_not_null(p, "Unexpected base_alloc() failure"); expect_ptr_eq(p, (void *)(ALIGNMENT_CEILING((uintptr_t)p, alignment)), "Expected quantum alignment"); q = base_alloc(tsdn, base, alignment, alignment); expect_ptr_not_null(q, "Unexpected base_alloc() failure"); expect_ptr_eq((void *)((uintptr_t)p + align_ceil), q, "Minimal allocation should take up %zu bytes", align_ceil); r = base_alloc(tsdn, base, 1, alignment); expect_ptr_not_null(r, "Unexpected base_alloc() failure"); expect_ptr_eq((void *)((uintptr_t)q + align_ceil), r, "Minimal allocation should take up %zu bytes", align_ceil); } } /* * Allocate an object that cannot fit in the first block, then verify * that the first block's remaining space is considered for subsequent * allocation. */ expect_zu_ge(edata_bsize_get(&base->blocks->edata), QUANTUM, "Remainder insufficient for test"); /* Use up all but one quantum of block. */ while (edata_bsize_get(&base->blocks->edata) > QUANTUM) { p = base_alloc(tsdn, base, QUANTUM, QUANTUM); expect_ptr_not_null(p, "Unexpected base_alloc() failure"); } r_exp = edata_addr_get(&base->blocks->edata); expect_zu_eq(base->extent_sn_next, 1, "One extant block expected"); q = base_alloc(tsdn, base, QUANTUM + 1, QUANTUM); expect_ptr_not_null(q, "Unexpected base_alloc() failure"); expect_ptr_ne(q, r_exp, "Expected allocation from new block"); expect_zu_eq(base->extent_sn_next, 2, "Two extant blocks expected"); r = base_alloc(tsdn, base, QUANTUM, QUANTUM); expect_ptr_not_null(r, "Unexpected base_alloc() failure"); expect_ptr_eq(r, r_exp, "Expected allocation from first block"); expect_zu_eq(base->extent_sn_next, 2, "Two extant blocks expected"); /* * Check for proper alignment support when normal blocks are too small. */ { const size_t alignments[] = { HUGEPAGE, HUGEPAGE << 1 }; unsigned i; for (i = 0; i < sizeof(alignments) / sizeof(size_t); i++) { size_t alignment = alignments[i]; p = base_alloc(tsdn, base, QUANTUM, alignment); expect_ptr_not_null(p, "Unexpected base_alloc() failure"); expect_ptr_eq(p, (void *)(ALIGNMENT_CEILING((uintptr_t)p, alignment)), "Expected %zu-byte alignment", alignment); } } called_dalloc = called_destroy = called_decommit = called_purge_lazy = called_purge_forced = false; base_delete(tsdn, base); expect_true(called_dalloc, "Expected dalloc call"); expect_true(!called_destroy, "Unexpected destroy call"); expect_true(called_decommit, "Expected decommit call"); expect_true(called_purge_lazy, "Expected purge_lazy call"); expect_true(called_purge_forced, "Expected purge_forced call"); try_dalloc = true; try_destroy = true; try_decommit = true; try_purge_lazy = true; try_purge_forced = true; memcpy(&hooks, &hooks_orig, sizeof(extent_hooks_t)); } TEST_END TEST_BEGIN(test_base_ehooks_get_for_metadata_default_hook) { extent_hooks_prep(); memcpy(&hooks, &hooks_not_null, sizeof(extent_hooks_t)); base_t *base; tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); base = base_new(tsdn, 0, &hooks, /* metadata_use_hooks */ false); ehooks_t *ehooks = base_ehooks_get_for_metadata(base); expect_true(ehooks_are_default(ehooks), "Expected default extent hook functions pointer"); base_delete(tsdn, base); } TEST_END TEST_BEGIN(test_base_ehooks_get_for_metadata_custom_hook) { extent_hooks_prep(); memcpy(&hooks, &hooks_not_null, sizeof(extent_hooks_t)); base_t *base; tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); base = base_new(tsdn, 0, &hooks, /* metadata_use_hooks */ true); ehooks_t *ehooks = base_ehooks_get_for_metadata(base); expect_ptr_eq(&hooks, ehooks_get_extent_hooks_ptr(ehooks), "Expected user-specified extend hook functions pointer"); base_delete(tsdn, base); } TEST_END int main(void) { return test( test_base_hooks_default, test_base_hooks_null, test_base_hooks_not_null, test_base_ehooks_get_for_metadata_default_hook, test_base_ehooks_get_for_metadata_custom_hook); } redis-8.0.2/deps/jemalloc/test/unit/batch_alloc.c000066400000000000000000000111261501533116600217070ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define BATCH_MAX ((1U << 16) + 1024) static void *global_ptrs[BATCH_MAX]; #define PAGE_ALIGNED(ptr) (((uintptr_t)ptr & PAGE_MASK) == 0) static void verify_batch_basic(tsd_t *tsd, void **ptrs, size_t batch, size_t usize, bool zero) { for (size_t i = 0; i < batch; ++i) { void *p = ptrs[i]; expect_zu_eq(isalloc(tsd_tsdn(tsd), p), usize, ""); if (zero) { for (size_t k = 0; k < usize; ++k) { expect_true(*((unsigned char *)p + k) == 0, ""); } } } } static void verify_batch_locality(tsd_t *tsd, void **ptrs, size_t batch, size_t usize, arena_t *arena, unsigned nregs) { if (config_prof && opt_prof) { /* * Checking batch locality when prof is on is feasible but * complicated, while checking the non-prof case suffices for * unit-test purpose. */ return; } for (size_t i = 0, j = 0; i < batch; ++i, ++j) { if (j == nregs) { j = 0; } if (j == 0 && batch - i < nregs) { break; } void *p = ptrs[i]; expect_ptr_eq(iaalloc(tsd_tsdn(tsd), p), arena, ""); if (j == 0) { expect_true(PAGE_ALIGNED(p), ""); continue; } assert(i > 0); void *q = ptrs[i - 1]; expect_true((uintptr_t)p > (uintptr_t)q && (size_t)((uintptr_t)p - (uintptr_t)q) == usize, ""); } } static void release_batch(void **ptrs, size_t batch, size_t size) { for (size_t i = 0; i < batch; ++i) { sdallocx(ptrs[i], size, 0); } } typedef struct batch_alloc_packet_s batch_alloc_packet_t; struct batch_alloc_packet_s { void **ptrs; size_t num; size_t size; int flags; }; static size_t batch_alloc_wrapper(void **ptrs, size_t num, size_t size, int flags) { batch_alloc_packet_t batch_alloc_packet = {ptrs, num, size, flags}; size_t filled; size_t len = sizeof(size_t); assert_d_eq(mallctl("experimental.batch_alloc", &filled, &len, &batch_alloc_packet, sizeof(batch_alloc_packet)), 0, ""); return filled; } static void test_wrapper(size_t size, size_t alignment, bool zero, unsigned arena_flag) { tsd_t *tsd = tsd_fetch(); assert(tsd != NULL); const size_t usize = (alignment != 0 ? sz_sa2u(size, alignment) : sz_s2u(size)); const szind_t ind = sz_size2index(usize); const bin_info_t *bin_info = &bin_infos[ind]; const unsigned nregs = bin_info->nregs; assert(nregs > 0); arena_t *arena; if (arena_flag != 0) { arena = arena_get(tsd_tsdn(tsd), MALLOCX_ARENA_GET(arena_flag), false); } else { arena = arena_choose(tsd, NULL); } assert(arena != NULL); int flags = arena_flag; if (alignment != 0) { flags |= MALLOCX_ALIGN(alignment); } if (zero) { flags |= MALLOCX_ZERO; } /* * Allocate for the purpose of bootstrapping arena_tdata, so that the * change in bin stats won't contaminate the stats to be verified below. */ void *p = mallocx(size, flags | MALLOCX_TCACHE_NONE); for (size_t i = 0; i < 4; ++i) { size_t base = 0; if (i == 1) { base = nregs; } else if (i == 2) { base = nregs * 2; } else if (i == 3) { base = (1 << 16); } for (int j = -1; j <= 1; ++j) { if (base == 0 && j == -1) { continue; } size_t batch = base + (size_t)j; assert(batch < BATCH_MAX); size_t filled = batch_alloc_wrapper(global_ptrs, batch, size, flags); assert_zu_eq(filled, batch, ""); verify_batch_basic(tsd, global_ptrs, batch, usize, zero); verify_batch_locality(tsd, global_ptrs, batch, usize, arena, nregs); release_batch(global_ptrs, batch, usize); } } free(p); } TEST_BEGIN(test_batch_alloc) { test_wrapper(11, 0, false, 0); } TEST_END TEST_BEGIN(test_batch_alloc_zero) { test_wrapper(11, 0, true, 0); } TEST_END TEST_BEGIN(test_batch_alloc_aligned) { test_wrapper(7, 16, false, 0); } TEST_END TEST_BEGIN(test_batch_alloc_manual_arena) { unsigned arena_ind; size_t len_unsigned = sizeof(unsigned); assert_d_eq(mallctl("arenas.create", &arena_ind, &len_unsigned, NULL, 0), 0, ""); test_wrapper(11, 0, false, MALLOCX_ARENA(arena_ind)); } TEST_END TEST_BEGIN(test_batch_alloc_large) { size_t size = SC_LARGE_MINCLASS; for (size_t batch = 0; batch < 4; ++batch) { assert(batch < BATCH_MAX); size_t filled = batch_alloc(global_ptrs, batch, size, 0); assert_zu_eq(filled, batch, ""); release_batch(global_ptrs, batch, size); } size = tcache_maxclass + 1; for (size_t batch = 0; batch < 4; ++batch) { assert(batch < BATCH_MAX); size_t filled = batch_alloc(global_ptrs, batch, size, 0); assert_zu_eq(filled, batch, ""); release_batch(global_ptrs, batch, size); } } TEST_END int main(void) { return test( test_batch_alloc, test_batch_alloc_zero, test_batch_alloc_aligned, test_batch_alloc_manual_arena, test_batch_alloc_large); } redis-8.0.2/deps/jemalloc/test/unit/batch_alloc.sh000066400000000000000000000001001501533116600220650ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="tcache_gc_incr_bytes:2147483648" redis-8.0.2/deps/jemalloc/test/unit/batch_alloc_prof.c000066400000000000000000000000311501533116600227260ustar00rootroot00000000000000#include "batch_alloc.c" redis-8.0.2/deps/jemalloc/test/unit/batch_alloc_prof.sh000066400000000000000000000000741501533116600231250ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="prof:true,lg_prof_sample:14" redis-8.0.2/deps/jemalloc/test/unit/binshard.c000066400000000000000000000073261501533116600212550ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* Config -- "narenas:1,bin_shards:1-160:16|129-512:4|256-256:8" */ #define NTHREADS 16 #define REMOTE_NALLOC 256 static void * thd_producer(void *varg) { void **mem = varg; unsigned arena, i; size_t sz; sz = sizeof(arena); /* Remote arena. */ expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); for (i = 0; i < REMOTE_NALLOC / 2; i++) { mem[i] = mallocx(1, MALLOCX_TCACHE_NONE | MALLOCX_ARENA(arena)); } /* Remote bin. */ for (; i < REMOTE_NALLOC; i++) { mem[i] = mallocx(1, MALLOCX_TCACHE_NONE | MALLOCX_ARENA(0)); } return NULL; } TEST_BEGIN(test_producer_consumer) { thd_t thds[NTHREADS]; void *mem[NTHREADS][REMOTE_NALLOC]; unsigned i; /* Create producer threads to allocate. */ for (i = 0; i < NTHREADS; i++) { thd_create(&thds[i], thd_producer, mem[i]); } for (i = 0; i < NTHREADS; i++) { thd_join(thds[i], NULL); } /* Remote deallocation by the current thread. */ for (i = 0; i < NTHREADS; i++) { for (unsigned j = 0; j < REMOTE_NALLOC; j++) { expect_ptr_not_null(mem[i][j], "Unexpected remote allocation failure"); dallocx(mem[i][j], 0); } } } TEST_END static void * thd_start(void *varg) { void *ptr, *ptr2; edata_t *edata; unsigned shard1, shard2; tsdn_t *tsdn = tsdn_fetch(); /* Try triggering allocations from sharded bins. */ for (unsigned i = 0; i < 1024; i++) { ptr = mallocx(1, MALLOCX_TCACHE_NONE); ptr2 = mallocx(129, MALLOCX_TCACHE_NONE); edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); shard1 = edata_binshard_get(edata); dallocx(ptr, 0); expect_u_lt(shard1, 16, "Unexpected bin shard used"); edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr2); shard2 = edata_binshard_get(edata); dallocx(ptr2, 0); expect_u_lt(shard2, 4, "Unexpected bin shard used"); if (shard1 > 0 || shard2 > 0) { /* Triggered sharded bin usage. */ return (void *)(uintptr_t)shard1; } } return NULL; } TEST_BEGIN(test_bin_shard_mt) { test_skip_if(have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)); thd_t thds[NTHREADS]; unsigned i; for (i = 0; i < NTHREADS; i++) { thd_create(&thds[i], thd_start, NULL); } bool sharded = false; for (i = 0; i < NTHREADS; i++) { void *ret; thd_join(thds[i], &ret); if (ret != NULL) { sharded = true; } } expect_b_eq(sharded, true, "Did not find sharded bins"); } TEST_END TEST_BEGIN(test_bin_shard) { unsigned nbins, i; size_t mib[4], mib2[4]; size_t miblen, miblen2, len; len = sizeof(nbins); expect_d_eq(mallctl("arenas.nbins", (void *)&nbins, &len, NULL, 0), 0, "Unexpected mallctl() failure"); miblen = 4; expect_d_eq(mallctlnametomib("arenas.bin.0.nshards", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); miblen2 = 4; expect_d_eq(mallctlnametomib("arenas.bin.0.size", mib2, &miblen2), 0, "Unexpected mallctlnametomib() failure"); for (i = 0; i < nbins; i++) { uint32_t nshards; size_t size, sz1, sz2; mib[2] = i; sz1 = sizeof(nshards); expect_d_eq(mallctlbymib(mib, miblen, (void *)&nshards, &sz1, NULL, 0), 0, "Unexpected mallctlbymib() failure"); mib2[2] = i; sz2 = sizeof(size); expect_d_eq(mallctlbymib(mib2, miblen2, (void *)&size, &sz2, NULL, 0), 0, "Unexpected mallctlbymib() failure"); if (size >= 1 && size <= 128) { expect_u_eq(nshards, 16, "Unexpected nshards"); } else if (size == 256) { expect_u_eq(nshards, 8, "Unexpected nshards"); } else if (size > 128 && size <= 512) { expect_u_eq(nshards, 4, "Unexpected nshards"); } else { expect_u_eq(nshards, 1, "Unexpected nshards"); } } } TEST_END int main(void) { return test_no_reentrancy( test_bin_shard, test_bin_shard_mt, test_producer_consumer); } redis-8.0.2/deps/jemalloc/test/unit/binshard.sh000066400000000000000000000001221501533116600214300ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="narenas:1,bin_shards:1-160:16|129-512:4|256-256:8" redis-8.0.2/deps/jemalloc/test/unit/bit_util.c000066400000000000000000000156461501533116600213020ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/bit_util.h" #define TEST_POW2_CEIL(t, suf, pri) do { \ unsigned i, pow2; \ t x; \ \ expect_##suf##_eq(pow2_ceil_##suf(0), 0, "Unexpected result"); \ \ for (i = 0; i < sizeof(t) * 8; i++) { \ expect_##suf##_eq(pow2_ceil_##suf(((t)1) << i), ((t)1) \ << i, "Unexpected result"); \ } \ \ for (i = 2; i < sizeof(t) * 8; i++) { \ expect_##suf##_eq(pow2_ceil_##suf((((t)1) << i) - 1), \ ((t)1) << i, "Unexpected result"); \ } \ \ for (i = 0; i < sizeof(t) * 8 - 1; i++) { \ expect_##suf##_eq(pow2_ceil_##suf((((t)1) << i) + 1), \ ((t)1) << (i+1), "Unexpected result"); \ } \ \ for (pow2 = 1; pow2 < 25; pow2++) { \ for (x = (((t)1) << (pow2-1)) + 1; x <= ((t)1) << pow2; \ x++) { \ expect_##suf##_eq(pow2_ceil_##suf(x), \ ((t)1) << pow2, \ "Unexpected result, x=%"pri, x); \ } \ } \ } while (0) TEST_BEGIN(test_pow2_ceil_u64) { TEST_POW2_CEIL(uint64_t, u64, FMTu64); } TEST_END TEST_BEGIN(test_pow2_ceil_u32) { TEST_POW2_CEIL(uint32_t, u32, FMTu32); } TEST_END TEST_BEGIN(test_pow2_ceil_zu) { TEST_POW2_CEIL(size_t, zu, "zu"); } TEST_END void expect_lg_ceil_range(size_t input, unsigned answer) { if (input == 1) { expect_u_eq(0, answer, "Got %u as lg_ceil of 1", answer); return; } expect_zu_le(input, (ZU(1) << answer), "Got %u as lg_ceil of %zu", answer, input); expect_zu_gt(input, (ZU(1) << (answer - 1)), "Got %u as lg_ceil of %zu", answer, input); } void expect_lg_floor_range(size_t input, unsigned answer) { if (input == 1) { expect_u_eq(0, answer, "Got %u as lg_floor of 1", answer); return; } expect_zu_ge(input, (ZU(1) << answer), "Got %u as lg_floor of %zu", answer, input); expect_zu_lt(input, (ZU(1) << (answer + 1)), "Got %u as lg_floor of %zu", answer, input); } TEST_BEGIN(test_lg_ceil_floor) { for (size_t i = 1; i < 10 * 1000 * 1000; i++) { expect_lg_ceil_range(i, lg_ceil(i)); expect_lg_ceil_range(i, LG_CEIL(i)); expect_lg_floor_range(i, lg_floor(i)); expect_lg_floor_range(i, LG_FLOOR(i)); } for (int i = 10; i < 8 * (1 << LG_SIZEOF_PTR) - 5; i++) { for (size_t j = 0; j < (1 << 4); j++) { size_t num1 = ((size_t)1 << i) - j * ((size_t)1 << (i - 4)); size_t num2 = ((size_t)1 << i) + j * ((size_t)1 << (i - 4)); expect_zu_ne(num1, 0, "Invalid lg argument"); expect_zu_ne(num2, 0, "Invalid lg argument"); expect_lg_ceil_range(num1, lg_ceil(num1)); expect_lg_ceil_range(num1, LG_CEIL(num1)); expect_lg_ceil_range(num2, lg_ceil(num2)); expect_lg_ceil_range(num2, LG_CEIL(num2)); expect_lg_floor_range(num1, lg_floor(num1)); expect_lg_floor_range(num1, LG_FLOOR(num1)); expect_lg_floor_range(num2, lg_floor(num2)); expect_lg_floor_range(num2, LG_FLOOR(num2)); } } } TEST_END #define TEST_FFS(t, suf, test_suf, pri) do { \ for (unsigned i = 0; i < sizeof(t) * 8; i++) { \ for (unsigned j = 0; j <= i; j++) { \ for (unsigned k = 0; k <= j; k++) { \ t x = (t)1 << i; \ x |= (t)1 << j; \ x |= (t)1 << k; \ expect_##test_suf##_eq(ffs_##suf(x), k, \ "Unexpected result, x=%"pri, x); \ } \ } \ } \ } while(0) TEST_BEGIN(test_ffs_u) { TEST_FFS(unsigned, u, u,"u"); } TEST_END TEST_BEGIN(test_ffs_lu) { TEST_FFS(unsigned long, lu, lu, "lu"); } TEST_END TEST_BEGIN(test_ffs_llu) { TEST_FFS(unsigned long long, llu, qd, "llu"); } TEST_END TEST_BEGIN(test_ffs_u32) { TEST_FFS(uint32_t, u32, u32, FMTu32); } TEST_END TEST_BEGIN(test_ffs_u64) { TEST_FFS(uint64_t, u64, u64, FMTu64); } TEST_END TEST_BEGIN(test_ffs_zu) { TEST_FFS(size_t, zu, zu, "zu"); } TEST_END #define TEST_FLS(t, suf, test_suf, pri) do { \ for (unsigned i = 0; i < sizeof(t) * 8; i++) { \ for (unsigned j = 0; j <= i; j++) { \ for (unsigned k = 0; k <= j; k++) { \ t x = (t)1 << i; \ x |= (t)1 << j; \ x |= (t)1 << k; \ expect_##test_suf##_eq(fls_##suf(x), i, \ "Unexpected result, x=%"pri, x); \ } \ } \ } \ } while(0) TEST_BEGIN(test_fls_u) { TEST_FLS(unsigned, u, u,"u"); } TEST_END TEST_BEGIN(test_fls_lu) { TEST_FLS(unsigned long, lu, lu, "lu"); } TEST_END TEST_BEGIN(test_fls_llu) { TEST_FLS(unsigned long long, llu, qd, "llu"); } TEST_END TEST_BEGIN(test_fls_u32) { TEST_FLS(uint32_t, u32, u32, FMTu32); } TEST_END TEST_BEGIN(test_fls_u64) { TEST_FLS(uint64_t, u64, u64, FMTu64); } TEST_END TEST_BEGIN(test_fls_zu) { TEST_FLS(size_t, zu, zu, "zu"); } TEST_END TEST_BEGIN(test_fls_u_slow) { TEST_FLS(unsigned, u_slow, u,"u"); } TEST_END TEST_BEGIN(test_fls_lu_slow) { TEST_FLS(unsigned long, lu_slow, lu, "lu"); } TEST_END TEST_BEGIN(test_fls_llu_slow) { TEST_FLS(unsigned long long, llu_slow, qd, "llu"); } TEST_END static unsigned popcount_byte(unsigned byte) { int count = 0; for (int i = 0; i < 8; i++) { if ((byte & (1 << i)) != 0) { count++; } } return count; } static uint64_t expand_byte_to_mask(unsigned byte) { uint64_t result = 0; for (int i = 0; i < 8; i++) { if ((byte & (1 << i)) != 0) { result |= ((uint64_t)0xFF << (i * 8)); } } return result; } #define TEST_POPCOUNT(t, suf, pri_hex) do { \ t bmul = (t)0x0101010101010101ULL; \ for (unsigned i = 0; i < (1 << sizeof(t)); i++) { \ for (unsigned j = 0; j < 256; j++) { \ /* \ * Replicate the byte j into various \ * bytes of the integer (as indicated by the \ * mask in i), and ensure that the popcount of \ * the result is popcount(i) * popcount(j) \ */ \ t mask = (t)expand_byte_to_mask(i); \ t x = (bmul * j) & mask; \ expect_u_eq( \ popcount_byte(i) * popcount_byte(j), \ popcount_##suf(x), \ "Unexpected result, x=0x%"pri_hex, x); \ } \ } \ } while (0) TEST_BEGIN(test_popcount_u) { TEST_POPCOUNT(unsigned, u, "x"); } TEST_END TEST_BEGIN(test_popcount_u_slow) { TEST_POPCOUNT(unsigned, u_slow, "x"); } TEST_END TEST_BEGIN(test_popcount_lu) { TEST_POPCOUNT(unsigned long, lu, "lx"); } TEST_END TEST_BEGIN(test_popcount_lu_slow) { TEST_POPCOUNT(unsigned long, lu_slow, "lx"); } TEST_END TEST_BEGIN(test_popcount_llu) { TEST_POPCOUNT(unsigned long long, llu, "llx"); } TEST_END TEST_BEGIN(test_popcount_llu_slow) { TEST_POPCOUNT(unsigned long long, llu_slow, "llx"); } TEST_END int main(void) { return test_no_reentrancy( test_pow2_ceil_u64, test_pow2_ceil_u32, test_pow2_ceil_zu, test_lg_ceil_floor, test_ffs_u, test_ffs_lu, test_ffs_llu, test_ffs_u32, test_ffs_u64, test_ffs_zu, test_fls_u, test_fls_lu, test_fls_llu, test_fls_u32, test_fls_u64, test_fls_zu, test_fls_u_slow, test_fls_lu_slow, test_fls_llu_slow, test_popcount_u, test_popcount_u_slow, test_popcount_lu, test_popcount_lu_slow, test_popcount_llu, test_popcount_llu_slow); } redis-8.0.2/deps/jemalloc/test/unit/bitmap.c000066400000000000000000000237401501533116600207350ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/nbits.h" static void test_bitmap_initializer_body(const bitmap_info_t *binfo, size_t nbits) { bitmap_info_t binfo_dyn; bitmap_info_init(&binfo_dyn, nbits); expect_zu_eq(bitmap_size(binfo), bitmap_size(&binfo_dyn), "Unexpected difference between static and dynamic initialization, " "nbits=%zu", nbits); expect_zu_eq(binfo->nbits, binfo_dyn.nbits, "Unexpected difference between static and dynamic initialization, " "nbits=%zu", nbits); #ifdef BITMAP_USE_TREE expect_u_eq(binfo->nlevels, binfo_dyn.nlevels, "Unexpected difference between static and dynamic initialization, " "nbits=%zu", nbits); { unsigned i; for (i = 0; i < binfo->nlevels; i++) { expect_zu_eq(binfo->levels[i].group_offset, binfo_dyn.levels[i].group_offset, "Unexpected difference between static and dynamic " "initialization, nbits=%zu, level=%u", nbits, i); } } #else expect_zu_eq(binfo->ngroups, binfo_dyn.ngroups, "Unexpected difference between static and dynamic initialization"); #endif } TEST_BEGIN(test_bitmap_initializer) { #define NB(nbits) { \ if (nbits <= BITMAP_MAXBITS) { \ bitmap_info_t binfo = \ BITMAP_INFO_INITIALIZER(nbits); \ test_bitmap_initializer_body(&binfo, nbits); \ } \ } NBITS_TAB #undef NB } TEST_END static size_t test_bitmap_size_body(const bitmap_info_t *binfo, size_t nbits, size_t prev_size) { size_t size = bitmap_size(binfo); expect_zu_ge(size, (nbits >> 3), "Bitmap size is smaller than expected"); expect_zu_ge(size, prev_size, "Bitmap size is smaller than expected"); return size; } TEST_BEGIN(test_bitmap_size) { size_t nbits, prev_size; prev_size = 0; for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) { bitmap_info_t binfo; bitmap_info_init(&binfo, nbits); prev_size = test_bitmap_size_body(&binfo, nbits, prev_size); } #define NB(nbits) { \ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \ prev_size = test_bitmap_size_body(&binfo, nbits, \ prev_size); \ } prev_size = 0; NBITS_TAB #undef NB } TEST_END static void test_bitmap_init_body(const bitmap_info_t *binfo, size_t nbits) { size_t i; bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo)); expect_ptr_not_null(bitmap, "Unexpected malloc() failure"); bitmap_init(bitmap, binfo, false); for (i = 0; i < nbits; i++) { expect_false(bitmap_get(bitmap, binfo, i), "Bit should be unset"); } bitmap_init(bitmap, binfo, true); for (i = 0; i < nbits; i++) { expect_true(bitmap_get(bitmap, binfo, i), "Bit should be set"); } free(bitmap); } TEST_BEGIN(test_bitmap_init) { size_t nbits; for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) { bitmap_info_t binfo; bitmap_info_init(&binfo, nbits); test_bitmap_init_body(&binfo, nbits); } #define NB(nbits) { \ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \ test_bitmap_init_body(&binfo, nbits); \ } NBITS_TAB #undef NB } TEST_END static void test_bitmap_set_body(const bitmap_info_t *binfo, size_t nbits) { size_t i; bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo)); expect_ptr_not_null(bitmap, "Unexpected malloc() failure"); bitmap_init(bitmap, binfo, false); for (i = 0; i < nbits; i++) { bitmap_set(bitmap, binfo, i); } expect_true(bitmap_full(bitmap, binfo), "All bits should be set"); free(bitmap); } TEST_BEGIN(test_bitmap_set) { size_t nbits; for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) { bitmap_info_t binfo; bitmap_info_init(&binfo, nbits); test_bitmap_set_body(&binfo, nbits); } #define NB(nbits) { \ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \ test_bitmap_set_body(&binfo, nbits); \ } NBITS_TAB #undef NB } TEST_END static void test_bitmap_unset_body(const bitmap_info_t *binfo, size_t nbits) { size_t i; bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo)); expect_ptr_not_null(bitmap, "Unexpected malloc() failure"); bitmap_init(bitmap, binfo, false); for (i = 0; i < nbits; i++) { bitmap_set(bitmap, binfo, i); } expect_true(bitmap_full(bitmap, binfo), "All bits should be set"); for (i = 0; i < nbits; i++) { bitmap_unset(bitmap, binfo, i); } for (i = 0; i < nbits; i++) { bitmap_set(bitmap, binfo, i); } expect_true(bitmap_full(bitmap, binfo), "All bits should be set"); free(bitmap); } TEST_BEGIN(test_bitmap_unset) { size_t nbits; for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) { bitmap_info_t binfo; bitmap_info_init(&binfo, nbits); test_bitmap_unset_body(&binfo, nbits); } #define NB(nbits) { \ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \ test_bitmap_unset_body(&binfo, nbits); \ } NBITS_TAB #undef NB } TEST_END static void test_bitmap_xfu_body(const bitmap_info_t *binfo, size_t nbits) { bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo)); expect_ptr_not_null(bitmap, "Unexpected malloc() failure"); bitmap_init(bitmap, binfo, false); /* Iteratively set bits starting at the beginning. */ for (size_t i = 0; i < nbits; i++) { expect_zu_eq(bitmap_ffu(bitmap, binfo, 0), i, "First unset bit should be just after previous first unset " "bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, (i > 0) ? i-1 : i), i, "First unset bit should be just after previous first unset " "bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, i), i, "First unset bit should be just after previous first unset " "bit"); expect_zu_eq(bitmap_sfu(bitmap, binfo), i, "First unset bit should be just after previous first unset " "bit"); } expect_true(bitmap_full(bitmap, binfo), "All bits should be set"); /* * Iteratively unset bits starting at the end, and verify that * bitmap_sfu() reaches the unset bits. */ for (size_t i = nbits - 1; i < nbits; i--) { /* (nbits..0] */ bitmap_unset(bitmap, binfo, i); expect_zu_eq(bitmap_ffu(bitmap, binfo, 0), i, "First unset bit should the bit previously unset"); expect_zu_eq(bitmap_ffu(bitmap, binfo, (i > 0) ? i-1 : i), i, "First unset bit should the bit previously unset"); expect_zu_eq(bitmap_ffu(bitmap, binfo, i), i, "First unset bit should the bit previously unset"); expect_zu_eq(bitmap_sfu(bitmap, binfo), i, "First unset bit should the bit previously unset"); bitmap_unset(bitmap, binfo, i); } expect_false(bitmap_get(bitmap, binfo, 0), "Bit should be unset"); /* * Iteratively set bits starting at the beginning, and verify that * bitmap_sfu() looks past them. */ for (size_t i = 1; i < nbits; i++) { bitmap_set(bitmap, binfo, i - 1); expect_zu_eq(bitmap_ffu(bitmap, binfo, 0), i, "First unset bit should be just after the bit previously " "set"); expect_zu_eq(bitmap_ffu(bitmap, binfo, (i > 0) ? i-1 : i), i, "First unset bit should be just after the bit previously " "set"); expect_zu_eq(bitmap_ffu(bitmap, binfo, i), i, "First unset bit should be just after the bit previously " "set"); expect_zu_eq(bitmap_sfu(bitmap, binfo), i, "First unset bit should be just after the bit previously " "set"); bitmap_unset(bitmap, binfo, i); } expect_zu_eq(bitmap_ffu(bitmap, binfo, 0), nbits - 1, "First unset bit should be the last bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, (nbits > 1) ? nbits-2 : nbits-1), nbits - 1, "First unset bit should be the last bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, nbits - 1), nbits - 1, "First unset bit should be the last bit"); expect_zu_eq(bitmap_sfu(bitmap, binfo), nbits - 1, "First unset bit should be the last bit"); expect_true(bitmap_full(bitmap, binfo), "All bits should be set"); /* * Bubble a "usu" pattern through the bitmap and verify that * bitmap_ffu() finds the correct bit for all five min_bit cases. */ if (nbits >= 3) { for (size_t i = 0; i < nbits-2; i++) { bitmap_unset(bitmap, binfo, i); bitmap_unset(bitmap, binfo, i+2); if (i > 0) { expect_zu_eq(bitmap_ffu(bitmap, binfo, i-1), i, "Unexpected first unset bit"); } expect_zu_eq(bitmap_ffu(bitmap, binfo, i), i, "Unexpected first unset bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, i+1), i+2, "Unexpected first unset bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, i+2), i+2, "Unexpected first unset bit"); if (i + 3 < nbits) { expect_zu_eq(bitmap_ffu(bitmap, binfo, i+3), nbits, "Unexpected first unset bit"); } expect_zu_eq(bitmap_sfu(bitmap, binfo), i, "Unexpected first unset bit"); expect_zu_eq(bitmap_sfu(bitmap, binfo), i+2, "Unexpected first unset bit"); } } /* * Unset the last bit, bubble another unset bit through the bitmap, and * verify that bitmap_ffu() finds the correct bit for all four min_bit * cases. */ if (nbits >= 3) { bitmap_unset(bitmap, binfo, nbits-1); for (size_t i = 0; i < nbits-1; i++) { bitmap_unset(bitmap, binfo, i); if (i > 0) { expect_zu_eq(bitmap_ffu(bitmap, binfo, i-1), i, "Unexpected first unset bit"); } expect_zu_eq(bitmap_ffu(bitmap, binfo, i), i, "Unexpected first unset bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, i+1), nbits-1, "Unexpected first unset bit"); expect_zu_eq(bitmap_ffu(bitmap, binfo, nbits-1), nbits-1, "Unexpected first unset bit"); expect_zu_eq(bitmap_sfu(bitmap, binfo), i, "Unexpected first unset bit"); } expect_zu_eq(bitmap_sfu(bitmap, binfo), nbits-1, "Unexpected first unset bit"); } free(bitmap); } TEST_BEGIN(test_bitmap_xfu) { size_t nbits, nbits_max; /* The test is O(n^2); large page sizes may slow down too much. */ nbits_max = BITMAP_MAXBITS > 512 ? 512 : BITMAP_MAXBITS; for (nbits = 1; nbits <= nbits_max; nbits++) { bitmap_info_t binfo; bitmap_info_init(&binfo, nbits); test_bitmap_xfu_body(&binfo, nbits); } #define NB(nbits) { \ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \ test_bitmap_xfu_body(&binfo, nbits); \ } NBITS_TAB #undef NB } TEST_END int main(void) { return test( test_bitmap_initializer, test_bitmap_size, test_bitmap_init, test_bitmap_set, test_bitmap_unset, test_bitmap_xfu); } redis-8.0.2/deps/jemalloc/test/unit/buf_writer.c000066400000000000000000000131751501533116600216320ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/buf_writer.h" #define TEST_BUF_SIZE 16 #define UNIT_MAX (TEST_BUF_SIZE * 3) static size_t test_write_len; static char test_buf[TEST_BUF_SIZE]; static uint64_t arg; static uint64_t arg_store; static void test_write_cb(void *cbopaque, const char *s) { size_t prev_test_write_len = test_write_len; test_write_len += strlen(s); /* only increase the length */ arg_store = *(uint64_t *)cbopaque; /* only pass along the argument */ assert_zu_le(prev_test_write_len, test_write_len, "Test write overflowed"); } static void test_buf_writer_body(tsdn_t *tsdn, buf_writer_t *buf_writer) { char s[UNIT_MAX + 1]; size_t n_unit, remain, i; ssize_t unit; assert(buf_writer->buf != NULL); memset(s, 'a', UNIT_MAX); arg = 4; /* Starting value of random argument. */ arg_store = arg; for (unit = UNIT_MAX; unit >= 0; --unit) { /* unit keeps decreasing, so strlen(s) is always unit. */ s[unit] = '\0'; for (n_unit = 1; n_unit <= 3; ++n_unit) { test_write_len = 0; remain = 0; for (i = 1; i <= n_unit; ++i) { arg = prng_lg_range_u64(&arg, 64); buf_writer_cb(buf_writer, s); remain += unit; if (remain > buf_writer->buf_size) { /* Flushes should have happened. */ assert_u64_eq(arg_store, arg, "Call " "back argument didn't get through"); remain %= buf_writer->buf_size; if (remain == 0) { /* Last flush should be lazy. */ remain += buf_writer->buf_size; } } assert_zu_eq(test_write_len + remain, i * unit, "Incorrect length after writing %zu strings" " of length %zu", i, unit); } buf_writer_flush(buf_writer); expect_zu_eq(test_write_len, n_unit * unit, "Incorrect length after flushing at the end of" " writing %zu strings of length %zu", n_unit, unit); } } buf_writer_terminate(tsdn, buf_writer); } TEST_BEGIN(test_buf_write_static) { buf_writer_t buf_writer; tsdn_t *tsdn = tsdn_fetch(); assert_false(buf_writer_init(tsdn, &buf_writer, test_write_cb, &arg, test_buf, TEST_BUF_SIZE), "buf_writer_init() should not encounter error on static buffer"); test_buf_writer_body(tsdn, &buf_writer); } TEST_END TEST_BEGIN(test_buf_write_dynamic) { buf_writer_t buf_writer; tsdn_t *tsdn = tsdn_fetch(); assert_false(buf_writer_init(tsdn, &buf_writer, test_write_cb, &arg, NULL, TEST_BUF_SIZE), "buf_writer_init() should not OOM"); test_buf_writer_body(tsdn, &buf_writer); } TEST_END TEST_BEGIN(test_buf_write_oom) { buf_writer_t buf_writer; tsdn_t *tsdn = tsdn_fetch(); assert_true(buf_writer_init(tsdn, &buf_writer, test_write_cb, &arg, NULL, SC_LARGE_MAXCLASS + 1), "buf_writer_init() should OOM"); assert(buf_writer.buf == NULL); char s[UNIT_MAX + 1]; size_t n_unit, i; ssize_t unit; memset(s, 'a', UNIT_MAX); arg = 4; /* Starting value of random argument. */ arg_store = arg; for (unit = UNIT_MAX; unit >= 0; unit -= UNIT_MAX / 4) { /* unit keeps decreasing, so strlen(s) is always unit. */ s[unit] = '\0'; for (n_unit = 1; n_unit <= 3; ++n_unit) { test_write_len = 0; for (i = 1; i <= n_unit; ++i) { arg = prng_lg_range_u64(&arg, 64); buf_writer_cb(&buf_writer, s); assert_u64_eq(arg_store, arg, "Call back argument didn't get through"); assert_zu_eq(test_write_len, i * unit, "Incorrect length after writing %zu strings" " of length %zu", i, unit); } buf_writer_flush(&buf_writer); expect_zu_eq(test_write_len, n_unit * unit, "Incorrect length after flushing at the end of" " writing %zu strings of length %zu", n_unit, unit); } } buf_writer_terminate(tsdn, &buf_writer); } TEST_END static int test_read_count; static size_t test_read_len; static uint64_t arg_sum; ssize_t test_read_cb(void *cbopaque, void *buf, size_t limit) { static uint64_t rand = 4; arg_sum += *(uint64_t *)cbopaque; assert_zu_gt(limit, 0, "Limit for read_cb must be positive"); --test_read_count; if (test_read_count == 0) { return -1; } else { size_t read_len = limit; if (limit > 1) { rand = prng_range_u64(&rand, (uint64_t)limit); read_len -= (size_t)rand; } assert(read_len > 0); memset(buf, 'a', read_len); size_t prev_test_read_len = test_read_len; test_read_len += read_len; assert_zu_le(prev_test_read_len, test_read_len, "Test read overflowed"); return read_len; } } static void test_buf_writer_pipe_body(tsdn_t *tsdn, buf_writer_t *buf_writer) { arg = 4; /* Starting value of random argument. */ for (int count = 5; count > 0; --count) { arg = prng_lg_range_u64(&arg, 64); arg_sum = 0; test_read_count = count; test_read_len = 0; test_write_len = 0; buf_writer_pipe(buf_writer, test_read_cb, &arg); assert(test_read_count == 0); expect_u64_eq(arg_sum, arg * count, ""); expect_zu_eq(test_write_len, test_read_len, "Write length should be equal to read length"); } buf_writer_terminate(tsdn, buf_writer); } TEST_BEGIN(test_buf_write_pipe) { buf_writer_t buf_writer; tsdn_t *tsdn = tsdn_fetch(); assert_false(buf_writer_init(tsdn, &buf_writer, test_write_cb, &arg, test_buf, TEST_BUF_SIZE), "buf_writer_init() should not encounter error on static buffer"); test_buf_writer_pipe_body(tsdn, &buf_writer); } TEST_END TEST_BEGIN(test_buf_write_pipe_oom) { buf_writer_t buf_writer; tsdn_t *tsdn = tsdn_fetch(); assert_true(buf_writer_init(tsdn, &buf_writer, test_write_cb, &arg, NULL, SC_LARGE_MAXCLASS + 1), "buf_writer_init() should OOM"); test_buf_writer_pipe_body(tsdn, &buf_writer); } TEST_END int main(void) { return test( test_buf_write_static, test_buf_write_dynamic, test_buf_write_oom, test_buf_write_pipe, test_buf_write_pipe_oom); } redis-8.0.2/deps/jemalloc/test/unit/cache_bin.c000066400000000000000000000330001501533116600213420ustar00rootroot00000000000000#include "test/jemalloc_test.h" static void do_fill_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t ncached_max, cache_bin_sz_t nfill_attempt, cache_bin_sz_t nfill_succeed) { bool success; void *ptr; assert_true(cache_bin_ncached_get_local(bin, info) == 0, ""); CACHE_BIN_PTR_ARRAY_DECLARE(arr, nfill_attempt); cache_bin_init_ptr_array_for_fill(bin, info, &arr, nfill_attempt); for (cache_bin_sz_t i = 0; i < nfill_succeed; i++) { arr.ptr[i] = &ptrs[i]; } cache_bin_finish_fill(bin, info, &arr, nfill_succeed); expect_true(cache_bin_ncached_get_local(bin, info) == nfill_succeed, ""); cache_bin_low_water_set(bin); for (cache_bin_sz_t i = 0; i < nfill_succeed; i++) { ptr = cache_bin_alloc(bin, &success); expect_true(success, ""); expect_ptr_eq(ptr, (void *)&ptrs[i], "Should pop in order filled"); expect_true(cache_bin_low_water_get(bin, info) == nfill_succeed - i - 1, ""); } expect_true(cache_bin_ncached_get_local(bin, info) == 0, ""); expect_true(cache_bin_low_water_get(bin, info) == 0, ""); } static void do_flush_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t nfill, cache_bin_sz_t nflush) { bool success; assert_true(cache_bin_ncached_get_local(bin, info) == 0, ""); for (cache_bin_sz_t i = 0; i < nfill; i++) { success = cache_bin_dalloc_easy(bin, &ptrs[i]); expect_true(success, ""); } CACHE_BIN_PTR_ARRAY_DECLARE(arr, nflush); cache_bin_init_ptr_array_for_flush(bin, info, &arr, nflush); for (cache_bin_sz_t i = 0; i < nflush; i++) { expect_ptr_eq(arr.ptr[i], &ptrs[nflush - i - 1], ""); } cache_bin_finish_flush(bin, info, &arr, nflush); expect_true(cache_bin_ncached_get_local(bin, info) == nfill - nflush, ""); while (cache_bin_ncached_get_local(bin, info) > 0) { cache_bin_alloc(bin, &success); } } static void do_batch_alloc_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t nfill, size_t batch) { assert_true(cache_bin_ncached_get_local(bin, info) == 0, ""); CACHE_BIN_PTR_ARRAY_DECLARE(arr, nfill); cache_bin_init_ptr_array_for_fill(bin, info, &arr, nfill); for (cache_bin_sz_t i = 0; i < nfill; i++) { arr.ptr[i] = &ptrs[i]; } cache_bin_finish_fill(bin, info, &arr, nfill); assert_true(cache_bin_ncached_get_local(bin, info) == nfill, ""); cache_bin_low_water_set(bin); void **out = malloc((batch + 1) * sizeof(void *)); size_t n = cache_bin_alloc_batch(bin, batch, out); assert_true(n == ((size_t)nfill < batch ? (size_t)nfill : batch), ""); for (cache_bin_sz_t i = 0; i < (cache_bin_sz_t)n; i++) { expect_ptr_eq(out[i], &ptrs[i], ""); } expect_true(cache_bin_low_water_get(bin, info) == nfill - (cache_bin_sz_t)n, ""); while (cache_bin_ncached_get_local(bin, info) > 0) { bool success; cache_bin_alloc(bin, &success); } free(out); } static void test_bin_init(cache_bin_t *bin, cache_bin_info_t *info) { size_t size; size_t alignment; cache_bin_info_compute_alloc(info, 1, &size, &alignment); void *mem = mallocx(size, MALLOCX_ALIGN(alignment)); assert_ptr_not_null(mem, "Unexpected mallocx failure"); size_t cur_offset = 0; cache_bin_preincrement(info, 1, mem, &cur_offset); cache_bin_init(bin, info, mem, &cur_offset); cache_bin_postincrement(info, 1, mem, &cur_offset); assert_zu_eq(cur_offset, size, "Should use all requested memory"); } TEST_BEGIN(test_cache_bin) { const int ncached_max = 100; bool success; void *ptr; cache_bin_info_t info; cache_bin_info_init(&info, ncached_max); cache_bin_t bin; test_bin_init(&bin, &info); /* Initialize to empty; should then have 0 elements. */ expect_d_eq(ncached_max, cache_bin_info_ncached_max(&info), ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == 0, ""); expect_true(cache_bin_low_water_get(&bin, &info) == 0, ""); ptr = cache_bin_alloc_easy(&bin, &success); expect_false(success, "Shouldn't successfully allocate when empty"); expect_ptr_null(ptr, "Shouldn't get a non-null pointer on failure"); ptr = cache_bin_alloc(&bin, &success); expect_false(success, "Shouldn't successfully allocate when empty"); expect_ptr_null(ptr, "Shouldn't get a non-null pointer on failure"); /* * We allocate one more item than ncached_max, so we can test cache bin * exhaustion. */ void **ptrs = mallocx(sizeof(void *) * (ncached_max + 1), 0); assert_ptr_not_null(ptrs, "Unexpected mallocx failure"); for (cache_bin_sz_t i = 0; i < ncached_max; i++) { expect_true(cache_bin_ncached_get_local(&bin, &info) == i, ""); success = cache_bin_dalloc_easy(&bin, &ptrs[i]); expect_true(success, "Should be able to dalloc into a non-full cache bin."); expect_true(cache_bin_low_water_get(&bin, &info) == 0, "Pushes and pops shouldn't change low water of zero."); } expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max, ""); success = cache_bin_dalloc_easy(&bin, &ptrs[ncached_max]); expect_false(success, "Shouldn't be able to dalloc into a full bin."); cache_bin_low_water_set(&bin); for (cache_bin_sz_t i = 0; i < ncached_max; i++) { expect_true(cache_bin_low_water_get(&bin, &info) == ncached_max - i, ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max - i, ""); /* * This should fail -- the easy variant can't change the low * water mark. */ ptr = cache_bin_alloc_easy(&bin, &success); expect_ptr_null(ptr, ""); expect_false(success, ""); expect_true(cache_bin_low_water_get(&bin, &info) == ncached_max - i, ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max - i, ""); /* This should succeed, though. */ ptr = cache_bin_alloc(&bin, &success); expect_true(success, ""); expect_ptr_eq(ptr, &ptrs[ncached_max - i - 1], "Alloc should pop in stack order"); expect_true(cache_bin_low_water_get(&bin, &info) == ncached_max - i - 1, ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max - i - 1, ""); } /* Now we're empty -- all alloc attempts should fail. */ expect_true(cache_bin_ncached_get_local(&bin, &info) == 0, ""); ptr = cache_bin_alloc_easy(&bin, &success); expect_ptr_null(ptr, ""); expect_false(success, ""); ptr = cache_bin_alloc(&bin, &success); expect_ptr_null(ptr, ""); expect_false(success, ""); for (cache_bin_sz_t i = 0; i < ncached_max / 2; i++) { cache_bin_dalloc_easy(&bin, &ptrs[i]); } cache_bin_low_water_set(&bin); for (cache_bin_sz_t i = ncached_max / 2; i < ncached_max; i++) { cache_bin_dalloc_easy(&bin, &ptrs[i]); } expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max, ""); for (cache_bin_sz_t i = ncached_max - 1; i >= ncached_max / 2; i--) { /* * Size is bigger than low water -- the reduced version should * succeed. */ ptr = cache_bin_alloc_easy(&bin, &success); expect_true(success, ""); expect_ptr_eq(ptr, &ptrs[i], ""); } /* But now, we've hit low-water. */ ptr = cache_bin_alloc_easy(&bin, &success); expect_false(success, ""); expect_ptr_null(ptr, ""); /* We're going to test filling -- we must be empty to start. */ while (cache_bin_ncached_get_local(&bin, &info)) { cache_bin_alloc(&bin, &success); expect_true(success, ""); } /* Test fill. */ /* Try to fill all, succeed fully. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, ncached_max); /* Try to fill all, succeed partially. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, ncached_max / 2); /* Try to fill all, fail completely. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, 0); /* Try to fill some, succeed fully. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, ncached_max / 2); /* Try to fill some, succeed partially. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, ncached_max / 4); /* Try to fill some, fail completely. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, 0); do_flush_test(&bin, &info, ptrs, ncached_max, ncached_max); do_flush_test(&bin, &info, ptrs, ncached_max, ncached_max / 2); do_flush_test(&bin, &info, ptrs, ncached_max, 0); do_flush_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2); do_flush_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4); do_flush_test(&bin, &info, ptrs, ncached_max / 2, 0); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max * 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max / 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 1); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 0); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 1); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 0); do_batch_alloc_test(&bin, &info, ptrs, 2, ncached_max); do_batch_alloc_test(&bin, &info, ptrs, 2, 2); do_batch_alloc_test(&bin, &info, ptrs, 2, 1); do_batch_alloc_test(&bin, &info, ptrs, 2, 0); do_batch_alloc_test(&bin, &info, ptrs, 1, 2); do_batch_alloc_test(&bin, &info, ptrs, 1, 1); do_batch_alloc_test(&bin, &info, ptrs, 1, 0); do_batch_alloc_test(&bin, &info, ptrs, 0, 2); do_batch_alloc_test(&bin, &info, ptrs, 0, 1); do_batch_alloc_test(&bin, &info, ptrs, 0, 0); free(ptrs); } TEST_END static void do_flush_stashed_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t nfill, cache_bin_sz_t nstash) { expect_true(cache_bin_ncached_get_local(bin, info) == 0, "Bin not empty"); expect_true(cache_bin_nstashed_get_local(bin, info) == 0, "Bin not empty"); expect_true(nfill + nstash <= info->ncached_max, "Exceeded max"); bool ret; /* Fill */ for (cache_bin_sz_t i = 0; i < nfill; i++) { ret = cache_bin_dalloc_easy(bin, &ptrs[i]); expect_true(ret, "Unexpected fill failure"); } expect_true(cache_bin_ncached_get_local(bin, info) == nfill, "Wrong cached count"); /* Stash */ for (cache_bin_sz_t i = 0; i < nstash; i++) { ret = cache_bin_stash(bin, &ptrs[i + nfill]); expect_true(ret, "Unexpected stash failure"); } expect_true(cache_bin_nstashed_get_local(bin, info) == nstash, "Wrong stashed count"); if (nfill + nstash == info->ncached_max) { ret = cache_bin_dalloc_easy(bin, &ptrs[0]); expect_false(ret, "Should not dalloc into a full bin"); ret = cache_bin_stash(bin, &ptrs[0]); expect_false(ret, "Should not stash into a full bin"); } /* Alloc filled ones */ for (cache_bin_sz_t i = 0; i < nfill; i++) { void *ptr = cache_bin_alloc(bin, &ret); expect_true(ret, "Unexpected alloc failure"); /* Verify it's not from the stashed range. */ expect_true((uintptr_t)ptr < (uintptr_t)&ptrs[nfill], "Should not alloc stashed ptrs"); } expect_true(cache_bin_ncached_get_local(bin, info) == 0, "Wrong cached count"); expect_true(cache_bin_nstashed_get_local(bin, info) == nstash, "Wrong stashed count"); cache_bin_alloc(bin, &ret); expect_false(ret, "Should not alloc stashed"); /* Clear stashed ones */ cache_bin_finish_flush_stashed(bin, info); expect_true(cache_bin_ncached_get_local(bin, info) == 0, "Wrong cached count"); expect_true(cache_bin_nstashed_get_local(bin, info) == 0, "Wrong stashed count"); cache_bin_alloc(bin, &ret); expect_false(ret, "Should not alloc from empty bin"); } TEST_BEGIN(test_cache_bin_stash) { const int ncached_max = 100; cache_bin_t bin; cache_bin_info_t info; cache_bin_info_init(&info, ncached_max); test_bin_init(&bin, &info); /* * The content of this array is not accessed; instead the interior * addresses are used to insert / stash into the bins as test pointers. */ void **ptrs = mallocx(sizeof(void *) * (ncached_max + 1), 0); assert_ptr_not_null(ptrs, "Unexpected mallocx failure"); bool ret; for (cache_bin_sz_t i = 0; i < ncached_max; i++) { expect_true(cache_bin_ncached_get_local(&bin, &info) == (i / 2 + i % 2), "Wrong ncached value"); expect_true(cache_bin_nstashed_get_local(&bin, &info) == i / 2, "Wrong nstashed value"); if (i % 2 == 0) { cache_bin_dalloc_easy(&bin, &ptrs[i]); } else { ret = cache_bin_stash(&bin, &ptrs[i]); expect_true(ret, "Should be able to stash into a " "non-full cache bin"); } } ret = cache_bin_dalloc_easy(&bin, &ptrs[0]); expect_false(ret, "Should not dalloc into a full cache bin"); ret = cache_bin_stash(&bin, &ptrs[0]); expect_false(ret, "Should not stash into a full cache bin"); for (cache_bin_sz_t i = 0; i < ncached_max; i++) { void *ptr = cache_bin_alloc(&bin, &ret); if (i < ncached_max / 2) { expect_true(ret, "Should be able to alloc"); uintptr_t diff = ((uintptr_t)ptr - (uintptr_t)&ptrs[0]) / sizeof(void *); expect_true(diff % 2 == 0, "Should be able to alloc"); } else { expect_false(ret, "Should not alloc stashed"); expect_true(cache_bin_nstashed_get_local(&bin, &info) == ncached_max / 2, "Wrong nstashed value"); } } test_bin_init(&bin, &info); do_flush_stashed_test(&bin, &info, ptrs, ncached_max, 0); do_flush_stashed_test(&bin, &info, ptrs, 0, ncached_max); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 4, ncached_max / 2); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 4, ncached_max / 4); } TEST_END int main(void) { return test(test_cache_bin, test_cache_bin_stash); } redis-8.0.2/deps/jemalloc/test/unit/ckh.c000066400000000000000000000125461501533116600202300ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_new_delete) { tsd_t *tsd; ckh_t ckh; tsd = tsd_fetch(); expect_false(ckh_new(tsd, &ckh, 2, ckh_string_hash, ckh_string_keycomp), "Unexpected ckh_new() error"); ckh_delete(tsd, &ckh); expect_false(ckh_new(tsd, &ckh, 3, ckh_pointer_hash, ckh_pointer_keycomp), "Unexpected ckh_new() error"); ckh_delete(tsd, &ckh); } TEST_END TEST_BEGIN(test_count_insert_search_remove) { tsd_t *tsd; ckh_t ckh; const char *strs[] = { "a string", "A string", "a string.", "A string." }; const char *missing = "A string not in the hash table."; size_t i; tsd = tsd_fetch(); expect_false(ckh_new(tsd, &ckh, 2, ckh_string_hash, ckh_string_keycomp), "Unexpected ckh_new() error"); expect_zu_eq(ckh_count(&ckh), 0, "ckh_count() should return %zu, but it returned %zu", ZU(0), ckh_count(&ckh)); /* Insert. */ for (i = 0; i < sizeof(strs)/sizeof(const char *); i++) { ckh_insert(tsd, &ckh, strs[i], strs[i]); expect_zu_eq(ckh_count(&ckh), i+1, "ckh_count() should return %zu, but it returned %zu", i+1, ckh_count(&ckh)); } /* Search. */ for (i = 0; i < sizeof(strs)/sizeof(const char *); i++) { union { void *p; const char *s; } k, v; void **kp, **vp; const char *ks, *vs; kp = (i & 1) ? &k.p : NULL; vp = (i & 2) ? &v.p : NULL; k.p = NULL; v.p = NULL; expect_false(ckh_search(&ckh, strs[i], kp, vp), "Unexpected ckh_search() error"); ks = (i & 1) ? strs[i] : (const char *)NULL; vs = (i & 2) ? strs[i] : (const char *)NULL; expect_ptr_eq((void *)ks, (void *)k.s, "Key mismatch, i=%zu", i); expect_ptr_eq((void *)vs, (void *)v.s, "Value mismatch, i=%zu", i); } expect_true(ckh_search(&ckh, missing, NULL, NULL), "Unexpected ckh_search() success"); /* Remove. */ for (i = 0; i < sizeof(strs)/sizeof(const char *); i++) { union { void *p; const char *s; } k, v; void **kp, **vp; const char *ks, *vs; kp = (i & 1) ? &k.p : NULL; vp = (i & 2) ? &v.p : NULL; k.p = NULL; v.p = NULL; expect_false(ckh_remove(tsd, &ckh, strs[i], kp, vp), "Unexpected ckh_remove() error"); ks = (i & 1) ? strs[i] : (const char *)NULL; vs = (i & 2) ? strs[i] : (const char *)NULL; expect_ptr_eq((void *)ks, (void *)k.s, "Key mismatch, i=%zu", i); expect_ptr_eq((void *)vs, (void *)v.s, "Value mismatch, i=%zu", i); expect_zu_eq(ckh_count(&ckh), sizeof(strs)/sizeof(const char *) - i - 1, "ckh_count() should return %zu, but it returned %zu", sizeof(strs)/sizeof(const char *) - i - 1, ckh_count(&ckh)); } ckh_delete(tsd, &ckh); } TEST_END TEST_BEGIN(test_insert_iter_remove) { #define NITEMS ZU(1000) tsd_t *tsd; ckh_t ckh; void **p[NITEMS]; void *q, *r; size_t i; tsd = tsd_fetch(); expect_false(ckh_new(tsd, &ckh, 2, ckh_pointer_hash, ckh_pointer_keycomp), "Unexpected ckh_new() error"); for (i = 0; i < NITEMS; i++) { p[i] = mallocx(i+1, 0); expect_ptr_not_null(p[i], "Unexpected mallocx() failure"); } for (i = 0; i < NITEMS; i++) { size_t j; for (j = i; j < NITEMS; j++) { expect_false(ckh_insert(tsd, &ckh, p[j], p[j]), "Unexpected ckh_insert() failure"); expect_false(ckh_search(&ckh, p[j], &q, &r), "Unexpected ckh_search() failure"); expect_ptr_eq(p[j], q, "Key pointer mismatch"); expect_ptr_eq(p[j], r, "Value pointer mismatch"); } expect_zu_eq(ckh_count(&ckh), NITEMS, "ckh_count() should return %zu, but it returned %zu", NITEMS, ckh_count(&ckh)); for (j = i + 1; j < NITEMS; j++) { expect_false(ckh_search(&ckh, p[j], NULL, NULL), "Unexpected ckh_search() failure"); expect_false(ckh_remove(tsd, &ckh, p[j], &q, &r), "Unexpected ckh_remove() failure"); expect_ptr_eq(p[j], q, "Key pointer mismatch"); expect_ptr_eq(p[j], r, "Value pointer mismatch"); expect_true(ckh_search(&ckh, p[j], NULL, NULL), "Unexpected ckh_search() success"); expect_true(ckh_remove(tsd, &ckh, p[j], &q, &r), "Unexpected ckh_remove() success"); } { bool seen[NITEMS]; size_t tabind; memset(seen, 0, sizeof(seen)); for (tabind = 0; !ckh_iter(&ckh, &tabind, &q, &r);) { size_t k; expect_ptr_eq(q, r, "Key and val not equal"); for (k = 0; k < NITEMS; k++) { if (p[k] == q) { expect_false(seen[k], "Item %zu already seen", k); seen[k] = true; break; } } } for (j = 0; j < i + 1; j++) { expect_true(seen[j], "Item %zu not seen", j); } for (; j < NITEMS; j++) { expect_false(seen[j], "Item %zu seen", j); } } } for (i = 0; i < NITEMS; i++) { expect_false(ckh_search(&ckh, p[i], NULL, NULL), "Unexpected ckh_search() failure"); expect_false(ckh_remove(tsd, &ckh, p[i], &q, &r), "Unexpected ckh_remove() failure"); expect_ptr_eq(p[i], q, "Key pointer mismatch"); expect_ptr_eq(p[i], r, "Value pointer mismatch"); expect_true(ckh_search(&ckh, p[i], NULL, NULL), "Unexpected ckh_search() success"); expect_true(ckh_remove(tsd, &ckh, p[i], &q, &r), "Unexpected ckh_remove() success"); dallocx(p[i], 0); } expect_zu_eq(ckh_count(&ckh), 0, "ckh_count() should return %zu, but it returned %zu", ZU(0), ckh_count(&ckh)); ckh_delete(tsd, &ckh); #undef NITEMS } TEST_END int main(void) { return test( test_new_delete, test_count_insert_search_remove, test_insert_iter_remove); } redis-8.0.2/deps/jemalloc/test/unit/counter.c000066400000000000000000000033711501533116600211360ustar00rootroot00000000000000#include "test/jemalloc_test.h" static const uint64_t interval = 1 << 20; TEST_BEGIN(test_counter_accum) { uint64_t increment = interval >> 4; unsigned n = interval / increment; uint64_t accum = 0; counter_accum_t c; counter_accum_init(&c, interval); tsd_t *tsd = tsd_fetch(); bool trigger; for (unsigned i = 0; i < n; i++) { trigger = counter_accum(tsd_tsdn(tsd), &c, increment); accum += increment; if (accum < interval) { expect_b_eq(trigger, false, "Should not trigger"); } else { expect_b_eq(trigger, true, "Should have triggered"); } } expect_b_eq(trigger, true, "Should have triggered"); } TEST_END void expect_counter_value(counter_accum_t *c, uint64_t v) { uint64_t accum = locked_read_u64_unsynchronized(&c->accumbytes); expect_u64_eq(accum, v, "Counter value mismatch"); } #define N_THDS (16) #define N_ITER_THD (1 << 12) #define ITER_INCREMENT (interval >> 4) static void * thd_start(void *varg) { counter_accum_t *c = (counter_accum_t *)varg; tsd_t *tsd = tsd_fetch(); bool trigger; uintptr_t n_triggered = 0; for (unsigned i = 0; i < N_ITER_THD; i++) { trigger = counter_accum(tsd_tsdn(tsd), c, ITER_INCREMENT); n_triggered += trigger ? 1 : 0; } return (void *)n_triggered; } TEST_BEGIN(test_counter_mt) { counter_accum_t shared_c; counter_accum_init(&shared_c, interval); thd_t thds[N_THDS]; unsigned i; for (i = 0; i < N_THDS; i++) { thd_create(&thds[i], thd_start, (void *)&shared_c); } uint64_t sum = 0; for (i = 0; i < N_THDS; i++) { void *ret; thd_join(thds[i], &ret); sum += (uintptr_t)ret; } expect_u64_eq(sum, N_THDS * N_ITER_THD / (interval / ITER_INCREMENT), "Incorrect number of triggers"); } TEST_END int main(void) { return test( test_counter_accum, test_counter_mt); } redis-8.0.2/deps/jemalloc/test/unit/decay.c000066400000000000000000000206001501533116600205360ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/decay.h" TEST_BEGIN(test_decay_init) { decay_t decay; memset(&decay, 0, sizeof(decay)); nstime_t curtime; nstime_init(&curtime, 0); ssize_t decay_ms = 1000; assert_true(decay_ms_valid(decay_ms), ""); expect_false(decay_init(&decay, &curtime, decay_ms), "Failed to initialize decay"); expect_zd_eq(decay_ms_read(&decay), decay_ms, "Decay_ms was initialized incorrectly"); expect_u64_ne(decay_epoch_duration_ns(&decay), 0, "Epoch duration was initialized incorrectly"); } TEST_END TEST_BEGIN(test_decay_ms_valid) { expect_false(decay_ms_valid(-7), "Misclassified negative decay as valid"); expect_true(decay_ms_valid(-1), "Misclassified -1 (never decay) as invalid decay"); expect_true(decay_ms_valid(8943), "Misclassified valid decay"); if (SSIZE_MAX > NSTIME_SEC_MAX) { expect_false( decay_ms_valid((ssize_t)(NSTIME_SEC_MAX * KQU(1000) + 39)), "Misclassified too large decay"); } } TEST_END TEST_BEGIN(test_decay_npages_purge_in) { decay_t decay; memset(&decay, 0, sizeof(decay)); nstime_t curtime; nstime_init(&curtime, 0); uint64_t decay_ms = 1000; nstime_t decay_nstime; nstime_init(&decay_nstime, decay_ms * 1000 * 1000); expect_false(decay_init(&decay, &curtime, (ssize_t)decay_ms), "Failed to initialize decay"); size_t new_pages = 100; nstime_t time; nstime_copy(&time, &decay_nstime); expect_u64_eq(decay_npages_purge_in(&decay, &time, new_pages), new_pages, "Not all pages are expected to decay in decay_ms"); nstime_init(&time, 0); expect_u64_eq(decay_npages_purge_in(&decay, &time, new_pages), 0, "More than zero pages are expected to instantly decay"); nstime_copy(&time, &decay_nstime); nstime_idivide(&time, 2); expect_u64_eq(decay_npages_purge_in(&decay, &time, new_pages), new_pages / 2, "Not half of pages decay in half the decay period"); } TEST_END TEST_BEGIN(test_decay_maybe_advance_epoch) { decay_t decay; memset(&decay, 0, sizeof(decay)); nstime_t curtime; nstime_init(&curtime, 0); uint64_t decay_ms = 1000; bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms); expect_false(err, ""); bool advanced; advanced = decay_maybe_advance_epoch(&decay, &curtime, 0); expect_false(advanced, "Epoch advanced while time didn't"); nstime_t interval; nstime_init(&interval, decay_epoch_duration_ns(&decay)); nstime_add(&curtime, &interval); advanced = decay_maybe_advance_epoch(&decay, &curtime, 0); expect_false(advanced, "Epoch advanced after first interval"); nstime_add(&curtime, &interval); advanced = decay_maybe_advance_epoch(&decay, &curtime, 0); expect_true(advanced, "Epoch didn't advance after two intervals"); } TEST_END TEST_BEGIN(test_decay_empty) { /* If we never have any decaying pages, npages_limit should be 0. */ decay_t decay; memset(&decay, 0, sizeof(decay)); nstime_t curtime; nstime_init(&curtime, 0); uint64_t decay_ms = 1000; uint64_t decay_ns = decay_ms * 1000 * 1000; bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms); assert_false(err, ""); uint64_t time_between_calls = decay_epoch_duration_ns(&decay) / 5; int nepochs = 0; for (uint64_t i = 0; i < decay_ns / time_between_calls * 10; i++) { size_t dirty_pages = 0; nstime_init(&curtime, i * time_between_calls); bool epoch_advanced = decay_maybe_advance_epoch(&decay, &curtime, dirty_pages); if (epoch_advanced) { nepochs++; expect_zu_eq(decay_npages_limit_get(&decay), 0, "Unexpectedly increased npages_limit"); } } expect_d_gt(nepochs, 0, "Epochs never advanced"); } TEST_END /* * Verify that npages_limit correctly decays as the time goes. * * During first 'nepoch_init' epochs, add new dirty pages. * After that, let them decay and verify npages_limit decreases. * Then proceed with another 'nepoch_init' epochs and check that * all dirty pages are flushed out of backlog, bringing npages_limit * down to zero. */ TEST_BEGIN(test_decay) { const uint64_t nepoch_init = 10; decay_t decay; memset(&decay, 0, sizeof(decay)); nstime_t curtime; nstime_init(&curtime, 0); uint64_t decay_ms = 1000; uint64_t decay_ns = decay_ms * 1000 * 1000; bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms); assert_false(err, ""); expect_zu_eq(decay_npages_limit_get(&decay), 0, "Empty decay returned nonzero npages_limit"); nstime_t epochtime; nstime_init(&epochtime, decay_epoch_duration_ns(&decay)); const size_t dirty_pages_per_epoch = 1000; size_t dirty_pages = 0; uint64_t epoch_ns = decay_epoch_duration_ns(&decay); bool epoch_advanced = false; /* Populate backlog with some dirty pages */ for (uint64_t i = 0; i < nepoch_init; i++) { nstime_add(&curtime, &epochtime); dirty_pages += dirty_pages_per_epoch; epoch_advanced |= decay_maybe_advance_epoch(&decay, &curtime, dirty_pages); } expect_true(epoch_advanced, "Epoch never advanced"); size_t npages_limit = decay_npages_limit_get(&decay); expect_zu_gt(npages_limit, 0, "npages_limit is incorrectly equal " "to zero after dirty pages have been added"); /* Keep dirty pages unchanged and verify that npages_limit decreases */ for (uint64_t i = nepoch_init; i * epoch_ns < decay_ns; ++i) { nstime_add(&curtime, &epochtime); epoch_advanced = decay_maybe_advance_epoch(&decay, &curtime, dirty_pages); if (epoch_advanced) { size_t npages_limit_new = decay_npages_limit_get(&decay); expect_zu_lt(npages_limit_new, npages_limit, "napges_limit failed to decay"); npages_limit = npages_limit_new; } } expect_zu_gt(npages_limit, 0, "npages_limit decayed to zero earlier " "than decay_ms since last dirty page was added"); /* Completely push all dirty pages out of the backlog */ epoch_advanced = false; for (uint64_t i = 0; i < nepoch_init; i++) { nstime_add(&curtime, &epochtime); epoch_advanced |= decay_maybe_advance_epoch(&decay, &curtime, dirty_pages); } expect_true(epoch_advanced, "Epoch never advanced"); npages_limit = decay_npages_limit_get(&decay); expect_zu_eq(npages_limit, 0, "npages_limit didn't decay to 0 after " "decay_ms since last bump in dirty pages"); } TEST_END TEST_BEGIN(test_decay_ns_until_purge) { const uint64_t nepoch_init = 10; decay_t decay; memset(&decay, 0, sizeof(decay)); nstime_t curtime; nstime_init(&curtime, 0); uint64_t decay_ms = 1000; uint64_t decay_ns = decay_ms * 1000 * 1000; bool err = decay_init(&decay, &curtime, (ssize_t)decay_ms); assert_false(err, ""); nstime_t epochtime; nstime_init(&epochtime, decay_epoch_duration_ns(&decay)); uint64_t ns_until_purge_empty = decay_ns_until_purge(&decay, 0, 0); expect_u64_eq(ns_until_purge_empty, DECAY_UNBOUNDED_TIME_TO_PURGE, "Failed to return unbounded wait time for zero threshold"); const size_t dirty_pages_per_epoch = 1000; size_t dirty_pages = 0; bool epoch_advanced = false; for (uint64_t i = 0; i < nepoch_init; i++) { nstime_add(&curtime, &epochtime); dirty_pages += dirty_pages_per_epoch; epoch_advanced |= decay_maybe_advance_epoch(&decay, &curtime, dirty_pages); } expect_true(epoch_advanced, "Epoch never advanced"); uint64_t ns_until_purge_all = decay_ns_until_purge(&decay, dirty_pages, dirty_pages); expect_u64_ge(ns_until_purge_all, decay_ns, "Incorrectly calculated time to purge all pages"); uint64_t ns_until_purge_none = decay_ns_until_purge(&decay, dirty_pages, 0); expect_u64_eq(ns_until_purge_none, decay_epoch_duration_ns(&decay) * 2, "Incorrectly calculated time to purge 0 pages"); uint64_t npages_threshold = dirty_pages / 2; uint64_t ns_until_purge_half = decay_ns_until_purge(&decay, dirty_pages, npages_threshold); nstime_t waittime; nstime_init(&waittime, ns_until_purge_half); nstime_add(&curtime, &waittime); decay_maybe_advance_epoch(&decay, &curtime, dirty_pages); size_t npages_limit = decay_npages_limit_get(&decay); expect_zu_lt(npages_limit, dirty_pages, "npages_limit failed to decrease after waiting"); size_t expected = dirty_pages - npages_limit; int deviation = abs((int)expected - (int)(npages_threshold)); expect_d_lt(deviation, (int)(npages_threshold / 2), "After waiting, number of pages is out of the expected interval " "[0.5 * npages_threshold .. 1.5 * npages_threshold]"); } TEST_END int main(void) { return test( test_decay_init, test_decay_ms_valid, test_decay_npages_purge_in, test_decay_maybe_advance_epoch, test_decay_empty, test_decay, test_decay_ns_until_purge); } redis-8.0.2/deps/jemalloc/test/unit/div.c000066400000000000000000000012651501533116600202410ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/div.h" TEST_BEGIN(test_div_exhaustive) { for (size_t divisor = 2; divisor < 1000 * 1000; ++divisor) { div_info_t div_info; div_init(&div_info, divisor); size_t max = 1000 * divisor; if (max < 1000 * 1000) { max = 1000 * 1000; } for (size_t dividend = 0; dividend < 1000 * divisor; dividend += divisor) { size_t quotient = div_compute( &div_info, dividend); expect_zu_eq(dividend, quotient * divisor, "With divisor = %zu, dividend = %zu, " "got quotient %zu", divisor, dividend, quotient); } } } TEST_END int main(void) { return test_no_reentrancy( test_div_exhaustive); } redis-8.0.2/deps/jemalloc/test/unit/double_free.c000066400000000000000000000036121501533116600217300ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/san.h" #include "jemalloc/internal/safety_check.h" bool fake_abort_called; void fake_abort(const char *message) { (void)message; fake_abort_called = true; } void test_large_double_free_pre(void) { safety_check_set_abort(&fake_abort); fake_abort_called = false; } void test_large_double_free_post() { expect_b_eq(fake_abort_called, true, "Double-free check didn't fire."); safety_check_set_abort(NULL); } TEST_BEGIN(test_large_double_free_tcache) { test_skip_if(!config_opt_safety_checks); /* * Skip debug builds, since too many assertions will be triggered with * double-free before hitting the one we are interested in. */ test_skip_if(config_debug); test_large_double_free_pre(); char *ptr = malloc(SC_LARGE_MINCLASS); bool guarded = extent_is_guarded(tsdn_fetch(), ptr); free(ptr); if (!guarded) { free(ptr); } else { /* * Skip because guarded extents may unguard immediately on * deallocation, in which case the second free will crash before * reaching the intended safety check. */ fake_abort_called = true; } mallctl("thread.tcache.flush", NULL, NULL, NULL, 0); test_large_double_free_post(); } TEST_END TEST_BEGIN(test_large_double_free_no_tcache) { test_skip_if(!config_opt_safety_checks); test_skip_if(config_debug); test_large_double_free_pre(); char *ptr = mallocx(SC_LARGE_MINCLASS, MALLOCX_TCACHE_NONE); bool guarded = extent_is_guarded(tsdn_fetch(), ptr); dallocx(ptr, MALLOCX_TCACHE_NONE); if (!guarded) { dallocx(ptr, MALLOCX_TCACHE_NONE); } else { /* * Skip because guarded extents may unguard immediately on * deallocation, in which case the second free will crash before * reaching the intended safety check. */ fake_abort_called = true; } test_large_double_free_post(); } TEST_END int main(void) { return test(test_large_double_free_no_tcache, test_large_double_free_tcache); } redis-8.0.2/deps/jemalloc/test/unit/double_free.h000066400000000000000000000000011501533116600217220ustar00rootroot00000000000000 redis-8.0.2/deps/jemalloc/test/unit/edata_cache.c000066400000000000000000000155401501533116600216610ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/edata_cache.h" static void test_edata_cache_init(edata_cache_t *edata_cache) { base_t *base = base_new(TSDN_NULL, /* ind */ 1, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); assert_ptr_not_null(base, ""); bool err = edata_cache_init(edata_cache, base); assert_false(err, ""); } static void test_edata_cache_destroy(edata_cache_t *edata_cache) { base_delete(TSDN_NULL, edata_cache->base); } TEST_BEGIN(test_edata_cache) { edata_cache_t ec; test_edata_cache_init(&ec); /* Get one */ edata_t *ed1 = edata_cache_get(TSDN_NULL, &ec); assert_ptr_not_null(ed1, ""); /* Cache should be empty */ assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); /* Get another */ edata_t *ed2 = edata_cache_get(TSDN_NULL, &ec); assert_ptr_not_null(ed2, ""); /* Still empty */ assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); /* Put one back, and the cache should now have one item */ edata_cache_put(TSDN_NULL, &ec, ed1); assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 1, ""); /* Reallocating should reuse the item, and leave an empty cache. */ edata_t *ed1_again = edata_cache_get(TSDN_NULL, &ec); assert_ptr_eq(ed1, ed1_again, ""); assert_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); test_edata_cache_destroy(&ec); } TEST_END static size_t ecf_count(edata_cache_fast_t *ecf) { size_t count = 0; edata_t *cur; ql_foreach(cur, &ecf->list.head, ql_link_inactive) { count++; } return count; } TEST_BEGIN(test_edata_cache_fast_simple) { edata_cache_t ec; edata_cache_fast_t ecf; test_edata_cache_init(&ec); edata_cache_fast_init(&ecf, &ec); edata_t *ed1 = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(ed1, ""); expect_zu_eq(ecf_count(&ecf), 0, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); edata_t *ed2 = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(ed2, ""); expect_zu_eq(ecf_count(&ecf), 0, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); edata_cache_fast_put(TSDN_NULL, &ecf, ed1); expect_zu_eq(ecf_count(&ecf), 1, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); edata_cache_fast_put(TSDN_NULL, &ecf, ed2); expect_zu_eq(ecf_count(&ecf), 2, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); /* LIFO ordering. */ expect_ptr_eq(ed2, edata_cache_fast_get(TSDN_NULL, &ecf), ""); expect_zu_eq(ecf_count(&ecf), 1, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); expect_ptr_eq(ed1, edata_cache_fast_get(TSDN_NULL, &ecf), ""); expect_zu_eq(ecf_count(&ecf), 0, ""); expect_zu_eq(atomic_load_zu(&ec.count, ATOMIC_RELAXED), 0, ""); test_edata_cache_destroy(&ec); } TEST_END TEST_BEGIN(test_edata_cache_fill) { edata_cache_t ec; edata_cache_fast_t ecf; test_edata_cache_init(&ec); edata_cache_fast_init(&ecf, &ec); edata_t *allocs[EDATA_CACHE_FAST_FILL * 2]; /* * If the fallback cache can't satisfy the request, we shouldn't do * extra allocations until compelled to. Put half the fill goal in the * fallback. */ for (int i = 0; i < EDATA_CACHE_FAST_FILL / 2; i++) { allocs[i] = edata_cache_get(TSDN_NULL, &ec); } for (int i = 0; i < EDATA_CACHE_FAST_FILL / 2; i++) { edata_cache_put(TSDN_NULL, &ec, allocs[i]); } expect_zu_eq(EDATA_CACHE_FAST_FILL / 2, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[0] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(EDATA_CACHE_FAST_FILL / 2 - 1, ecf_count(&ecf), "Should have grabbed all edatas available but no more."); for (int i = 1; i < EDATA_CACHE_FAST_FILL / 2; i++) { allocs[i] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(allocs[i], ""); } expect_zu_eq(0, ecf_count(&ecf), ""); /* When forced, we should alloc from the base. */ edata_t *edata = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(edata, ""); expect_zu_eq(0, ecf_count(&ecf), "Allocated more than necessary"); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Allocated more than necessary"); /* * We should correctly fill in the common case where the fallback isn't * exhausted, too. */ for (int i = 0; i < EDATA_CACHE_FAST_FILL * 2; i++) { allocs[i] = edata_cache_get(TSDN_NULL, &ec); expect_ptr_not_null(allocs[i], ""); } for (int i = 0; i < EDATA_CACHE_FAST_FILL * 2; i++) { edata_cache_put(TSDN_NULL, &ec, allocs[i]); } allocs[0] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(EDATA_CACHE_FAST_FILL - 1, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); for (int i = 1; i < EDATA_CACHE_FAST_FILL; i++) { expect_zu_eq(EDATA_CACHE_FAST_FILL - i, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[i] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(allocs[i], ""); } expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[0] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(EDATA_CACHE_FAST_FILL - 1, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); for (int i = 1; i < EDATA_CACHE_FAST_FILL; i++) { expect_zu_eq(EDATA_CACHE_FAST_FILL - i, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); allocs[i] = edata_cache_fast_get(TSDN_NULL, &ecf); expect_ptr_not_null(allocs[i], ""); } expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); test_edata_cache_destroy(&ec); } TEST_END TEST_BEGIN(test_edata_cache_disable) { edata_cache_t ec; edata_cache_fast_t ecf; test_edata_cache_init(&ec); edata_cache_fast_init(&ecf, &ec); for (int i = 0; i < EDATA_CACHE_FAST_FILL; i++) { edata_t *edata = edata_cache_get(TSDN_NULL, &ec); expect_ptr_not_null(edata, ""); edata_cache_fast_put(TSDN_NULL, &ecf, edata); } expect_zu_eq(EDATA_CACHE_FAST_FILL, ecf_count(&ecf), ""); expect_zu_eq(0, atomic_load_zu(&ec.count, ATOMIC_RELAXED), ""); edata_cache_fast_disable(TSDN_NULL, &ecf); expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Disabling should flush"); edata_t *edata = edata_cache_fast_get(TSDN_NULL, &ecf); expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL - 1, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Disabled ecf should forward on get"); edata_cache_fast_put(TSDN_NULL, &ecf, edata); expect_zu_eq(0, ecf_count(&ecf), ""); expect_zu_eq(EDATA_CACHE_FAST_FILL, atomic_load_zu(&ec.count, ATOMIC_RELAXED), "Disabled ecf should forward on put"); test_edata_cache_destroy(&ec); } TEST_END int main(void) { return test( test_edata_cache, test_edata_cache_fast_simple, test_edata_cache_fill, test_edata_cache_disable); } redis-8.0.2/deps/jemalloc/test/unit/emitter.c000066400000000000000000000311761501533116600211340ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/emitter.h" /* * This is so useful for debugging and feature work, we'll leave printing * functionality committed but disabled by default. */ /* Print the text as it will appear. */ static bool print_raw = false; /* Print the text escaped, so it can be copied back into the test case. */ static bool print_escaped = false; typedef struct buf_descriptor_s buf_descriptor_t; struct buf_descriptor_s { char *buf; size_t len; bool mid_quote; }; /* * Forwards all writes to the passed-in buf_v (which should be cast from a * buf_descriptor_t *). */ static void forwarding_cb(void *buf_descriptor_v, const char *str) { buf_descriptor_t *buf_descriptor = (buf_descriptor_t *)buf_descriptor_v; if (print_raw) { malloc_printf("%s", str); } if (print_escaped) { const char *it = str; while (*it != '\0') { if (!buf_descriptor->mid_quote) { malloc_printf("\""); buf_descriptor->mid_quote = true; } switch (*it) { case '\\': malloc_printf("\\"); break; case '\"': malloc_printf("\\\""); break; case '\t': malloc_printf("\\t"); break; case '\n': malloc_printf("\\n\"\n"); buf_descriptor->mid_quote = false; break; default: malloc_printf("%c", *it); } it++; } } size_t written = malloc_snprintf(buf_descriptor->buf, buf_descriptor->len, "%s", str); expect_zu_eq(written, strlen(str), "Buffer overflow!"); buf_descriptor->buf += written; buf_descriptor->len -= written; expect_zu_gt(buf_descriptor->len, 0, "Buffer out of space!"); } static void expect_emit_output(void (*emit_fn)(emitter_t *), const char *expected_json_output, const char *expected_json_compact_output, const char *expected_table_output) { emitter_t emitter; char buf[MALLOC_PRINTF_BUFSIZE]; buf_descriptor_t buf_descriptor; buf_descriptor.buf = buf; buf_descriptor.len = MALLOC_PRINTF_BUFSIZE; buf_descriptor.mid_quote = false; emitter_init(&emitter, emitter_output_json, &forwarding_cb, &buf_descriptor); (*emit_fn)(&emitter); expect_str_eq(expected_json_output, buf, "json output failure"); buf_descriptor.buf = buf; buf_descriptor.len = MALLOC_PRINTF_BUFSIZE; buf_descriptor.mid_quote = false; emitter_init(&emitter, emitter_output_json_compact, &forwarding_cb, &buf_descriptor); (*emit_fn)(&emitter); expect_str_eq(expected_json_compact_output, buf, "compact json output failure"); buf_descriptor.buf = buf; buf_descriptor.len = MALLOC_PRINTF_BUFSIZE; buf_descriptor.mid_quote = false; emitter_init(&emitter, emitter_output_table, &forwarding_cb, &buf_descriptor); (*emit_fn)(&emitter); expect_str_eq(expected_table_output, buf, "table output failure"); } static void emit_dict(emitter_t *emitter) { bool b_false = false; bool b_true = true; int i_123 = 123; const char *str = "a string"; emitter_begin(emitter); emitter_dict_begin(emitter, "foo", "This is the foo table:"); emitter_kv(emitter, "abc", "ABC", emitter_type_bool, &b_false); emitter_kv(emitter, "def", "DEF", emitter_type_bool, &b_true); emitter_kv_note(emitter, "ghi", "GHI", emitter_type_int, &i_123, "note_key1", emitter_type_string, &str); emitter_kv_note(emitter, "jkl", "JKL", emitter_type_string, &str, "note_key2", emitter_type_bool, &b_false); emitter_dict_end(emitter); emitter_end(emitter); } static const char *dict_json = "{\n" "\t\"foo\": {\n" "\t\t\"abc\": false,\n" "\t\t\"def\": true,\n" "\t\t\"ghi\": 123,\n" "\t\t\"jkl\": \"a string\"\n" "\t}\n" "}\n"; static const char *dict_json_compact = "{" "\"foo\":{" "\"abc\":false," "\"def\":true," "\"ghi\":123," "\"jkl\":\"a string\"" "}" "}"; static const char *dict_table = "This is the foo table:\n" " ABC: false\n" " DEF: true\n" " GHI: 123 (note_key1: \"a string\")\n" " JKL: \"a string\" (note_key2: false)\n"; static void emit_table_printf(emitter_t *emitter) { emitter_begin(emitter); emitter_table_printf(emitter, "Table note 1\n"); emitter_table_printf(emitter, "Table note 2 %s\n", "with format string"); emitter_end(emitter); } static const char *table_printf_json = "{\n" "}\n"; static const char *table_printf_json_compact = "{}"; static const char *table_printf_table = "Table note 1\n" "Table note 2 with format string\n"; static void emit_nested_dict(emitter_t *emitter) { int val = 123; emitter_begin(emitter); emitter_dict_begin(emitter, "json1", "Dict 1"); emitter_dict_begin(emitter, "json2", "Dict 2"); emitter_kv(emitter, "primitive", "A primitive", emitter_type_int, &val); emitter_dict_end(emitter); /* Close 2 */ emitter_dict_begin(emitter, "json3", "Dict 3"); emitter_dict_end(emitter); /* Close 3 */ emitter_dict_end(emitter); /* Close 1 */ emitter_dict_begin(emitter, "json4", "Dict 4"); emitter_kv(emitter, "primitive", "Another primitive", emitter_type_int, &val); emitter_dict_end(emitter); /* Close 4 */ emitter_end(emitter); } static const char *nested_dict_json = "{\n" "\t\"json1\": {\n" "\t\t\"json2\": {\n" "\t\t\t\"primitive\": 123\n" "\t\t},\n" "\t\t\"json3\": {\n" "\t\t}\n" "\t},\n" "\t\"json4\": {\n" "\t\t\"primitive\": 123\n" "\t}\n" "}\n"; static const char *nested_dict_json_compact = "{" "\"json1\":{" "\"json2\":{" "\"primitive\":123" "}," "\"json3\":{" "}" "}," "\"json4\":{" "\"primitive\":123" "}" "}"; static const char *nested_dict_table = "Dict 1\n" " Dict 2\n" " A primitive: 123\n" " Dict 3\n" "Dict 4\n" " Another primitive: 123\n"; static void emit_types(emitter_t *emitter) { bool b = false; int i = -123; unsigned u = 123; ssize_t zd = -456; size_t zu = 456; const char *str = "string"; uint32_t u32 = 789; uint64_t u64 = 10000000000ULL; emitter_begin(emitter); emitter_kv(emitter, "k1", "K1", emitter_type_bool, &b); emitter_kv(emitter, "k2", "K2", emitter_type_int, &i); emitter_kv(emitter, "k3", "K3", emitter_type_unsigned, &u); emitter_kv(emitter, "k4", "K4", emitter_type_ssize, &zd); emitter_kv(emitter, "k5", "K5", emitter_type_size, &zu); emitter_kv(emitter, "k6", "K6", emitter_type_string, &str); emitter_kv(emitter, "k7", "K7", emitter_type_uint32, &u32); emitter_kv(emitter, "k8", "K8", emitter_type_uint64, &u64); /* * We don't test the title type, since it's only used for tables. It's * tested in the emitter_table_row tests. */ emitter_end(emitter); } static const char *types_json = "{\n" "\t\"k1\": false,\n" "\t\"k2\": -123,\n" "\t\"k3\": 123,\n" "\t\"k4\": -456,\n" "\t\"k5\": 456,\n" "\t\"k6\": \"string\",\n" "\t\"k7\": 789,\n" "\t\"k8\": 10000000000\n" "}\n"; static const char *types_json_compact = "{" "\"k1\":false," "\"k2\":-123," "\"k3\":123," "\"k4\":-456," "\"k5\":456," "\"k6\":\"string\"," "\"k7\":789," "\"k8\":10000000000" "}"; static const char *types_table = "K1: false\n" "K2: -123\n" "K3: 123\n" "K4: -456\n" "K5: 456\n" "K6: \"string\"\n" "K7: 789\n" "K8: 10000000000\n"; static void emit_modal(emitter_t *emitter) { int val = 123; emitter_begin(emitter); emitter_dict_begin(emitter, "j0", "T0"); emitter_json_key(emitter, "j1"); emitter_json_object_begin(emitter); emitter_kv(emitter, "i1", "I1", emitter_type_int, &val); emitter_json_kv(emitter, "i2", emitter_type_int, &val); emitter_table_kv(emitter, "I3", emitter_type_int, &val); emitter_table_dict_begin(emitter, "T1"); emitter_kv(emitter, "i4", "I4", emitter_type_int, &val); emitter_json_object_end(emitter); /* Close j1 */ emitter_kv(emitter, "i5", "I5", emitter_type_int, &val); emitter_table_dict_end(emitter); /* Close T1 */ emitter_kv(emitter, "i6", "I6", emitter_type_int, &val); emitter_dict_end(emitter); /* Close j0 / T0 */ emitter_end(emitter); } const char *modal_json = "{\n" "\t\"j0\": {\n" "\t\t\"j1\": {\n" "\t\t\t\"i1\": 123,\n" "\t\t\t\"i2\": 123,\n" "\t\t\t\"i4\": 123\n" "\t\t},\n" "\t\t\"i5\": 123,\n" "\t\t\"i6\": 123\n" "\t}\n" "}\n"; const char *modal_json_compact = "{" "\"j0\":{" "\"j1\":{" "\"i1\":123," "\"i2\":123," "\"i4\":123" "}," "\"i5\":123," "\"i6\":123" "}" "}"; const char *modal_table = "T0\n" " I1: 123\n" " I3: 123\n" " T1\n" " I4: 123\n" " I5: 123\n" " I6: 123\n"; static void emit_json_array(emitter_t *emitter) { int ival = 123; emitter_begin(emitter); emitter_json_key(emitter, "dict"); emitter_json_object_begin(emitter); emitter_json_key(emitter, "arr"); emitter_json_array_begin(emitter); emitter_json_object_begin(emitter); emitter_json_kv(emitter, "foo", emitter_type_int, &ival); emitter_json_object_end(emitter); /* Close arr[0] */ /* arr[1] and arr[2] are primitives. */ emitter_json_value(emitter, emitter_type_int, &ival); emitter_json_value(emitter, emitter_type_int, &ival); emitter_json_object_begin(emitter); emitter_json_kv(emitter, "bar", emitter_type_int, &ival); emitter_json_kv(emitter, "baz", emitter_type_int, &ival); emitter_json_object_end(emitter); /* Close arr[3]. */ emitter_json_array_end(emitter); /* Close arr. */ emitter_json_object_end(emitter); /* Close dict. */ emitter_end(emitter); } static const char *json_array_json = "{\n" "\t\"dict\": {\n" "\t\t\"arr\": [\n" "\t\t\t{\n" "\t\t\t\t\"foo\": 123\n" "\t\t\t},\n" "\t\t\t123,\n" "\t\t\t123,\n" "\t\t\t{\n" "\t\t\t\t\"bar\": 123,\n" "\t\t\t\t\"baz\": 123\n" "\t\t\t}\n" "\t\t]\n" "\t}\n" "}\n"; static const char *json_array_json_compact = "{" "\"dict\":{" "\"arr\":[" "{" "\"foo\":123" "}," "123," "123," "{" "\"bar\":123," "\"baz\":123" "}" "]" "}" "}"; static const char *json_array_table = ""; static void emit_json_nested_array(emitter_t *emitter) { int ival = 123; char *sval = "foo"; emitter_begin(emitter); emitter_json_array_begin(emitter); emitter_json_array_begin(emitter); emitter_json_value(emitter, emitter_type_int, &ival); emitter_json_value(emitter, emitter_type_string, &sval); emitter_json_value(emitter, emitter_type_int, &ival); emitter_json_value(emitter, emitter_type_string, &sval); emitter_json_array_end(emitter); emitter_json_array_begin(emitter); emitter_json_value(emitter, emitter_type_int, &ival); emitter_json_array_end(emitter); emitter_json_array_begin(emitter); emitter_json_value(emitter, emitter_type_string, &sval); emitter_json_value(emitter, emitter_type_int, &ival); emitter_json_array_end(emitter); emitter_json_array_begin(emitter); emitter_json_array_end(emitter); emitter_json_array_end(emitter); emitter_end(emitter); } static const char *json_nested_array_json = "{\n" "\t[\n" "\t\t[\n" "\t\t\t123,\n" "\t\t\t\"foo\",\n" "\t\t\t123,\n" "\t\t\t\"foo\"\n" "\t\t],\n" "\t\t[\n" "\t\t\t123\n" "\t\t],\n" "\t\t[\n" "\t\t\t\"foo\",\n" "\t\t\t123\n" "\t\t],\n" "\t\t[\n" "\t\t]\n" "\t]\n" "}\n"; static const char *json_nested_array_json_compact = "{" "[" "[" "123," "\"foo\"," "123," "\"foo\"" "]," "[" "123" "]," "[" "\"foo\"," "123" "]," "[" "]" "]" "}"; static const char *json_nested_array_table = ""; static void emit_table_row(emitter_t *emitter) { emitter_begin(emitter); emitter_row_t row; emitter_col_t abc = {emitter_justify_left, 10, emitter_type_title, {0}, {0, 0}}; abc.str_val = "ABC title"; emitter_col_t def = {emitter_justify_right, 15, emitter_type_title, {0}, {0, 0}}; def.str_val = "DEF title"; emitter_col_t ghi = {emitter_justify_right, 5, emitter_type_title, {0}, {0, 0}}; ghi.str_val = "GHI"; emitter_row_init(&row); emitter_col_init(&abc, &row); emitter_col_init(&def, &row); emitter_col_init(&ghi, &row); emitter_table_row(emitter, &row); abc.type = emitter_type_int; def.type = emitter_type_bool; ghi.type = emitter_type_int; abc.int_val = 123; def.bool_val = true; ghi.int_val = 456; emitter_table_row(emitter, &row); abc.int_val = 789; def.bool_val = false; ghi.int_val = 1011; emitter_table_row(emitter, &row); abc.type = emitter_type_string; abc.str_val = "a string"; def.bool_val = false; ghi.type = emitter_type_title; ghi.str_val = "ghi"; emitter_table_row(emitter, &row); emitter_end(emitter); } static const char *table_row_json = "{\n" "}\n"; static const char *table_row_json_compact = "{}"; static const char *table_row_table = "ABC title DEF title GHI\n" "123 true 456\n" "789 false 1011\n" "\"a string\" false ghi\n"; #define GENERATE_TEST(feature) \ TEST_BEGIN(test_##feature) { \ expect_emit_output(emit_##feature, feature##_json, \ feature##_json_compact, feature##_table); \ } \ TEST_END GENERATE_TEST(dict) GENERATE_TEST(table_printf) GENERATE_TEST(nested_dict) GENERATE_TEST(types) GENERATE_TEST(modal) GENERATE_TEST(json_array) GENERATE_TEST(json_nested_array) GENERATE_TEST(table_row) int main(void) { return test_no_reentrancy( test_dict, test_table_printf, test_nested_dict, test_types, test_modal, test_json_array, test_json_nested_array, test_table_row); } redis-8.0.2/deps/jemalloc/test/unit/extent_quantize.c000066400000000000000000000102761501533116600227100ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_small_extent_size) { unsigned nbins, i; size_t sz, extent_size; size_t mib[4]; size_t miblen = sizeof(mib) / sizeof(size_t); /* * Iterate over all small size classes, get their extent sizes, and * verify that the quantized size is the same as the extent size. */ sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.nbins", (void *)&nbins, &sz, NULL, 0), 0, "Unexpected mallctl failure"); expect_d_eq(mallctlnametomib("arenas.bin.0.slab_size", mib, &miblen), 0, "Unexpected mallctlnametomib failure"); for (i = 0; i < nbins; i++) { mib[2] = i; sz = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&extent_size, &sz, NULL, 0), 0, "Unexpected mallctlbymib failure"); expect_zu_eq(extent_size, sz_psz_quantize_floor(extent_size), "Small extent quantization should be a no-op " "(extent_size=%zu)", extent_size); expect_zu_eq(extent_size, sz_psz_quantize_ceil(extent_size), "Small extent quantization should be a no-op " "(extent_size=%zu)", extent_size); } } TEST_END TEST_BEGIN(test_large_extent_size) { bool cache_oblivious; unsigned nlextents, i; size_t sz, extent_size_prev, ceil_prev; size_t mib[4]; size_t miblen = sizeof(mib) / sizeof(size_t); /* * Iterate over all large size classes, get their extent sizes, and * verify that the quantized size is the same as the extent size. */ sz = sizeof(bool); expect_d_eq(mallctl("opt.cache_oblivious", (void *)&cache_oblivious, &sz, NULL, 0), 0, "Unexpected mallctl failure"); sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.nlextents", (void *)&nlextents, &sz, NULL, 0), 0, "Unexpected mallctl failure"); expect_d_eq(mallctlnametomib("arenas.lextent.0.size", mib, &miblen), 0, "Unexpected mallctlnametomib failure"); for (i = 0; i < nlextents; i++) { size_t lextent_size, extent_size, floor, ceil; mib[2] = i; sz = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&lextent_size, &sz, NULL, 0), 0, "Unexpected mallctlbymib failure"); extent_size = cache_oblivious ? lextent_size + PAGE : lextent_size; floor = sz_psz_quantize_floor(extent_size); ceil = sz_psz_quantize_ceil(extent_size); expect_zu_eq(extent_size, floor, "Extent quantization should be a no-op for precise size " "(lextent_size=%zu, extent_size=%zu)", lextent_size, extent_size); expect_zu_eq(extent_size, ceil, "Extent quantization should be a no-op for precise size " "(lextent_size=%zu, extent_size=%zu)", lextent_size, extent_size); if (i > 0) { expect_zu_eq(extent_size_prev, sz_psz_quantize_floor(extent_size - PAGE), "Floor should be a precise size"); if (extent_size_prev < ceil_prev) { expect_zu_eq(ceil_prev, extent_size, "Ceiling should be a precise size " "(extent_size_prev=%zu, ceil_prev=%zu, " "extent_size=%zu)", extent_size_prev, ceil_prev, extent_size); } } if (i + 1 < nlextents) { extent_size_prev = floor; ceil_prev = sz_psz_quantize_ceil(extent_size + PAGE); } } } TEST_END TEST_BEGIN(test_monotonic) { #define SZ_MAX ZU(4 * 1024 * 1024) unsigned i; size_t floor_prev, ceil_prev; floor_prev = 0; ceil_prev = 0; for (i = 1; i <= SZ_MAX >> LG_PAGE; i++) { size_t extent_size, floor, ceil; extent_size = i << LG_PAGE; floor = sz_psz_quantize_floor(extent_size); ceil = sz_psz_quantize_ceil(extent_size); expect_zu_le(floor, extent_size, "Floor should be <= (floor=%zu, extent_size=%zu, ceil=%zu)", floor, extent_size, ceil); expect_zu_ge(ceil, extent_size, "Ceiling should be >= (floor=%zu, extent_size=%zu, " "ceil=%zu)", floor, extent_size, ceil); expect_zu_le(floor_prev, floor, "Floor should be monotonic " "(floor_prev=%zu, floor=%zu, extent_size=%zu, ceil=%zu)", floor_prev, floor, extent_size, ceil); expect_zu_le(ceil_prev, ceil, "Ceiling should be monotonic " "(floor=%zu, extent_size=%zu, ceil_prev=%zu, ceil=%zu)", floor, extent_size, ceil_prev, ceil); floor_prev = floor; ceil_prev = ceil; } } TEST_END int main(void) { return test( test_small_extent_size, test_large_extent_size, test_monotonic); } redis-8.0.2/deps/jemalloc/test/unit/fb.c000066400000000000000000000663071501533116600200560ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/fb.h" #include "test/nbits.h" static void do_test_init(size_t nbits) { size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb = malloc(sz); /* Junk fb's contents. */ memset(fb, 99, sz); fb_init(fb, nbits); for (size_t i = 0; i < nbits; i++) { expect_false(fb_get(fb, nbits, i), "bitmap should start empty"); } free(fb); } TEST_BEGIN(test_fb_init) { #define NB(nbits) \ do_test_init(nbits); NBITS_TAB #undef NB } TEST_END static void do_test_get_set_unset(size_t nbits) { size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb = malloc(sz); fb_init(fb, nbits); /* Set the bits divisible by 3. */ for (size_t i = 0; i < nbits; i++) { if (i % 3 == 0) { fb_set(fb, nbits, i); } } /* Check them. */ for (size_t i = 0; i < nbits; i++) { expect_b_eq(i % 3 == 0, fb_get(fb, nbits, i), "Unexpected bit at position %zu", i); } /* Unset those divisible by 5. */ for (size_t i = 0; i < nbits; i++) { if (i % 5 == 0) { fb_unset(fb, nbits, i); } } /* Check them. */ for (size_t i = 0; i < nbits; i++) { expect_b_eq(i % 3 == 0 && i % 5 != 0, fb_get(fb, nbits, i), "Unexpected bit at position %zu", i); } free(fb); } TEST_BEGIN(test_get_set_unset) { #define NB(nbits) \ do_test_get_set_unset(nbits); NBITS_TAB #undef NB } TEST_END static ssize_t find_3_5_compute(ssize_t i, size_t nbits, bool bit, bool forward) { for(; i < (ssize_t)nbits && i >= 0; i += (forward ? 1 : -1)) { bool expected_bit = i % 3 == 0 || i % 5 == 0; if (expected_bit == bit) { return i; } } return forward ? (ssize_t)nbits : (ssize_t)-1; } static void do_test_search_simple(size_t nbits) { size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb = malloc(sz); fb_init(fb, nbits); /* We pick multiples of 3 or 5. */ for (size_t i = 0; i < nbits; i++) { if (i % 3 == 0) { fb_set(fb, nbits, i); } /* This tests double-setting a little, too. */ if (i % 5 == 0) { fb_set(fb, nbits, i); } } for (size_t i = 0; i < nbits; i++) { size_t ffs_compute = find_3_5_compute(i, nbits, true, true); size_t ffs_search = fb_ffs(fb, nbits, i); expect_zu_eq(ffs_compute, ffs_search, "ffs mismatch at %zu", i); ssize_t fls_compute = find_3_5_compute(i, nbits, true, false); size_t fls_search = fb_fls(fb, nbits, i); expect_zu_eq(fls_compute, fls_search, "fls mismatch at %zu", i); size_t ffu_compute = find_3_5_compute(i, nbits, false, true); size_t ffu_search = fb_ffu(fb, nbits, i); expect_zu_eq(ffu_compute, ffu_search, "ffu mismatch at %zu", i); size_t flu_compute = find_3_5_compute(i, nbits, false, false); size_t flu_search = fb_flu(fb, nbits, i); expect_zu_eq(flu_compute, flu_search, "flu mismatch at %zu", i); } free(fb); } TEST_BEGIN(test_search_simple) { #define NB(nbits) \ do_test_search_simple(nbits); NBITS_TAB #undef NB } TEST_END static void expect_exhaustive_results(fb_group_t *mostly_full, fb_group_t *mostly_empty, size_t nbits, size_t special_bit, size_t position) { if (position < special_bit) { expect_zu_eq(special_bit, fb_ffs(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(-1, fb_fls(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position, fb_ffu(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position, fb_flu(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position, fb_ffs(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position, fb_fls(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(special_bit, fb_ffu(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(-1, fb_flu(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); } else if (position == special_bit) { expect_zu_eq(special_bit, fb_ffs(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(special_bit, fb_fls(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position + 1, fb_ffu(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position - 1, fb_flu(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position + 1, fb_ffs(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position - 1, fb_fls(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position, fb_ffu(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position, fb_flu(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); } else { /* position > special_bit. */ expect_zu_eq(nbits, fb_ffs(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(special_bit, fb_fls(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position, fb_ffu(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position, fb_flu(mostly_empty, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(position, fb_ffs(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(position, fb_fls(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zu_eq(nbits, fb_ffu(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); expect_zd_eq(special_bit, fb_flu(mostly_full, nbits, position), "mismatch at %zu, %zu", position, special_bit); } } static void do_test_search_exhaustive(size_t nbits) { /* This test is quadratic; let's not get too big. */ if (nbits > 1000) { return; } size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *empty = malloc(sz); fb_init(empty, nbits); fb_group_t *full = malloc(sz); fb_init(full, nbits); fb_set_range(full, nbits, 0, nbits); for (size_t i = 0; i < nbits; i++) { fb_set(empty, nbits, i); fb_unset(full, nbits, i); for (size_t j = 0; j < nbits; j++) { expect_exhaustive_results(full, empty, nbits, i, j); } fb_unset(empty, nbits, i); fb_set(full, nbits, i); } free(empty); free(full); } TEST_BEGIN(test_search_exhaustive) { #define NB(nbits) \ do_test_search_exhaustive(nbits); NBITS_TAB #undef NB } TEST_END TEST_BEGIN(test_range_simple) { /* * Just pick a constant big enough to have nontrivial middle sizes, and * big enough that usages of things like weirdnum (below) near the * beginning fit comfortably into the beginning of the bitmap. */ size_t nbits = 64 * 10; size_t ngroups = FB_NGROUPS(nbits); fb_group_t *fb = malloc(sizeof(fb_group_t) * ngroups); fb_init(fb, nbits); for (size_t i = 0; i < nbits; i++) { if (i % 2 == 0) { fb_set_range(fb, nbits, i, 1); } } for (size_t i = 0; i < nbits; i++) { expect_b_eq(i % 2 == 0, fb_get(fb, nbits, i), "mismatch at position %zu", i); } fb_set_range(fb, nbits, 0, nbits / 2); fb_unset_range(fb, nbits, nbits / 2, nbits / 2); for (size_t i = 0; i < nbits; i++) { expect_b_eq(i < nbits / 2, fb_get(fb, nbits, i), "mismatch at position %zu", i); } static const size_t weirdnum = 7; fb_set_range(fb, nbits, 0, nbits); fb_unset_range(fb, nbits, weirdnum, FB_GROUP_BITS + weirdnum); for (size_t i = 0; i < nbits; i++) { expect_b_eq(7 <= i && i <= 2 * weirdnum + FB_GROUP_BITS - 1, !fb_get(fb, nbits, i), "mismatch at position %zu", i); } free(fb); } TEST_END static void do_test_empty_full_exhaustive(size_t nbits) { size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *empty = malloc(sz); fb_init(empty, nbits); fb_group_t *full = malloc(sz); fb_init(full, nbits); fb_set_range(full, nbits, 0, nbits); expect_true(fb_full(full, nbits), ""); expect_false(fb_empty(full, nbits), ""); expect_false(fb_full(empty, nbits), ""); expect_true(fb_empty(empty, nbits), ""); for (size_t i = 0; i < nbits; i++) { fb_set(empty, nbits, i); fb_unset(full, nbits, i); expect_false(fb_empty(empty, nbits), "error at bit %zu", i); if (nbits != 1) { expect_false(fb_full(empty, nbits), "error at bit %zu", i); expect_false(fb_empty(full, nbits), "error at bit %zu", i); } else { expect_true(fb_full(empty, nbits), "error at bit %zu", i); expect_true(fb_empty(full, nbits), "error at bit %zu", i); } expect_false(fb_full(full, nbits), "error at bit %zu", i); fb_unset(empty, nbits, i); fb_set(full, nbits, i); } free(empty); free(full); } TEST_BEGIN(test_empty_full) { #define NB(nbits) \ do_test_empty_full_exhaustive(nbits); NBITS_TAB #undef NB } TEST_END /* * This tests both iter_range and the longest range functionality, which is * built closely on top of it. */ TEST_BEGIN(test_iter_range_simple) { size_t set_limit = 30; size_t nbits = 100; fb_group_t fb[FB_NGROUPS(100)]; fb_init(fb, nbits); /* * Failing to initialize these can lead to build failures with -Wall; * the compiler can't prove that they're set. */ size_t begin = (size_t)-1; size_t len = (size_t)-1; bool result; /* A set of checks with only the first set_limit bits *set*. */ fb_set_range(fb, nbits, 0, set_limit); expect_zu_eq(set_limit, fb_srange_longest(fb, nbits), "Incorrect longest set range"); expect_zu_eq(nbits - set_limit, fb_urange_longest(fb, nbits), "Incorrect longest unset range"); for (size_t i = 0; i < set_limit; i++) { result = fb_srange_iter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(i, begin, "Incorrect begin at %zu", i); expect_zu_eq(set_limit - i, len, "Incorrect len at %zu", i); result = fb_urange_iter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(set_limit, begin, "Incorrect begin at %zu", i); expect_zu_eq(nbits - set_limit, len, "Incorrect len at %zu", i); result = fb_srange_riter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(0, begin, "Incorrect begin at %zu", i); expect_zu_eq(i + 1, len, "Incorrect len at %zu", i); result = fb_urange_riter(fb, nbits, i, &begin, &len); expect_false(result, "Should not have found a range at %zu", i); } for (size_t i = set_limit; i < nbits; i++) { result = fb_srange_iter(fb, nbits, i, &begin, &len); expect_false(result, "Should not have found a range at %zu", i); result = fb_urange_iter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(i, begin, "Incorrect begin at %zu", i); expect_zu_eq(nbits - i, len, "Incorrect len at %zu", i); result = fb_srange_riter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(0, begin, "Incorrect begin at %zu", i); expect_zu_eq(set_limit, len, "Incorrect len at %zu", i); result = fb_urange_riter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(set_limit, begin, "Incorrect begin at %zu", i); expect_zu_eq(i - set_limit + 1, len, "Incorrect len at %zu", i); } /* A set of checks with only the first set_limit bits *unset*. */ fb_unset_range(fb, nbits, 0, set_limit); fb_set_range(fb, nbits, set_limit, nbits - set_limit); expect_zu_eq(nbits - set_limit, fb_srange_longest(fb, nbits), "Incorrect longest set range"); expect_zu_eq(set_limit, fb_urange_longest(fb, nbits), "Incorrect longest unset range"); for (size_t i = 0; i < set_limit; i++) { result = fb_srange_iter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(set_limit, begin, "Incorrect begin at %zu", i); expect_zu_eq(nbits - set_limit, len, "Incorrect len at %zu", i); result = fb_urange_iter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(i, begin, "Incorrect begin at %zu", i); expect_zu_eq(set_limit - i, len, "Incorrect len at %zu", i); result = fb_srange_riter(fb, nbits, i, &begin, &len); expect_false(result, "Should not have found a range at %zu", i); result = fb_urange_riter(fb, nbits, i, &begin, &len); expect_true(result, "Should not have found a range at %zu", i); expect_zu_eq(0, begin, "Incorrect begin at %zu", i); expect_zu_eq(i + 1, len, "Incorrect len at %zu", i); } for (size_t i = set_limit; i < nbits; i++) { result = fb_srange_iter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(i, begin, "Incorrect begin at %zu", i); expect_zu_eq(nbits - i, len, "Incorrect len at %zu", i); result = fb_urange_iter(fb, nbits, i, &begin, &len); expect_false(result, "Should not have found a range at %zu", i); result = fb_srange_riter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(set_limit, begin, "Incorrect begin at %zu", i); expect_zu_eq(i - set_limit + 1, len, "Incorrect len at %zu", i); result = fb_urange_riter(fb, nbits, i, &begin, &len); expect_true(result, "Should have found a range at %zu", i); expect_zu_eq(0, begin, "Incorrect begin at %zu", i); expect_zu_eq(set_limit, len, "Incorrect len at %zu", i); } } TEST_END /* * Doing this bit-by-bit is too slow for a real implementation, but for testing * code, it's easy to get right. In the exhaustive tests, we'll compare the * (fast but tricky) real implementation against the (slow but simple) testing * one. */ static bool fb_iter_simple(fb_group_t *fb, size_t nbits, size_t start, size_t *r_begin, size_t *r_len, bool val, bool forward) { ssize_t stride = (forward ? (ssize_t)1 : (ssize_t)-1); ssize_t range_begin = (ssize_t)start; for (; range_begin != (ssize_t)nbits && range_begin != -1; range_begin += stride) { if (fb_get(fb, nbits, range_begin) == val) { ssize_t range_end = range_begin; for (; range_end != (ssize_t)nbits && range_end != -1; range_end += stride) { if (fb_get(fb, nbits, range_end) != val) { break; } } if (forward) { *r_begin = range_begin; *r_len = range_end - range_begin; } else { *r_begin = range_end + 1; *r_len = range_begin - range_end; } return true; } } return false; } /* Similar, but for finding longest ranges. */ static size_t fb_range_longest_simple(fb_group_t *fb, size_t nbits, bool val) { size_t longest_so_far = 0; for (size_t begin = 0; begin < nbits; begin++) { if (fb_get(fb, nbits, begin) != val) { continue; } size_t end = begin + 1; for (; end < nbits; end++) { if (fb_get(fb, nbits, end) != val) { break; } } if (end - begin > longest_so_far) { longest_so_far = end - begin; } } return longest_so_far; } static void expect_iter_results_at(fb_group_t *fb, size_t nbits, size_t pos, bool val, bool forward) { bool iter_res; size_t iter_begin JEMALLOC_CC_SILENCE_INIT(0); size_t iter_len JEMALLOC_CC_SILENCE_INIT(0); if (val) { if (forward) { iter_res = fb_srange_iter(fb, nbits, pos, &iter_begin, &iter_len); } else { iter_res = fb_srange_riter(fb, nbits, pos, &iter_begin, &iter_len); } } else { if (forward) { iter_res = fb_urange_iter(fb, nbits, pos, &iter_begin, &iter_len); } else { iter_res = fb_urange_riter(fb, nbits, pos, &iter_begin, &iter_len); } } bool simple_iter_res; /* * These are dead stores, but the compiler can't always figure that out * statically, and warns on the uninitialized variable. */ size_t simple_iter_begin = 0; size_t simple_iter_len = 0; simple_iter_res = fb_iter_simple(fb, nbits, pos, &simple_iter_begin, &simple_iter_len, val, forward); expect_b_eq(iter_res, simple_iter_res, "Result mismatch at %zu", pos); if (iter_res && simple_iter_res) { assert_zu_eq(iter_begin, simple_iter_begin, "Begin mismatch at %zu", pos); expect_zu_eq(iter_len, simple_iter_len, "Length mismatch at %zu", pos); } } static void expect_iter_results(fb_group_t *fb, size_t nbits) { for (size_t i = 0; i < nbits; i++) { expect_iter_results_at(fb, nbits, i, false, false); expect_iter_results_at(fb, nbits, i, false, true); expect_iter_results_at(fb, nbits, i, true, false); expect_iter_results_at(fb, nbits, i, true, true); } expect_zu_eq(fb_range_longest_simple(fb, nbits, true), fb_srange_longest(fb, nbits), "Longest range mismatch"); expect_zu_eq(fb_range_longest_simple(fb, nbits, false), fb_urange_longest(fb, nbits), "Longest range mismatch"); } static void set_pattern_3(fb_group_t *fb, size_t nbits, bool zero_val) { for (size_t i = 0; i < nbits; i++) { if ((i % 6 < 3 && zero_val) || (i % 6 >= 3 && !zero_val)) { fb_set(fb, nbits, i); } else { fb_unset(fb, nbits, i); } } } static void do_test_iter_range_exhaustive(size_t nbits) { /* This test is also pretty slow. */ if (nbits > 1000) { return; } size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb = malloc(sz); fb_init(fb, nbits); set_pattern_3(fb, nbits, /* zero_val */ true); expect_iter_results(fb, nbits); set_pattern_3(fb, nbits, /* zero_val */ false); expect_iter_results(fb, nbits); fb_set_range(fb, nbits, 0, nbits); fb_unset_range(fb, nbits, 0, nbits / 2 == 0 ? 1 : nbits / 2); expect_iter_results(fb, nbits); fb_unset_range(fb, nbits, 0, nbits); fb_set_range(fb, nbits, 0, nbits / 2 == 0 ? 1: nbits / 2); expect_iter_results(fb, nbits); free(fb); } /* * Like test_iter_range_simple, this tests both iteration and longest-range * computation. */ TEST_BEGIN(test_iter_range_exhaustive) { #define NB(nbits) \ do_test_iter_range_exhaustive(nbits); NBITS_TAB #undef NB } TEST_END /* * If all set bits in the bitmap are contiguous, in [set_start, set_end), * returns the number of set bits in [scount_start, scount_end). */ static size_t scount_contiguous(size_t set_start, size_t set_end, size_t scount_start, size_t scount_end) { /* No overlap. */ if (set_end <= scount_start || scount_end <= set_start) { return 0; } /* set range contains scount range */ if (set_start <= scount_start && set_end >= scount_end) { return scount_end - scount_start; } /* scount range contains set range. */ if (scount_start <= set_start && scount_end >= set_end) { return set_end - set_start; } /* Partial overlap, with set range starting first. */ if (set_start < scount_start && set_end < scount_end) { return set_end - scount_start; } /* Partial overlap, with scount range starting first. */ if (scount_start < set_start && scount_end < set_end) { return scount_end - set_start; } /* * Trigger an assert failure; the above list should have been * exhaustive. */ unreachable(); } static size_t ucount_contiguous(size_t set_start, size_t set_end, size_t ucount_start, size_t ucount_end) { /* No overlap. */ if (set_end <= ucount_start || ucount_end <= set_start) { return ucount_end - ucount_start; } /* set range contains ucount range */ if (set_start <= ucount_start && set_end >= ucount_end) { return 0; } /* ucount range contains set range. */ if (ucount_start <= set_start && ucount_end >= set_end) { return (ucount_end - ucount_start) - (set_end - set_start); } /* Partial overlap, with set range starting first. */ if (set_start < ucount_start && set_end < ucount_end) { return ucount_end - set_end; } /* Partial overlap, with ucount range starting first. */ if (ucount_start < set_start && ucount_end < set_end) { return set_start - ucount_start; } /* * Trigger an assert failure; the above list should have been * exhaustive. */ unreachable(); } static void expect_count_match_contiguous(fb_group_t *fb, size_t nbits, size_t set_start, size_t set_end) { for (size_t i = 0; i < nbits; i++) { for (size_t j = i + 1; j <= nbits; j++) { size_t cnt = j - i; size_t scount_expected = scount_contiguous(set_start, set_end, i, j); size_t scount_computed = fb_scount(fb, nbits, i, cnt); expect_zu_eq(scount_expected, scount_computed, "fb_scount error with nbits=%zu, start=%zu, " "cnt=%zu, with bits set in [%zu, %zu)", nbits, i, cnt, set_start, set_end); size_t ucount_expected = ucount_contiguous(set_start, set_end, i, j); size_t ucount_computed = fb_ucount(fb, nbits, i, cnt); assert_zu_eq(ucount_expected, ucount_computed, "fb_ucount error with nbits=%zu, start=%zu, " "cnt=%zu, with bits set in [%zu, %zu)", nbits, i, cnt, set_start, set_end); } } } static void do_test_count_contiguous(size_t nbits) { size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb = malloc(sz); fb_init(fb, nbits); expect_count_match_contiguous(fb, nbits, 0, 0); for (size_t i = 0; i < nbits; i++) { fb_set(fb, nbits, i); expect_count_match_contiguous(fb, nbits, 0, i + 1); } for (size_t i = 0; i < nbits; i++) { fb_unset(fb, nbits, i); expect_count_match_contiguous(fb, nbits, i + 1, nbits); } free(fb); } TEST_BEGIN(test_count_contiguous_simple) { enum {nbits = 300}; fb_group_t fb[FB_NGROUPS(nbits)]; fb_init(fb, nbits); /* Just an arbitrary number. */ size_t start = 23; fb_set_range(fb, nbits, start, 30 - start); expect_count_match_contiguous(fb, nbits, start, 30); fb_set_range(fb, nbits, start, 40 - start); expect_count_match_contiguous(fb, nbits, start, 40); fb_set_range(fb, nbits, start, 70 - start); expect_count_match_contiguous(fb, nbits, start, 70); fb_set_range(fb, nbits, start, 120 - start); expect_count_match_contiguous(fb, nbits, start, 120); fb_set_range(fb, nbits, start, 150 - start); expect_count_match_contiguous(fb, nbits, start, 150); fb_set_range(fb, nbits, start, 200 - start); expect_count_match_contiguous(fb, nbits, start, 200); fb_set_range(fb, nbits, start, 290 - start); expect_count_match_contiguous(fb, nbits, start, 290); } TEST_END TEST_BEGIN(test_count_contiguous) { #define NB(nbits) \ /* This test is *particularly* slow in debug builds. */ \ if ((!config_debug && nbits < 300) || nbits < 150) { \ do_test_count_contiguous(nbits); \ } NBITS_TAB #undef NB } TEST_END static void expect_count_match_alternating(fb_group_t *fb_even, fb_group_t *fb_odd, size_t nbits) { for (size_t i = 0; i < nbits; i++) { for (size_t j = i + 1; j <= nbits; j++) { size_t cnt = j - i; size_t odd_scount = cnt / 2 + (size_t)(cnt % 2 == 1 && i % 2 == 1); size_t odd_scount_computed = fb_scount(fb_odd, nbits, i, j - i); assert_zu_eq(odd_scount, odd_scount_computed, "fb_scount error with nbits=%zu, start=%zu, " "cnt=%zu, with alternating bits set.", nbits, i, j - i); size_t odd_ucount = cnt / 2 + (size_t)(cnt % 2 == 1 && i % 2 == 0); size_t odd_ucount_computed = fb_ucount(fb_odd, nbits, i, j - i); assert_zu_eq(odd_ucount, odd_ucount_computed, "fb_ucount error with nbits=%zu, start=%zu, " "cnt=%zu, with alternating bits set.", nbits, i, j - i); size_t even_scount = cnt / 2 + (size_t)(cnt % 2 == 1 && i % 2 == 0); size_t even_scount_computed = fb_scount(fb_even, nbits, i, j - i); assert_zu_eq(even_scount, even_scount_computed, "fb_scount error with nbits=%zu, start=%zu, " "cnt=%zu, with alternating bits set.", nbits, i, j - i); size_t even_ucount = cnt / 2 + (size_t)(cnt % 2 == 1 && i % 2 == 1); size_t even_ucount_computed = fb_ucount(fb_even, nbits, i, j - i); assert_zu_eq(even_ucount, even_ucount_computed, "fb_ucount error with nbits=%zu, start=%zu, " "cnt=%zu, with alternating bits set.", nbits, i, j - i); } } } static void do_test_count_alternating(size_t nbits) { if (nbits > 1000) { return; } size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb_even = malloc(sz); fb_group_t *fb_odd = malloc(sz); fb_init(fb_even, nbits); fb_init(fb_odd, nbits); for (size_t i = 0; i < nbits; i++) { if (i % 2 == 0) { fb_set(fb_even, nbits, i); } else { fb_set(fb_odd, nbits, i); } } expect_count_match_alternating(fb_even, fb_odd, nbits); free(fb_even); free(fb_odd); } TEST_BEGIN(test_count_alternating) { #define NB(nbits) \ do_test_count_alternating(nbits); NBITS_TAB #undef NB } TEST_END static void do_test_bit_op(size_t nbits, bool (*op)(bool a, bool b), void (*fb_op)(fb_group_t *dst, fb_group_t *src1, fb_group_t *src2, size_t nbits)) { size_t sz = FB_NGROUPS(nbits) * sizeof(fb_group_t); fb_group_t *fb1 = malloc(sz); fb_group_t *fb2 = malloc(sz); fb_group_t *fb_result = malloc(sz); fb_init(fb1, nbits); fb_init(fb2, nbits); fb_init(fb_result, nbits); /* Just two random numbers. */ const uint64_t prng_init1 = (uint64_t)0X4E9A9DE6A35691CDULL; const uint64_t prng_init2 = (uint64_t)0X7856E396B063C36EULL; uint64_t prng1 = prng_init1; uint64_t prng2 = prng_init2; for (size_t i = 0; i < nbits; i++) { bool bit1 = ((prng1 & (1ULL << (i % 64))) != 0); bool bit2 = ((prng2 & (1ULL << (i % 64))) != 0); if (bit1) { fb_set(fb1, nbits, i); } if (bit2) { fb_set(fb2, nbits, i); } if (i % 64 == 0) { prng1 = prng_state_next_u64(prng1); prng2 = prng_state_next_u64(prng2); } } fb_op(fb_result, fb1, fb2, nbits); /* Reset the prngs to replay them. */ prng1 = prng_init1; prng2 = prng_init2; for (size_t i = 0; i < nbits; i++) { bool bit1 = ((prng1 & (1ULL << (i % 64))) != 0); bool bit2 = ((prng2 & (1ULL << (i % 64))) != 0); /* Original bitmaps shouldn't change. */ expect_b_eq(bit1, fb_get(fb1, nbits, i), "difference at bit %zu", i); expect_b_eq(bit2, fb_get(fb2, nbits, i), "difference at bit %zu", i); /* New one should be bitwise and. */ expect_b_eq(op(bit1, bit2), fb_get(fb_result, nbits, i), "difference at bit %zu", i); /* Update the same way we did last time. */ if (i % 64 == 0) { prng1 = prng_state_next_u64(prng1); prng2 = prng_state_next_u64(prng2); } } free(fb1); free(fb2); free(fb_result); } static bool binary_and(bool a, bool b) { return a & b; } static void do_test_bit_and(size_t nbits) { do_test_bit_op(nbits, &binary_and, &fb_bit_and); } TEST_BEGIN(test_bit_and) { #define NB(nbits) \ do_test_bit_and(nbits); NBITS_TAB #undef NB } TEST_END static bool binary_or(bool a, bool b) { return a | b; } static void do_test_bit_or(size_t nbits) { do_test_bit_op(nbits, &binary_or, &fb_bit_or); } TEST_BEGIN(test_bit_or) { #define NB(nbits) \ do_test_bit_or(nbits); NBITS_TAB #undef NB } TEST_END static bool binary_not(bool a, bool b) { (void)b; return !a; } static void fb_bit_not_shim(fb_group_t *dst, fb_group_t *src1, fb_group_t *src2, size_t nbits) { (void)src2; fb_bit_not(dst, src1, nbits); } static void do_test_bit_not(size_t nbits) { do_test_bit_op(nbits, &binary_not, &fb_bit_not_shim); } TEST_BEGIN(test_bit_not) { #define NB(nbits) \ do_test_bit_not(nbits); NBITS_TAB #undef NB } TEST_END int main(void) { return test_no_reentrancy( test_fb_init, test_get_set_unset, test_search_simple, test_search_exhaustive, test_range_simple, test_empty_full, test_iter_range_simple, test_iter_range_exhaustive, test_count_contiguous_simple, test_count_contiguous, test_count_alternating, test_bit_and, test_bit_or, test_bit_not); } redis-8.0.2/deps/jemalloc/test/unit/fork.c000066400000000000000000000056571501533116600204310ustar00rootroot00000000000000#include "test/jemalloc_test.h" #ifndef _WIN32 #include #endif #ifndef _WIN32 static void wait_for_child_exit(int pid) { int status; while (true) { if (waitpid(pid, &status, 0) == -1) { test_fail("Unexpected waitpid() failure."); } if (WIFSIGNALED(status)) { test_fail("Unexpected child termination due to " "signal %d", WTERMSIG(status)); break; } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { test_fail("Unexpected child exit value %d", WEXITSTATUS(status)); } break; } } } #endif TEST_BEGIN(test_fork) { #ifndef _WIN32 void *p; pid_t pid; /* Set up a manually managed arena for test. */ unsigned arena_ind; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); /* Migrate to the new arena. */ unsigned old_arena_ind; sz = sizeof(old_arena_ind); expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz, (void *)&arena_ind, sizeof(arena_ind)), 0, "Unexpected mallctl() failure"); p = malloc(1); expect_ptr_not_null(p, "Unexpected malloc() failure"); pid = fork(); free(p); p = malloc(64); expect_ptr_not_null(p, "Unexpected malloc() failure"); free(p); if (pid == -1) { /* Error. */ test_fail("Unexpected fork() failure"); } else if (pid == 0) { /* Child. */ _exit(0); } else { wait_for_child_exit(pid); } #else test_skip("fork(2) is irrelevant to Windows"); #endif } TEST_END #ifndef _WIN32 static void * do_fork_thd(void *arg) { malloc(1); int pid = fork(); if (pid == -1) { /* Error. */ test_fail("Unexpected fork() failure"); } else if (pid == 0) { /* Child. */ char *args[] = {"true", NULL}; execvp(args[0], args); test_fail("Exec failed"); } else { /* Parent */ wait_for_child_exit(pid); } return NULL; } #endif #ifndef _WIN32 static void do_test_fork_multithreaded() { thd_t child; thd_create(&child, do_fork_thd, NULL); do_fork_thd(NULL); thd_join(child, NULL); } #endif TEST_BEGIN(test_fork_multithreaded) { #ifndef _WIN32 /* * We've seen bugs involving hanging on arenas_lock (though the same * class of bugs can happen on any mutex). The bugs are intermittent * though, so we want to run the test multiple times. Since we hold the * arenas lock only early in the process lifetime, we can't just run * this test in a loop (since, after all the arenas are initialized, we * won't acquire arenas_lock any further). We therefore repeat the test * with multiple processes. */ for (int i = 0; i < 100; i++) { int pid = fork(); if (pid == -1) { /* Error. */ test_fail("Unexpected fork() failure,"); } else if (pid == 0) { /* Child. */ do_test_fork_multithreaded(); _exit(0); } else { wait_for_child_exit(pid); } } #else test_skip("fork(2) is irrelevant to Windows"); #endif } TEST_END int main(void) { return test_no_reentrancy( test_fork, test_fork_multithreaded); } redis-8.0.2/deps/jemalloc/test/unit/fxp.c000066400000000000000000000256541501533116600202640ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/fxp.h" static double fxp2double(fxp_t a) { double intpart = (double)(a >> 16); double fracpart = (double)(a & ((1U << 16) - 1)) / (1U << 16); return intpart + fracpart; } /* Is a close to b? */ static bool double_close(double a, double b) { /* * Our implementation doesn't try for precision. Correspondingly, don't * enforce it too strenuously here; accept values that are close in * either relative or absolute terms. */ return fabs(a - b) < 0.01 || fabs(a - b) / a < 0.01; } static bool fxp_close(fxp_t a, fxp_t b) { return double_close(fxp2double(a), fxp2double(b)); } static fxp_t xparse_fxp(const char *str) { fxp_t result; bool err = fxp_parse(&result, str, NULL); assert_false(err, "Invalid fxp string: %s", str); return result; } static void expect_parse_accurate(const char *str, const char *parse_str) { double true_val = strtod(str, NULL); fxp_t fxp_val; char *end; bool err = fxp_parse(&fxp_val, parse_str, &end); expect_false(err, "Unexpected parse failure"); expect_ptr_eq(parse_str + strlen(str), end, "Didn't parse whole string"); expect_true(double_close(fxp2double(fxp_val), true_val), "Misparsed %s", str); } static void parse_valid_trial(const char *str) { /* The value it parses should be correct. */ expect_parse_accurate(str, str); char buf[100]; snprintf(buf, sizeof(buf), "%swith_some_trailing_text", str); expect_parse_accurate(str, buf); snprintf(buf, sizeof(buf), "%s with a space", str); expect_parse_accurate(str, buf); snprintf(buf, sizeof(buf), "%s,in_a_malloc_conf_string:1", str); expect_parse_accurate(str, buf); } TEST_BEGIN(test_parse_valid) { parse_valid_trial("0"); parse_valid_trial("1"); parse_valid_trial("2"); parse_valid_trial("100"); parse_valid_trial("345"); parse_valid_trial("00000000123"); parse_valid_trial("00000000987"); parse_valid_trial("0.0"); parse_valid_trial("0.00000000000456456456"); parse_valid_trial("100.00000000000456456456"); parse_valid_trial("123.1"); parse_valid_trial("123.01"); parse_valid_trial("123.001"); parse_valid_trial("123.0001"); parse_valid_trial("123.00001"); parse_valid_trial("123.000001"); parse_valid_trial("123.0000001"); parse_valid_trial(".0"); parse_valid_trial(".1"); parse_valid_trial(".01"); parse_valid_trial(".001"); parse_valid_trial(".0001"); parse_valid_trial(".00001"); parse_valid_trial(".000001"); parse_valid_trial(".1"); parse_valid_trial(".10"); parse_valid_trial(".100"); parse_valid_trial(".1000"); parse_valid_trial(".100000"); } TEST_END static void expect_parse_failure(const char *str) { fxp_t result = FXP_INIT_INT(333); char *end = (void *)0x123; bool err = fxp_parse(&result, str, &end); expect_true(err, "Expected a parse error on: %s", str); expect_ptr_eq((void *)0x123, end, "Parse error shouldn't change results"); expect_u32_eq(result, FXP_INIT_INT(333), "Parse error shouldn't change results"); } TEST_BEGIN(test_parse_invalid) { expect_parse_failure("123."); expect_parse_failure("3.a"); expect_parse_failure(".a"); expect_parse_failure("a.1"); expect_parse_failure("a"); /* A valid string, but one that overflows. */ expect_parse_failure("123456789"); expect_parse_failure("0000000123456789"); expect_parse_failure("1000000"); } TEST_END static void expect_init_percent(unsigned percent, const char *str) { fxp_t result_init = FXP_INIT_PERCENT(percent); fxp_t result_parse = xparse_fxp(str); expect_u32_eq(result_init, result_parse, "Expect representations of FXP_INIT_PERCENT(%u) and " "fxp_parse(\"%s\") to be equal; got %x and %x", percent, str, result_init, result_parse); } /* * Every other test uses either parsing or FXP_INIT_INT; it gets tested in those * ways. We need a one-off for the percent-based initialization, though. */ TEST_BEGIN(test_init_percent) { expect_init_percent(100, "1"); expect_init_percent(75, ".75"); expect_init_percent(1, ".01"); expect_init_percent(50, ".5"); } TEST_END static void expect_add(const char *astr, const char *bstr, const char* resultstr) { fxp_t a = xparse_fxp(astr); fxp_t b = xparse_fxp(bstr); fxp_t result = xparse_fxp(resultstr); expect_true(fxp_close(fxp_add(a, b), result), "Expected %s + %s == %s", astr, bstr, resultstr); } TEST_BEGIN(test_add_simple) { expect_add("0", "0", "0"); expect_add("0", "1", "1"); expect_add("1", "1", "2"); expect_add("1.5", "1.5", "3"); expect_add("0.1", "0.1", "0.2"); expect_add("123", "456", "579"); } TEST_END static void expect_sub(const char *astr, const char *bstr, const char* resultstr) { fxp_t a = xparse_fxp(astr); fxp_t b = xparse_fxp(bstr); fxp_t result = xparse_fxp(resultstr); expect_true(fxp_close(fxp_sub(a, b), result), "Expected %s - %s == %s", astr, bstr, resultstr); } TEST_BEGIN(test_sub_simple) { expect_sub("0", "0", "0"); expect_sub("1", "0", "1"); expect_sub("1", "1", "0"); expect_sub("3.5", "1.5", "2"); expect_sub("0.3", "0.1", "0.2"); expect_sub("456", "123", "333"); } TEST_END static void expect_mul(const char *astr, const char *bstr, const char* resultstr) { fxp_t a = xparse_fxp(astr); fxp_t b = xparse_fxp(bstr); fxp_t result = xparse_fxp(resultstr); expect_true(fxp_close(fxp_mul(a, b), result), "Expected %s * %s == %s", astr, bstr, resultstr); } TEST_BEGIN(test_mul_simple) { expect_mul("0", "0", "0"); expect_mul("1", "0", "0"); expect_mul("1", "1", "1"); expect_mul("1.5", "1.5", "2.25"); expect_mul("100.0", "10", "1000"); expect_mul(".1", "10", "1"); } TEST_END static void expect_div(const char *astr, const char *bstr, const char* resultstr) { fxp_t a = xparse_fxp(astr); fxp_t b = xparse_fxp(bstr); fxp_t result = xparse_fxp(resultstr); expect_true(fxp_close(fxp_div(a, b), result), "Expected %s / %s == %s", astr, bstr, resultstr); } TEST_BEGIN(test_div_simple) { expect_div("1", "1", "1"); expect_div("0", "1", "0"); expect_div("2", "1", "2"); expect_div("3", "2", "1.5"); expect_div("3", "1.5", "2"); expect_div("10", ".1", "100"); expect_div("123", "456", ".2697368421"); } TEST_END static void expect_round(const char *str, uint32_t rounded_down, uint32_t rounded_nearest) { fxp_t fxp = xparse_fxp(str); uint32_t fxp_rounded_down = fxp_round_down(fxp); uint32_t fxp_rounded_nearest = fxp_round_nearest(fxp); expect_u32_eq(rounded_down, fxp_rounded_down, "Mistake rounding %s down", str); expect_u32_eq(rounded_nearest, fxp_rounded_nearest, "Mistake rounding %s to nearest", str); } TEST_BEGIN(test_round_simple) { expect_round("1.5", 1, 2); expect_round("0", 0, 0); expect_round("0.1", 0, 0); expect_round("0.4", 0, 0); expect_round("0.40000", 0, 0); expect_round("0.5", 0, 1); expect_round("0.6", 0, 1); expect_round("123", 123, 123); expect_round("123.4", 123, 123); expect_round("123.5", 123, 124); } TEST_END static void expect_mul_frac(size_t a, const char *fracstr, size_t expected) { fxp_t frac = xparse_fxp(fracstr); size_t result = fxp_mul_frac(a, frac); expect_true(double_close(expected, result), "Expected %zu * %s == %zu (fracmul); got %zu", a, fracstr, expected, result); } TEST_BEGIN(test_mul_frac_simple) { expect_mul_frac(SIZE_MAX, "1.0", SIZE_MAX); expect_mul_frac(SIZE_MAX, ".75", SIZE_MAX / 4 * 3); expect_mul_frac(SIZE_MAX, ".5", SIZE_MAX / 2); expect_mul_frac(SIZE_MAX, ".25", SIZE_MAX / 4); expect_mul_frac(1U << 16, "1.0", 1U << 16); expect_mul_frac(1U << 30, "0.5", 1U << 29); expect_mul_frac(1U << 30, "0.25", 1U << 28); expect_mul_frac(1U << 30, "0.125", 1U << 27); expect_mul_frac((1U << 30) + 1, "0.125", 1U << 27); expect_mul_frac(100, "0.25", 25); expect_mul_frac(1000 * 1000, "0.001", 1000); } TEST_END static void expect_print(const char *str) { fxp_t fxp = xparse_fxp(str); char buf[FXP_BUF_SIZE]; fxp_print(fxp, buf); expect_d_eq(0, strcmp(str, buf), "Couldn't round-trip print %s", str); } TEST_BEGIN(test_print_simple) { expect_print("0.0"); expect_print("1.0"); expect_print("2.0"); expect_print("123.0"); /* * We hit the possibility of roundoff errors whenever the fractional * component isn't a round binary number; only check these here (we * round-trip properly in the stress test). */ expect_print("1.5"); expect_print("3.375"); expect_print("0.25"); expect_print("0.125"); /* 1 / 2**14 */ expect_print("0.00006103515625"); } TEST_END TEST_BEGIN(test_stress) { const char *numbers[] = { "0.0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8", "1.9", "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9", "17.0", "17.1", "17.2", "17.3", "17.4", "17.5", "17.6", "17.7", "17.8", "17.9", "18.0", "18.1", "18.2", "18.3", "18.4", "18.5", "18.6", "18.7", "18.8", "18.9", "123.0", "123.1", "123.2", "123.3", "123.4", "123.5", "123.6", "123.7", "123.8", "123.9", "124.0", "124.1", "124.2", "124.3", "124.4", "124.5", "124.6", "124.7", "124.8", "124.9", "125.0", "125.1", "125.2", "125.3", "125.4", "125.5", "125.6", "125.7", "125.8", "125.9"}; size_t numbers_len = sizeof(numbers)/sizeof(numbers[0]); for (size_t i = 0; i < numbers_len; i++) { fxp_t fxp_a = xparse_fxp(numbers[i]); double double_a = strtod(numbers[i], NULL); uint32_t fxp_rounded_down = fxp_round_down(fxp_a); uint32_t fxp_rounded_nearest = fxp_round_nearest(fxp_a); uint32_t double_rounded_down = (uint32_t)double_a; uint32_t double_rounded_nearest = (uint32_t)round(double_a); expect_u32_eq(double_rounded_down, fxp_rounded_down, "Incorrectly rounded down %s", numbers[i]); expect_u32_eq(double_rounded_nearest, fxp_rounded_nearest, "Incorrectly rounded-to-nearest %s", numbers[i]); for (size_t j = 0; j < numbers_len; j++) { fxp_t fxp_b = xparse_fxp(numbers[j]); double double_b = strtod(numbers[j], NULL); fxp_t fxp_sum = fxp_add(fxp_a, fxp_b); double double_sum = double_a + double_b; expect_true( double_close(fxp2double(fxp_sum), double_sum), "Miscomputed %s + %s", numbers[i], numbers[j]); if (double_a > double_b) { fxp_t fxp_diff = fxp_sub(fxp_a, fxp_b); double double_diff = double_a - double_b; expect_true( double_close(fxp2double(fxp_diff), double_diff), "Miscomputed %s - %s", numbers[i], numbers[j]); } fxp_t fxp_prod = fxp_mul(fxp_a, fxp_b); double double_prod = double_a * double_b; expect_true( double_close(fxp2double(fxp_prod), double_prod), "Miscomputed %s * %s", numbers[i], numbers[j]); if (double_b != 0.0) { fxp_t fxp_quot = fxp_div(fxp_a, fxp_b); double double_quot = double_a / double_b; expect_true( double_close(fxp2double(fxp_quot), double_quot), "Miscomputed %s / %s", numbers[i], numbers[j]); } } } } TEST_END int main(void) { return test_no_reentrancy( test_parse_valid, test_parse_invalid, test_init_percent, test_add_simple, test_sub_simple, test_mul_simple, test_div_simple, test_round_simple, test_mul_frac_simple, test_print_simple, test_stress); } redis-8.0.2/deps/jemalloc/test/unit/hash.c000066400000000000000000000116741501533116600204070ustar00rootroot00000000000000/* * This file is based on code that is part of SMHasher * (https://code.google.com/p/smhasher/), and is subject to the MIT license * (http://www.opensource.org/licenses/mit-license.php). Both email addresses * associated with the source code's revision history belong to Austin Appleby, * and the revision history ranges from 2010 to 2012. Therefore the copyright * and license are here taken to be: * * Copyright (c) 2010-2012 Austin Appleby * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "test/jemalloc_test.h" #include "jemalloc/internal/hash.h" typedef enum { hash_variant_x86_32, hash_variant_x86_128, hash_variant_x64_128 } hash_variant_t; static int hash_variant_bits(hash_variant_t variant) { switch (variant) { case hash_variant_x86_32: return 32; case hash_variant_x86_128: return 128; case hash_variant_x64_128: return 128; default: not_reached(); } } static const char * hash_variant_string(hash_variant_t variant) { switch (variant) { case hash_variant_x86_32: return "hash_x86_32"; case hash_variant_x86_128: return "hash_x86_128"; case hash_variant_x64_128: return "hash_x64_128"; default: not_reached(); } } #define KEY_SIZE 256 static void hash_variant_verify_key(hash_variant_t variant, uint8_t *key) { const int hashbytes = hash_variant_bits(variant) / 8; const int hashes_size = hashbytes * 256; VARIABLE_ARRAY(uint8_t, hashes, hashes_size); VARIABLE_ARRAY(uint8_t, final, hashbytes); unsigned i; uint32_t computed, expected; memset(key, 0, KEY_SIZE); memset(hashes, 0, hashes_size); memset(final, 0, hashbytes); /* * Hash keys of the form {0}, {0,1}, {0,1,2}, ..., {0,1,...,255} as the * seed. */ for (i = 0; i < 256; i++) { key[i] = (uint8_t)i; switch (variant) { case hash_variant_x86_32: { uint32_t out; out = hash_x86_32(key, i, 256-i); memcpy(&hashes[i*hashbytes], &out, hashbytes); break; } case hash_variant_x86_128: { uint64_t out[2]; hash_x86_128(key, i, 256-i, out); memcpy(&hashes[i*hashbytes], out, hashbytes); break; } case hash_variant_x64_128: { uint64_t out[2]; hash_x64_128(key, i, 256-i, out); memcpy(&hashes[i*hashbytes], out, hashbytes); break; } default: not_reached(); } } /* Hash the result array. */ switch (variant) { case hash_variant_x86_32: { uint32_t out = hash_x86_32(hashes, hashes_size, 0); memcpy(final, &out, sizeof(out)); break; } case hash_variant_x86_128: { uint64_t out[2]; hash_x86_128(hashes, hashes_size, 0, out); memcpy(final, out, sizeof(out)); break; } case hash_variant_x64_128: { uint64_t out[2]; hash_x64_128(hashes, hashes_size, 0, out); memcpy(final, out, sizeof(out)); break; } default: not_reached(); } computed = (final[0] << 0) | (final[1] << 8) | (final[2] << 16) | (final[3] << 24); switch (variant) { #ifdef JEMALLOC_BIG_ENDIAN case hash_variant_x86_32: expected = 0x6213303eU; break; case hash_variant_x86_128: expected = 0x266820caU; break; case hash_variant_x64_128: expected = 0xcc622b6fU; break; #else case hash_variant_x86_32: expected = 0xb0f57ee3U; break; case hash_variant_x86_128: expected = 0xb3ece62aU; break; case hash_variant_x64_128: expected = 0x6384ba69U; break; #endif default: not_reached(); } expect_u32_eq(computed, expected, "Hash mismatch for %s(): expected %#x but got %#x", hash_variant_string(variant), expected, computed); } static void hash_variant_verify(hash_variant_t variant) { #define MAX_ALIGN 16 uint8_t key[KEY_SIZE + (MAX_ALIGN - 1)]; unsigned i; for (i = 0; i < MAX_ALIGN; i++) { hash_variant_verify_key(variant, &key[i]); } #undef MAX_ALIGN } #undef KEY_SIZE TEST_BEGIN(test_hash_x86_32) { hash_variant_verify(hash_variant_x86_32); } TEST_END TEST_BEGIN(test_hash_x86_128) { hash_variant_verify(hash_variant_x86_128); } TEST_END TEST_BEGIN(test_hash_x64_128) { hash_variant_verify(hash_variant_x64_128); } TEST_END int main(void) { return test( test_hash_x86_32, test_hash_x86_128, test_hash_x64_128); } redis-8.0.2/deps/jemalloc/test/unit/hook.c000066400000000000000000000447541501533116600204310ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/hook.h" static void *arg_extra; static int arg_type; static void *arg_result; static void *arg_address; static size_t arg_old_usize; static size_t arg_new_usize; static uintptr_t arg_result_raw; static uintptr_t arg_args_raw[4]; static int call_count = 0; static void reset_args() { arg_extra = NULL; arg_type = 12345; arg_result = NULL; arg_address = NULL; arg_old_usize = 0; arg_new_usize = 0; arg_result_raw = 0; memset(arg_args_raw, 77, sizeof(arg_args_raw)); } static void alloc_free_size(size_t sz) { void *ptr = mallocx(1, 0); free(ptr); ptr = mallocx(1, 0); free(ptr); ptr = mallocx(1, MALLOCX_TCACHE_NONE); dallocx(ptr, MALLOCX_TCACHE_NONE); } /* * We want to support a degree of user reentrancy. This tests a variety of * allocation scenarios. */ static void be_reentrant() { /* Let's make sure the tcache is non-empty if enabled. */ alloc_free_size(1); alloc_free_size(1024); alloc_free_size(64 * 1024); alloc_free_size(256 * 1024); alloc_free_size(1024 * 1024); /* Some reallocation. */ void *ptr = mallocx(129, 0); ptr = rallocx(ptr, 130, 0); free(ptr); ptr = mallocx(2 * 1024 * 1024, 0); free(ptr); ptr = mallocx(1 * 1024 * 1024, 0); ptr = rallocx(ptr, 2 * 1024 * 1024, 0); free(ptr); ptr = mallocx(1, 0); ptr = rallocx(ptr, 1000, 0); free(ptr); } static void set_args_raw(uintptr_t *args_raw, int nargs) { memcpy(arg_args_raw, args_raw, sizeof(uintptr_t) * nargs); } static void expect_args_raw(uintptr_t *args_raw_expected, int nargs) { int cmp = memcmp(args_raw_expected, arg_args_raw, sizeof(uintptr_t) * nargs); expect_d_eq(cmp, 0, "Raw args mismatch"); } static void reset() { call_count = 0; reset_args(); } static void test_alloc_hook(void *extra, hook_alloc_t type, void *result, uintptr_t result_raw, uintptr_t args_raw[3]) { call_count++; arg_extra = extra; arg_type = (int)type; arg_result = result; arg_result_raw = result_raw; set_args_raw(args_raw, 3); be_reentrant(); } static void test_dalloc_hook(void *extra, hook_dalloc_t type, void *address, uintptr_t args_raw[3]) { call_count++; arg_extra = extra; arg_type = (int)type; arg_address = address; set_args_raw(args_raw, 3); be_reentrant(); } static void test_expand_hook(void *extra, hook_expand_t type, void *address, size_t old_usize, size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]) { call_count++; arg_extra = extra; arg_type = (int)type; arg_address = address; arg_old_usize = old_usize; arg_new_usize = new_usize; arg_result_raw = result_raw; set_args_raw(args_raw, 4); be_reentrant(); } TEST_BEGIN(test_hooks_basic) { /* Just verify that the record their arguments correctly. */ hooks_t hooks = { &test_alloc_hook, &test_dalloc_hook, &test_expand_hook, (void *)111}; void *handle = hook_install(TSDN_NULL, &hooks); uintptr_t args_raw[4] = {10, 20, 30, 40}; /* Alloc */ reset_args(); hook_invoke_alloc(hook_alloc_posix_memalign, (void *)222, 333, args_raw); expect_ptr_eq(arg_extra, (void *)111, "Passed wrong user pointer"); expect_d_eq((int)hook_alloc_posix_memalign, arg_type, "Passed wrong alloc type"); expect_ptr_eq((void *)222, arg_result, "Passed wrong result address"); expect_u64_eq(333, arg_result_raw, "Passed wrong result"); expect_args_raw(args_raw, 3); /* Dalloc */ reset_args(); hook_invoke_dalloc(hook_dalloc_sdallocx, (void *)222, args_raw); expect_d_eq((int)hook_dalloc_sdallocx, arg_type, "Passed wrong dalloc type"); expect_ptr_eq((void *)111, arg_extra, "Passed wrong user pointer"); expect_ptr_eq((void *)222, arg_address, "Passed wrong address"); expect_args_raw(args_raw, 3); /* Expand */ reset_args(); hook_invoke_expand(hook_expand_xallocx, (void *)222, 333, 444, 555, args_raw); expect_d_eq((int)hook_expand_xallocx, arg_type, "Passed wrong expand type"); expect_ptr_eq((void *)111, arg_extra, "Passed wrong user pointer"); expect_ptr_eq((void *)222, arg_address, "Passed wrong address"); expect_zu_eq(333, arg_old_usize, "Passed wrong old usize"); expect_zu_eq(444, arg_new_usize, "Passed wrong new usize"); expect_zu_eq(555, arg_result_raw, "Passed wrong result"); expect_args_raw(args_raw, 4); hook_remove(TSDN_NULL, handle); } TEST_END TEST_BEGIN(test_hooks_null) { /* Null hooks should be ignored, not crash. */ hooks_t hooks1 = {NULL, NULL, NULL, NULL}; hooks_t hooks2 = {&test_alloc_hook, NULL, NULL, NULL}; hooks_t hooks3 = {NULL, &test_dalloc_hook, NULL, NULL}; hooks_t hooks4 = {NULL, NULL, &test_expand_hook, NULL}; void *handle1 = hook_install(TSDN_NULL, &hooks1); void *handle2 = hook_install(TSDN_NULL, &hooks2); void *handle3 = hook_install(TSDN_NULL, &hooks3); void *handle4 = hook_install(TSDN_NULL, &hooks4); expect_ptr_ne(handle1, NULL, "Hook installation failed"); expect_ptr_ne(handle2, NULL, "Hook installation failed"); expect_ptr_ne(handle3, NULL, "Hook installation failed"); expect_ptr_ne(handle4, NULL, "Hook installation failed"); uintptr_t args_raw[4] = {10, 20, 30, 40}; call_count = 0; hook_invoke_alloc(hook_alloc_malloc, NULL, 0, args_raw); expect_d_eq(call_count, 1, "Called wrong number of times"); call_count = 0; hook_invoke_dalloc(hook_dalloc_free, NULL, args_raw); expect_d_eq(call_count, 1, "Called wrong number of times"); call_count = 0; hook_invoke_expand(hook_expand_realloc, NULL, 0, 0, 0, args_raw); expect_d_eq(call_count, 1, "Called wrong number of times"); hook_remove(TSDN_NULL, handle1); hook_remove(TSDN_NULL, handle2); hook_remove(TSDN_NULL, handle3); hook_remove(TSDN_NULL, handle4); } TEST_END TEST_BEGIN(test_hooks_remove) { hooks_t hooks = {&test_alloc_hook, NULL, NULL, NULL}; void *handle = hook_install(TSDN_NULL, &hooks); expect_ptr_ne(handle, NULL, "Hook installation failed"); call_count = 0; uintptr_t args_raw[4] = {10, 20, 30, 40}; hook_invoke_alloc(hook_alloc_malloc, NULL, 0, args_raw); expect_d_eq(call_count, 1, "Hook not invoked"); call_count = 0; hook_remove(TSDN_NULL, handle); hook_invoke_alloc(hook_alloc_malloc, NULL, 0, NULL); expect_d_eq(call_count, 0, "Hook invoked after removal"); } TEST_END TEST_BEGIN(test_hooks_alloc_simple) { /* "Simple" in the sense that we're not in a realloc variant. */ hooks_t hooks = {&test_alloc_hook, NULL, NULL, (void *)123}; void *handle = hook_install(TSDN_NULL, &hooks); expect_ptr_ne(handle, NULL, "Hook installation failed"); /* Stop malloc from being optimized away. */ volatile int err; void *volatile ptr; /* malloc */ reset(); ptr = malloc(1); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_malloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)1, arg_args_raw[0], "Wrong argument"); free(ptr); /* posix_memalign */ reset(); err = posix_memalign((void **)&ptr, 1024, 1); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_posix_memalign, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)err, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)&ptr, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)1024, arg_args_raw[1], "Wrong argument"); expect_u64_eq((uintptr_t)1, arg_args_raw[2], "Wrong argument"); free(ptr); /* aligned_alloc */ reset(); ptr = aligned_alloc(1024, 1); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_aligned_alloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)1024, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)1, arg_args_raw[1], "Wrong argument"); free(ptr); /* calloc */ reset(); ptr = calloc(11, 13); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_calloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)11, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)13, arg_args_raw[1], "Wrong argument"); free(ptr); /* memalign */ #ifdef JEMALLOC_OVERRIDE_MEMALIGN reset(); ptr = memalign(1024, 1); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_memalign, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)1024, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)1, arg_args_raw[1], "Wrong argument"); free(ptr); #endif /* JEMALLOC_OVERRIDE_MEMALIGN */ /* valloc */ #ifdef JEMALLOC_OVERRIDE_VALLOC reset(); ptr = valloc(1); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_valloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)1, arg_args_raw[0], "Wrong argument"); free(ptr); #endif /* JEMALLOC_OVERRIDE_VALLOC */ /* mallocx */ reset(); ptr = mallocx(1, MALLOCX_LG_ALIGN(10)); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_mallocx, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)1, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)MALLOCX_LG_ALIGN(10), arg_args_raw[1], "Wrong flags"); free(ptr); hook_remove(TSDN_NULL, handle); } TEST_END TEST_BEGIN(test_hooks_dalloc_simple) { /* "Simple" in the sense that we're not in a realloc variant. */ hooks_t hooks = {NULL, &test_dalloc_hook, NULL, (void *)123}; void *handle = hook_install(TSDN_NULL, &hooks); expect_ptr_ne(handle, NULL, "Hook installation failed"); void *volatile ptr; /* free() */ reset(); ptr = malloc(1); free(ptr); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_dalloc_free, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong pointer freed"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong raw arg"); /* dallocx() */ reset(); ptr = malloc(1); dallocx(ptr, MALLOCX_TCACHE_NONE); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_dalloc_dallocx, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong pointer freed"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong raw arg"); expect_u64_eq((uintptr_t)MALLOCX_TCACHE_NONE, arg_args_raw[1], "Wrong raw arg"); /* sdallocx() */ reset(); ptr = malloc(1); sdallocx(ptr, 1, MALLOCX_TCACHE_NONE); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_dalloc_sdallocx, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong pointer freed"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong raw arg"); expect_u64_eq((uintptr_t)1, arg_args_raw[1], "Wrong raw arg"); expect_u64_eq((uintptr_t)MALLOCX_TCACHE_NONE, arg_args_raw[2], "Wrong raw arg"); hook_remove(TSDN_NULL, handle); } TEST_END TEST_BEGIN(test_hooks_expand_simple) { /* "Simple" in the sense that we're not in a realloc variant. */ hooks_t hooks = {NULL, NULL, &test_expand_hook, (void *)123}; void *handle = hook_install(TSDN_NULL, &hooks); expect_ptr_ne(handle, NULL, "Hook installation failed"); void *volatile ptr; /* xallocx() */ reset(); ptr = malloc(1); size_t new_usize = xallocx(ptr, 100, 200, MALLOCX_TCACHE_NONE); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_expand_xallocx, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong pointer expanded"); expect_u64_eq(arg_old_usize, nallocx(1, 0), "Wrong old usize"); expect_u64_eq(arg_new_usize, sallocx(ptr, 0), "Wrong new usize"); expect_u64_eq(new_usize, arg_result_raw, "Wrong result"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong arg"); expect_u64_eq(100, arg_args_raw[1], "Wrong arg"); expect_u64_eq(200, arg_args_raw[2], "Wrong arg"); expect_u64_eq(MALLOCX_TCACHE_NONE, arg_args_raw[3], "Wrong arg"); hook_remove(TSDN_NULL, handle); } TEST_END TEST_BEGIN(test_hooks_realloc_as_malloc_or_free) { hooks_t hooks = {&test_alloc_hook, &test_dalloc_hook, &test_expand_hook, (void *)123}; void *handle = hook_install(TSDN_NULL, &hooks); expect_ptr_ne(handle, NULL, "Hook installation failed"); void *volatile ptr; /* realloc(NULL, size) as malloc */ reset(); ptr = realloc(NULL, 1); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_realloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)NULL, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)1, arg_args_raw[1], "Wrong argument"); free(ptr); /* realloc(ptr, 0) as free */ if (opt_zero_realloc_action == zero_realloc_action_free) { ptr = malloc(1); reset(); realloc(ptr, 0); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_dalloc_realloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong pointer freed"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong raw arg"); expect_u64_eq((uintptr_t)0, arg_args_raw[1], "Wrong raw arg"); } /* realloc(NULL, 0) as malloc(0) */ reset(); ptr = realloc(NULL, 0); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, (int)hook_alloc_realloc, "Wrong hook type"); expect_ptr_eq(ptr, arg_result, "Wrong result"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)NULL, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)0, arg_args_raw[1], "Wrong argument"); free(ptr); hook_remove(TSDN_NULL, handle); } TEST_END static void do_realloc_test(void *(*ralloc)(void *, size_t, int), int flags, int expand_type, int dalloc_type) { hooks_t hooks = {&test_alloc_hook, &test_dalloc_hook, &test_expand_hook, (void *)123}; void *handle = hook_install(TSDN_NULL, &hooks); expect_ptr_ne(handle, NULL, "Hook installation failed"); void *volatile ptr; void *volatile ptr2; /* Realloc in-place, small. */ ptr = malloc(129); reset(); ptr2 = ralloc(ptr, 130, flags); expect_ptr_eq(ptr, ptr2, "Small realloc moved"); expect_d_eq(call_count, 1, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, expand_type, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong address"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)130, arg_args_raw[1], "Wrong argument"); free(ptr); /* * Realloc in-place, large. Since we can't guarantee the large case * across all platforms, we stay resilient to moving results. */ ptr = malloc(2 * 1024 * 1024); free(ptr); ptr2 = malloc(1 * 1024 * 1024); reset(); ptr = ralloc(ptr2, 2 * 1024 * 1024, flags); /* ptr is the new address, ptr2 is the old address. */ if (ptr == ptr2) { expect_d_eq(call_count, 1, "Hook not called"); expect_d_eq(arg_type, expand_type, "Wrong hook type"); } else { expect_d_eq(call_count, 2, "Wrong hooks called"); expect_ptr_eq(ptr, arg_result, "Wrong address"); expect_d_eq(arg_type, dalloc_type, "Wrong hook type"); } expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_ptr_eq(ptr2, arg_address, "Wrong address"); expect_u64_eq((uintptr_t)ptr, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)ptr2, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)2 * 1024 * 1024, arg_args_raw[1], "Wrong argument"); free(ptr); /* Realloc with move, small. */ ptr = malloc(8); reset(); ptr2 = ralloc(ptr, 128, flags); expect_ptr_ne(ptr, ptr2, "Small realloc didn't move"); expect_d_eq(call_count, 2, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, dalloc_type, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong address"); expect_ptr_eq(ptr2, arg_result, "Wrong address"); expect_u64_eq((uintptr_t)ptr2, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)128, arg_args_raw[1], "Wrong argument"); free(ptr2); /* Realloc with move, large. */ ptr = malloc(1); reset(); ptr2 = ralloc(ptr, 2 * 1024 * 1024, flags); expect_ptr_ne(ptr, ptr2, "Large realloc didn't move"); expect_d_eq(call_count, 2, "Hook not called"); expect_ptr_eq(arg_extra, (void *)123, "Wrong extra"); expect_d_eq(arg_type, dalloc_type, "Wrong hook type"); expect_ptr_eq(ptr, arg_address, "Wrong address"); expect_ptr_eq(ptr2, arg_result, "Wrong address"); expect_u64_eq((uintptr_t)ptr2, (uintptr_t)arg_result_raw, "Wrong raw result"); expect_u64_eq((uintptr_t)ptr, arg_args_raw[0], "Wrong argument"); expect_u64_eq((uintptr_t)2 * 1024 * 1024, arg_args_raw[1], "Wrong argument"); free(ptr2); hook_remove(TSDN_NULL, handle); } static void * realloc_wrapper(void *ptr, size_t size, UNUSED int flags) { return realloc(ptr, size); } TEST_BEGIN(test_hooks_realloc) { do_realloc_test(&realloc_wrapper, 0, hook_expand_realloc, hook_dalloc_realloc); } TEST_END TEST_BEGIN(test_hooks_rallocx) { do_realloc_test(&rallocx, MALLOCX_TCACHE_NONE, hook_expand_rallocx, hook_dalloc_rallocx); } TEST_END int main(void) { /* We assert on call counts. */ return test_no_reentrancy( test_hooks_basic, test_hooks_null, test_hooks_remove, test_hooks_alloc_simple, test_hooks_dalloc_simple, test_hooks_expand_simple, test_hooks_realloc_as_malloc_or_free, test_hooks_realloc, test_hooks_rallocx); } redis-8.0.2/deps/jemalloc/test/unit/hpa.c000066400000000000000000000310201501533116600202170ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/hpa.h" #include "jemalloc/internal/nstime.h" #define SHARD_IND 111 #define ALLOC_MAX (HUGEPAGE / 4) typedef struct test_data_s test_data_t; struct test_data_s { /* * Must be the first member -- we convert back and forth between the * test_data_t and the hpa_shard_t; */ hpa_shard_t shard; hpa_central_t central; base_t *base; edata_cache_t shard_edata_cache; emap_t emap; }; static hpa_shard_opts_t test_hpa_shard_opts_default = { /* slab_max_alloc */ ALLOC_MAX, /* hugification threshold */ HUGEPAGE, /* dirty_mult */ FXP_INIT_PERCENT(25), /* deferral_allowed */ false, /* hugify_delay_ms */ 10 * 1000, }; static hpa_shard_t * create_test_data(hpa_hooks_t *hooks, hpa_shard_opts_t *opts) { bool err; base_t *base = base_new(TSDN_NULL, /* ind */ SHARD_IND, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); assert_ptr_not_null(base, ""); test_data_t *test_data = malloc(sizeof(test_data_t)); assert_ptr_not_null(test_data, ""); test_data->base = base; err = edata_cache_init(&test_data->shard_edata_cache, base); assert_false(err, ""); err = emap_init(&test_data->emap, test_data->base, /* zeroed */ false); assert_false(err, ""); err = hpa_central_init(&test_data->central, test_data->base, hooks); assert_false(err, ""); err = hpa_shard_init(&test_data->shard, &test_data->central, &test_data->emap, test_data->base, &test_data->shard_edata_cache, SHARD_IND, opts); assert_false(err, ""); return (hpa_shard_t *)test_data; } static void destroy_test_data(hpa_shard_t *shard) { test_data_t *test_data = (test_data_t *)shard; base_delete(TSDN_NULL, test_data->base); free(test_data); } TEST_BEGIN(test_alloc_max) { test_skip_if(!hpa_supported()); hpa_shard_t *shard = create_test_data(&hpa_hooks_default, &test_hpa_shard_opts_default); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); edata_t *edata; /* Small max */ bool deferred_work_generated = false; edata = pai_alloc(tsdn, &shard->pai, ALLOC_MAX, PAGE, false, false, false, &deferred_work_generated); expect_ptr_not_null(edata, "Allocation of small max failed"); edata = pai_alloc(tsdn, &shard->pai, ALLOC_MAX + PAGE, PAGE, false, false, false, &deferred_work_generated); expect_ptr_null(edata, "Allocation of larger than small max succeeded"); destroy_test_data(shard); } TEST_END typedef struct mem_contents_s mem_contents_t; struct mem_contents_s { uintptr_t my_addr; size_t size; edata_t *my_edata; rb_node(mem_contents_t) link; }; static int mem_contents_cmp(const mem_contents_t *a, const mem_contents_t *b) { return (a->my_addr > b->my_addr) - (a->my_addr < b->my_addr); } typedef rb_tree(mem_contents_t) mem_tree_t; rb_gen(static, mem_tree_, mem_tree_t, mem_contents_t, link, mem_contents_cmp); static void node_assert_ordered(mem_contents_t *a, mem_contents_t *b) { assert_zu_lt(a->my_addr, a->my_addr + a->size, "Overflow"); assert_zu_le(a->my_addr + a->size, b->my_addr, ""); } static void node_check(mem_tree_t *tree, mem_contents_t *contents) { edata_t *edata = contents->my_edata; assert_ptr_eq(contents, (void *)contents->my_addr, ""); assert_ptr_eq(contents, edata_base_get(edata), ""); assert_zu_eq(contents->size, edata_size_get(edata), ""); assert_ptr_eq(contents->my_edata, edata, ""); mem_contents_t *next = mem_tree_next(tree, contents); if (next != NULL) { node_assert_ordered(contents, next); } mem_contents_t *prev = mem_tree_prev(tree, contents); if (prev != NULL) { node_assert_ordered(prev, contents); } } static void node_insert(mem_tree_t *tree, edata_t *edata, size_t npages) { mem_contents_t *contents = (mem_contents_t *)edata_base_get(edata); contents->my_addr = (uintptr_t)edata_base_get(edata); contents->size = edata_size_get(edata); contents->my_edata = edata; mem_tree_insert(tree, contents); node_check(tree, contents); } static void node_remove(mem_tree_t *tree, edata_t *edata) { mem_contents_t *contents = (mem_contents_t *)edata_base_get(edata); node_check(tree, contents); mem_tree_remove(tree, contents); } TEST_BEGIN(test_stress) { test_skip_if(!hpa_supported()); hpa_shard_t *shard = create_test_data(&hpa_hooks_default, &test_hpa_shard_opts_default); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); const size_t nlive_edatas_max = 500; size_t nlive_edatas = 0; edata_t **live_edatas = calloc(nlive_edatas_max, sizeof(edata_t *)); /* * Nothing special about this constant; we're only fixing it for * consistency across runs. */ size_t prng_state = (size_t)0x76999ffb014df07c; mem_tree_t tree; mem_tree_new(&tree); bool deferred_work_generated = false; for (size_t i = 0; i < 100 * 1000; i++) { size_t operation = prng_range_zu(&prng_state, 2); if (operation == 0) { /* Alloc */ if (nlive_edatas == nlive_edatas_max) { continue; } /* * We make sure to get an even balance of small and * large allocations. */ size_t npages_min = 1; size_t npages_max = ALLOC_MAX / PAGE; size_t npages = npages_min + prng_range_zu(&prng_state, npages_max - npages_min); edata_t *edata = pai_alloc(tsdn, &shard->pai, npages * PAGE, PAGE, false, false, false, &deferred_work_generated); assert_ptr_not_null(edata, "Unexpected allocation failure"); live_edatas[nlive_edatas] = edata; nlive_edatas++; node_insert(&tree, edata, npages); } else { /* Free. */ if (nlive_edatas == 0) { continue; } size_t victim = prng_range_zu(&prng_state, nlive_edatas); edata_t *to_free = live_edatas[victim]; live_edatas[victim] = live_edatas[nlive_edatas - 1]; nlive_edatas--; node_remove(&tree, to_free); pai_dalloc(tsdn, &shard->pai, to_free, &deferred_work_generated); } } size_t ntreenodes = 0; for (mem_contents_t *contents = mem_tree_first(&tree); contents != NULL; contents = mem_tree_next(&tree, contents)) { ntreenodes++; node_check(&tree, contents); } expect_zu_eq(ntreenodes, nlive_edatas, ""); /* * Test hpa_shard_destroy, which requires as a precondition that all its * extents have been deallocated. */ for (size_t i = 0; i < nlive_edatas; i++) { edata_t *to_free = live_edatas[i]; node_remove(&tree, to_free); pai_dalloc(tsdn, &shard->pai, to_free, &deferred_work_generated); } hpa_shard_destroy(tsdn, shard); free(live_edatas); destroy_test_data(shard); } TEST_END static void expect_contiguous(edata_t **edatas, size_t nedatas) { for (size_t i = 0; i < nedatas; i++) { size_t expected = (size_t)edata_base_get(edatas[0]) + i * PAGE; expect_zu_eq(expected, (size_t)edata_base_get(edatas[i]), "Mismatch at index %zu", i); } } TEST_BEGIN(test_alloc_dalloc_batch) { test_skip_if(!hpa_supported()); hpa_shard_t *shard = create_test_data(&hpa_hooks_default, &test_hpa_shard_opts_default); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); bool deferred_work_generated = false; enum {NALLOCS = 8}; edata_t *allocs[NALLOCS]; /* * Allocate a mix of ways; first half from regular alloc, second half * from alloc_batch. */ for (size_t i = 0; i < NALLOCS / 2; i++) { allocs[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(allocs[i], "Unexpected alloc failure"); } edata_list_active_t allocs_list; edata_list_active_init(&allocs_list); size_t nsuccess = pai_alloc_batch(tsdn, &shard->pai, PAGE, NALLOCS / 2, &allocs_list, &deferred_work_generated); expect_zu_eq(NALLOCS / 2, nsuccess, "Unexpected oom"); for (size_t i = NALLOCS / 2; i < NALLOCS; i++) { allocs[i] = edata_list_active_first(&allocs_list); edata_list_active_remove(&allocs_list, allocs[i]); } /* * Should have allocated them contiguously, despite the differing * methods used. */ void *orig_base = edata_base_get(allocs[0]); expect_contiguous(allocs, NALLOCS); /* * Batch dalloc the first half, individually deallocate the second half. */ for (size_t i = 0; i < NALLOCS / 2; i++) { edata_list_active_append(&allocs_list, allocs[i]); } pai_dalloc_batch(tsdn, &shard->pai, &allocs_list, &deferred_work_generated); for (size_t i = NALLOCS / 2; i < NALLOCS; i++) { pai_dalloc(tsdn, &shard->pai, allocs[i], &deferred_work_generated); } /* Reallocate (individually), and ensure reuse and contiguity. */ for (size_t i = 0; i < NALLOCS; i++) { allocs[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(allocs[i], "Unexpected alloc failure."); } void *new_base = edata_base_get(allocs[0]); expect_ptr_eq(orig_base, new_base, "Failed to reuse the allocated memory."); expect_contiguous(allocs, NALLOCS); destroy_test_data(shard); } TEST_END static uintptr_t defer_bump_ptr = HUGEPAGE * 123; static void * defer_test_map(size_t size) { void *result = (void *)defer_bump_ptr; defer_bump_ptr += size; return result; } static void defer_test_unmap(void *ptr, size_t size) { (void)ptr; (void)size; } static bool defer_purge_called = false; static void defer_test_purge(void *ptr, size_t size) { (void)ptr; (void)size; defer_purge_called = true; } static bool defer_hugify_called = false; static void defer_test_hugify(void *ptr, size_t size) { defer_hugify_called = true; } static bool defer_dehugify_called = false; static void defer_test_dehugify(void *ptr, size_t size) { defer_dehugify_called = true; } static nstime_t defer_curtime; static void defer_test_curtime(nstime_t *r_time, bool first_reading) { *r_time = defer_curtime; } static uint64_t defer_test_ms_since(nstime_t *past_time) { return (nstime_ns(&defer_curtime) - nstime_ns(past_time)) / 1000 / 1000; } TEST_BEGIN(test_defer_time) { test_skip_if(!hpa_supported()); hpa_hooks_t hooks; hooks.map = &defer_test_map; hooks.unmap = &defer_test_unmap; hooks.purge = &defer_test_purge; hooks.hugify = &defer_test_hugify; hooks.dehugify = &defer_test_dehugify; hooks.curtime = &defer_test_curtime; hooks.ms_since = &defer_test_ms_since; hpa_shard_opts_t opts = test_hpa_shard_opts_default; opts.deferral_allowed = true; hpa_shard_t *shard = create_test_data(&hooks, &opts); bool deferred_work_generated = false; nstime_init(&defer_curtime, 0); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); edata_t *edatas[HUGEPAGE_PAGES]; for (int i = 0; i < (int)HUGEPAGE_PAGES; i++) { edatas[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, false, false, false, &deferred_work_generated); expect_ptr_not_null(edatas[i], "Unexpected null edata"); } hpa_shard_do_deferred_work(tsdn, shard); expect_false(defer_hugify_called, "Hugified too early"); /* Hugification delay is set to 10 seconds in options. */ nstime_init2(&defer_curtime, 11, 0); hpa_shard_do_deferred_work(tsdn, shard); expect_true(defer_hugify_called, "Failed to hugify"); defer_hugify_called = false; /* Purge. Recall that dirty_mult is .25. */ for (int i = 0; i < (int)HUGEPAGE_PAGES / 2; i++) { pai_dalloc(tsdn, &shard->pai, edatas[i], &deferred_work_generated); } hpa_shard_do_deferred_work(tsdn, shard); expect_false(defer_hugify_called, "Hugified too early"); expect_true(defer_dehugify_called, "Should have dehugified"); expect_true(defer_purge_called, "Should have purged"); defer_hugify_called = false; defer_dehugify_called = false; defer_purge_called = false; /* * Refill the page. We now meet the hugification threshold; we should * be marked for pending hugify. */ for (int i = 0; i < (int)HUGEPAGE_PAGES / 2; i++) { edatas[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, false, false, false, &deferred_work_generated); expect_ptr_not_null(edatas[i], "Unexpected null edata"); } /* * We would be ineligible for hugification, had we not already met the * threshold before dipping below it. */ pai_dalloc(tsdn, &shard->pai, edatas[0], &deferred_work_generated); /* Wait for the threshold again. */ nstime_init2(&defer_curtime, 22, 0); hpa_shard_do_deferred_work(tsdn, shard); expect_true(defer_hugify_called, "Hugified too early"); expect_false(defer_dehugify_called, "Unexpected dehugify"); expect_false(defer_purge_called, "Unexpected purge"); destroy_test_data(shard); } TEST_END int main(void) { /* * These trigger unused-function warnings on CI runs, even if declared * with static inline. */ (void)mem_tree_empty; (void)mem_tree_last; (void)mem_tree_search; (void)mem_tree_nsearch; (void)mem_tree_psearch; (void)mem_tree_iter; (void)mem_tree_reverse_iter; (void)mem_tree_destroy; return test_no_reentrancy( test_alloc_max, test_stress, test_alloc_dalloc_batch, test_defer_time); } redis-8.0.2/deps/jemalloc/test/unit/hpa_background_thread.c000066400000000000000000000130421501533116600237510ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/sleep.h" static void sleep_for_background_thread_interval() { /* * The sleep interval set in our .sh file is 50ms. So it likely will * run if we sleep for four times that. */ sleep_ns(200 * 1000 * 1000); } static unsigned create_arena() { unsigned arena_ind; size_t sz; sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 2), 0, "Unexpected mallctl() failure"); return arena_ind; } static size_t get_empty_ndirty(unsigned arena_ind) { int err; size_t ndirty_huge; size_t ndirty_nonhuge; uint64_t epoch = 1; size_t sz = sizeof(epoch); err = je_mallctl("epoch", (void *)&epoch, &sz, (void *)&epoch, sizeof(epoch)); expect_d_eq(0, err, "Unexpected mallctl() failure"); size_t mib[6]; size_t miblen = sizeof(mib)/sizeof(mib[0]); err = mallctlnametomib( "stats.arenas.0.hpa_shard.empty_slabs.ndirty_nonhuge", mib, &miblen); expect_d_eq(0, err, "Unexpected mallctlnametomib() failure"); sz = sizeof(ndirty_nonhuge); mib[2] = arena_ind; err = mallctlbymib(mib, miblen, &ndirty_nonhuge, &sz, NULL, 0); expect_d_eq(0, err, "Unexpected mallctlbymib() failure"); err = mallctlnametomib( "stats.arenas.0.hpa_shard.empty_slabs.ndirty_huge", mib, &miblen); expect_d_eq(0, err, "Unexpected mallctlnametomib() failure"); sz = sizeof(ndirty_huge); mib[2] = arena_ind; err = mallctlbymib(mib, miblen, &ndirty_huge, &sz, NULL, 0); expect_d_eq(0, err, "Unexpected mallctlbymib() failure"); return ndirty_huge + ndirty_nonhuge; } static void set_background_thread_enabled(bool enabled) { int err; err = je_mallctl("background_thread", NULL, NULL, &enabled, sizeof(enabled)); expect_d_eq(0, err, "Unexpected mallctl failure"); } static void wait_until_thread_is_enabled(unsigned arena_id) { tsd_t* tsd = tsd_fetch(); bool sleeping = false; int iterations = 0; do { background_thread_info_t *info = background_thread_info_get(arena_id); malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); sleeping = background_thread_indefinite_sleep(info); assert_d_lt(iterations, UINT64_C(1000000), "Waiting for a thread to start for too long"); } while (!sleeping); } static void expect_purging(unsigned arena_ind, bool expect_deferred) { size_t empty_ndirty; empty_ndirty = get_empty_ndirty(arena_ind); expect_zu_eq(0, empty_ndirty, "Expected arena to start unused."); /* * It's possible that we get unlucky with our stats collection timing, * and the background thread runs in between the deallocation and the * stats collection. So we retry 10 times, and see if we *ever* see * deferred reclamation. */ bool observed_dirty_page = false; for (int i = 0; i < 10; i++) { void *ptr = mallocx(PAGE, MALLOCX_TCACHE_NONE | MALLOCX_ARENA(arena_ind)); empty_ndirty = get_empty_ndirty(arena_ind); expect_zu_eq(0, empty_ndirty, "All pages should be active"); dallocx(ptr, MALLOCX_TCACHE_NONE); empty_ndirty = get_empty_ndirty(arena_ind); if (expect_deferred) { expect_true(empty_ndirty == 0 || empty_ndirty == 1 || opt_prof, "Unexpected extra dirty page count: %zu", empty_ndirty); } else { assert_zu_eq(0, empty_ndirty, "Saw dirty pages without deferred purging"); } if (empty_ndirty > 0) { observed_dirty_page = true; break; } } expect_b_eq(expect_deferred, observed_dirty_page, ""); /* * Under high concurrency / heavy test load (e.g. using run_test.sh), * the background thread may not get scheduled for a longer period of * time. Retry 100 times max before bailing out. */ unsigned retry = 0; while ((empty_ndirty = get_empty_ndirty(arena_ind)) > 0 && expect_deferred && (retry++ < 100)) { sleep_for_background_thread_interval(); } expect_zu_eq(0, empty_ndirty, "Should have seen a background purge"); } TEST_BEGIN(test_hpa_background_thread_purges) { test_skip_if(!config_stats); test_skip_if(!hpa_supported()); test_skip_if(!have_background_thread); /* Skip since guarded pages cannot be allocated from hpa. */ test_skip_if(san_guard_enabled()); unsigned arena_ind = create_arena(); /* * Our .sh sets dirty mult to 0, so all dirty pages should get purged * any time any thread frees. */ expect_purging(arena_ind, /* expect_deferred */ true); } TEST_END TEST_BEGIN(test_hpa_background_thread_enable_disable) { test_skip_if(!config_stats); test_skip_if(!hpa_supported()); test_skip_if(!have_background_thread); /* Skip since guarded pages cannot be allocated from hpa. */ test_skip_if(san_guard_enabled()); unsigned arena_ind = create_arena(); set_background_thread_enabled(false); expect_purging(arena_ind, false); set_background_thread_enabled(true); wait_until_thread_is_enabled(arena_ind); expect_purging(arena_ind, true); } TEST_END int main(void) { /* * OK, this is a sort of nasty hack. We don't want to add *another* * config option for HPA (the intent is that it becomes available on * more platforms over time, and we're trying to prune back config * options generally. But we'll get initialization errors on other * platforms if we set hpa:true in the MALLOC_CONF (even if we set * abort_conf:false as well). So we reach into the internals and set * them directly, but only if we know that we're actually going to do * something nontrivial in the tests. */ if (config_stats && hpa_supported() && have_background_thread) { opt_hpa = true; opt_background_thread = true; } return test_no_reentrancy( test_hpa_background_thread_purges, test_hpa_background_thread_enable_disable); } redis-8.0.2/deps/jemalloc/test/unit/hpa_background_thread.sh000066400000000000000000000001411501533116600241350ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="hpa_dirty_mult:0,hpa_min_purge_interval_ms:50,hpa_sec_nshards:0" redis-8.0.2/deps/jemalloc/test/unit/hpdata.c000066400000000000000000000203711501533116600207170ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define HPDATA_ADDR ((void *)(10 * HUGEPAGE)) #define HPDATA_AGE 123 TEST_BEGIN(test_reserve_alloc) { hpdata_t hpdata; hpdata_init(&hpdata, HPDATA_ADDR, HPDATA_AGE); /* Allocating a page at a time, we should do first fit. */ for (size_t i = 0; i < HUGEPAGE_PAGES; i++) { expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(HUGEPAGE_PAGES - i, hpdata_longest_free_range_get(&hpdata), ""); void *alloc = hpdata_reserve_alloc(&hpdata, PAGE); expect_ptr_eq((char *)HPDATA_ADDR + i * PAGE, alloc, ""); expect_true(hpdata_consistent(&hpdata), ""); } expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(0, hpdata_longest_free_range_get(&hpdata), ""); /* * Build up a bigger free-range, 2 pages at a time, until we've got 6 * adjacent free pages total. Pages 8-13 should be unreserved after * this. */ hpdata_unreserve(&hpdata, (char *)HPDATA_ADDR + 10 * PAGE, 2 * PAGE); expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(2, hpdata_longest_free_range_get(&hpdata), ""); hpdata_unreserve(&hpdata, (char *)HPDATA_ADDR + 12 * PAGE, 2 * PAGE); expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(4, hpdata_longest_free_range_get(&hpdata), ""); hpdata_unreserve(&hpdata, (char *)HPDATA_ADDR + 8 * PAGE, 2 * PAGE); expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(6, hpdata_longest_free_range_get(&hpdata), ""); /* * Leave page 14 reserved, but free page 15 (this test the case where * unreserving combines two ranges). */ hpdata_unreserve(&hpdata, (char *)HPDATA_ADDR + 15 * PAGE, PAGE); /* * Longest free range shouldn't change; we've got a free range of size * 6, then a reserved page, then another free range. */ expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(6, hpdata_longest_free_range_get(&hpdata), ""); /* After freeing page 14, the two ranges get combined. */ hpdata_unreserve(&hpdata, (char *)HPDATA_ADDR + 14 * PAGE, PAGE); expect_true(hpdata_consistent(&hpdata), ""); expect_zu_eq(8, hpdata_longest_free_range_get(&hpdata), ""); } TEST_END TEST_BEGIN(test_purge_simple) { hpdata_t hpdata; hpdata_init(&hpdata, HPDATA_ADDR, HPDATA_AGE); void *alloc = hpdata_reserve_alloc(&hpdata, HUGEPAGE_PAGES / 2 * PAGE); expect_ptr_eq(alloc, HPDATA_ADDR, ""); /* Create HUGEPAGE_PAGES / 4 dirty inactive pages at the beginning. */ hpdata_unreserve(&hpdata, alloc, HUGEPAGE_PAGES / 4 * PAGE); expect_zu_eq(hpdata_ntouched_get(&hpdata), HUGEPAGE_PAGES / 2, ""); hpdata_alloc_allowed_set(&hpdata, false); hpdata_purge_state_t purge_state; size_t to_purge = hpdata_purge_begin(&hpdata, &purge_state); expect_zu_eq(HUGEPAGE_PAGES / 4, to_purge, ""); void *purge_addr; size_t purge_size; bool got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_true(got_result, ""); expect_ptr_eq(HPDATA_ADDR, purge_addr, ""); expect_zu_eq(HUGEPAGE_PAGES / 4 * PAGE, purge_size, ""); got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_false(got_result, "Unexpected additional purge range: " "extent at %p of size %zu", purge_addr, purge_size); hpdata_purge_end(&hpdata, &purge_state); expect_zu_eq(hpdata_ntouched_get(&hpdata), HUGEPAGE_PAGES / 4, ""); } TEST_END /* * We only test intervening dalloc's not intervening allocs; the latter are * disallowed as a purging precondition (because they interfere with purging * across a retained extent, saving a purge call). */ TEST_BEGIN(test_purge_intervening_dalloc) { hpdata_t hpdata; hpdata_init(&hpdata, HPDATA_ADDR, HPDATA_AGE); /* Allocate the first 3/4 of the pages. */ void *alloc = hpdata_reserve_alloc(&hpdata, 3 * HUGEPAGE_PAGES / 4 * PAGE); expect_ptr_eq(alloc, HPDATA_ADDR, ""); /* Free the first 1/4 and the third 1/4 of the pages. */ hpdata_unreserve(&hpdata, alloc, HUGEPAGE_PAGES / 4 * PAGE); hpdata_unreserve(&hpdata, (void *)((uintptr_t)alloc + 2 * HUGEPAGE_PAGES / 4 * PAGE), HUGEPAGE_PAGES / 4 * PAGE); expect_zu_eq(hpdata_ntouched_get(&hpdata), 3 * HUGEPAGE_PAGES / 4, ""); hpdata_alloc_allowed_set(&hpdata, false); hpdata_purge_state_t purge_state; size_t to_purge = hpdata_purge_begin(&hpdata, &purge_state); expect_zu_eq(HUGEPAGE_PAGES / 2, to_purge, ""); void *purge_addr; size_t purge_size; /* First purge. */ bool got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_true(got_result, ""); expect_ptr_eq(HPDATA_ADDR, purge_addr, ""); expect_zu_eq(HUGEPAGE_PAGES / 4 * PAGE, purge_size, ""); /* Deallocate the second 1/4 before the second purge occurs. */ hpdata_unreserve(&hpdata, (void *)((uintptr_t)alloc + 1 * HUGEPAGE_PAGES / 4 * PAGE), HUGEPAGE_PAGES / 4 * PAGE); /* Now continue purging. */ got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_true(got_result, ""); expect_ptr_eq( (void *)((uintptr_t)alloc + 2 * HUGEPAGE_PAGES / 4 * PAGE), purge_addr, ""); expect_zu_ge(HUGEPAGE_PAGES / 4 * PAGE, purge_size, ""); got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_false(got_result, "Unexpected additional purge range: " "extent at %p of size %zu", purge_addr, purge_size); hpdata_purge_end(&hpdata, &purge_state); expect_zu_eq(hpdata_ntouched_get(&hpdata), HUGEPAGE_PAGES / 4, ""); } TEST_END TEST_BEGIN(test_purge_over_retained) { void *purge_addr; size_t purge_size; hpdata_t hpdata; hpdata_init(&hpdata, HPDATA_ADDR, HPDATA_AGE); /* Allocate the first 3/4 of the pages. */ void *alloc = hpdata_reserve_alloc(&hpdata, 3 * HUGEPAGE_PAGES / 4 * PAGE); expect_ptr_eq(alloc, HPDATA_ADDR, ""); /* Free the second quarter. */ void *second_quarter = (void *)((uintptr_t)alloc + HUGEPAGE_PAGES / 4 * PAGE); hpdata_unreserve(&hpdata, second_quarter, HUGEPAGE_PAGES / 4 * PAGE); expect_zu_eq(hpdata_ntouched_get(&hpdata), 3 * HUGEPAGE_PAGES / 4, ""); /* Purge the second quarter. */ hpdata_alloc_allowed_set(&hpdata, false); hpdata_purge_state_t purge_state; size_t to_purge_dirty = hpdata_purge_begin(&hpdata, &purge_state); expect_zu_eq(HUGEPAGE_PAGES / 4, to_purge_dirty, ""); bool got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_true(got_result, ""); expect_ptr_eq(second_quarter, purge_addr, ""); expect_zu_eq(HUGEPAGE_PAGES / 4 * PAGE, purge_size, ""); got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_false(got_result, "Unexpected additional purge range: " "extent at %p of size %zu", purge_addr, purge_size); hpdata_purge_end(&hpdata, &purge_state); expect_zu_eq(hpdata_ntouched_get(&hpdata), HUGEPAGE_PAGES / 2, ""); /* Free the first and third quarter. */ hpdata_unreserve(&hpdata, HPDATA_ADDR, HUGEPAGE_PAGES / 4 * PAGE); hpdata_unreserve(&hpdata, (void *)((uintptr_t)alloc + 2 * HUGEPAGE_PAGES / 4 * PAGE), HUGEPAGE_PAGES / 4 * PAGE); /* * Purge again. The second quarter is retained, so we can safely * re-purge it. We expect a single purge of 3/4 of the hugepage, * purging half its pages. */ to_purge_dirty = hpdata_purge_begin(&hpdata, &purge_state); expect_zu_eq(HUGEPAGE_PAGES / 2, to_purge_dirty, ""); got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_true(got_result, ""); expect_ptr_eq(HPDATA_ADDR, purge_addr, ""); expect_zu_eq(3 * HUGEPAGE_PAGES / 4 * PAGE, purge_size, ""); got_result = hpdata_purge_next(&hpdata, &purge_state, &purge_addr, &purge_size); expect_false(got_result, "Unexpected additional purge range: " "extent at %p of size %zu", purge_addr, purge_size); hpdata_purge_end(&hpdata, &purge_state); expect_zu_eq(hpdata_ntouched_get(&hpdata), 0, ""); } TEST_END TEST_BEGIN(test_hugify) { hpdata_t hpdata; hpdata_init(&hpdata, HPDATA_ADDR, HPDATA_AGE); void *alloc = hpdata_reserve_alloc(&hpdata, HUGEPAGE / 2); expect_ptr_eq(alloc, HPDATA_ADDR, ""); expect_zu_eq(HUGEPAGE_PAGES / 2, hpdata_ntouched_get(&hpdata), ""); hpdata_hugify(&hpdata); /* Hugeifying should have increased the dirty page count. */ expect_zu_eq(HUGEPAGE_PAGES, hpdata_ntouched_get(&hpdata), ""); } TEST_END int main(void) { return test_no_reentrancy( test_reserve_alloc, test_purge_simple, test_purge_intervening_dalloc, test_purge_over_retained, test_hugify); } redis-8.0.2/deps/jemalloc/test/unit/huge.c000066400000000000000000000072051501533116600204070ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* Threshold: 2 << 20 = 2097152. */ const char *malloc_conf = "oversize_threshold:2097152"; #define HUGE_SZ (2 << 20) #define SMALL_SZ (8) TEST_BEGIN(huge_bind_thread) { unsigned arena1, arena2; size_t sz = sizeof(unsigned); /* Bind to a manual arena. */ expect_d_eq(mallctl("arenas.create", &arena1, &sz, NULL, 0), 0, "Failed to create arena"); expect_d_eq(mallctl("thread.arena", NULL, NULL, &arena1, sizeof(arena1)), 0, "Fail to bind thread"); void *ptr = mallocx(HUGE_SZ, 0); expect_ptr_not_null(ptr, "Fail to allocate huge size"); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_eq(arena1, arena2, "Wrong arena used after binding"); dallocx(ptr, 0); /* Switch back to arena 0. */ test_skip_if(have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)); arena2 = 0; expect_d_eq(mallctl("thread.arena", NULL, NULL, &arena2, sizeof(arena2)), 0, "Fail to bind thread"); ptr = mallocx(SMALL_SZ, MALLOCX_TCACHE_NONE); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_eq(arena2, 0, "Wrong arena used after binding"); dallocx(ptr, MALLOCX_TCACHE_NONE); /* Then huge allocation should use the huge arena. */ ptr = mallocx(HUGE_SZ, 0); expect_ptr_not_null(ptr, "Fail to allocate huge size"); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_ne(arena2, 0, "Wrong arena used after binding"); expect_u_ne(arena1, arena2, "Wrong arena used after binding"); dallocx(ptr, 0); } TEST_END TEST_BEGIN(huge_mallocx) { unsigned arena1, arena2; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", &arena1, &sz, NULL, 0), 0, "Failed to create arena"); void *huge = mallocx(HUGE_SZ, MALLOCX_ARENA(arena1)); expect_ptr_not_null(huge, "Fail to allocate huge size"); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &huge, sizeof(huge)), 0, "Unexpected mallctl() failure"); expect_u_eq(arena1, arena2, "Wrong arena used for mallocx"); dallocx(huge, MALLOCX_ARENA(arena1)); void *huge2 = mallocx(HUGE_SZ, 0); expect_ptr_not_null(huge, "Fail to allocate huge size"); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &huge2, sizeof(huge2)), 0, "Unexpected mallctl() failure"); expect_u_ne(arena1, arena2, "Huge allocation should not come from the manual arena."); expect_u_ne(arena2, 0, "Huge allocation should not come from the arena 0."); dallocx(huge2, 0); } TEST_END TEST_BEGIN(huge_allocation) { unsigned arena1, arena2; void *ptr = mallocx(HUGE_SZ, 0); expect_ptr_not_null(ptr, "Fail to allocate huge size"); size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.lookup", &arena1, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_gt(arena1, 0, "Huge allocation should not come from arena 0"); dallocx(ptr, 0); ptr = mallocx(HUGE_SZ >> 1, 0); expect_ptr_not_null(ptr, "Fail to allocate half huge size"); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_ne(arena1, arena2, "Wrong arena used for half huge"); dallocx(ptr, 0); ptr = mallocx(SMALL_SZ, MALLOCX_TCACHE_NONE); expect_ptr_not_null(ptr, "Fail to allocate small size"); expect_d_eq(mallctl("arenas.lookup", &arena2, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_ne(arena1, arena2, "Huge and small should be from different arenas"); dallocx(ptr, 0); } TEST_END int main(void) { return test( huge_allocation, huge_mallocx, huge_bind_thread); } redis-8.0.2/deps/jemalloc/test/unit/inspect.c000066400000000000000000000222201501533116600211160ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define TEST_UTIL_EINVAL(node, a, b, c, d, why_inval) do { \ assert_d_eq(mallctl("experimental.utilization." node, \ a, b, c, d), EINVAL, "Should fail when " why_inval); \ assert_zu_eq(out_sz, out_sz_ref, \ "Output size touched when given invalid arguments"); \ assert_d_eq(memcmp(out, out_ref, out_sz_ref), 0, \ "Output content touched when given invalid arguments"); \ } while (0) #define TEST_UTIL_QUERY_EINVAL(a, b, c, d, why_inval) \ TEST_UTIL_EINVAL("query", a, b, c, d, why_inval) #define TEST_UTIL_BATCH_EINVAL(a, b, c, d, why_inval) \ TEST_UTIL_EINVAL("batch_query", a, b, c, d, why_inval) #define TEST_UTIL_VALID(node) do { \ assert_d_eq(mallctl("experimental.utilization." node, \ out, &out_sz, in, in_sz), 0, \ "Should return 0 on correct arguments"); \ expect_zu_eq(out_sz, out_sz_ref, "incorrect output size"); \ expect_d_ne(memcmp(out, out_ref, out_sz_ref), 0, \ "Output content should be changed"); \ } while (0) #define TEST_UTIL_BATCH_VALID TEST_UTIL_VALID("batch_query") #define TEST_MAX_SIZE (1 << 20) TEST_BEGIN(test_query) { size_t sz; /* * Select some sizes that can span both small and large sizes, and are * numerically unrelated to any size boundaries. */ for (sz = 7; sz <= TEST_MAX_SIZE && sz <= SC_LARGE_MAXCLASS; sz += (sz <= SC_SMALL_MAXCLASS ? 1009 : 99989)) { void *p = mallocx(sz, 0); void **in = &p; size_t in_sz = sizeof(const void *); size_t out_sz = sizeof(void *) + sizeof(size_t) * 5; void *out = mallocx(out_sz, 0); void *out_ref = mallocx(out_sz, 0); size_t out_sz_ref = out_sz; assert_ptr_not_null(p, "test pointer allocation failed"); assert_ptr_not_null(out, "test output allocation failed"); assert_ptr_not_null(out_ref, "test reference output allocation failed"); #define SLABCUR_READ(out) (*(void **)out) #define COUNTS(out) ((size_t *)((void **)out + 1)) #define NFREE_READ(out) COUNTS(out)[0] #define NREGS_READ(out) COUNTS(out)[1] #define SIZE_READ(out) COUNTS(out)[2] #define BIN_NFREE_READ(out) COUNTS(out)[3] #define BIN_NREGS_READ(out) COUNTS(out)[4] SLABCUR_READ(out) = NULL; NFREE_READ(out) = NREGS_READ(out) = SIZE_READ(out) = -1; BIN_NFREE_READ(out) = BIN_NREGS_READ(out) = -1; memcpy(out_ref, out, out_sz); /* Test invalid argument(s) errors */ TEST_UTIL_QUERY_EINVAL(NULL, &out_sz, in, in_sz, "old is NULL"); TEST_UTIL_QUERY_EINVAL(out, NULL, in, in_sz, "oldlenp is NULL"); TEST_UTIL_QUERY_EINVAL(out, &out_sz, NULL, in_sz, "newp is NULL"); TEST_UTIL_QUERY_EINVAL(out, &out_sz, in, 0, "newlen is zero"); in_sz -= 1; TEST_UTIL_QUERY_EINVAL(out, &out_sz, in, in_sz, "invalid newlen"); in_sz += 1; out_sz_ref = out_sz -= 2 * sizeof(size_t); TEST_UTIL_QUERY_EINVAL(out, &out_sz, in, in_sz, "invalid *oldlenp"); out_sz_ref = out_sz += 2 * sizeof(size_t); /* Examine output for valid call */ TEST_UTIL_VALID("query"); expect_zu_le(sz, SIZE_READ(out), "Extent size should be at least allocation size"); expect_zu_eq(SIZE_READ(out) & (PAGE - 1), 0, "Extent size should be a multiple of page size"); /* * We don't do much bin checking if prof is on, since profiling * can produce extents that are for small size classes but not * slabs, which interferes with things like region counts. */ if (!opt_prof && sz <= SC_SMALL_MAXCLASS) { expect_zu_le(NFREE_READ(out), NREGS_READ(out), "Extent free count exceeded region count"); expect_zu_le(NREGS_READ(out), SIZE_READ(out), "Extent region count exceeded size"); expect_zu_ne(NREGS_READ(out), 0, "Extent region count must be positive"); expect_true(NFREE_READ(out) == 0 || (SLABCUR_READ(out) != NULL && SLABCUR_READ(out) <= p), "Allocation should follow first fit principle"); if (config_stats) { expect_zu_le(BIN_NFREE_READ(out), BIN_NREGS_READ(out), "Bin free count exceeded region count"); expect_zu_ne(BIN_NREGS_READ(out), 0, "Bin region count must be positive"); expect_zu_le(NFREE_READ(out), BIN_NFREE_READ(out), "Extent free count exceeded bin free count"); expect_zu_le(NREGS_READ(out), BIN_NREGS_READ(out), "Extent region count exceeded " "bin region count"); expect_zu_eq(BIN_NREGS_READ(out) % NREGS_READ(out), 0, "Bin region count isn't a multiple of " "extent region count"); expect_zu_le( BIN_NFREE_READ(out) - NFREE_READ(out), BIN_NREGS_READ(out) - NREGS_READ(out), "Free count in other extents in the bin " "exceeded region count in other extents " "in the bin"); expect_zu_le(NREGS_READ(out) - NFREE_READ(out), BIN_NREGS_READ(out) - BIN_NFREE_READ(out), "Extent utilized count exceeded " "bin utilized count"); } } else if (sz > SC_SMALL_MAXCLASS) { expect_zu_eq(NFREE_READ(out), 0, "Extent free count should be zero"); expect_zu_eq(NREGS_READ(out), 1, "Extent region count should be one"); expect_ptr_null(SLABCUR_READ(out), "Current slab must be null for large size classes"); if (config_stats) { expect_zu_eq(BIN_NFREE_READ(out), 0, "Bin free count must be zero for " "large sizes"); expect_zu_eq(BIN_NREGS_READ(out), 0, "Bin region count must be zero for " "large sizes"); } } #undef BIN_NREGS_READ #undef BIN_NFREE_READ #undef SIZE_READ #undef NREGS_READ #undef NFREE_READ #undef COUNTS #undef SLABCUR_READ free(out_ref); free(out); free(p); } } TEST_END TEST_BEGIN(test_batch) { size_t sz; /* * Select some sizes that can span both small and large sizes, and are * numerically unrelated to any size boundaries. */ for (sz = 17; sz <= TEST_MAX_SIZE && sz <= SC_LARGE_MAXCLASS; sz += (sz <= SC_SMALL_MAXCLASS ? 1019 : 99991)) { void *p = mallocx(sz, 0); void *q = mallocx(sz, 0); void *in[] = {p, q}; size_t in_sz = sizeof(const void *) * 2; size_t out[] = {-1, -1, -1, -1, -1, -1}; size_t out_sz = sizeof(size_t) * 6; size_t out_ref[] = {-1, -1, -1, -1, -1, -1}; size_t out_sz_ref = out_sz; assert_ptr_not_null(p, "test pointer allocation failed"); assert_ptr_not_null(q, "test pointer allocation failed"); /* Test invalid argument(s) errors */ TEST_UTIL_BATCH_EINVAL(NULL, &out_sz, in, in_sz, "old is NULL"); TEST_UTIL_BATCH_EINVAL(out, NULL, in, in_sz, "oldlenp is NULL"); TEST_UTIL_BATCH_EINVAL(out, &out_sz, NULL, in_sz, "newp is NULL"); TEST_UTIL_BATCH_EINVAL(out, &out_sz, in, 0, "newlen is zero"); in_sz -= 1; TEST_UTIL_BATCH_EINVAL(out, &out_sz, in, in_sz, "newlen is not an exact multiple"); in_sz += 1; out_sz_ref = out_sz -= 2 * sizeof(size_t); TEST_UTIL_BATCH_EINVAL(out, &out_sz, in, in_sz, "*oldlenp is not an exact multiple"); out_sz_ref = out_sz += 2 * sizeof(size_t); in_sz -= sizeof(const void *); TEST_UTIL_BATCH_EINVAL(out, &out_sz, in, in_sz, "*oldlenp and newlen do not match"); in_sz += sizeof(const void *); /* Examine output for valid calls */ #define TEST_EQUAL_REF(i, message) \ assert_d_eq(memcmp(out + (i) * 3, out_ref + (i) * 3, 3), 0, message) #define NFREE_READ(out, i) out[(i) * 3] #define NREGS_READ(out, i) out[(i) * 3 + 1] #define SIZE_READ(out, i) out[(i) * 3 + 2] out_sz_ref = out_sz /= 2; in_sz /= 2; TEST_UTIL_BATCH_VALID; expect_zu_le(sz, SIZE_READ(out, 0), "Extent size should be at least allocation size"); expect_zu_eq(SIZE_READ(out, 0) & (PAGE - 1), 0, "Extent size should be a multiple of page size"); /* * See the corresponding comment in test_query; profiling breaks * our slab count expectations. */ if (sz <= SC_SMALL_MAXCLASS && !opt_prof) { expect_zu_le(NFREE_READ(out, 0), NREGS_READ(out, 0), "Extent free count exceeded region count"); expect_zu_le(NREGS_READ(out, 0), SIZE_READ(out, 0), "Extent region count exceeded size"); expect_zu_ne(NREGS_READ(out, 0), 0, "Extent region count must be positive"); } else if (sz > SC_SMALL_MAXCLASS) { expect_zu_eq(NFREE_READ(out, 0), 0, "Extent free count should be zero"); expect_zu_eq(NREGS_READ(out, 0), 1, "Extent region count should be one"); } TEST_EQUAL_REF(1, "Should not overwrite content beyond what's needed"); in_sz *= 2; out_sz_ref = out_sz *= 2; memcpy(out_ref, out, 3 * sizeof(size_t)); TEST_UTIL_BATCH_VALID; TEST_EQUAL_REF(0, "Statistics should be stable across calls"); if (sz <= SC_SMALL_MAXCLASS) { expect_zu_le(NFREE_READ(out, 1), NREGS_READ(out, 1), "Extent free count exceeded region count"); } else { expect_zu_eq(NFREE_READ(out, 0), 0, "Extent free count should be zero"); } expect_zu_eq(NREGS_READ(out, 0), NREGS_READ(out, 1), "Extent region count should be same for same region size"); expect_zu_eq(SIZE_READ(out, 0), SIZE_READ(out, 1), "Extent size should be same for same region size"); #undef SIZE_READ #undef NREGS_READ #undef NFREE_READ #undef TEST_EQUAL_REF free(q); free(p); } } TEST_END int main(void) { assert_zu_lt(SC_SMALL_MAXCLASS + 100000, TEST_MAX_SIZE, "Test case cannot cover large classes"); return test(test_query, test_batch); } redis-8.0.2/deps/jemalloc/test/unit/inspect.sh000066400000000000000000000001271501533116600213100ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:false" fi redis-8.0.2/deps/jemalloc/test/unit/junk.c000066400000000000000000000127141501533116600204270ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define arraylen(arr) (sizeof(arr)/sizeof(arr[0])) static size_t ptr_ind; static void *volatile ptrs[100]; static void *last_junked_ptr; static size_t last_junked_usize; static void reset() { ptr_ind = 0; last_junked_ptr = NULL; last_junked_usize = 0; } static void test_junk(void *ptr, size_t usize) { last_junked_ptr = ptr; last_junked_usize = usize; } static void do_allocs(size_t size, bool zero, size_t lg_align) { #define JUNK_ALLOC(...) \ do { \ assert(ptr_ind + 1 < arraylen(ptrs)); \ void *ptr = __VA_ARGS__; \ assert_ptr_not_null(ptr, ""); \ ptrs[ptr_ind++] = ptr; \ if (opt_junk_alloc && !zero) { \ expect_ptr_eq(ptr, last_junked_ptr, ""); \ expect_zu_eq(last_junked_usize, \ TEST_MALLOC_SIZE(ptr), ""); \ } \ } while (0) if (!zero && lg_align == 0) { JUNK_ALLOC(malloc(size)); } if (!zero) { JUNK_ALLOC(aligned_alloc(1 << lg_align, size)); } #ifdef JEMALLOC_OVERRIDE_MEMALIGN if (!zero) { JUNK_ALLOC(je_memalign(1 << lg_align, size)); } #endif #ifdef JEMALLOC_OVERRIDE_VALLOC if (!zero && lg_align == LG_PAGE) { JUNK_ALLOC(je_valloc(size)); } #endif int zero_flag = zero ? MALLOCX_ZERO : 0; JUNK_ALLOC(mallocx(size, zero_flag | MALLOCX_LG_ALIGN(lg_align))); JUNK_ALLOC(mallocx(size, zero_flag | MALLOCX_LG_ALIGN(lg_align) | MALLOCX_TCACHE_NONE)); if (lg_align >= LG_SIZEOF_PTR) { void *memalign_result; int err = posix_memalign(&memalign_result, (1 << lg_align), size); assert_d_eq(err, 0, ""); JUNK_ALLOC(memalign_result); } } TEST_BEGIN(test_junk_alloc_free) { bool zerovals[] = {false, true}; size_t sizevals[] = { 1, 8, 100, 1000, 100*1000 /* * Memory allocation failure is a real possibility in 32-bit mode. * Rather than try to check in the face of resource exhaustion, we just * rely more on the 64-bit tests. This is a little bit white-box-y in * the sense that this is only a good test strategy if we know that the * junk pathways don't touch interact with the allocation selection * mechanisms; but this is in fact the case. */ #if LG_SIZEOF_PTR == 3 , 10 * 1000 * 1000 #endif }; size_t lg_alignvals[] = { 0, 4, 10, 15, 16, LG_PAGE #if LG_SIZEOF_PTR == 3 , 20, 24 #endif }; #define JUNK_FREE(...) \ do { \ do_allocs(size, zero, lg_align); \ for (size_t n = 0; n < ptr_ind; n++) { \ void *ptr = ptrs[n]; \ __VA_ARGS__; \ if (opt_junk_free) { \ assert_ptr_eq(ptr, last_junked_ptr, \ ""); \ assert_zu_eq(usize, last_junked_usize, \ ""); \ } \ reset(); \ } \ } while (0) for (size_t i = 0; i < arraylen(zerovals); i++) { for (size_t j = 0; j < arraylen(sizevals); j++) { for (size_t k = 0; k < arraylen(lg_alignvals); k++) { bool zero = zerovals[i]; size_t size = sizevals[j]; size_t lg_align = lg_alignvals[k]; size_t usize = nallocx(size, MALLOCX_LG_ALIGN(lg_align)); JUNK_FREE(free(ptr)); JUNK_FREE(dallocx(ptr, 0)); JUNK_FREE(dallocx(ptr, MALLOCX_TCACHE_NONE)); JUNK_FREE(dallocx(ptr, MALLOCX_LG_ALIGN( lg_align))); JUNK_FREE(sdallocx(ptr, usize, MALLOCX_LG_ALIGN( lg_align))); JUNK_FREE(sdallocx(ptr, usize, MALLOCX_TCACHE_NONE | MALLOCX_LG_ALIGN(lg_align))); if (opt_zero_realloc_action == zero_realloc_action_free) { JUNK_FREE(realloc(ptr, 0)); } } } } } TEST_END TEST_BEGIN(test_realloc_expand) { char *volatile ptr; char *volatile expanded; test_skip_if(!opt_junk_alloc); /* Realloc */ ptr = malloc(SC_SMALL_MAXCLASS); expanded = realloc(ptr, SC_LARGE_MINCLASS); expect_ptr_eq(last_junked_ptr, &expanded[SC_SMALL_MAXCLASS], ""); expect_zu_eq(last_junked_usize, SC_LARGE_MINCLASS - SC_SMALL_MAXCLASS, ""); free(expanded); /* rallocx(..., 0) */ ptr = malloc(SC_SMALL_MAXCLASS); expanded = rallocx(ptr, SC_LARGE_MINCLASS, 0); expect_ptr_eq(last_junked_ptr, &expanded[SC_SMALL_MAXCLASS], ""); expect_zu_eq(last_junked_usize, SC_LARGE_MINCLASS - SC_SMALL_MAXCLASS, ""); free(expanded); /* rallocx(..., nonzero) */ ptr = malloc(SC_SMALL_MAXCLASS); expanded = rallocx(ptr, SC_LARGE_MINCLASS, MALLOCX_TCACHE_NONE); expect_ptr_eq(last_junked_ptr, &expanded[SC_SMALL_MAXCLASS], ""); expect_zu_eq(last_junked_usize, SC_LARGE_MINCLASS - SC_SMALL_MAXCLASS, ""); free(expanded); /* rallocx(..., MALLOCX_ZERO) */ ptr = malloc(SC_SMALL_MAXCLASS); last_junked_ptr = (void *)-1; last_junked_usize = (size_t)-1; expanded = rallocx(ptr, SC_LARGE_MINCLASS, MALLOCX_ZERO); expect_ptr_eq(last_junked_ptr, (void *)-1, ""); expect_zu_eq(last_junked_usize, (size_t)-1, ""); free(expanded); /* * Unfortunately, testing xallocx reliably is difficult to do portably * (since allocations can be expanded / not expanded differently on * different platforms. We rely on manual inspection there -- the * xallocx pathway is easy to inspect, though. * * Likewise, we don't test the shrinking pathways. It's difficult to do * so consistently (because of the risk of split failure or memory * exhaustion, in which case no junking should happen). This is fine * -- junking is a best-effort debug mechanism in the first place. */ } TEST_END int main(void) { junk_alloc_callback = &test_junk; junk_free_callback = &test_junk; /* * We check the last pointer junked. If a reentrant call happens, that * might be an internal allocation. */ return test_no_reentrancy( test_junk_alloc_free, test_realloc_expand); } redis-8.0.2/deps/jemalloc/test/unit/junk.sh000066400000000000000000000001551501533116600206130ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="abort:false,zero:false,junk:true" fi redis-8.0.2/deps/jemalloc/test/unit/junk_alloc.c000066400000000000000000000000221501533116600215660ustar00rootroot00000000000000#include "junk.c" redis-8.0.2/deps/jemalloc/test/unit/junk_alloc.sh000066400000000000000000000001561501533116600217660ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="abort:false,zero:false,junk:alloc" fi redis-8.0.2/deps/jemalloc/test/unit/junk_free.c000066400000000000000000000000221501533116600214150ustar00rootroot00000000000000#include "junk.c" redis-8.0.2/deps/jemalloc/test/unit/junk_free.sh000066400000000000000000000001551501533116600216140ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="abort:false,zero:false,junk:free" fi redis-8.0.2/deps/jemalloc/test/unit/log.c000066400000000000000000000100661501533116600202370ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/log.h" static void update_log_var_names(const char *names) { strncpy(log_var_names, names, sizeof(log_var_names)); } static void expect_no_logging(const char *names) { log_var_t log_l1 = LOG_VAR_INIT("l1"); log_var_t log_l2 = LOG_VAR_INIT("l2"); log_var_t log_l2_a = LOG_VAR_INIT("l2.a"); update_log_var_names(names); int count = 0; for (int i = 0; i < 10; i++) { log_do_begin(log_l1) count++; log_do_end(log_l1) log_do_begin(log_l2) count++; log_do_end(log_l2) log_do_begin(log_l2_a) count++; log_do_end(log_l2_a) } expect_d_eq(count, 0, "Disabled logging not ignored!"); } TEST_BEGIN(test_log_disabled) { test_skip_if(!config_log); atomic_store_b(&log_init_done, true, ATOMIC_RELAXED); expect_no_logging(""); expect_no_logging("abc"); expect_no_logging("a.b.c"); expect_no_logging("l12"); expect_no_logging("l123|a456|b789"); expect_no_logging("|||"); } TEST_END TEST_BEGIN(test_log_enabled_direct) { test_skip_if(!config_log); atomic_store_b(&log_init_done, true, ATOMIC_RELAXED); log_var_t log_l1 = LOG_VAR_INIT("l1"); log_var_t log_l1_a = LOG_VAR_INIT("l1.a"); log_var_t log_l2 = LOG_VAR_INIT("l2"); int count; count = 0; update_log_var_names("l1"); for (int i = 0; i < 10; i++) { log_do_begin(log_l1) count++; log_do_end(log_l1) } expect_d_eq(count, 10, "Mis-logged!"); count = 0; update_log_var_names("l1.a"); for (int i = 0; i < 10; i++) { log_do_begin(log_l1_a) count++; log_do_end(log_l1_a) } expect_d_eq(count, 10, "Mis-logged!"); count = 0; update_log_var_names("l1.a|abc|l2|def"); for (int i = 0; i < 10; i++) { log_do_begin(log_l1_a) count++; log_do_end(log_l1_a) log_do_begin(log_l2) count++; log_do_end(log_l2) } expect_d_eq(count, 20, "Mis-logged!"); } TEST_END TEST_BEGIN(test_log_enabled_indirect) { test_skip_if(!config_log); atomic_store_b(&log_init_done, true, ATOMIC_RELAXED); update_log_var_names("l0|l1|abc|l2.b|def"); /* On. */ log_var_t log_l1 = LOG_VAR_INIT("l1"); /* Off. */ log_var_t log_l1a = LOG_VAR_INIT("l1a"); /* On. */ log_var_t log_l1_a = LOG_VAR_INIT("l1.a"); /* Off. */ log_var_t log_l2_a = LOG_VAR_INIT("l2.a"); /* On. */ log_var_t log_l2_b_a = LOG_VAR_INIT("l2.b.a"); /* On. */ log_var_t log_l2_b_b = LOG_VAR_INIT("l2.b.b"); /* 4 are on total, so should sum to 40. */ int count = 0; for (int i = 0; i < 10; i++) { log_do_begin(log_l1) count++; log_do_end(log_l1) log_do_begin(log_l1a) count++; log_do_end(log_l1a) log_do_begin(log_l1_a) count++; log_do_end(log_l1_a) log_do_begin(log_l2_a) count++; log_do_end(log_l2_a) log_do_begin(log_l2_b_a) count++; log_do_end(log_l2_b_a) log_do_begin(log_l2_b_b) count++; log_do_end(log_l2_b_b) } expect_d_eq(count, 40, "Mis-logged!"); } TEST_END TEST_BEGIN(test_log_enabled_global) { test_skip_if(!config_log); atomic_store_b(&log_init_done, true, ATOMIC_RELAXED); update_log_var_names("abc|.|def"); log_var_t log_l1 = LOG_VAR_INIT("l1"); log_var_t log_l2_a_a = LOG_VAR_INIT("l2.a.a"); int count = 0; for (int i = 0; i < 10; i++) { log_do_begin(log_l1) count++; log_do_end(log_l1) log_do_begin(log_l2_a_a) count++; log_do_end(log_l2_a_a) } expect_d_eq(count, 20, "Mis-logged!"); } TEST_END TEST_BEGIN(test_logs_if_no_init) { test_skip_if(!config_log); atomic_store_b(&log_init_done, false, ATOMIC_RELAXED); log_var_t l = LOG_VAR_INIT("definitely.not.enabled"); int count = 0; for (int i = 0; i < 10; i++) { log_do_begin(l) count++; log_do_end(l) } expect_d_eq(count, 0, "Logging shouldn't happen if not initialized."); } TEST_END /* * This really just checks to make sure that this usage compiles; we don't have * any test code to run. */ TEST_BEGIN(test_log_only_format_string) { if (false) { LOG("log_str", "No arguments follow this format string."); } } TEST_END int main(void) { return test( test_log_disabled, test_log_enabled_direct, test_log_enabled_indirect, test_log_enabled_global, test_logs_if_no_init, test_log_only_format_string); } redis-8.0.2/deps/jemalloc/test/unit/mallctl.c000066400000000000000000001213021501533116600211020ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/ctl.h" #include "jemalloc/internal/hook.h" #include "jemalloc/internal/util.h" TEST_BEGIN(test_mallctl_errors) { uint64_t epoch; size_t sz; expect_d_eq(mallctl("no_such_name", NULL, NULL, NULL, 0), ENOENT, "mallctl() should return ENOENT for non-existent names"); expect_d_eq(mallctl("version", NULL, NULL, "0.0.0", strlen("0.0.0")), EPERM, "mallctl() should return EPERM on attempt to write " "read-only value"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)-1), EINVAL, "mallctl() should return EINVAL for input size mismatch"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)+1), EINVAL, "mallctl() should return EINVAL for input size mismatch"); sz = sizeof(epoch)-1; expect_d_eq(mallctl("epoch", (void *)&epoch, &sz, NULL, 0), EINVAL, "mallctl() should return EINVAL for output size mismatch"); sz = sizeof(epoch)+1; expect_d_eq(mallctl("epoch", (void *)&epoch, &sz, NULL, 0), EINVAL, "mallctl() should return EINVAL for output size mismatch"); } TEST_END TEST_BEGIN(test_mallctlnametomib_errors) { size_t mib[1]; size_t miblen; miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("no_such_name", mib, &miblen), ENOENT, "mallctlnametomib() should return ENOENT for non-existent names"); } TEST_END TEST_BEGIN(test_mallctlbymib_errors) { uint64_t epoch; size_t sz; size_t mib[1]; size_t miblen; miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("version", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, "0.0.0", strlen("0.0.0")), EPERM, "mallctl() should return EPERM on " "attempt to write read-only value"); miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("epoch", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&epoch, sizeof(epoch)-1), EINVAL, "mallctlbymib() should return EINVAL for input size mismatch"); expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&epoch, sizeof(epoch)+1), EINVAL, "mallctlbymib() should return EINVAL for input size mismatch"); sz = sizeof(epoch)-1; expect_d_eq(mallctlbymib(mib, miblen, (void *)&epoch, &sz, NULL, 0), EINVAL, "mallctlbymib() should return EINVAL for output size mismatch"); sz = sizeof(epoch)+1; expect_d_eq(mallctlbymib(mib, miblen, (void *)&epoch, &sz, NULL, 0), EINVAL, "mallctlbymib() should return EINVAL for output size mismatch"); } TEST_END TEST_BEGIN(test_mallctl_read_write) { uint64_t old_epoch, new_epoch; size_t sz = sizeof(old_epoch); /* Blind. */ expect_d_eq(mallctl("epoch", NULL, NULL, NULL, 0), 0, "Unexpected mallctl() failure"); expect_zu_eq(sz, sizeof(old_epoch), "Unexpected output size"); /* Read. */ expect_d_eq(mallctl("epoch", (void *)&old_epoch, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_zu_eq(sz, sizeof(old_epoch), "Unexpected output size"); /* Write. */ expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&new_epoch, sizeof(new_epoch)), 0, "Unexpected mallctl() failure"); expect_zu_eq(sz, sizeof(old_epoch), "Unexpected output size"); /* Read+write. */ expect_d_eq(mallctl("epoch", (void *)&old_epoch, &sz, (void *)&new_epoch, sizeof(new_epoch)), 0, "Unexpected mallctl() failure"); expect_zu_eq(sz, sizeof(old_epoch), "Unexpected output size"); } TEST_END TEST_BEGIN(test_mallctlnametomib_short_mib) { size_t mib[4]; size_t miblen; miblen = 3; mib[3] = 42; expect_d_eq(mallctlnametomib("arenas.bin.0.nregs", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); expect_zu_eq(miblen, 3, "Unexpected mib output length"); expect_zu_eq(mib[3], 42, "mallctlnametomib() wrote past the end of the input mib"); } TEST_END TEST_BEGIN(test_mallctlnametomib_short_name) { size_t mib[4]; size_t miblen; miblen = 4; mib[3] = 42; expect_d_eq(mallctlnametomib("arenas.bin.0", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); expect_zu_eq(miblen, 3, "Unexpected mib output length"); expect_zu_eq(mib[3], 42, "mallctlnametomib() wrote past the end of the input mib"); } TEST_END TEST_BEGIN(test_mallctlmibnametomib) { size_t mib[4]; size_t miblen = 4; uint32_t result, result_ref; size_t len_result = sizeof(uint32_t); tsd_t *tsd = tsd_fetch(); /* Error cases */ assert_d_eq(ctl_mibnametomib(tsd, mib, 0, "bob", &miblen), ENOENT, ""); assert_zu_eq(miblen, 4, ""); assert_d_eq(ctl_mibnametomib(tsd, mib, 0, "9999", &miblen), ENOENT, ""); assert_zu_eq(miblen, 4, ""); /* Valid case. */ assert_d_eq(ctl_mibnametomib(tsd, mib, 0, "arenas", &miblen), 0, ""); assert_zu_eq(miblen, 1, ""); miblen = 4; assert_d_eq(ctl_mibnametomib(tsd, mib, 1, "bin", &miblen), 0, ""); assert_zu_eq(miblen, 2, ""); expect_d_eq(mallctlbymib(mib, miblen, &result, &len_result, NULL, 0), ENOENT, "mallctlbymib() should fail on partial path"); /* Error cases. */ miblen = 4; assert_d_eq(ctl_mibnametomib(tsd, mib, 2, "bob", &miblen), ENOENT, ""); assert_zu_eq(miblen, 4, ""); assert_d_eq(ctl_mibnametomib(tsd, mib, 2, "9999", &miblen), ENOENT, ""); assert_zu_eq(miblen, 4, ""); /* Valid case. */ assert_d_eq(ctl_mibnametomib(tsd, mib, 2, "0", &miblen), 0, ""); assert_zu_eq(miblen, 3, ""); expect_d_eq(mallctlbymib(mib, miblen, &result, &len_result, NULL, 0), ENOENT, "mallctlbymib() should fail on partial path"); /* Error cases. */ miblen = 4; assert_d_eq(ctl_mibnametomib(tsd, mib, 3, "bob", &miblen), ENOENT, ""); assert_zu_eq(miblen, 4, ""); assert_d_eq(ctl_mibnametomib(tsd, mib, 3, "9999", &miblen), ENOENT, ""); assert_zu_eq(miblen, 4, ""); /* Valid case. */ assert_d_eq(ctl_mibnametomib(tsd, mib, 3, "nregs", &miblen), 0, ""); assert_zu_eq(miblen, 4, ""); assert_d_eq(mallctlbymib(mib, miblen, &result, &len_result, NULL, 0), 0, "Unexpected mallctlbymib() failure"); assert_d_eq(mallctl("arenas.bin.0.nregs", &result_ref, &len_result, NULL, 0), 0, "Unexpected mallctl() failure"); expect_zu_eq(result, result_ref, "mallctlbymib() and mallctl() returned different result"); } TEST_END TEST_BEGIN(test_mallctlbymibname) { size_t mib[4]; size_t miblen = 4; uint32_t result, result_ref; size_t len_result = sizeof(uint32_t); tsd_t *tsd = tsd_fetch(); /* Error cases. */ assert_d_eq(mallctlnametomib("arenas", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); assert_zu_eq(miblen, 1, ""); miblen = 4; assert_d_eq(ctl_bymibname(tsd, mib, 1, "bin.0", &miblen, &result, &len_result, NULL, 0), ENOENT, ""); miblen = 4; assert_d_eq(ctl_bymibname(tsd, mib, 1, "bin.0.bob", &miblen, &result, &len_result, NULL, 0), ENOENT, ""); assert_zu_eq(miblen, 4, ""); /* Valid cases. */ assert_d_eq(mallctl("arenas.bin.0.nregs", &result_ref, &len_result, NULL, 0), 0, "Unexpected mallctl() failure"); miblen = 4; assert_d_eq(ctl_bymibname(tsd, mib, 0, "arenas.bin.0.nregs", &miblen, &result, &len_result, NULL, 0), 0, ""); assert_zu_eq(miblen, 4, ""); expect_zu_eq(result, result_ref, "Unexpected result"); assert_d_eq(ctl_bymibname(tsd, mib, 1, "bin.0.nregs", &miblen, &result, &len_result, NULL, 0), 0, ""); assert_zu_eq(miblen, 4, ""); expect_zu_eq(result, result_ref, "Unexpected result"); assert_d_eq(ctl_bymibname(tsd, mib, 2, "0.nregs", &miblen, &result, &len_result, NULL, 0), 0, ""); assert_zu_eq(miblen, 4, ""); expect_zu_eq(result, result_ref, "Unexpected result"); assert_d_eq(ctl_bymibname(tsd, mib, 3, "nregs", &miblen, &result, &len_result, NULL, 0), 0, ""); assert_zu_eq(miblen, 4, ""); expect_zu_eq(result, result_ref, "Unexpected result"); } TEST_END TEST_BEGIN(test_mallctl_config) { #define TEST_MALLCTL_CONFIG(config, t) do { \ t oldval; \ size_t sz = sizeof(oldval); \ expect_d_eq(mallctl("config."#config, (void *)&oldval, &sz, \ NULL, 0), 0, "Unexpected mallctl() failure"); \ expect_b_eq(oldval, config_##config, "Incorrect config value"); \ expect_zu_eq(sz, sizeof(oldval), "Unexpected output size"); \ } while (0) TEST_MALLCTL_CONFIG(cache_oblivious, bool); TEST_MALLCTL_CONFIG(debug, bool); TEST_MALLCTL_CONFIG(fill, bool); TEST_MALLCTL_CONFIG(lazy_lock, bool); TEST_MALLCTL_CONFIG(malloc_conf, const char *); TEST_MALLCTL_CONFIG(prof, bool); TEST_MALLCTL_CONFIG(prof_libgcc, bool); TEST_MALLCTL_CONFIG(prof_libunwind, bool); TEST_MALLCTL_CONFIG(stats, bool); TEST_MALLCTL_CONFIG(utrace, bool); TEST_MALLCTL_CONFIG(xmalloc, bool); #undef TEST_MALLCTL_CONFIG } TEST_END TEST_BEGIN(test_mallctl_opt) { bool config_always = true; #define TEST_MALLCTL_OPT(t, opt, config) do { \ t oldval; \ size_t sz = sizeof(oldval); \ int expected = config_##config ? 0 : ENOENT; \ int result = mallctl("opt."#opt, (void *)&oldval, &sz, NULL, \ 0); \ expect_d_eq(result, expected, \ "Unexpected mallctl() result for opt."#opt); \ expect_zu_eq(sz, sizeof(oldval), "Unexpected output size"); \ } while (0) TEST_MALLCTL_OPT(bool, abort, always); TEST_MALLCTL_OPT(bool, abort_conf, always); TEST_MALLCTL_OPT(bool, cache_oblivious, always); TEST_MALLCTL_OPT(bool, trust_madvise, always); TEST_MALLCTL_OPT(bool, confirm_conf, always); TEST_MALLCTL_OPT(const char *, metadata_thp, always); TEST_MALLCTL_OPT(bool, retain, always); TEST_MALLCTL_OPT(const char *, dss, always); TEST_MALLCTL_OPT(bool, hpa, always); TEST_MALLCTL_OPT(size_t, hpa_slab_max_alloc, always); TEST_MALLCTL_OPT(size_t, hpa_sec_nshards, always); TEST_MALLCTL_OPT(size_t, hpa_sec_max_alloc, always); TEST_MALLCTL_OPT(size_t, hpa_sec_max_bytes, always); TEST_MALLCTL_OPT(size_t, hpa_sec_bytes_after_flush, always); TEST_MALLCTL_OPT(size_t, hpa_sec_batch_fill_extra, always); TEST_MALLCTL_OPT(unsigned, narenas, always); TEST_MALLCTL_OPT(const char *, percpu_arena, always); TEST_MALLCTL_OPT(size_t, oversize_threshold, always); TEST_MALLCTL_OPT(bool, background_thread, always); TEST_MALLCTL_OPT(ssize_t, dirty_decay_ms, always); TEST_MALLCTL_OPT(ssize_t, muzzy_decay_ms, always); TEST_MALLCTL_OPT(bool, stats_print, always); TEST_MALLCTL_OPT(const char *, stats_print_opts, always); TEST_MALLCTL_OPT(int64_t, stats_interval, always); TEST_MALLCTL_OPT(const char *, stats_interval_opts, always); TEST_MALLCTL_OPT(const char *, junk, fill); TEST_MALLCTL_OPT(bool, zero, fill); TEST_MALLCTL_OPT(bool, utrace, utrace); TEST_MALLCTL_OPT(bool, xmalloc, xmalloc); TEST_MALLCTL_OPT(bool, tcache, always); TEST_MALLCTL_OPT(size_t, lg_extent_max_active_fit, always); TEST_MALLCTL_OPT(size_t, tcache_max, always); TEST_MALLCTL_OPT(const char *, thp, always); TEST_MALLCTL_OPT(const char *, zero_realloc, always); TEST_MALLCTL_OPT(bool, prof, prof); TEST_MALLCTL_OPT(const char *, prof_prefix, prof); TEST_MALLCTL_OPT(bool, prof_active, prof); TEST_MALLCTL_OPT(ssize_t, lg_prof_sample, prof); TEST_MALLCTL_OPT(bool, prof_accum, prof); TEST_MALLCTL_OPT(ssize_t, lg_prof_interval, prof); TEST_MALLCTL_OPT(bool, prof_gdump, prof); TEST_MALLCTL_OPT(bool, prof_final, prof); TEST_MALLCTL_OPT(bool, prof_leak, prof); TEST_MALLCTL_OPT(bool, prof_leak_error, prof); TEST_MALLCTL_OPT(ssize_t, prof_recent_alloc_max, prof); TEST_MALLCTL_OPT(bool, prof_stats, prof); TEST_MALLCTL_OPT(bool, prof_sys_thread_name, prof); TEST_MALLCTL_OPT(ssize_t, lg_san_uaf_align, uaf_detection); #undef TEST_MALLCTL_OPT } TEST_END TEST_BEGIN(test_manpage_example) { unsigned nbins, i; size_t mib[4]; size_t len, miblen; len = sizeof(nbins); expect_d_eq(mallctl("arenas.nbins", (void *)&nbins, &len, NULL, 0), 0, "Unexpected mallctl() failure"); miblen = 4; expect_d_eq(mallctlnametomib("arenas.bin.0.size", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); for (i = 0; i < nbins; i++) { size_t bin_size; mib[2] = i; len = sizeof(bin_size); expect_d_eq(mallctlbymib(mib, miblen, (void *)&bin_size, &len, NULL, 0), 0, "Unexpected mallctlbymib() failure"); /* Do something with bin_size... */ } } TEST_END TEST_BEGIN(test_tcache_none) { test_skip_if(!opt_tcache); /* Allocate p and q. */ void *p0 = mallocx(42, 0); expect_ptr_not_null(p0, "Unexpected mallocx() failure"); void *q = mallocx(42, 0); expect_ptr_not_null(q, "Unexpected mallocx() failure"); /* Deallocate p and q, but bypass the tcache for q. */ dallocx(p0, 0); dallocx(q, MALLOCX_TCACHE_NONE); /* Make sure that tcache-based allocation returns p, not q. */ void *p1 = mallocx(42, 0); expect_ptr_not_null(p1, "Unexpected mallocx() failure"); if (!opt_prof && !san_uaf_detection_enabled()) { expect_ptr_eq(p0, p1, "Expected tcache to allocate cached region"); } /* Clean up. */ dallocx(p1, MALLOCX_TCACHE_NONE); } TEST_END TEST_BEGIN(test_tcache) { #define NTCACHES 10 unsigned tis[NTCACHES]; void *ps[NTCACHES]; void *qs[NTCACHES]; unsigned i; size_t sz, psz, qsz; psz = 42; qsz = nallocx(psz, 0) + 1; /* Create tcaches. */ for (i = 0; i < NTCACHES; i++) { sz = sizeof(unsigned); expect_d_eq(mallctl("tcache.create", (void *)&tis[i], &sz, NULL, 0), 0, "Unexpected mallctl() failure, i=%u", i); } /* Exercise tcache ID recycling. */ for (i = 0; i < NTCACHES; i++) { expect_d_eq(mallctl("tcache.destroy", NULL, NULL, (void *)&tis[i], sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u", i); } for (i = 0; i < NTCACHES; i++) { sz = sizeof(unsigned); expect_d_eq(mallctl("tcache.create", (void *)&tis[i], &sz, NULL, 0), 0, "Unexpected mallctl() failure, i=%u", i); } /* Flush empty tcaches. */ for (i = 0; i < NTCACHES; i++) { expect_d_eq(mallctl("tcache.flush", NULL, NULL, (void *)&tis[i], sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u", i); } /* Cache some allocations. */ for (i = 0; i < NTCACHES; i++) { ps[i] = mallocx(psz, MALLOCX_TCACHE(tis[i])); expect_ptr_not_null(ps[i], "Unexpected mallocx() failure, i=%u", i); dallocx(ps[i], MALLOCX_TCACHE(tis[i])); qs[i] = mallocx(qsz, MALLOCX_TCACHE(tis[i])); expect_ptr_not_null(qs[i], "Unexpected mallocx() failure, i=%u", i); dallocx(qs[i], MALLOCX_TCACHE(tis[i])); } /* Verify that tcaches allocate cached regions. */ for (i = 0; i < NTCACHES; i++) { void *p0 = ps[i]; ps[i] = mallocx(psz, MALLOCX_TCACHE(tis[i])); expect_ptr_not_null(ps[i], "Unexpected mallocx() failure, i=%u", i); if (!san_uaf_detection_enabled()) { expect_ptr_eq(ps[i], p0, "Expected mallocx() to " "allocate cached region, i=%u", i); } } /* Verify that reallocation uses cached regions. */ for (i = 0; i < NTCACHES; i++) { void *q0 = qs[i]; qs[i] = rallocx(ps[i], qsz, MALLOCX_TCACHE(tis[i])); expect_ptr_not_null(qs[i], "Unexpected rallocx() failure, i=%u", i); if (!san_uaf_detection_enabled()) { expect_ptr_eq(qs[i], q0, "Expected rallocx() to " "allocate cached region, i=%u", i); } /* Avoid undefined behavior in case of test failure. */ if (qs[i] == NULL) { qs[i] = ps[i]; } } for (i = 0; i < NTCACHES; i++) { dallocx(qs[i], MALLOCX_TCACHE(tis[i])); } /* Flush some non-empty tcaches. */ for (i = 0; i < NTCACHES/2; i++) { expect_d_eq(mallctl("tcache.flush", NULL, NULL, (void *)&tis[i], sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u", i); } /* Destroy tcaches. */ for (i = 0; i < NTCACHES; i++) { expect_d_eq(mallctl("tcache.destroy", NULL, NULL, (void *)&tis[i], sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u", i); } } TEST_END TEST_BEGIN(test_thread_arena) { unsigned old_arena_ind, new_arena_ind, narenas; const char *opa; size_t sz = sizeof(opa); expect_d_eq(mallctl("opt.percpu_arena", (void *)&opa, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); if (opt_oversize_threshold != 0) { narenas--; } expect_u_eq(narenas, opt_narenas, "Number of arenas incorrect"); if (strcmp(opa, "disabled") == 0) { new_arena_ind = narenas - 1; expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz, (void *)&new_arena_ind, sizeof(unsigned)), 0, "Unexpected mallctl() failure"); new_arena_ind = 0; expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz, (void *)&new_arena_ind, sizeof(unsigned)), 0, "Unexpected mallctl() failure"); } else { expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); new_arena_ind = percpu_arena_ind_limit(opt_percpu_arena) - 1; if (old_arena_ind != new_arena_ind) { expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz, (void *)&new_arena_ind, sizeof(unsigned)), EPERM, "thread.arena ctl " "should not be allowed with percpu arena"); } } } TEST_END TEST_BEGIN(test_arena_i_initialized) { unsigned narenas, i; size_t sz; size_t mib[3]; size_t miblen = sizeof(mib) / sizeof(size_t); bool initialized; sz = sizeof(narenas); expect_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlnametomib("arena.0.initialized", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); for (i = 0; i < narenas; i++) { mib[1] = i; sz = sizeof(initialized); expect_d_eq(mallctlbymib(mib, miblen, &initialized, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); } mib[1] = MALLCTL_ARENAS_ALL; sz = sizeof(initialized); expect_d_eq(mallctlbymib(mib, miblen, &initialized, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_true(initialized, "Merged arena statistics should always be initialized"); /* Equivalent to the above but using mallctl() directly. */ sz = sizeof(initialized); expect_d_eq(mallctl( "arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".initialized", (void *)&initialized, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_true(initialized, "Merged arena statistics should always be initialized"); } TEST_END TEST_BEGIN(test_arena_i_dirty_decay_ms) { ssize_t dirty_decay_ms, orig_dirty_decay_ms, prev_dirty_decay_ms; size_t sz = sizeof(ssize_t); expect_d_eq(mallctl("arena.0.dirty_decay_ms", (void *)&orig_dirty_decay_ms, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); dirty_decay_ms = -2; expect_d_eq(mallctl("arena.0.dirty_decay_ms", NULL, NULL, (void *)&dirty_decay_ms, sizeof(ssize_t)), EFAULT, "Unexpected mallctl() success"); dirty_decay_ms = 0x7fffffff; expect_d_eq(mallctl("arena.0.dirty_decay_ms", NULL, NULL, (void *)&dirty_decay_ms, sizeof(ssize_t)), 0, "Unexpected mallctl() failure"); for (prev_dirty_decay_ms = dirty_decay_ms, dirty_decay_ms = -1; dirty_decay_ms < 20; prev_dirty_decay_ms = dirty_decay_ms, dirty_decay_ms++) { ssize_t old_dirty_decay_ms; expect_d_eq(mallctl("arena.0.dirty_decay_ms", (void *)&old_dirty_decay_ms, &sz, (void *)&dirty_decay_ms, sizeof(ssize_t)), 0, "Unexpected mallctl() failure"); expect_zd_eq(old_dirty_decay_ms, prev_dirty_decay_ms, "Unexpected old arena.0.dirty_decay_ms"); } } TEST_END TEST_BEGIN(test_arena_i_muzzy_decay_ms) { ssize_t muzzy_decay_ms, orig_muzzy_decay_ms, prev_muzzy_decay_ms; size_t sz = sizeof(ssize_t); expect_d_eq(mallctl("arena.0.muzzy_decay_ms", (void *)&orig_muzzy_decay_ms, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); muzzy_decay_ms = -2; expect_d_eq(mallctl("arena.0.muzzy_decay_ms", NULL, NULL, (void *)&muzzy_decay_ms, sizeof(ssize_t)), EFAULT, "Unexpected mallctl() success"); muzzy_decay_ms = 0x7fffffff; expect_d_eq(mallctl("arena.0.muzzy_decay_ms", NULL, NULL, (void *)&muzzy_decay_ms, sizeof(ssize_t)), 0, "Unexpected mallctl() failure"); for (prev_muzzy_decay_ms = muzzy_decay_ms, muzzy_decay_ms = -1; muzzy_decay_ms < 20; prev_muzzy_decay_ms = muzzy_decay_ms, muzzy_decay_ms++) { ssize_t old_muzzy_decay_ms; expect_d_eq(mallctl("arena.0.muzzy_decay_ms", (void *)&old_muzzy_decay_ms, &sz, (void *)&muzzy_decay_ms, sizeof(ssize_t)), 0, "Unexpected mallctl() failure"); expect_zd_eq(old_muzzy_decay_ms, prev_muzzy_decay_ms, "Unexpected old arena.0.muzzy_decay_ms"); } } TEST_END TEST_BEGIN(test_arena_i_purge) { unsigned narenas; size_t sz = sizeof(unsigned); size_t mib[3]; size_t miblen = 3; expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlnametomib("arena.0.purge", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = narenas; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); mib[1] = MALLCTL_ARENAS_ALL; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } TEST_END TEST_BEGIN(test_arena_i_decay) { unsigned narenas; size_t sz = sizeof(unsigned); size_t mib[3]; size_t miblen = 3; expect_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlnametomib("arena.0.decay", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = narenas; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); mib[1] = MALLCTL_ARENAS_ALL; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } TEST_END TEST_BEGIN(test_arena_i_dss) { const char *dss_prec_old, *dss_prec_new; size_t sz = sizeof(dss_prec_old); size_t mib[3]; size_t miblen; miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.dss", mib, &miblen), 0, "Unexpected mallctlnametomib() error"); dss_prec_new = "disabled"; expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, (void *)&dss_prec_new, sizeof(dss_prec_new)), 0, "Unexpected mallctl() failure"); expect_str_ne(dss_prec_old, "primary", "Unexpected default for dss precedence"); expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_new, &sz, (void *)&dss_prec_old, sizeof(dss_prec_old)), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_str_ne(dss_prec_old, "primary", "Unexpected value for dss precedence"); mib[1] = narenas_total_get(); dss_prec_new = "disabled"; expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, (void *)&dss_prec_new, sizeof(dss_prec_new)), 0, "Unexpected mallctl() failure"); expect_str_ne(dss_prec_old, "primary", "Unexpected default for dss precedence"); expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_new, &sz, (void *)&dss_prec_old, sizeof(dss_prec_new)), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_str_ne(dss_prec_old, "primary", "Unexpected value for dss precedence"); } TEST_END TEST_BEGIN(test_arena_i_retain_grow_limit) { size_t old_limit, new_limit, default_limit; size_t mib[3]; size_t miblen; bool retain_enabled; size_t sz = sizeof(retain_enabled); expect_d_eq(mallctl("opt.retain", &retain_enabled, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); test_skip_if(!retain_enabled); sz = sizeof(default_limit); miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.retain_grow_limit", mib, &miblen), 0, "Unexpected mallctlnametomib() error"); expect_d_eq(mallctlbymib(mib, miblen, &default_limit, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_zu_eq(default_limit, SC_LARGE_MAXCLASS, "Unexpected default for retain_grow_limit"); new_limit = PAGE - 1; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &new_limit, sizeof(new_limit)), EFAULT, "Unexpected mallctl() success"); new_limit = PAGE + 1; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &new_limit, sizeof(new_limit)), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlbymib(mib, miblen, &old_limit, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_zu_eq(old_limit, PAGE, "Unexpected value for retain_grow_limit"); /* Expect grow less than psize class 10. */ new_limit = sz_pind2sz(10) - 1; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &new_limit, sizeof(new_limit)), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctlbymib(mib, miblen, &old_limit, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_zu_eq(old_limit, sz_pind2sz(9), "Unexpected value for retain_grow_limit"); /* Restore to default. */ expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &default_limit, sizeof(default_limit)), 0, "Unexpected mallctl() failure"); } TEST_END TEST_BEGIN(test_arenas_dirty_decay_ms) { ssize_t dirty_decay_ms, orig_dirty_decay_ms, prev_dirty_decay_ms; size_t sz = sizeof(ssize_t); expect_d_eq(mallctl("arenas.dirty_decay_ms", (void *)&orig_dirty_decay_ms, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); dirty_decay_ms = -2; expect_d_eq(mallctl("arenas.dirty_decay_ms", NULL, NULL, (void *)&dirty_decay_ms, sizeof(ssize_t)), EFAULT, "Unexpected mallctl() success"); dirty_decay_ms = 0x7fffffff; expect_d_eq(mallctl("arenas.dirty_decay_ms", NULL, NULL, (void *)&dirty_decay_ms, sizeof(ssize_t)), 0, "Expected mallctl() failure"); for (prev_dirty_decay_ms = dirty_decay_ms, dirty_decay_ms = -1; dirty_decay_ms < 20; prev_dirty_decay_ms = dirty_decay_ms, dirty_decay_ms++) { ssize_t old_dirty_decay_ms; expect_d_eq(mallctl("arenas.dirty_decay_ms", (void *)&old_dirty_decay_ms, &sz, (void *)&dirty_decay_ms, sizeof(ssize_t)), 0, "Unexpected mallctl() failure"); expect_zd_eq(old_dirty_decay_ms, prev_dirty_decay_ms, "Unexpected old arenas.dirty_decay_ms"); } } TEST_END TEST_BEGIN(test_arenas_muzzy_decay_ms) { ssize_t muzzy_decay_ms, orig_muzzy_decay_ms, prev_muzzy_decay_ms; size_t sz = sizeof(ssize_t); expect_d_eq(mallctl("arenas.muzzy_decay_ms", (void *)&orig_muzzy_decay_ms, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); muzzy_decay_ms = -2; expect_d_eq(mallctl("arenas.muzzy_decay_ms", NULL, NULL, (void *)&muzzy_decay_ms, sizeof(ssize_t)), EFAULT, "Unexpected mallctl() success"); muzzy_decay_ms = 0x7fffffff; expect_d_eq(mallctl("arenas.muzzy_decay_ms", NULL, NULL, (void *)&muzzy_decay_ms, sizeof(ssize_t)), 0, "Expected mallctl() failure"); for (prev_muzzy_decay_ms = muzzy_decay_ms, muzzy_decay_ms = -1; muzzy_decay_ms < 20; prev_muzzy_decay_ms = muzzy_decay_ms, muzzy_decay_ms++) { ssize_t old_muzzy_decay_ms; expect_d_eq(mallctl("arenas.muzzy_decay_ms", (void *)&old_muzzy_decay_ms, &sz, (void *)&muzzy_decay_ms, sizeof(ssize_t)), 0, "Unexpected mallctl() failure"); expect_zd_eq(old_muzzy_decay_ms, prev_muzzy_decay_ms, "Unexpected old arenas.muzzy_decay_ms"); } } TEST_END TEST_BEGIN(test_arenas_constants) { #define TEST_ARENAS_CONSTANT(t, name, expected) do { \ t name; \ size_t sz = sizeof(t); \ expect_d_eq(mallctl("arenas."#name, (void *)&name, &sz, NULL, \ 0), 0, "Unexpected mallctl() failure"); \ expect_zu_eq(name, expected, "Incorrect "#name" size"); \ } while (0) TEST_ARENAS_CONSTANT(size_t, quantum, QUANTUM); TEST_ARENAS_CONSTANT(size_t, page, PAGE); TEST_ARENAS_CONSTANT(unsigned, nbins, SC_NBINS); TEST_ARENAS_CONSTANT(unsigned, nlextents, SC_NSIZES - SC_NBINS); #undef TEST_ARENAS_CONSTANT } TEST_END TEST_BEGIN(test_arenas_bin_constants) { #define TEST_ARENAS_BIN_CONSTANT(t, name, expected) do { \ t name; \ size_t sz = sizeof(t); \ expect_d_eq(mallctl("arenas.bin.0."#name, (void *)&name, &sz, \ NULL, 0), 0, "Unexpected mallctl() failure"); \ expect_zu_eq(name, expected, "Incorrect "#name" size"); \ } while (0) TEST_ARENAS_BIN_CONSTANT(size_t, size, bin_infos[0].reg_size); TEST_ARENAS_BIN_CONSTANT(uint32_t, nregs, bin_infos[0].nregs); TEST_ARENAS_BIN_CONSTANT(size_t, slab_size, bin_infos[0].slab_size); TEST_ARENAS_BIN_CONSTANT(uint32_t, nshards, bin_infos[0].n_shards); #undef TEST_ARENAS_BIN_CONSTANT } TEST_END TEST_BEGIN(test_arenas_lextent_constants) { #define TEST_ARENAS_LEXTENT_CONSTANT(t, name, expected) do { \ t name; \ size_t sz = sizeof(t); \ expect_d_eq(mallctl("arenas.lextent.0."#name, (void *)&name, \ &sz, NULL, 0), 0, "Unexpected mallctl() failure"); \ expect_zu_eq(name, expected, "Incorrect "#name" size"); \ } while (0) TEST_ARENAS_LEXTENT_CONSTANT(size_t, size, SC_LARGE_MINCLASS); #undef TEST_ARENAS_LEXTENT_CONSTANT } TEST_END TEST_BEGIN(test_arenas_create) { unsigned narenas_before, arena, narenas_after; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.narenas", (void *)&narenas_before, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctl("arenas.narenas", (void *)&narenas_after, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_u_eq(narenas_before+1, narenas_after, "Unexpected number of arenas before versus after extension"); expect_u_eq(arena, narenas_after-1, "Unexpected arena index"); } TEST_END TEST_BEGIN(test_arenas_lookup) { unsigned arena, arena1; void *ptr; size_t sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); ptr = mallocx(42, MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE); expect_ptr_not_null(ptr, "Unexpected mallocx() failure"); expect_d_eq(mallctl("arenas.lookup", &arena1, &sz, &ptr, sizeof(ptr)), 0, "Unexpected mallctl() failure"); expect_u_eq(arena, arena1, "Unexpected arena index"); dallocx(ptr, 0); } TEST_END TEST_BEGIN(test_prof_active) { /* * If config_prof is off, then the test for prof_active in * test_mallctl_opt was already enough. */ test_skip_if(!config_prof); test_skip_if(opt_prof); bool active, old; size_t len = sizeof(bool); active = true; expect_d_eq(mallctl("prof.active", NULL, NULL, &active, len), ENOENT, "Setting prof_active to true should fail when opt_prof is off"); old = true; expect_d_eq(mallctl("prof.active", &old, &len, &active, len), ENOENT, "Setting prof_active to true should fail when opt_prof is off"); expect_true(old, "old value should not be touched when mallctl fails"); active = false; expect_d_eq(mallctl("prof.active", NULL, NULL, &active, len), 0, "Setting prof_active to false should succeed when opt_prof is off"); expect_d_eq(mallctl("prof.active", &old, &len, &active, len), 0, "Setting prof_active to false should succeed when opt_prof is off"); expect_false(old, "prof_active should be false when opt_prof is off"); } TEST_END TEST_BEGIN(test_stats_arenas) { #define TEST_STATS_ARENAS(t, name) do { \ t name; \ size_t sz = sizeof(t); \ expect_d_eq(mallctl("stats.arenas.0."#name, (void *)&name, &sz, \ NULL, 0), 0, "Unexpected mallctl() failure"); \ } while (0) TEST_STATS_ARENAS(unsigned, nthreads); TEST_STATS_ARENAS(const char *, dss); TEST_STATS_ARENAS(ssize_t, dirty_decay_ms); TEST_STATS_ARENAS(ssize_t, muzzy_decay_ms); TEST_STATS_ARENAS(size_t, pactive); TEST_STATS_ARENAS(size_t, pdirty); #undef TEST_STATS_ARENAS } TEST_END static void alloc_hook(void *extra, UNUSED hook_alloc_t type, UNUSED void *result, UNUSED uintptr_t result_raw, UNUSED uintptr_t args_raw[3]) { *(bool *)extra = true; } static void dalloc_hook(void *extra, UNUSED hook_dalloc_t type, UNUSED void *address, UNUSED uintptr_t args_raw[3]) { *(bool *)extra = true; } TEST_BEGIN(test_hooks) { bool hook_called = false; hooks_t hooks = {&alloc_hook, &dalloc_hook, NULL, &hook_called}; void *handle = NULL; size_t sz = sizeof(handle); int err = mallctl("experimental.hooks.install", &handle, &sz, &hooks, sizeof(hooks)); expect_d_eq(err, 0, "Hook installation failed"); expect_ptr_ne(handle, NULL, "Hook installation gave null handle"); void *ptr = mallocx(1, 0); expect_true(hook_called, "Alloc hook not called"); hook_called = false; free(ptr); expect_true(hook_called, "Free hook not called"); err = mallctl("experimental.hooks.remove", NULL, NULL, &handle, sizeof(handle)); expect_d_eq(err, 0, "Hook removal failed"); hook_called = false; ptr = mallocx(1, 0); free(ptr); expect_false(hook_called, "Hook called after removal"); } TEST_END TEST_BEGIN(test_hooks_exhaustion) { bool hook_called = false; hooks_t hooks = {&alloc_hook, &dalloc_hook, NULL, &hook_called}; void *handle; void *handles[HOOK_MAX]; size_t sz = sizeof(handle); int err; for (int i = 0; i < HOOK_MAX; i++) { handle = NULL; err = mallctl("experimental.hooks.install", &handle, &sz, &hooks, sizeof(hooks)); expect_d_eq(err, 0, "Error installation hooks"); expect_ptr_ne(handle, NULL, "Got NULL handle"); handles[i] = handle; } err = mallctl("experimental.hooks.install", &handle, &sz, &hooks, sizeof(hooks)); expect_d_eq(err, EAGAIN, "Should have failed hook installation"); for (int i = 0; i < HOOK_MAX; i++) { err = mallctl("experimental.hooks.remove", NULL, NULL, &handles[i], sizeof(handles[i])); expect_d_eq(err, 0, "Hook removal failed"); } /* Insertion failed, but then we removed some; it should work now. */ handle = NULL; err = mallctl("experimental.hooks.install", &handle, &sz, &hooks, sizeof(hooks)); expect_d_eq(err, 0, "Hook insertion failed"); expect_ptr_ne(handle, NULL, "Got NULL handle"); err = mallctl("experimental.hooks.remove", NULL, NULL, &handle, sizeof(handle)); expect_d_eq(err, 0, "Hook removal failed"); } TEST_END TEST_BEGIN(test_thread_idle) { /* * We're cheating a little bit in this test, and inferring things about * implementation internals (like tcache details). We have to; * thread.idle has no guaranteed effects. We need stats to make these * inferences. */ test_skip_if(!config_stats); int err; size_t sz; size_t miblen; bool tcache_enabled = false; sz = sizeof(tcache_enabled); err = mallctl("thread.tcache.enabled", &tcache_enabled, &sz, NULL, 0); expect_d_eq(err, 0, ""); test_skip_if(!tcache_enabled); size_t tcache_max; sz = sizeof(tcache_max); err = mallctl("arenas.tcache_max", &tcache_max, &sz, NULL, 0); expect_d_eq(err, 0, ""); test_skip_if(tcache_max == 0); unsigned arena_ind; sz = sizeof(arena_ind); err = mallctl("thread.arena", &arena_ind, &sz, NULL, 0); expect_d_eq(err, 0, ""); /* We're going to do an allocation of size 1, which we know is small. */ size_t mib[5]; miblen = sizeof(mib)/sizeof(mib[0]); err = mallctlnametomib("stats.arenas.0.small.ndalloc", mib, &miblen); expect_d_eq(err, 0, ""); mib[2] = arena_ind; /* * This alloc and dalloc should leave something in the tcache, in a * small size's cache bin. */ void *ptr = mallocx(1, 0); dallocx(ptr, 0); uint64_t epoch; err = mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)); expect_d_eq(err, 0, ""); uint64_t small_dalloc_pre_idle; sz = sizeof(small_dalloc_pre_idle); err = mallctlbymib(mib, miblen, &small_dalloc_pre_idle, &sz, NULL, 0); expect_d_eq(err, 0, ""); err = mallctl("thread.idle", NULL, NULL, NULL, 0); expect_d_eq(err, 0, ""); err = mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)); expect_d_eq(err, 0, ""); uint64_t small_dalloc_post_idle; sz = sizeof(small_dalloc_post_idle); err = mallctlbymib(mib, miblen, &small_dalloc_post_idle, &sz, NULL, 0); expect_d_eq(err, 0, ""); expect_u64_lt(small_dalloc_pre_idle, small_dalloc_post_idle, "Purge didn't flush the tcache"); } TEST_END TEST_BEGIN(test_thread_peak) { test_skip_if(!config_stats); /* * We don't commit to any stable amount of accuracy for peak tracking * (in practice, when this test was written, we made sure to be within * 100k). But 10MB is big for more or less any definition of big. */ size_t big_size = 10 * 1024 * 1024; size_t small_size = 256; void *ptr; int err; size_t sz; uint64_t peak; sz = sizeof(uint64_t); err = mallctl("thread.peak.reset", NULL, NULL, NULL, 0); expect_d_eq(err, 0, ""); ptr = mallocx(SC_SMALL_MAXCLASS, 0); err = mallctl("thread.peak.read", &peak, &sz, NULL, 0); expect_d_eq(err, 0, ""); expect_u64_eq(peak, SC_SMALL_MAXCLASS, "Missed an update"); free(ptr); err = mallctl("thread.peak.read", &peak, &sz, NULL, 0); expect_d_eq(err, 0, ""); expect_u64_eq(peak, SC_SMALL_MAXCLASS, "Freeing changed peak"); ptr = mallocx(big_size, 0); free(ptr); /* * The peak should have hit big_size in the last two lines, even though * the net allocated bytes has since dropped back down to zero. We * should have noticed the peak change without having down any mallctl * calls while net allocated bytes was high. */ err = mallctl("thread.peak.read", &peak, &sz, NULL, 0); expect_d_eq(err, 0, ""); expect_u64_ge(peak, big_size, "Missed a peak change."); /* Allocate big_size, but using small allocations. */ size_t nallocs = big_size / small_size; void **ptrs = calloc(nallocs, sizeof(void *)); err = mallctl("thread.peak.reset", NULL, NULL, NULL, 0); expect_d_eq(err, 0, ""); err = mallctl("thread.peak.read", &peak, &sz, NULL, 0); expect_d_eq(err, 0, ""); expect_u64_eq(0, peak, "Missed a reset."); for (size_t i = 0; i < nallocs; i++) { ptrs[i] = mallocx(small_size, 0); } for (size_t i = 0; i < nallocs; i++) { free(ptrs[i]); } err = mallctl("thread.peak.read", &peak, &sz, NULL, 0); expect_d_eq(err, 0, ""); /* * We don't guarantee exactness; make sure we're within 10% of the peak, * though. */ expect_u64_ge(peak, nallocx(small_size, 0) * nallocs * 9 / 10, "Missed some peak changes."); expect_u64_le(peak, nallocx(small_size, 0) * nallocs * 11 / 10, "Overcounted peak changes."); free(ptrs); } TEST_END typedef struct activity_test_data_s activity_test_data_t; struct activity_test_data_s { uint64_t obtained_alloc; uint64_t obtained_dalloc; }; static void activity_test_callback(void *uctx, uint64_t alloc, uint64_t dalloc) { activity_test_data_t *test_data = (activity_test_data_t *)uctx; test_data->obtained_alloc = alloc; test_data->obtained_dalloc = dalloc; } TEST_BEGIN(test_thread_activity_callback) { test_skip_if(!config_stats); const size_t big_size = 10 * 1024 * 1024; void *ptr; int err; size_t sz; uint64_t *allocatedp; uint64_t *deallocatedp; sz = sizeof(allocatedp); err = mallctl("thread.allocatedp", &allocatedp, &sz, NULL, 0); assert_d_eq(0, err, ""); err = mallctl("thread.deallocatedp", &deallocatedp, &sz, NULL, 0); assert_d_eq(0, err, ""); activity_callback_thunk_t old_thunk = {(activity_callback_t)111, (void *)222}; activity_test_data_t test_data = {333, 444}; activity_callback_thunk_t new_thunk = {&activity_test_callback, &test_data}; sz = sizeof(old_thunk); err = mallctl("experimental.thread.activity_callback", &old_thunk, &sz, &new_thunk, sizeof(new_thunk)); assert_d_eq(0, err, ""); expect_true(old_thunk.callback == NULL, "Callback already installed"); expect_true(old_thunk.uctx == NULL, "Callback data already installed"); ptr = mallocx(big_size, 0); expect_u64_eq(test_data.obtained_alloc, *allocatedp, ""); expect_u64_eq(test_data.obtained_dalloc, *deallocatedp, ""); free(ptr); expect_u64_eq(test_data.obtained_alloc, *allocatedp, ""); expect_u64_eq(test_data.obtained_dalloc, *deallocatedp, ""); sz = sizeof(old_thunk); new_thunk = (activity_callback_thunk_t){ NULL, NULL }; err = mallctl("experimental.thread.activity_callback", &old_thunk, &sz, &new_thunk, sizeof(new_thunk)); assert_d_eq(0, err, ""); expect_true(old_thunk.callback == &activity_test_callback, ""); expect_true(old_thunk.uctx == &test_data, ""); /* Inserting NULL should have turned off tracking. */ test_data.obtained_alloc = 333; test_data.obtained_dalloc = 444; ptr = mallocx(big_size, 0); free(ptr); expect_u64_eq(333, test_data.obtained_alloc, ""); expect_u64_eq(444, test_data.obtained_dalloc, ""); } TEST_END int main(void) { return test( test_mallctl_errors, test_mallctlnametomib_errors, test_mallctlbymib_errors, test_mallctl_read_write, test_mallctlnametomib_short_mib, test_mallctlnametomib_short_name, test_mallctlmibnametomib, test_mallctlbymibname, test_mallctl_config, test_mallctl_opt, test_manpage_example, test_tcache_none, test_tcache, test_thread_arena, test_arena_i_initialized, test_arena_i_dirty_decay_ms, test_arena_i_muzzy_decay_ms, test_arena_i_purge, test_arena_i_decay, test_arena_i_dss, test_arena_i_retain_grow_limit, test_arenas_dirty_decay_ms, test_arenas_muzzy_decay_ms, test_arenas_constants, test_arenas_bin_constants, test_arenas_lextent_constants, test_arenas_create, test_arenas_lookup, test_prof_active, test_stats_arenas, test_hooks, test_hooks_exhaustion, test_thread_idle, test_thread_peak, test_thread_activity_callback); } redis-8.0.2/deps/jemalloc/test/unit/malloc_conf_2.c000066400000000000000000000012511501533116600221470ustar00rootroot00000000000000#include "test/jemalloc_test.h" const char *malloc_conf = "dirty_decay_ms:1000"; const char *malloc_conf_2_conf_harder = "dirty_decay_ms:1234"; TEST_BEGIN(test_malloc_conf_2) { #ifdef _WIN32 bool windows = true; #else bool windows = false; #endif /* Windows doesn't support weak symbol linker trickery. */ test_skip_if(windows); ssize_t dirty_decay_ms; size_t sz = sizeof(dirty_decay_ms); int err = mallctl("opt.dirty_decay_ms", &dirty_decay_ms, &sz, NULL, 0); assert_d_eq(err, 0, "Unexpected mallctl failure"); expect_zd_eq(dirty_decay_ms, 1234, "malloc_conf_2 setting didn't take effect"); } TEST_END int main(void) { return test( test_malloc_conf_2); } redis-8.0.2/deps/jemalloc/test/unit/malloc_conf_2.sh000066400000000000000000000000501501533116600223330ustar00rootroot00000000000000export MALLOC_CONF="dirty_decay_ms:500" redis-8.0.2/deps/jemalloc/test/unit/malloc_io.c000066400000000000000000000201121501533116600214050ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_malloc_strtoumax_no_endptr) { int err; set_errno(0); expect_ju_eq(malloc_strtoumax("0", NULL, 0), 0, "Unexpected result"); err = get_errno(); expect_d_eq(err, 0, "Unexpected failure"); } TEST_END TEST_BEGIN(test_malloc_strtoumax) { struct test_s { const char *input; const char *expected_remainder; int base; int expected_errno; const char *expected_errno_name; uintmax_t expected_x; }; #define ERR(e) e, #e #define KUMAX(x) ((uintmax_t)x##ULL) #define KSMAX(x) ((uintmax_t)(intmax_t)x##LL) struct test_s tests[] = { {"0", "0", -1, ERR(EINVAL), UINTMAX_MAX}, {"0", "0", 1, ERR(EINVAL), UINTMAX_MAX}, {"0", "0", 37, ERR(EINVAL), UINTMAX_MAX}, {"", "", 0, ERR(EINVAL), UINTMAX_MAX}, {"+", "+", 0, ERR(EINVAL), UINTMAX_MAX}, {"++3", "++3", 0, ERR(EINVAL), UINTMAX_MAX}, {"-", "-", 0, ERR(EINVAL), UINTMAX_MAX}, {"42", "", 0, ERR(0), KUMAX(42)}, {"+42", "", 0, ERR(0), KUMAX(42)}, {"-42", "", 0, ERR(0), KSMAX(-42)}, {"042", "", 0, ERR(0), KUMAX(042)}, {"+042", "", 0, ERR(0), KUMAX(042)}, {"-042", "", 0, ERR(0), KSMAX(-042)}, {"0x42", "", 0, ERR(0), KUMAX(0x42)}, {"+0x42", "", 0, ERR(0), KUMAX(0x42)}, {"-0x42", "", 0, ERR(0), KSMAX(-0x42)}, {"0", "", 0, ERR(0), KUMAX(0)}, {"1", "", 0, ERR(0), KUMAX(1)}, {"42", "", 0, ERR(0), KUMAX(42)}, {" 42", "", 0, ERR(0), KUMAX(42)}, {"42 ", " ", 0, ERR(0), KUMAX(42)}, {"0x", "x", 0, ERR(0), KUMAX(0)}, {"42x", "x", 0, ERR(0), KUMAX(42)}, {"07", "", 0, ERR(0), KUMAX(7)}, {"010", "", 0, ERR(0), KUMAX(8)}, {"08", "8", 0, ERR(0), KUMAX(0)}, {"0_", "_", 0, ERR(0), KUMAX(0)}, {"0x", "x", 0, ERR(0), KUMAX(0)}, {"0X", "X", 0, ERR(0), KUMAX(0)}, {"0xg", "xg", 0, ERR(0), KUMAX(0)}, {"0XA", "", 0, ERR(0), KUMAX(10)}, {"010", "", 10, ERR(0), KUMAX(10)}, {"0x3", "x3", 10, ERR(0), KUMAX(0)}, {"12", "2", 2, ERR(0), KUMAX(1)}, {"78", "8", 8, ERR(0), KUMAX(7)}, {"9a", "a", 10, ERR(0), KUMAX(9)}, {"9A", "A", 10, ERR(0), KUMAX(9)}, {"fg", "g", 16, ERR(0), KUMAX(15)}, {"FG", "G", 16, ERR(0), KUMAX(15)}, {"0xfg", "g", 16, ERR(0), KUMAX(15)}, {"0XFG", "G", 16, ERR(0), KUMAX(15)}, {"z_", "_", 36, ERR(0), KUMAX(35)}, {"Z_", "_", 36, ERR(0), KUMAX(35)} }; #undef ERR #undef KUMAX #undef KSMAX unsigned i; for (i = 0; i < sizeof(tests)/sizeof(struct test_s); i++) { struct test_s *test = &tests[i]; int err; uintmax_t result; char *remainder; set_errno(0); result = malloc_strtoumax(test->input, &remainder, test->base); err = get_errno(); expect_d_eq(err, test->expected_errno, "Expected errno %s for \"%s\", base %d", test->expected_errno_name, test->input, test->base); expect_str_eq(remainder, test->expected_remainder, "Unexpected remainder for \"%s\", base %d", test->input, test->base); if (err == 0) { expect_ju_eq(result, test->expected_x, "Unexpected result for \"%s\", base %d", test->input, test->base); } } } TEST_END TEST_BEGIN(test_malloc_snprintf_truncated) { #define BUFLEN 15 char buf[BUFLEN]; size_t result; size_t len; #define TEST(expected_str_untruncated, ...) do { \ result = malloc_snprintf(buf, len, __VA_ARGS__); \ expect_d_eq(strncmp(buf, expected_str_untruncated, len-1), 0, \ "Unexpected string inequality (\"%s\" vs \"%s\")", \ buf, expected_str_untruncated); \ expect_zu_eq(result, strlen(expected_str_untruncated), \ "Unexpected result"); \ } while (0) for (len = 1; len < BUFLEN; len++) { TEST("012346789", "012346789"); TEST("a0123b", "a%sb", "0123"); TEST("a01234567", "a%s%s", "0123", "4567"); TEST("a0123 ", "a%-6s", "0123"); TEST("a 0123", "a%6s", "0123"); TEST("a 012", "a%6.3s", "0123"); TEST("a 012", "a%*.*s", 6, 3, "0123"); TEST("a 123b", "a% db", 123); TEST("a123b", "a%-db", 123); TEST("a-123b", "a%-db", -123); TEST("a+123b", "a%+db", 123); } #undef BUFLEN #undef TEST } TEST_END TEST_BEGIN(test_malloc_snprintf) { #define BUFLEN 128 char buf[BUFLEN]; size_t result; #define TEST(expected_str, ...) do { \ result = malloc_snprintf(buf, sizeof(buf), __VA_ARGS__); \ expect_str_eq(buf, expected_str, "Unexpected output"); \ expect_zu_eq(result, strlen(expected_str), "Unexpected result");\ } while (0) TEST("hello", "hello"); TEST("50%, 100%", "50%%, %d%%", 100); TEST("a0123b", "a%sb", "0123"); TEST("a 0123b", "a%5sb", "0123"); TEST("a 0123b", "a%*sb", 5, "0123"); TEST("a0123 b", "a%-5sb", "0123"); TEST("a0123b", "a%*sb", -1, "0123"); TEST("a0123 b", "a%*sb", -5, "0123"); TEST("a0123 b", "a%-*sb", -5, "0123"); TEST("a012b", "a%.3sb", "0123"); TEST("a012b", "a%.*sb", 3, "0123"); TEST("a0123b", "a%.*sb", -3, "0123"); TEST("a 012b", "a%5.3sb", "0123"); TEST("a 012b", "a%5.*sb", 3, "0123"); TEST("a 012b", "a%*.3sb", 5, "0123"); TEST("a 012b", "a%*.*sb", 5, 3, "0123"); TEST("a 0123b", "a%*.*sb", 5, -3, "0123"); TEST("_abcd_", "_%x_", 0xabcd); TEST("_0xabcd_", "_%#x_", 0xabcd); TEST("_1234_", "_%o_", 01234); TEST("_01234_", "_%#o_", 01234); TEST("_1234_", "_%u_", 1234); TEST("01234", "%05u", 1234); TEST("_1234_", "_%d_", 1234); TEST("_ 1234_", "_% d_", 1234); TEST("_+1234_", "_%+d_", 1234); TEST("_-1234_", "_%d_", -1234); TEST("_-1234_", "_% d_", -1234); TEST("_-1234_", "_%+d_", -1234); /* * Morally, we should test these too, but 0-padded signed types are not * yet supported. * * TEST("01234", "%05", 1234); * TEST("-1234", "%05d", -1234); * TEST("-01234", "%06d", -1234); */ TEST("_-1234_", "_%d_", -1234); TEST("_1234_", "_%d_", 1234); TEST("_-1234_", "_%i_", -1234); TEST("_1234_", "_%i_", 1234); TEST("_01234_", "_%#o_", 01234); TEST("_1234_", "_%u_", 1234); TEST("_0x1234abc_", "_%#x_", 0x1234abc); TEST("_0X1234ABC_", "_%#X_", 0x1234abc); TEST("_c_", "_%c_", 'c'); TEST("_string_", "_%s_", "string"); TEST("_0x42_", "_%p_", ((void *)0x42)); TEST("_-1234_", "_%ld_", ((long)-1234)); TEST("_1234_", "_%ld_", ((long)1234)); TEST("_-1234_", "_%li_", ((long)-1234)); TEST("_1234_", "_%li_", ((long)1234)); TEST("_01234_", "_%#lo_", ((long)01234)); TEST("_1234_", "_%lu_", ((long)1234)); TEST("_0x1234abc_", "_%#lx_", ((long)0x1234abc)); TEST("_0X1234ABC_", "_%#lX_", ((long)0x1234ABC)); TEST("_-1234_", "_%lld_", ((long long)-1234)); TEST("_1234_", "_%lld_", ((long long)1234)); TEST("_-1234_", "_%lli_", ((long long)-1234)); TEST("_1234_", "_%lli_", ((long long)1234)); TEST("_01234_", "_%#llo_", ((long long)01234)); TEST("_1234_", "_%llu_", ((long long)1234)); TEST("_0x1234abc_", "_%#llx_", ((long long)0x1234abc)); TEST("_0X1234ABC_", "_%#llX_", ((long long)0x1234ABC)); TEST("_-1234_", "_%qd_", ((long long)-1234)); TEST("_1234_", "_%qd_", ((long long)1234)); TEST("_-1234_", "_%qi_", ((long long)-1234)); TEST("_1234_", "_%qi_", ((long long)1234)); TEST("_01234_", "_%#qo_", ((long long)01234)); TEST("_1234_", "_%qu_", ((long long)1234)); TEST("_0x1234abc_", "_%#qx_", ((long long)0x1234abc)); TEST("_0X1234ABC_", "_%#qX_", ((long long)0x1234ABC)); TEST("_-1234_", "_%jd_", ((intmax_t)-1234)); TEST("_1234_", "_%jd_", ((intmax_t)1234)); TEST("_-1234_", "_%ji_", ((intmax_t)-1234)); TEST("_1234_", "_%ji_", ((intmax_t)1234)); TEST("_01234_", "_%#jo_", ((intmax_t)01234)); TEST("_1234_", "_%ju_", ((intmax_t)1234)); TEST("_0x1234abc_", "_%#jx_", ((intmax_t)0x1234abc)); TEST("_0X1234ABC_", "_%#jX_", ((intmax_t)0x1234ABC)); TEST("_1234_", "_%td_", ((ptrdiff_t)1234)); TEST("_-1234_", "_%td_", ((ptrdiff_t)-1234)); TEST("_1234_", "_%ti_", ((ptrdiff_t)1234)); TEST("_-1234_", "_%ti_", ((ptrdiff_t)-1234)); TEST("_-1234_", "_%zd_", ((ssize_t)-1234)); TEST("_1234_", "_%zd_", ((ssize_t)1234)); TEST("_-1234_", "_%zi_", ((ssize_t)-1234)); TEST("_1234_", "_%zi_", ((ssize_t)1234)); TEST("_01234_", "_%#zo_", ((ssize_t)01234)); TEST("_1234_", "_%zu_", ((ssize_t)1234)); TEST("_0x1234abc_", "_%#zx_", ((ssize_t)0x1234abc)); TEST("_0X1234ABC_", "_%#zX_", ((ssize_t)0x1234ABC)); #undef BUFLEN } TEST_END int main(void) { return test( test_malloc_strtoumax_no_endptr, test_malloc_strtoumax, test_malloc_snprintf_truncated, test_malloc_snprintf); } redis-8.0.2/deps/jemalloc/test/unit/math.c000066400000000000000000000440701501533116600204110ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define MAX_REL_ERR 1.0e-9 #define MAX_ABS_ERR 1.0e-9 #include #ifdef __PGI #undef INFINITY #endif #ifndef INFINITY #define INFINITY (DBL_MAX + DBL_MAX) #endif static bool double_eq_rel(double a, double b, double max_rel_err, double max_abs_err) { double rel_err; if (fabs(a - b) < max_abs_err) { return true; } rel_err = (fabs(b) > fabs(a)) ? fabs((a-b)/b) : fabs((a-b)/a); return (rel_err < max_rel_err); } static uint64_t factorial(unsigned x) { uint64_t ret = 1; unsigned i; for (i = 2; i <= x; i++) { ret *= (uint64_t)i; } return ret; } TEST_BEGIN(test_ln_gamma_factorial) { unsigned x; /* exp(ln_gamma(x)) == (x-1)! for integer x. */ for (x = 1; x <= 21; x++) { expect_true(double_eq_rel(exp(ln_gamma(x)), (double)factorial(x-1), MAX_REL_ERR, MAX_ABS_ERR), "Incorrect factorial result for x=%u", x); } } TEST_END /* Expected ln_gamma([0.0..100.0] increment=0.25). */ static const double ln_gamma_misc_expected[] = { INFINITY, 1.28802252469807743, 0.57236494292470008, 0.20328095143129538, 0.00000000000000000, -0.09827183642181320, -0.12078223763524518, -0.08440112102048555, 0.00000000000000000, 0.12487171489239651, 0.28468287047291918, 0.47521466691493719, 0.69314718055994529, 0.93580193110872523, 1.20097360234707429, 1.48681557859341718, 1.79175946922805496, 2.11445692745037128, 2.45373657084244234, 2.80857141857573644, 3.17805383034794575, 3.56137591038669710, 3.95781396761871651, 4.36671603662228680, 4.78749174278204581, 5.21960398699022932, 5.66256205985714178, 6.11591589143154568, 6.57925121201010121, 7.05218545073853953, 7.53436423675873268, 8.02545839631598312, 8.52516136106541467, 9.03318691960512332, 9.54926725730099690, 10.07315123968123949, 10.60460290274525086, 11.14340011995171231, 11.68933342079726856, 12.24220494005076176, 12.80182748008146909, 13.36802367147604720, 13.94062521940376342, 14.51947222506051816, 15.10441257307551943, 15.69530137706046524, 16.29200047656724237, 16.89437797963419285, 17.50230784587389010, 18.11566950571089407, 18.73434751193644843, 19.35823122022435427, 19.98721449566188468, 20.62119544270163018, 21.26007615624470048, 21.90376249182879320, 22.55216385312342098, 23.20519299513386002, 23.86276584168908954, 24.52480131594137802, 25.19122118273868338, 25.86194990184851861, 26.53691449111561340, 27.21604439872720604, 27.89927138384089389, 28.58652940490193828, 29.27775451504081516, 29.97288476399884871, 30.67186010608067548, 31.37462231367769050, 32.08111489594735843, 32.79128302226991565, 33.50507345013689076, 34.22243445715505317, 34.94331577687681545, 35.66766853819134298, 36.39544520803305261, 37.12659953718355865, 37.86108650896109395, 38.59886229060776230, 39.33988418719949465, 40.08411059791735198, 40.83150097453079752, 41.58201578195490100, 42.33561646075348506, 43.09226539146988699, 43.85192586067515208, 44.61456202863158893, 45.38013889847690052, 46.14862228684032885, 46.91997879580877395, 47.69417578616628361, 48.47118135183522014, 49.25096429545256882, 50.03349410501914463, 50.81874093156324790, 51.60667556776436982, 52.39726942748592364, 53.19049452616926743, 53.98632346204390586, 54.78472939811231157, 55.58568604486942633, 56.38916764371992940, 57.19514895105859864, 58.00360522298051080, 58.81451220059079787, 59.62784609588432261, 60.44358357816834371, 61.26170176100199427, 62.08217818962842927, 62.90499082887649962, 63.73011805151035958, 64.55753862700632340, 65.38723171073768015, 66.21917683354901385, 67.05335389170279825, 67.88974313718154008, 68.72832516833013017, 69.56908092082363737, 70.41199165894616385, 71.25703896716800045, 72.10420474200799390, 72.95347118416940191, 73.80482079093779646, 74.65823634883015814, 75.51370092648485866, 76.37119786778275454, 77.23071078519033961, 78.09222355331530707, 78.95572030266725960, 79.82118541361435859, 80.68860351052903468, 81.55795945611502873, 82.42923834590904164, 83.30242550295004378, 84.17750647261028973, 85.05446701758152983, 85.93329311301090456, 86.81397094178107920, 87.69648688992882057, 88.58082754219766741, 89.46697967771913795, 90.35493026581838194, 91.24466646193963015, 92.13617560368709292, 93.02944520697742803, 93.92446296229978486, 94.82121673107967297, 95.71969454214321615, 96.61988458827809723, 97.52177522288820910, 98.42535495673848800, 99.33061245478741341, 100.23753653310367895, 101.14611615586458981, 102.05634043243354370, 102.96819861451382394, 103.88168009337621811, 104.79677439715833032, 105.71347118823287303, 106.63176026064346047, 107.55163153760463501, 108.47307506906540198, 109.39608102933323153, 110.32063971475740516, 111.24674154146920557, 112.17437704317786995, 113.10353686902013237, 114.03421178146170689, 114.96639265424990128, 115.90007047041454769, 116.83523632031698014, 117.77188139974506953, 118.70999700805310795, 119.64957454634490830, 120.59060551569974962, 121.53308151543865279, 122.47699424143097247, 123.42233548443955726, 124.36909712850338394, 125.31727114935689826, 126.26684961288492559, 127.21782467361175861, 128.17018857322420899, 129.12393363912724453, 130.07905228303084755, 131.03553699956862033, 131.99338036494577864, 132.95257503561629164, 133.91311374698926784, 134.87498931216194364, 135.83819462068046846, 136.80272263732638294, 137.76856640092901785, 138.73571902320256299, 139.70417368760718091, 140.67392364823425055, 141.64496222871400732, 142.61728282114600574, 143.59087888505104047, 144.56574394634486680, 145.54187159633210058, 146.51925549072063859, 147.49788934865566148, 148.47776695177302031, 149.45888214327129617, 150.44122882700193600, 151.42480096657754984, 152.40959258449737490, 153.39559776128982094, 154.38281063467164245, 155.37122539872302696, 156.36083630307879844, 157.35163765213474107, 158.34362380426921391, 159.33678917107920370, 160.33112821663092973, 161.32663545672428995, 162.32330545817117695, 163.32113283808695314, 164.32011226319519892, 165.32023844914485267, 166.32150615984036790, 167.32391020678358018, 168.32744544842768164, 169.33210678954270634, 170.33788918059275375, 171.34478761712384198, 172.35279713916281707, 173.36191283062726143, 174.37212981874515094, 175.38344327348534080, 176.39584840699734514, 177.40934047306160437, 178.42391476654847793, 179.43956662288721304, 180.45629141754378111, 181.47408456550741107, 182.49294152078630304, 183.51285777591152737, 184.53382886144947861, 185.55585034552262869, 186.57891783333786861, 187.60302696672312095, 188.62817342367162610, 189.65435291789341932, 190.68156119837468054, 191.70979404894376330, 192.73904728784492590, 193.76931676731820176, 194.80059837318714244, 195.83288802445184729, 196.86618167288995096, 197.90047530266301123, 198.93576492992946214, 199.97204660246373464, 201.00931639928148797, 202.04757043027063901, 203.08680483582807597, 204.12701578650228385, 205.16819948264117102, 206.21035215404597807, 207.25347005962987623, 208.29754948708190909, 209.34258675253678916, 210.38857820024875878, 211.43552020227099320, 212.48340915813977858, 213.53224149456323744, 214.58201366511514152, 215.63272214993284592, 216.68436345542014010, 217.73693411395422004, 218.79043068359703739, 219.84484974781133815, 220.90018791517996988, 221.95644181913033322, 223.01360811766215875, 224.07168349307951871, 225.13066465172661879, 226.19054832372759734, 227.25133126272962159, 228.31301024565024704, 229.37558207242807384, 230.43904356577689896, 231.50339157094342113, 232.56862295546847008, 233.63473460895144740, 234.70172344281823484, 235.76958639009222907, 236.83832040516844586, 237.90792246359117712, 238.97838956183431947, 240.04971871708477238, 241.12190696702904802, 242.19495136964280846, 243.26884900298270509, 244.34359696498191283, 245.41919237324782443, 246.49563236486270057, 247.57291409618682110, 248.65103474266476269, 249.72999149863338175, 250.80978157713354904, 251.89040220972316320, 252.97185064629374551, 254.05412415488834199, 255.13722002152300661, 256.22113555000953511, 257.30586806178126835, 258.39141489572085675, 259.47777340799029844, 260.56494097186322279, 261.65291497755913497, 262.74169283208021852, 263.83127195904967266, 264.92164979855277807, 266.01282380697938379, 267.10479145686849733, 268.19755023675537586, 269.29109765101975427, 270.38543121973674488, 271.48054847852881721, 272.57644697842033565, 273.67312428569374561, 274.77057798174683967, 275.86880566295326389, 276.96780494052313770, 278.06757344036617496, 279.16810880295668085, 280.26940868320008349, 281.37147075030043197, 282.47429268763045229, 283.57787219260217171, 284.68220697654078322, 285.78729476455760050, 286.89313329542699194, 287.99972032146268930, 289.10705360839756395, 290.21513093526289140, 291.32395009427028754, 292.43350889069523646, 293.54380514276073200, 294.65483668152336350, 295.76660135076059532, 296.87909700685889902, 297.99232151870342022, 299.10627276756946458, 300.22094864701409733, 301.33634706277030091, 302.45246593264130297, 303.56930318639643929, 304.68685676566872189, 305.80512462385280514, 306.92410472600477078, 308.04379504874236773, 309.16419358014690033, 310.28529831966631036, 311.40710727801865687, 312.52961847709792664, 313.65282994987899201, 314.77673974032603610, 315.90134590329950015, 317.02664650446632777, 318.15263962020929966, 319.27932333753892635, 320.40669575400545455, 321.53475497761127144, 322.66349912672620803, 323.79292633000159185, 324.92303472628691452, 326.05382246454587403, 327.18528770377525916, 328.31742861292224234, 329.45024337080525356, 330.58373016603343331, 331.71788719692847280, 332.85271267144611329, 333.98820480709991898, 335.12436183088397001, 336.26118197919845443, 337.39866349777429377, 338.53680464159958774, 339.67560367484657036, 340.81505887079896411, 341.95516851178109619, 343.09593088908627578, 344.23734430290727460, 345.37940706226686416, 346.52211748494903532, 347.66547389743118401, 348.80947463481720661, 349.95411804077025408, 351.09940246744753267, 352.24532627543504759, 353.39188783368263103, 354.53908551944078908, 355.68691771819692349, 356.83538282361303118, 357.98447923746385868, 359.13420536957539753 }; TEST_BEGIN(test_ln_gamma_misc) { unsigned i; for (i = 1; i < sizeof(ln_gamma_misc_expected)/sizeof(double); i++) { double x = (double)i * 0.25; expect_true(double_eq_rel(ln_gamma(x), ln_gamma_misc_expected[i], MAX_REL_ERR, MAX_ABS_ERR), "Incorrect ln_gamma result for i=%u", i); } } TEST_END /* Expected pt_norm([0.01..0.99] increment=0.01). */ static const double pt_norm_expected[] = { -INFINITY, -2.32634787404084076, -2.05374891063182252, -1.88079360815125085, -1.75068607125216946, -1.64485362695147264, -1.55477359459685305, -1.47579102817917063, -1.40507156030963221, -1.34075503369021654, -1.28155156554460081, -1.22652812003661049, -1.17498679206608991, -1.12639112903880045, -1.08031934081495606, -1.03643338949378938, -0.99445788320975281, -0.95416525314619416, -0.91536508784281390, -0.87789629505122846, -0.84162123357291418, -0.80642124701824025, -0.77219321418868492, -0.73884684918521371, -0.70630256284008752, -0.67448975019608171, -0.64334540539291685, -0.61281299101662701, -0.58284150727121620, -0.55338471955567281, -0.52440051270804067, -0.49585034734745320, -0.46769879911450812, -0.43991316567323380, -0.41246312944140462, -0.38532046640756751, -0.35845879325119373, -0.33185334643681652, -0.30548078809939738, -0.27931903444745404, -0.25334710313579978, -0.22754497664114931, -0.20189347914185077, -0.17637416478086135, -0.15096921549677725, -0.12566134685507399, -0.10043372051146975, -0.07526986209982976, -0.05015358346473352, -0.02506890825871106, 0.00000000000000000, 0.02506890825871106, 0.05015358346473366, 0.07526986209982990, 0.10043372051146990, 0.12566134685507413, 0.15096921549677739, 0.17637416478086146, 0.20189347914185105, 0.22754497664114931, 0.25334710313579978, 0.27931903444745404, 0.30548078809939738, 0.33185334643681652, 0.35845879325119373, 0.38532046640756762, 0.41246312944140484, 0.43991316567323391, 0.46769879911450835, 0.49585034734745348, 0.52440051270804111, 0.55338471955567303, 0.58284150727121620, 0.61281299101662701, 0.64334540539291685, 0.67448975019608171, 0.70630256284008752, 0.73884684918521371, 0.77219321418868492, 0.80642124701824036, 0.84162123357291441, 0.87789629505122879, 0.91536508784281423, 0.95416525314619460, 0.99445788320975348, 1.03643338949378938, 1.08031934081495606, 1.12639112903880045, 1.17498679206608991, 1.22652812003661049, 1.28155156554460081, 1.34075503369021654, 1.40507156030963265, 1.47579102817917085, 1.55477359459685394, 1.64485362695147308, 1.75068607125217102, 1.88079360815125041, 2.05374891063182208, 2.32634787404084076 }; TEST_BEGIN(test_pt_norm) { unsigned i; for (i = 1; i < sizeof(pt_norm_expected)/sizeof(double); i++) { double p = (double)i * 0.01; expect_true(double_eq_rel(pt_norm(p), pt_norm_expected[i], MAX_REL_ERR, MAX_ABS_ERR), "Incorrect pt_norm result for i=%u", i); } } TEST_END /* * Expected pt_chi2(p=[0.01..0.99] increment=0.07, * df={0.1, 1.1, 10.1, 100.1, 1000.1}). */ static const double pt_chi2_df[] = {0.1, 1.1, 10.1, 100.1, 1000.1}; static const double pt_chi2_expected[] = { 1.168926411457320e-40, 1.347680397072034e-22, 3.886980416666260e-17, 8.245951724356564e-14, 2.068936347497604e-11, 1.562561743309233e-09, 5.459543043426564e-08, 1.114775688149252e-06, 1.532101202364371e-05, 1.553884683726585e-04, 1.239396954915939e-03, 8.153872320255721e-03, 4.631183739647523e-02, 2.473187311701327e-01, 2.175254800183617e+00, 0.0003729887888876379, 0.0164409238228929513, 0.0521523015190650113, 0.1064701372271216612, 0.1800913735793082115, 0.2748704281195626931, 0.3939246282787986497, 0.5420727552260817816, 0.7267265822221973259, 0.9596554296000253670, 1.2607440376386165326, 1.6671185084541604304, 2.2604828984738705167, 3.2868613342148607082, 6.9298574921692139839, 2.606673548632508, 4.602913725294877, 5.646152813924212, 6.488971315540869, 7.249823275816285, 7.977314231410841, 8.700354939944047, 9.441728024225892, 10.224338321374127, 11.076435368801061, 12.039320937038386, 13.183878752697167, 14.657791935084575, 16.885728216339373, 23.361991680031817, 70.14844087392152, 80.92379498849355, 85.53325420085891, 88.94433120715347, 91.83732712857017, 94.46719943606301, 96.96896479994635, 99.43412843510363, 101.94074719829733, 104.57228644307247, 107.43900093448734, 110.71844673417287, 114.76616819871325, 120.57422505959563, 135.92318818757556, 899.0072447849649, 937.9271278858220, 953.8117189560207, 965.3079371501154, 974.8974061207954, 983.4936235182347, 991.5691170518946, 999.4334123954690, 1007.3391826856553, 1015.5445154999951, 1024.3777075619569, 1034.3538789836223, 1046.4872561869577, 1063.5717461999654, 1107.0741966053859 }; TEST_BEGIN(test_pt_chi2) { unsigned i, j; unsigned e = 0; for (i = 0; i < sizeof(pt_chi2_df)/sizeof(double); i++) { double df = pt_chi2_df[i]; double ln_gamma_df = ln_gamma(df * 0.5); for (j = 1; j < 100; j += 7) { double p = (double)j * 0.01; expect_true(double_eq_rel(pt_chi2(p, df, ln_gamma_df), pt_chi2_expected[e], MAX_REL_ERR, MAX_ABS_ERR), "Incorrect pt_chi2 result for i=%u, j=%u", i, j); e++; } } } TEST_END /* * Expected pt_gamma(p=[0.1..0.99] increment=0.07, * shape=[0.5..3.0] increment=0.5). */ static const double pt_gamma_shape[] = {0.5, 1.0, 1.5, 2.0, 2.5, 3.0}; static const double pt_gamma_expected[] = { 7.854392895485103e-05, 5.043466107888016e-03, 1.788288957794883e-02, 3.900956150232906e-02, 6.913847560638034e-02, 1.093710833465766e-01, 1.613412523825817e-01, 2.274682115597864e-01, 3.114117323127083e-01, 4.189466220207417e-01, 5.598106789059246e-01, 7.521856146202706e-01, 1.036125427911119e+00, 1.532450860038180e+00, 3.317448300510606e+00, 0.01005033585350144, 0.08338160893905107, 0.16251892949777497, 0.24846135929849966, 0.34249030894677596, 0.44628710262841947, 0.56211891815354142, 0.69314718055994529, 0.84397007029452920, 1.02165124753198167, 1.23787435600161766, 1.51412773262977574, 1.89711998488588196, 2.52572864430825783, 4.60517018598809091, 0.05741590094955853, 0.24747378084860744, 0.39888572212236084, 0.54394139997444901, 0.69048812513915159, 0.84311389861296104, 1.00580622221479898, 1.18298694218766931, 1.38038096305861213, 1.60627736383027453, 1.87396970522337947, 2.20749220408081070, 2.65852391865854942, 3.37934630984842244, 5.67243336507218476, 0.1485547402532659, 0.4657458011640391, 0.6832386130709406, 0.8794297834672100, 1.0700752852474524, 1.2629614217350744, 1.4638400448580779, 1.6783469900166610, 1.9132338090606940, 2.1778589228618777, 2.4868823970010991, 2.8664695666264195, 3.3724415436062114, 4.1682658512758071, 6.6383520679938108, 0.2771490383641385, 0.7195001279643727, 0.9969081732265243, 1.2383497880608061, 1.4675206597269927, 1.6953064251816552, 1.9291243435606809, 2.1757300955477641, 2.4428032131216391, 2.7406534569230616, 3.0851445039665513, 3.5043101122033367, 4.0575997065264637, 4.9182956424675286, 7.5431362346944937, 0.4360451650782932, 0.9983600902486267, 1.3306365880734528, 1.6129750834753802, 1.8767241606994294, 2.1357032436097660, 2.3988853336865565, 2.6740603137235603, 2.9697561737517959, 3.2971457713883265, 3.6731795898504660, 4.1275751617770631, 4.7230515633946677, 5.6417477865306020, 8.4059469148854635 }; TEST_BEGIN(test_pt_gamma_shape) { unsigned i, j; unsigned e = 0; for (i = 0; i < sizeof(pt_gamma_shape)/sizeof(double); i++) { double shape = pt_gamma_shape[i]; double ln_gamma_shape = ln_gamma(shape); for (j = 1; j < 100; j += 7) { double p = (double)j * 0.01; expect_true(double_eq_rel(pt_gamma(p, shape, 1.0, ln_gamma_shape), pt_gamma_expected[e], MAX_REL_ERR, MAX_ABS_ERR), "Incorrect pt_gamma result for i=%u, j=%u", i, j); e++; } } } TEST_END TEST_BEGIN(test_pt_gamma_scale) { double shape = 1.0; double ln_gamma_shape = ln_gamma(shape); expect_true(double_eq_rel( pt_gamma(0.5, shape, 1.0, ln_gamma_shape) * 10.0, pt_gamma(0.5, shape, 10.0, ln_gamma_shape), MAX_REL_ERR, MAX_ABS_ERR), "Scale should be trivially equivalent to external multiplication"); } TEST_END int main(void) { return test( test_ln_gamma_factorial, test_ln_gamma_misc, test_pt_norm, test_pt_chi2, test_pt_gamma_shape, test_pt_gamma_scale); } redis-8.0.2/deps/jemalloc/test/unit/mpsc_queue.c000066400000000000000000000173571501533116600216360ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/mpsc_queue.h" typedef struct elem_s elem_t; typedef ql_head(elem_t) elem_list_t; typedef mpsc_queue(elem_t) elem_mpsc_queue_t; struct elem_s { int thread; int idx; ql_elm(elem_t) link; }; /* Include both proto and gen to make sure they match up. */ mpsc_queue_proto(static, elem_mpsc_queue_, elem_mpsc_queue_t, elem_t, elem_list_t); mpsc_queue_gen(static, elem_mpsc_queue_, elem_mpsc_queue_t, elem_t, elem_list_t, link); static void init_elems_simple(elem_t *elems, int nelems, int thread) { for (int i = 0; i < nelems; i++) { elems[i].thread = thread; elems[i].idx = i; ql_elm_new(&elems[i], link); } } static void check_elems_simple(elem_list_t *list, int nelems, int thread) { elem_t *elem; int next_idx = 0; ql_foreach(elem, list, link) { expect_d_lt(next_idx, nelems, "Too many list items"); expect_d_eq(thread, elem->thread, ""); expect_d_eq(next_idx, elem->idx, "List out of order"); next_idx++; } } TEST_BEGIN(test_simple) { enum {NELEMS = 10}; elem_t elems[NELEMS]; elem_list_t list; elem_mpsc_queue_t queue; /* Pop empty queue onto empty list -> empty list */ ql_new(&list); elem_mpsc_queue_new(&queue); elem_mpsc_queue_pop_batch(&queue, &list); expect_true(ql_empty(&list), ""); /* Pop empty queue onto nonempty list -> list unchanged */ ql_new(&list); elem_mpsc_queue_new(&queue); init_elems_simple(elems, NELEMS, 0); for (int i = 0; i < NELEMS; i++) { ql_tail_insert(&list, &elems[i], link); } elem_mpsc_queue_pop_batch(&queue, &list); check_elems_simple(&list, NELEMS, 0); /* Pop nonempty queue onto empty list -> list takes queue contents */ ql_new(&list); elem_mpsc_queue_new(&queue); init_elems_simple(elems, NELEMS, 0); for (int i = 0; i < NELEMS; i++) { elem_mpsc_queue_push(&queue, &elems[i]); } elem_mpsc_queue_pop_batch(&queue, &list); check_elems_simple(&list, NELEMS, 0); /* Pop nonempty queue onto nonempty list -> list gains queue contents */ ql_new(&list); elem_mpsc_queue_new(&queue); init_elems_simple(elems, NELEMS, 0); for (int i = 0; i < NELEMS / 2; i++) { ql_tail_insert(&list, &elems[i], link); } for (int i = NELEMS / 2; i < NELEMS; i++) { elem_mpsc_queue_push(&queue, &elems[i]); } elem_mpsc_queue_pop_batch(&queue, &list); check_elems_simple(&list, NELEMS, 0); } TEST_END TEST_BEGIN(test_push_single_or_batch) { enum { BATCH_MAX = 10, /* * We'll push i items one-at-a-time, then i items as a batch, * then i items as a batch again, as i ranges from 1 to * BATCH_MAX. So we need 3 times the sum of the numbers from 1 * to BATCH_MAX elements total. */ NELEMS = 3 * BATCH_MAX * (BATCH_MAX - 1) / 2 }; elem_t elems[NELEMS]; init_elems_simple(elems, NELEMS, 0); elem_list_t list; ql_new(&list); elem_mpsc_queue_t queue; elem_mpsc_queue_new(&queue); int next_idx = 0; for (int i = 1; i < 10; i++) { /* Push i items 1 at a time. */ for (int j = 0; j < i; j++) { elem_mpsc_queue_push(&queue, &elems[next_idx]); next_idx++; } /* Push i items in batch. */ for (int j = 0; j < i; j++) { ql_tail_insert(&list, &elems[next_idx], link); next_idx++; } elem_mpsc_queue_push_batch(&queue, &list); expect_true(ql_empty(&list), "Batch push should empty source"); /* * Push i items in batch, again. This tests two batches * proceeding one after the other. */ for (int j = 0; j < i; j++) { ql_tail_insert(&list, &elems[next_idx], link); next_idx++; } elem_mpsc_queue_push_batch(&queue, &list); expect_true(ql_empty(&list), "Batch push should empty source"); } expect_d_eq(NELEMS, next_idx, "Miscomputed number of elems to push."); expect_true(ql_empty(&list), ""); elem_mpsc_queue_pop_batch(&queue, &list); check_elems_simple(&list, NELEMS, 0); } TEST_END TEST_BEGIN(test_multi_op) { enum {NELEMS = 20}; elem_t elems[NELEMS]; init_elems_simple(elems, NELEMS, 0); elem_list_t push_list; ql_new(&push_list); elem_list_t result_list; ql_new(&result_list); elem_mpsc_queue_t queue; elem_mpsc_queue_new(&queue); int next_idx = 0; /* Push first quarter 1-at-a-time. */ for (int i = 0; i < NELEMS / 4; i++) { elem_mpsc_queue_push(&queue, &elems[next_idx]); next_idx++; } /* Push second quarter in batch. */ for (int i = NELEMS / 4; i < NELEMS / 2; i++) { ql_tail_insert(&push_list, &elems[next_idx], link); next_idx++; } elem_mpsc_queue_push_batch(&queue, &push_list); /* Batch pop all pushed elements. */ elem_mpsc_queue_pop_batch(&queue, &result_list); /* Push third quarter in batch. */ for (int i = NELEMS / 2; i < 3 * NELEMS / 4; i++) { ql_tail_insert(&push_list, &elems[next_idx], link); next_idx++; } elem_mpsc_queue_push_batch(&queue, &push_list); /* Push last quarter one-at-a-time. */ for (int i = 3 * NELEMS / 4; i < NELEMS; i++) { elem_mpsc_queue_push(&queue, &elems[next_idx]); next_idx++; } /* Pop them again. Order of existing list should be preserved. */ elem_mpsc_queue_pop_batch(&queue, &result_list); check_elems_simple(&result_list, NELEMS, 0); } TEST_END typedef struct pusher_arg_s pusher_arg_t; struct pusher_arg_s { elem_mpsc_queue_t *queue; int thread; elem_t *elems; int nelems; }; typedef struct popper_arg_s popper_arg_t; struct popper_arg_s { elem_mpsc_queue_t *queue; int npushers; int nelems_per_pusher; int *pusher_counts; }; static void * thd_pusher(void *void_arg) { pusher_arg_t *arg = (pusher_arg_t *)void_arg; int next_idx = 0; while (next_idx < arg->nelems) { /* Push 10 items in batch. */ elem_list_t list; ql_new(&list); int limit = next_idx + 10; while (next_idx < arg->nelems && next_idx < limit) { ql_tail_insert(&list, &arg->elems[next_idx], link); next_idx++; } elem_mpsc_queue_push_batch(arg->queue, &list); /* Push 10 items one-at-a-time. */ limit = next_idx + 10; while (next_idx < arg->nelems && next_idx < limit) { elem_mpsc_queue_push(arg->queue, &arg->elems[next_idx]); next_idx++; } } return NULL; } static void * thd_popper(void *void_arg) { popper_arg_t *arg = (popper_arg_t *)void_arg; int done_pushers = 0; while (done_pushers < arg->npushers) { elem_list_t list; ql_new(&list); elem_mpsc_queue_pop_batch(arg->queue, &list); elem_t *elem; ql_foreach(elem, &list, link) { int thread = elem->thread; int idx = elem->idx; expect_d_eq(arg->pusher_counts[thread], idx, "Thread's pushes reordered"); arg->pusher_counts[thread]++; if (arg->pusher_counts[thread] == arg->nelems_per_pusher) { done_pushers++; } } } return NULL; } TEST_BEGIN(test_multiple_threads) { enum { NPUSHERS = 4, NELEMS_PER_PUSHER = 1000*1000, }; thd_t pushers[NPUSHERS]; pusher_arg_t pusher_arg[NPUSHERS]; thd_t popper; popper_arg_t popper_arg; elem_mpsc_queue_t queue; elem_mpsc_queue_new(&queue); elem_t *elems = calloc(NPUSHERS * NELEMS_PER_PUSHER, sizeof(elem_t)); elem_t *elem_iter = elems; for (int i = 0; i < NPUSHERS; i++) { pusher_arg[i].queue = &queue; pusher_arg[i].thread = i; pusher_arg[i].elems = elem_iter; pusher_arg[i].nelems = NELEMS_PER_PUSHER; init_elems_simple(elem_iter, NELEMS_PER_PUSHER, i); elem_iter += NELEMS_PER_PUSHER; } popper_arg.queue = &queue; popper_arg.npushers = NPUSHERS; popper_arg.nelems_per_pusher = NELEMS_PER_PUSHER; int pusher_counts[NPUSHERS] = {0}; popper_arg.pusher_counts = pusher_counts; thd_create(&popper, thd_popper, (void *)&popper_arg); for (int i = 0; i < NPUSHERS; i++) { thd_create(&pushers[i], thd_pusher, &pusher_arg[i]); } thd_join(popper, NULL); for (int i = 0; i < NPUSHERS; i++) { thd_join(pushers[i], NULL); } for (int i = 0; i < NPUSHERS; i++) { expect_d_eq(NELEMS_PER_PUSHER, pusher_counts[i], ""); } free(elems); } TEST_END int main(void) { return test_no_reentrancy( test_simple, test_push_single_or_batch, test_multi_op, test_multiple_threads); } redis-8.0.2/deps/jemalloc/test/unit/mq.c000066400000000000000000000034111501533116600200670ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define NSENDERS 3 #define NMSGS 100000 typedef struct mq_msg_s mq_msg_t; struct mq_msg_s { mq_msg(mq_msg_t) link; }; mq_gen(static, mq_, mq_t, mq_msg_t, link) TEST_BEGIN(test_mq_basic) { mq_t mq; mq_msg_t msg; expect_false(mq_init(&mq), "Unexpected mq_init() failure"); expect_u_eq(mq_count(&mq), 0, "mq should be empty"); expect_ptr_null(mq_tryget(&mq), "mq_tryget() should fail when the queue is empty"); mq_put(&mq, &msg); expect_u_eq(mq_count(&mq), 1, "mq should contain one message"); expect_ptr_eq(mq_tryget(&mq), &msg, "mq_tryget() should return msg"); mq_put(&mq, &msg); expect_ptr_eq(mq_get(&mq), &msg, "mq_get() should return msg"); mq_fini(&mq); } TEST_END static void * thd_receiver_start(void *arg) { mq_t *mq = (mq_t *)arg; unsigned i; for (i = 0; i < (NSENDERS * NMSGS); i++) { mq_msg_t *msg = mq_get(mq); expect_ptr_not_null(msg, "mq_get() should never return NULL"); dallocx(msg, 0); } return NULL; } static void * thd_sender_start(void *arg) { mq_t *mq = (mq_t *)arg; unsigned i; for (i = 0; i < NMSGS; i++) { mq_msg_t *msg; void *p; p = mallocx(sizeof(mq_msg_t), 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); msg = (mq_msg_t *)p; mq_put(mq, msg); } return NULL; } TEST_BEGIN(test_mq_threaded) { mq_t mq; thd_t receiver; thd_t senders[NSENDERS]; unsigned i; expect_false(mq_init(&mq), "Unexpected mq_init() failure"); thd_create(&receiver, thd_receiver_start, (void *)&mq); for (i = 0; i < NSENDERS; i++) { thd_create(&senders[i], thd_sender_start, (void *)&mq); } thd_join(receiver, NULL); for (i = 0; i < NSENDERS; i++) { thd_join(senders[i], NULL); } mq_fini(&mq); } TEST_END int main(void) { return test( test_mq_basic, test_mq_threaded); } redis-8.0.2/deps/jemalloc/test/unit/mtx.c000066400000000000000000000017601501533116600202670ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define NTHREADS 2 #define NINCRS 2000000 TEST_BEGIN(test_mtx_basic) { mtx_t mtx; expect_false(mtx_init(&mtx), "Unexpected mtx_init() failure"); mtx_lock(&mtx); mtx_unlock(&mtx); mtx_fini(&mtx); } TEST_END typedef struct { mtx_t mtx; unsigned x; } thd_start_arg_t; static void * thd_start(void *varg) { thd_start_arg_t *arg = (thd_start_arg_t *)varg; unsigned i; for (i = 0; i < NINCRS; i++) { mtx_lock(&arg->mtx); arg->x++; mtx_unlock(&arg->mtx); } return NULL; } TEST_BEGIN(test_mtx_race) { thd_start_arg_t arg; thd_t thds[NTHREADS]; unsigned i; expect_false(mtx_init(&arg.mtx), "Unexpected mtx_init() failure"); arg.x = 0; for (i = 0; i < NTHREADS; i++) { thd_create(&thds[i], thd_start, (void *)&arg); } for (i = 0; i < NTHREADS; i++) { thd_join(thds[i], NULL); } expect_u_eq(arg.x, NTHREADS * NINCRS, "Race-related counter corruption"); } TEST_END int main(void) { return test( test_mtx_basic, test_mtx_race); } redis-8.0.2/deps/jemalloc/test/unit/nstime.c000066400000000000000000000143051501533116600207550ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define BILLION UINT64_C(1000000000) TEST_BEGIN(test_nstime_init) { nstime_t nst; nstime_init(&nst, 42000000043); expect_u64_eq(nstime_ns(&nst), 42000000043, "ns incorrectly read"); expect_u64_eq(nstime_sec(&nst), 42, "sec incorrectly read"); expect_u64_eq(nstime_nsec(&nst), 43, "nsec incorrectly read"); } TEST_END TEST_BEGIN(test_nstime_init2) { nstime_t nst; nstime_init2(&nst, 42, 43); expect_u64_eq(nstime_sec(&nst), 42, "sec incorrectly read"); expect_u64_eq(nstime_nsec(&nst), 43, "nsec incorrectly read"); } TEST_END TEST_BEGIN(test_nstime_copy) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_init_zero(&nstb); nstime_copy(&nstb, &nsta); expect_u64_eq(nstime_sec(&nstb), 42, "sec incorrectly copied"); expect_u64_eq(nstime_nsec(&nstb), 43, "nsec incorrectly copied"); } TEST_END TEST_BEGIN(test_nstime_compare) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Times should be equal"); expect_d_eq(nstime_compare(&nstb, &nsta), 0, "Times should be equal"); nstime_init2(&nstb, 42, 42); expect_d_eq(nstime_compare(&nsta, &nstb), 1, "nsta should be greater than nstb"); expect_d_eq(nstime_compare(&nstb, &nsta), -1, "nstb should be less than nsta"); nstime_init2(&nstb, 42, 44); expect_d_eq(nstime_compare(&nsta, &nstb), -1, "nsta should be less than nstb"); expect_d_eq(nstime_compare(&nstb, &nsta), 1, "nstb should be greater than nsta"); nstime_init2(&nstb, 41, BILLION - 1); expect_d_eq(nstime_compare(&nsta, &nstb), 1, "nsta should be greater than nstb"); expect_d_eq(nstime_compare(&nstb, &nsta), -1, "nstb should be less than nsta"); nstime_init2(&nstb, 43, 0); expect_d_eq(nstime_compare(&nsta, &nstb), -1, "nsta should be less than nstb"); expect_d_eq(nstime_compare(&nstb, &nsta), 1, "nstb should be greater than nsta"); } TEST_END TEST_BEGIN(test_nstime_add) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); nstime_add(&nsta, &nstb); nstime_init2(&nstb, 84, 86); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect addition result"); nstime_init2(&nsta, 42, BILLION - 1); nstime_copy(&nstb, &nsta); nstime_add(&nsta, &nstb); nstime_init2(&nstb, 85, BILLION - 2); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect addition result"); } TEST_END TEST_BEGIN(test_nstime_iadd) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, BILLION - 1); nstime_iadd(&nsta, 1); nstime_init2(&nstb, 43, 0); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect addition result"); nstime_init2(&nsta, 42, 1); nstime_iadd(&nsta, BILLION + 1); nstime_init2(&nstb, 43, 2); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect addition result"); } TEST_END TEST_BEGIN(test_nstime_subtract) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); nstime_subtract(&nsta, &nstb); nstime_init_zero(&nstb); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect subtraction result"); nstime_init2(&nsta, 42, 43); nstime_init2(&nstb, 41, 44); nstime_subtract(&nsta, &nstb); nstime_init2(&nstb, 0, BILLION - 1); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect subtraction result"); } TEST_END TEST_BEGIN(test_nstime_isubtract) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_isubtract(&nsta, 42*BILLION + 43); nstime_init_zero(&nstb); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect subtraction result"); nstime_init2(&nsta, 42, 43); nstime_isubtract(&nsta, 41*BILLION + 44); nstime_init2(&nstb, 0, BILLION - 1); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect subtraction result"); } TEST_END TEST_BEGIN(test_nstime_imultiply) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_imultiply(&nsta, 10); nstime_init2(&nstb, 420, 430); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect multiplication result"); nstime_init2(&nsta, 42, 666666666); nstime_imultiply(&nsta, 3); nstime_init2(&nstb, 127, 999999998); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect multiplication result"); } TEST_END TEST_BEGIN(test_nstime_idivide) { nstime_t nsta, nstb; nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); nstime_imultiply(&nsta, 10); nstime_idivide(&nsta, 10); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect division result"); nstime_init2(&nsta, 42, 666666666); nstime_copy(&nstb, &nsta); nstime_imultiply(&nsta, 3); nstime_idivide(&nsta, 3); expect_d_eq(nstime_compare(&nsta, &nstb), 0, "Incorrect division result"); } TEST_END TEST_BEGIN(test_nstime_divide) { nstime_t nsta, nstb, nstc; nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); nstime_imultiply(&nsta, 10); expect_u64_eq(nstime_divide(&nsta, &nstb), 10, "Incorrect division result"); nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); nstime_imultiply(&nsta, 10); nstime_init(&nstc, 1); nstime_add(&nsta, &nstc); expect_u64_eq(nstime_divide(&nsta, &nstb), 10, "Incorrect division result"); nstime_init2(&nsta, 42, 43); nstime_copy(&nstb, &nsta); nstime_imultiply(&nsta, 10); nstime_init(&nstc, 1); nstime_subtract(&nsta, &nstc); expect_u64_eq(nstime_divide(&nsta, &nstb), 9, "Incorrect division result"); } TEST_END void test_nstime_since_once(nstime_t *t) { nstime_t old_t; nstime_copy(&old_t, t); uint64_t ns_since = nstime_ns_since(t); nstime_update(t); nstime_t new_t; nstime_copy(&new_t, t); nstime_subtract(&new_t, &old_t); expect_u64_ge(nstime_ns(&new_t), ns_since, "Incorrect time since result"); } TEST_BEGIN(test_nstime_ns_since) { nstime_t t; nstime_init_update(&t); for (uint64_t i = 0; i < 10000; i++) { /* Keeps updating t and verifies ns_since is valid. */ test_nstime_since_once(&t); } } TEST_END TEST_BEGIN(test_nstime_monotonic) { nstime_monotonic(); } TEST_END int main(void) { return test( test_nstime_init, test_nstime_init2, test_nstime_copy, test_nstime_compare, test_nstime_add, test_nstime_iadd, test_nstime_subtract, test_nstime_isubtract, test_nstime_imultiply, test_nstime_idivide, test_nstime_divide, test_nstime_ns_since, test_nstime_monotonic); } redis-8.0.2/deps/jemalloc/test/unit/oversize_threshold.c000066400000000000000000000077471501533116600234140ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/ctl.h" static void arena_mallctl(const char *mallctl_str, unsigned arena, void *oldp, size_t *oldlen, void *newp, size_t newlen) { int err; char buf[100]; malloc_snprintf(buf, sizeof(buf), mallctl_str, arena); err = mallctl(buf, oldp, oldlen, newp, newlen); expect_d_eq(0, err, "Mallctl failed; %s", buf); } TEST_BEGIN(test_oversize_threshold_get_set) { int err; size_t old_threshold; size_t new_threshold; size_t threshold_sz = sizeof(old_threshold); unsigned arena; size_t arena_sz = sizeof(arena); err = mallctl("arenas.create", (void *)&arena, &arena_sz, NULL, 0); expect_d_eq(0, err, "Arena creation failed"); /* Just a write. */ new_threshold = 1024 * 1024; arena_mallctl("arena.%u.oversize_threshold", arena, NULL, NULL, &new_threshold, threshold_sz); /* Read and write */ new_threshold = 2 * 1024 * 1024; arena_mallctl("arena.%u.oversize_threshold", arena, &old_threshold, &threshold_sz, &new_threshold, threshold_sz); expect_zu_eq(1024 * 1024, old_threshold, "Should have read old value"); /* Just a read */ arena_mallctl("arena.%u.oversize_threshold", arena, &old_threshold, &threshold_sz, NULL, 0); expect_zu_eq(2 * 1024 * 1024, old_threshold, "Should have read old value"); } TEST_END static size_t max_purged = 0; static bool purge_forced_record_max(extent_hooks_t* hooks, void *addr, size_t sz, size_t offset, size_t length, unsigned arena_ind) { if (length > max_purged) { max_purged = length; } return false; } static bool dalloc_record_max(extent_hooks_t *extent_hooks, void *addr, size_t sz, bool comitted, unsigned arena_ind) { if (sz > max_purged) { max_purged = sz; } return false; } extent_hooks_t max_recording_extent_hooks; TEST_BEGIN(test_oversize_threshold) { max_recording_extent_hooks = ehooks_default_extent_hooks; max_recording_extent_hooks.purge_forced = &purge_forced_record_max; max_recording_extent_hooks.dalloc = &dalloc_record_max; extent_hooks_t *extent_hooks = &max_recording_extent_hooks; int err; unsigned arena; size_t arena_sz = sizeof(arena); err = mallctl("arenas.create", (void *)&arena, &arena_sz, NULL, 0); expect_d_eq(0, err, "Arena creation failed"); arena_mallctl("arena.%u.extent_hooks", arena, NULL, NULL, &extent_hooks, sizeof(extent_hooks)); /* * This test will fundamentally race with purging, since we're going to * check the dirty stats to see if our oversized allocation got purged. * We don't want other purging to happen accidentally. We can't just * disable purging entirely, though, since that will also disable * oversize purging. Just set purging intervals to be very large. */ ssize_t decay_ms = 100 * 1000; ssize_t decay_ms_sz = sizeof(decay_ms); arena_mallctl("arena.%u.dirty_decay_ms", arena, NULL, NULL, &decay_ms, decay_ms_sz); arena_mallctl("arena.%u.muzzy_decay_ms", arena, NULL, NULL, &decay_ms, decay_ms_sz); /* Clean everything out. */ arena_mallctl("arena.%u.purge", arena, NULL, NULL, NULL, 0); max_purged = 0; /* Set threshold to 1MB. */ size_t threshold = 1024 * 1024; size_t threshold_sz = sizeof(threshold); arena_mallctl("arena.%u.oversize_threshold", arena, NULL, NULL, &threshold, threshold_sz); /* Allocating and freeing half a megabyte should leave them dirty. */ void *ptr = mallocx(512 * 1024, MALLOCX_ARENA(arena)); dallocx(ptr, MALLOCX_TCACHE_NONE); if (!is_background_thread_enabled()) { expect_zu_lt(max_purged, 512 * 1024, "Expected no 512k purge"); } /* Purge again to reset everything out. */ arena_mallctl("arena.%u.purge", arena, NULL, NULL, NULL, 0); max_purged = 0; /* * Allocating and freeing 2 megabytes should have them purged because of * the oversize threshold. */ ptr = mallocx(2 * 1024 * 1024, MALLOCX_ARENA(arena)); dallocx(ptr, MALLOCX_TCACHE_NONE); expect_zu_ge(max_purged, 2 * 1024 * 1024, "Expected a 2MB purge"); } TEST_END int main(void) { return test_no_reentrancy( test_oversize_threshold_get_set, test_oversize_threshold); } redis-8.0.2/deps/jemalloc/test/unit/pa.c000066400000000000000000000072011501533116600200530ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/pa.h" static void * alloc_hook(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { void *ret = pages_map(new_addr, size, alignment, commit); return ret; } static bool merge_hook(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, void *addr_b, size_t size_b, bool committed, unsigned arena_ind) { return !maps_coalesce; } static bool split_hook(extent_hooks_t *extent_hooks, void *addr, size_t size, size_t size_a, size_t size_b, bool committed, unsigned arena_ind) { return !maps_coalesce; } static void init_test_extent_hooks(extent_hooks_t *hooks) { /* * The default hooks are mostly fine for testing. A few of them, * though, access globals (alloc for dss setting in an arena, split and * merge touch the global emap to find head state. The first of these * can be fixed by keeping that state with the hooks, where it logically * belongs. The second, though, we can only fix when we use the extent * hook API. */ memcpy(hooks, &ehooks_default_extent_hooks, sizeof(extent_hooks_t)); hooks->alloc = &alloc_hook; hooks->merge = &merge_hook; hooks->split = &split_hook; } typedef struct test_data_s test_data_t; struct test_data_s { pa_shard_t shard; pa_central_t central; base_t *base; emap_t emap; pa_shard_stats_t stats; malloc_mutex_t stats_mtx; extent_hooks_t hooks; }; test_data_t *init_test_data(ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms) { test_data_t *test_data = calloc(1, sizeof(test_data_t)); assert_ptr_not_null(test_data, ""); init_test_extent_hooks(&test_data->hooks); base_t *base = base_new(TSDN_NULL, /* ind */ 1, &test_data->hooks, /* metadata_use_hooks */ true); assert_ptr_not_null(base, ""); test_data->base = base; bool err = emap_init(&test_data->emap, test_data->base, /* zeroed */ true); assert_false(err, ""); nstime_t time; nstime_init(&time, 0); err = pa_central_init(&test_data->central, base, opt_hpa, &hpa_hooks_default); assert_false(err, ""); const size_t pa_oversize_threshold = 8 * 1024 * 1024; err = pa_shard_init(TSDN_NULL, &test_data->shard, &test_data->central, &test_data->emap, test_data->base, /* ind */ 1, &test_data->stats, &test_data->stats_mtx, &time, pa_oversize_threshold, dirty_decay_ms, muzzy_decay_ms); assert_false(err, ""); return test_data; } void destroy_test_data(test_data_t *data) { base_delete(TSDN_NULL, data->base); free(data); } static void * do_alloc_free_purge(void *arg) { test_data_t *test_data = (test_data_t *)arg; for (int i = 0; i < 10 * 1000; i++) { bool deferred_work_generated = false; edata_t *edata = pa_alloc(TSDN_NULL, &test_data->shard, PAGE, PAGE, /* slab */ false, /* szind */ 0, /* zero */ false, /* guarded */ false, &deferred_work_generated); assert_ptr_not_null(edata, ""); pa_dalloc(TSDN_NULL, &test_data->shard, edata, &deferred_work_generated); malloc_mutex_lock(TSDN_NULL, &test_data->shard.pac.decay_dirty.mtx); pac_decay_all(TSDN_NULL, &test_data->shard.pac, &test_data->shard.pac.decay_dirty, &test_data->shard.pac.stats->decay_dirty, &test_data->shard.pac.ecache_dirty, true); malloc_mutex_unlock(TSDN_NULL, &test_data->shard.pac.decay_dirty.mtx); } return NULL; } TEST_BEGIN(test_alloc_free_purge_thds) { test_data_t *test_data = init_test_data(0, 0); thd_t thds[4]; for (int i = 0; i < 4; i++) { thd_create(&thds[i], do_alloc_free_purge, test_data); } for (int i = 0; i < 4; i++) { thd_join(thds[i], NULL); } } TEST_END int main(void) { return test( test_alloc_free_purge_thds); } redis-8.0.2/deps/jemalloc/test/unit/pack.c000066400000000000000000000076761501533116600204110ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * Size class that is a divisor of the page size, ideally 4+ regions per run. */ #if LG_PAGE <= 14 #define SZ (ZU(1) << (LG_PAGE - 2)) #else #define SZ ZU(4096) #endif /* * Number of slabs to consume at high water mark. Should be at least 2 so that * if mmap()ed memory grows downward, downward growth of mmap()ed memory is * tested. */ #define NSLABS 8 static unsigned binind_compute(void) { size_t sz; unsigned nbins, i; sz = sizeof(nbins); expect_d_eq(mallctl("arenas.nbins", (void *)&nbins, &sz, NULL, 0), 0, "Unexpected mallctl failure"); for (i = 0; i < nbins; i++) { size_t mib[4]; size_t miblen = sizeof(mib)/sizeof(size_t); size_t size; expect_d_eq(mallctlnametomib("arenas.bin.0.size", mib, &miblen), 0, "Unexpected mallctlnametomb failure"); mib[2] = (size_t)i; sz = sizeof(size); expect_d_eq(mallctlbymib(mib, miblen, (void *)&size, &sz, NULL, 0), 0, "Unexpected mallctlbymib failure"); if (size == SZ) { return i; } } test_fail("Unable to compute nregs_per_run"); return 0; } static size_t nregs_per_run_compute(void) { uint32_t nregs; size_t sz; unsigned binind = binind_compute(); size_t mib[4]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arenas.bin.0.nregs", mib, &miblen), 0, "Unexpected mallctlnametomb failure"); mib[2] = (size_t)binind; sz = sizeof(nregs); expect_d_eq(mallctlbymib(mib, miblen, (void *)&nregs, &sz, NULL, 0), 0, "Unexpected mallctlbymib failure"); return nregs; } static unsigned arenas_create_mallctl(void) { unsigned arena_ind; size_t sz; sz = sizeof(arena_ind); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Error in arenas.create"); return arena_ind; } static void arena_reset_mallctl(unsigned arena_ind) { size_t mib[3]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.reset", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } TEST_BEGIN(test_pack) { bool prof_enabled; size_t sz = sizeof(prof_enabled); if (mallctl("opt.prof", (void *)&prof_enabled, &sz, NULL, 0) == 0) { test_skip_if(prof_enabled); } unsigned arena_ind = arenas_create_mallctl(); size_t nregs_per_run = nregs_per_run_compute(); size_t nregs = nregs_per_run * NSLABS; VARIABLE_ARRAY(void *, ptrs, nregs); size_t i, j, offset; /* Fill matrix. */ for (i = offset = 0; i < NSLABS; i++) { for (j = 0; j < nregs_per_run; j++) { void *p = mallocx(SZ, MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE); expect_ptr_not_null(p, "Unexpected mallocx(%zu, MALLOCX_ARENA(%u) |" " MALLOCX_TCACHE_NONE) failure, run=%zu, reg=%zu", SZ, arena_ind, i, j); ptrs[(i * nregs_per_run) + j] = p; } } /* * Free all but one region of each run, but rotate which region is * preserved, so that subsequent allocations exercise the within-run * layout policy. */ offset = 0; for (i = offset = 0; i < NSLABS; i++, offset = (offset + 1) % nregs_per_run) { for (j = 0; j < nregs_per_run; j++) { void *p = ptrs[(i * nregs_per_run) + j]; if (offset == j) { continue; } dallocx(p, MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE); } } /* * Logically refill matrix, skipping preserved regions and verifying * that the matrix is unmodified. */ offset = 0; for (i = offset = 0; i < NSLABS; i++, offset = (offset + 1) % nregs_per_run) { for (j = 0; j < nregs_per_run; j++) { void *p; if (offset == j) { continue; } p = mallocx(SZ, MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE); expect_ptr_eq(p, ptrs[(i * nregs_per_run) + j], "Unexpected refill discrepancy, run=%zu, reg=%zu\n", i, j); } } /* Clean up. */ arena_reset_mallctl(arena_ind); } TEST_END int main(void) { return test( test_pack); } redis-8.0.2/deps/jemalloc/test/unit/pack.sh000066400000000000000000000001611501533116600205570ustar00rootroot00000000000000#!/bin/sh # Immediately purge to minimize fragmentation. export MALLOC_CONF="dirty_decay_ms:0,muzzy_decay_ms:0" redis-8.0.2/deps/jemalloc/test/unit/pages.c000066400000000000000000000013261501533116600205540ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_pages_huge) { size_t alloc_size; bool commit; void *pages, *hugepage; alloc_size = HUGEPAGE * 2 - PAGE; commit = true; pages = pages_map(NULL, alloc_size, PAGE, &commit); expect_ptr_not_null(pages, "Unexpected pages_map() error"); if (init_system_thp_mode == thp_mode_default) { hugepage = (void *)(ALIGNMENT_CEILING((uintptr_t)pages, HUGEPAGE)); expect_b_ne(pages_huge(hugepage, HUGEPAGE), have_madvise_huge, "Unexpected pages_huge() result"); expect_false(pages_nohuge(hugepage, HUGEPAGE), "Unexpected pages_nohuge() result"); } pages_unmap(pages, alloc_size); } TEST_END int main(void) { return test( test_pages_huge); } redis-8.0.2/deps/jemalloc/test/unit/peak.c000066400000000000000000000031331501533116600203730ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/peak.h" TEST_BEGIN(test_peak) { peak_t peak = PEAK_INITIALIZER; expect_u64_eq(0, peak_max(&peak), "Peak should be zero at initialization"); peak_update(&peak, 100, 50); expect_u64_eq(50, peak_max(&peak), "Missed update"); peak_update(&peak, 100, 100); expect_u64_eq(50, peak_max(&peak), "Dallocs shouldn't change peak"); peak_update(&peak, 100, 200); expect_u64_eq(50, peak_max(&peak), "Dallocs shouldn't change peak"); peak_update(&peak, 200, 200); expect_u64_eq(50, peak_max(&peak), "Haven't reached peak again"); peak_update(&peak, 300, 200); expect_u64_eq(100, peak_max(&peak), "Missed an update."); peak_set_zero(&peak, 300, 200); expect_u64_eq(0, peak_max(&peak), "No effect from zeroing"); peak_update(&peak, 300, 300); expect_u64_eq(0, peak_max(&peak), "Dalloc shouldn't change peak"); peak_update(&peak, 400, 300); expect_u64_eq(0, peak_max(&peak), "Should still be net negative"); peak_update(&peak, 500, 300); expect_u64_eq(100, peak_max(&peak), "Missed an update."); /* * Above, we set to zero while a net allocator; let's try as a * net-deallocator. */ peak_set_zero(&peak, 600, 700); expect_u64_eq(0, peak_max(&peak), "No effect from zeroing."); peak_update(&peak, 600, 800); expect_u64_eq(0, peak_max(&peak), "Dalloc shouldn't change peak."); peak_update(&peak, 700, 800); expect_u64_eq(0, peak_max(&peak), "Should still be net negative."); peak_update(&peak, 800, 800); expect_u64_eq(100, peak_max(&peak), "Missed an update."); } TEST_END int main(void) { return test_no_reentrancy( test_peak); } redis-8.0.2/deps/jemalloc/test/unit/ph.c000066400000000000000000000164571501533116600200770ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/ph.h" typedef struct node_s node_t; ph_structs(heap, node_t); struct node_s { #define NODE_MAGIC 0x9823af7e uint32_t magic; heap_link_t link; uint64_t key; }; static int node_cmp(const node_t *a, const node_t *b) { int ret; ret = (a->key > b->key) - (a->key < b->key); if (ret == 0) { /* * Duplicates are not allowed in the heap, so force an * arbitrary ordering for non-identical items with equal keys. */ ret = (((uintptr_t)a) > ((uintptr_t)b)) - (((uintptr_t)a) < ((uintptr_t)b)); } return ret; } static int node_cmp_magic(const node_t *a, const node_t *b) { expect_u32_eq(a->magic, NODE_MAGIC, "Bad magic"); expect_u32_eq(b->magic, NODE_MAGIC, "Bad magic"); return node_cmp(a, b); } ph_gen(static, heap, node_t, link, node_cmp_magic); static node_t * node_next_get(const node_t *node) { return phn_next_get((node_t *)node, offsetof(node_t, link)); } static node_t * node_prev_get(const node_t *node) { return phn_prev_get((node_t *)node, offsetof(node_t, link)); } static node_t * node_lchild_get(const node_t *node) { return phn_lchild_get((node_t *)node, offsetof(node_t, link)); } static void node_print(const node_t *node, unsigned depth) { unsigned i; node_t *leftmost_child, *sibling; for (i = 0; i < depth; i++) { malloc_printf("\t"); } malloc_printf("%2"FMTu64"\n", node->key); leftmost_child = node_lchild_get(node); if (leftmost_child == NULL) { return; } node_print(leftmost_child, depth + 1); for (sibling = node_next_get(leftmost_child); sibling != NULL; sibling = node_next_get(sibling)) { node_print(sibling, depth + 1); } } static void heap_print(const heap_t *heap) { node_t *auxelm; malloc_printf("vvv heap %p vvv\n", heap); if (heap->ph.root == NULL) { goto label_return; } node_print(heap->ph.root, 0); for (auxelm = node_next_get(heap->ph.root); auxelm != NULL; auxelm = node_next_get(auxelm)) { expect_ptr_eq(node_next_get(node_prev_get(auxelm)), auxelm, "auxelm's prev doesn't link to auxelm"); node_print(auxelm, 0); } label_return: malloc_printf("^^^ heap %p ^^^\n", heap); } static unsigned node_validate(const node_t *node, const node_t *parent) { unsigned nnodes = 1; node_t *leftmost_child, *sibling; if (parent != NULL) { expect_d_ge(node_cmp_magic(node, parent), 0, "Child is less than parent"); } leftmost_child = node_lchild_get(node); if (leftmost_child == NULL) { return nnodes; } expect_ptr_eq(node_prev_get(leftmost_child), (void *)node, "Leftmost child does not link to node"); nnodes += node_validate(leftmost_child, node); for (sibling = node_next_get(leftmost_child); sibling != NULL; sibling = node_next_get(sibling)) { expect_ptr_eq(node_next_get(node_prev_get(sibling)), sibling, "sibling's prev doesn't link to sibling"); nnodes += node_validate(sibling, node); } return nnodes; } static unsigned heap_validate(const heap_t *heap) { unsigned nnodes = 0; node_t *auxelm; if (heap->ph.root == NULL) { goto label_return; } nnodes += node_validate(heap->ph.root, NULL); for (auxelm = node_next_get(heap->ph.root); auxelm != NULL; auxelm = node_next_get(auxelm)) { expect_ptr_eq(node_next_get(node_prev_get(auxelm)), auxelm, "auxelm's prev doesn't link to auxelm"); nnodes += node_validate(auxelm, NULL); } label_return: if (false) { heap_print(heap); } return nnodes; } TEST_BEGIN(test_ph_empty) { heap_t heap; heap_new(&heap); expect_true(heap_empty(&heap), "Heap should be empty"); expect_ptr_null(heap_first(&heap), "Unexpected node"); expect_ptr_null(heap_any(&heap), "Unexpected node"); } TEST_END static void node_remove(heap_t *heap, node_t *node) { heap_remove(heap, node); node->magic = 0; } static node_t * node_remove_first(heap_t *heap) { node_t *node = heap_remove_first(heap); node->magic = 0; return node; } static node_t * node_remove_any(heap_t *heap) { node_t *node = heap_remove_any(heap); node->magic = 0; return node; } TEST_BEGIN(test_ph_random) { #define NNODES 25 #define NBAGS 250 #define SEED 42 sfmt_t *sfmt; uint64_t bag[NNODES]; heap_t heap; node_t nodes[NNODES]; unsigned i, j, k; sfmt = init_gen_rand(SEED); for (i = 0; i < NBAGS; i++) { switch (i) { case 0: /* Insert in order. */ for (j = 0; j < NNODES; j++) { bag[j] = j; } break; case 1: /* Insert in reverse order. */ for (j = 0; j < NNODES; j++) { bag[j] = NNODES - j - 1; } break; default: for (j = 0; j < NNODES; j++) { bag[j] = gen_rand64_range(sfmt, NNODES); } } for (j = 1; j <= NNODES; j++) { /* Initialize heap and nodes. */ heap_new(&heap); expect_u_eq(heap_validate(&heap), 0, "Incorrect node count"); for (k = 0; k < j; k++) { nodes[k].magic = NODE_MAGIC; nodes[k].key = bag[k]; } /* Insert nodes. */ for (k = 0; k < j; k++) { heap_insert(&heap, &nodes[k]); if (i % 13 == 12) { expect_ptr_not_null(heap_any(&heap), "Heap should not be empty"); /* Trigger merging. */ expect_ptr_not_null(heap_first(&heap), "Heap should not be empty"); } expect_u_eq(heap_validate(&heap), k + 1, "Incorrect node count"); } expect_false(heap_empty(&heap), "Heap should not be empty"); /* Remove nodes. */ switch (i % 6) { case 0: for (k = 0; k < j; k++) { expect_u_eq(heap_validate(&heap), j - k, "Incorrect node count"); node_remove(&heap, &nodes[k]); expect_u_eq(heap_validate(&heap), j - k - 1, "Incorrect node count"); } break; case 1: for (k = j; k > 0; k--) { node_remove(&heap, &nodes[k-1]); expect_u_eq(heap_validate(&heap), k - 1, "Incorrect node count"); } break; case 2: { node_t *prev = NULL; for (k = 0; k < j; k++) { node_t *node = node_remove_first(&heap); expect_u_eq(heap_validate(&heap), j - k - 1, "Incorrect node count"); if (prev != NULL) { expect_d_ge(node_cmp(node, prev), 0, "Bad removal order"); } prev = node; } break; } case 3: { node_t *prev = NULL; for (k = 0; k < j; k++) { node_t *node = heap_first(&heap); expect_u_eq(heap_validate(&heap), j - k, "Incorrect node count"); if (prev != NULL) { expect_d_ge(node_cmp(node, prev), 0, "Bad removal order"); } node_remove(&heap, node); expect_u_eq(heap_validate(&heap), j - k - 1, "Incorrect node count"); prev = node; } break; } case 4: { for (k = 0; k < j; k++) { node_remove_any(&heap); expect_u_eq(heap_validate(&heap), j - k - 1, "Incorrect node count"); } break; } case 5: { for (k = 0; k < j; k++) { node_t *node = heap_any(&heap); expect_u_eq(heap_validate(&heap), j - k, "Incorrect node count"); node_remove(&heap, node); expect_u_eq(heap_validate(&heap), j - k - 1, "Incorrect node count"); } break; } default: not_reached(); } expect_ptr_null(heap_first(&heap), "Heap should be empty"); expect_ptr_null(heap_any(&heap), "Heap should be empty"); expect_true(heap_empty(&heap), "Heap should be empty"); } } fini_gen_rand(sfmt); #undef NNODES #undef SEED } TEST_END int main(void) { return test( test_ph_empty, test_ph_random); } redis-8.0.2/deps/jemalloc/test/unit/prng.c000066400000000000000000000107041501533116600204230ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_prng_lg_range_u32) { uint32_t sa, sb; uint32_t ra, rb; unsigned lg_range; sa = 42; ra = prng_lg_range_u32(&sa, 32); sa = 42; rb = prng_lg_range_u32(&sa, 32); expect_u32_eq(ra, rb, "Repeated generation should produce repeated results"); sb = 42; rb = prng_lg_range_u32(&sb, 32); expect_u32_eq(ra, rb, "Equivalent generation should produce equivalent results"); sa = 42; ra = prng_lg_range_u32(&sa, 32); rb = prng_lg_range_u32(&sa, 32); expect_u32_ne(ra, rb, "Full-width results must not immediately repeat"); sa = 42; ra = prng_lg_range_u32(&sa, 32); for (lg_range = 31; lg_range > 0; lg_range--) { sb = 42; rb = prng_lg_range_u32(&sb, lg_range); expect_u32_eq((rb & (UINT32_C(0xffffffff) << lg_range)), 0, "High order bits should be 0, lg_range=%u", lg_range); expect_u32_eq(rb, (ra >> (32 - lg_range)), "Expected high order bits of full-width result, " "lg_range=%u", lg_range); } } TEST_END TEST_BEGIN(test_prng_lg_range_u64) { uint64_t sa, sb, ra, rb; unsigned lg_range; sa = 42; ra = prng_lg_range_u64(&sa, 64); sa = 42; rb = prng_lg_range_u64(&sa, 64); expect_u64_eq(ra, rb, "Repeated generation should produce repeated results"); sb = 42; rb = prng_lg_range_u64(&sb, 64); expect_u64_eq(ra, rb, "Equivalent generation should produce equivalent results"); sa = 42; ra = prng_lg_range_u64(&sa, 64); rb = prng_lg_range_u64(&sa, 64); expect_u64_ne(ra, rb, "Full-width results must not immediately repeat"); sa = 42; ra = prng_lg_range_u64(&sa, 64); for (lg_range = 63; lg_range > 0; lg_range--) { sb = 42; rb = prng_lg_range_u64(&sb, lg_range); expect_u64_eq((rb & (UINT64_C(0xffffffffffffffff) << lg_range)), 0, "High order bits should be 0, lg_range=%u", lg_range); expect_u64_eq(rb, (ra >> (64 - lg_range)), "Expected high order bits of full-width result, " "lg_range=%u", lg_range); } } TEST_END TEST_BEGIN(test_prng_lg_range_zu) { size_t sa, sb; size_t ra, rb; unsigned lg_range; sa = 42; ra = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR)); sa = 42; rb = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR)); expect_zu_eq(ra, rb, "Repeated generation should produce repeated results"); sb = 42; rb = prng_lg_range_zu(&sb, ZU(1) << (3 + LG_SIZEOF_PTR)); expect_zu_eq(ra, rb, "Equivalent generation should produce equivalent results"); sa = 42; ra = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR)); rb = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR)); expect_zu_ne(ra, rb, "Full-width results must not immediately repeat"); sa = 42; ra = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR)); for (lg_range = (ZU(1) << (3 + LG_SIZEOF_PTR)) - 1; lg_range > 0; lg_range--) { sb = 42; rb = prng_lg_range_zu(&sb, lg_range); expect_zu_eq((rb & (SIZE_T_MAX << lg_range)), 0, "High order bits should be 0, lg_range=%u", lg_range); expect_zu_eq(rb, (ra >> ((ZU(1) << (3 + LG_SIZEOF_PTR)) - lg_range)), "Expected high order bits of full-width " "result, lg_range=%u", lg_range); } } TEST_END TEST_BEGIN(test_prng_range_u32) { uint32_t range; const uint32_t max_range = 10000000; const uint32_t range_step = 97; const unsigned nreps = 10; for (range = 2; range < max_range; range += range_step) { uint32_t s; unsigned rep; s = range; for (rep = 0; rep < nreps; rep++) { uint32_t r = prng_range_u32(&s, range); expect_u32_lt(r, range, "Out of range"); } } } TEST_END TEST_BEGIN(test_prng_range_u64) { uint64_t range; const uint64_t max_range = 10000000; const uint64_t range_step = 97; const unsigned nreps = 10; for (range = 2; range < max_range; range += range_step) { uint64_t s; unsigned rep; s = range; for (rep = 0; rep < nreps; rep++) { uint64_t r = prng_range_u64(&s, range); expect_u64_lt(r, range, "Out of range"); } } } TEST_END TEST_BEGIN(test_prng_range_zu) { size_t range; const size_t max_range = 10000000; const size_t range_step = 97; const unsigned nreps = 10; for (range = 2; range < max_range; range += range_step) { size_t s; unsigned rep; s = range; for (rep = 0; rep < nreps; rep++) { size_t r = prng_range_zu(&s, range); expect_zu_lt(r, range, "Out of range"); } } } TEST_END int main(void) { return test_no_reentrancy( test_prng_lg_range_u32, test_prng_lg_range_u64, test_prng_lg_range_zu, test_prng_range_u32, test_prng_range_u64, test_prng_range_zu); } redis-8.0.2/deps/jemalloc/test/unit/prof_accum.c000066400000000000000000000035411501533116600215740ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_sys.h" #define NTHREADS 4 #define NALLOCS_PER_THREAD 50 #define DUMP_INTERVAL 1 #define BT_COUNT_CHECK_INTERVAL 5 static int prof_dump_open_file_intercept(const char *filename, int mode) { int fd; fd = open("/dev/null", O_WRONLY); assert_d_ne(fd, -1, "Unexpected open() failure"); return fd; } static void * alloc_from_permuted_backtrace(unsigned thd_ind, unsigned iteration) { return btalloc(1, thd_ind*NALLOCS_PER_THREAD + iteration); } static void * thd_start(void *varg) { unsigned thd_ind = *(unsigned *)varg; size_t bt_count_prev, bt_count; unsigned i_prev, i; i_prev = 0; bt_count_prev = 0; for (i = 0; i < NALLOCS_PER_THREAD; i++) { void *p = alloc_from_permuted_backtrace(thd_ind, i); dallocx(p, 0); if (i % DUMP_INTERVAL == 0) { expect_d_eq(mallctl("prof.dump", NULL, NULL, NULL, 0), 0, "Unexpected error while dumping heap profile"); } if (i % BT_COUNT_CHECK_INTERVAL == 0 || i+1 == NALLOCS_PER_THREAD) { bt_count = prof_bt_count(); expect_zu_le(bt_count_prev+(i-i_prev), bt_count, "Expected larger backtrace count increase"); i_prev = i; bt_count_prev = bt_count; } } return NULL; } TEST_BEGIN(test_idump) { bool active; thd_t thds[NTHREADS]; unsigned thd_args[NTHREADS]; unsigned i; test_skip_if(!config_prof); active = true; expect_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active, sizeof(active)), 0, "Unexpected mallctl failure while activating profiling"); prof_dump_open_file = prof_dump_open_file_intercept; for (i = 0; i < NTHREADS; i++) { thd_args[i] = i; thd_create(&thds[i], thd_start, (void *)&thd_args[i]); } for (i = 0; i < NTHREADS; i++) { thd_join(thds[i], NULL); } } TEST_END int main(void) { return test_no_reentrancy( test_idump); } redis-8.0.2/deps/jemalloc/test/unit/prof_accum.sh000066400000000000000000000002111501533116600217530ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_accum:true,prof_active:false,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_active.c000066400000000000000000000070631501533116600217620ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_data.h" static void mallctl_bool_get(const char *name, bool expected, const char *func, int line) { bool old; size_t sz; sz = sizeof(old); expect_d_eq(mallctl(name, (void *)&old, &sz, NULL, 0), 0, "%s():%d: Unexpected mallctl failure reading %s", func, line, name); expect_b_eq(old, expected, "%s():%d: Unexpected %s value", func, line, name); } static void mallctl_bool_set(const char *name, bool old_expected, bool val_new, const char *func, int line) { bool old; size_t sz; sz = sizeof(old); expect_d_eq(mallctl(name, (void *)&old, &sz, (void *)&val_new, sizeof(val_new)), 0, "%s():%d: Unexpected mallctl failure reading/writing %s", func, line, name); expect_b_eq(old, old_expected, "%s():%d: Unexpected %s value", func, line, name); } static void mallctl_prof_active_get_impl(bool prof_active_old_expected, const char *func, int line) { mallctl_bool_get("prof.active", prof_active_old_expected, func, line); } #define mallctl_prof_active_get(a) \ mallctl_prof_active_get_impl(a, __func__, __LINE__) static void mallctl_prof_active_set_impl(bool prof_active_old_expected, bool prof_active_new, const char *func, int line) { mallctl_bool_set("prof.active", prof_active_old_expected, prof_active_new, func, line); } #define mallctl_prof_active_set(a, b) \ mallctl_prof_active_set_impl(a, b, __func__, __LINE__) static void mallctl_thread_prof_active_get_impl(bool thread_prof_active_old_expected, const char *func, int line) { mallctl_bool_get("thread.prof.active", thread_prof_active_old_expected, func, line); } #define mallctl_thread_prof_active_get(a) \ mallctl_thread_prof_active_get_impl(a, __func__, __LINE__) static void mallctl_thread_prof_active_set_impl(bool thread_prof_active_old_expected, bool thread_prof_active_new, const char *func, int line) { mallctl_bool_set("thread.prof.active", thread_prof_active_old_expected, thread_prof_active_new, func, line); } #define mallctl_thread_prof_active_set(a, b) \ mallctl_thread_prof_active_set_impl(a, b, __func__, __LINE__) static void prof_sampling_probe_impl(bool expect_sample, const char *func, int line) { void *p; size_t expected_backtraces = expect_sample ? 1 : 0; expect_zu_eq(prof_bt_count(), 0, "%s():%d: Expected 0 backtraces", func, line); p = mallocx(1, 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_zu_eq(prof_bt_count(), expected_backtraces, "%s():%d: Unexpected backtrace count", func, line); dallocx(p, 0); } #define prof_sampling_probe(a) \ prof_sampling_probe_impl(a, __func__, __LINE__) TEST_BEGIN(test_prof_active) { test_skip_if(!config_prof); mallctl_prof_active_get(true); mallctl_thread_prof_active_get(false); mallctl_prof_active_set(true, true); mallctl_thread_prof_active_set(false, false); /* prof.active, !thread.prof.active. */ prof_sampling_probe(false); mallctl_prof_active_set(true, false); mallctl_thread_prof_active_set(false, false); /* !prof.active, !thread.prof.active. */ prof_sampling_probe(false); mallctl_prof_active_set(false, false); mallctl_thread_prof_active_set(false, true); /* !prof.active, thread.prof.active. */ prof_sampling_probe(false); mallctl_prof_active_set(false, true); mallctl_thread_prof_active_set(true, true); /* prof.active, thread.prof.active. */ prof_sampling_probe(true); /* Restore settings. */ mallctl_prof_active_set(true, true); mallctl_thread_prof_active_set(true, false); } TEST_END int main(void) { return test_no_reentrancy( test_prof_active); } redis-8.0.2/deps/jemalloc/test/unit/prof_active.sh000066400000000000000000000002261501533116600221440ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,prof_thread_active_init:false,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_gdump.c000066400000000000000000000040171501533116600216170ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_sys.h" static bool did_prof_dump_open; static int prof_dump_open_file_intercept(const char *filename, int mode) { int fd; did_prof_dump_open = true; fd = open("/dev/null", O_WRONLY); assert_d_ne(fd, -1, "Unexpected open() failure"); return fd; } TEST_BEGIN(test_gdump) { test_skip_if(opt_hpa); bool active, gdump, gdump_old; void *p, *q, *r, *s; size_t sz; test_skip_if(!config_prof); active = true; expect_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active, sizeof(active)), 0, "Unexpected mallctl failure while activating profiling"); prof_dump_open_file = prof_dump_open_file_intercept; did_prof_dump_open = false; p = mallocx((1U << SC_LG_LARGE_MINCLASS), 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_true(did_prof_dump_open, "Expected a profile dump"); did_prof_dump_open = false; q = mallocx((1U << SC_LG_LARGE_MINCLASS), 0); expect_ptr_not_null(q, "Unexpected mallocx() failure"); expect_true(did_prof_dump_open, "Expected a profile dump"); gdump = false; sz = sizeof(gdump_old); expect_d_eq(mallctl("prof.gdump", (void *)&gdump_old, &sz, (void *)&gdump, sizeof(gdump)), 0, "Unexpected mallctl failure while disabling prof.gdump"); assert(gdump_old); did_prof_dump_open = false; r = mallocx((1U << SC_LG_LARGE_MINCLASS), 0); expect_ptr_not_null(q, "Unexpected mallocx() failure"); expect_false(did_prof_dump_open, "Unexpected profile dump"); gdump = true; sz = sizeof(gdump_old); expect_d_eq(mallctl("prof.gdump", (void *)&gdump_old, &sz, (void *)&gdump, sizeof(gdump)), 0, "Unexpected mallctl failure while enabling prof.gdump"); assert(!gdump_old); did_prof_dump_open = false; s = mallocx((1U << SC_LG_LARGE_MINCLASS), 0); expect_ptr_not_null(q, "Unexpected mallocx() failure"); expect_true(did_prof_dump_open, "Expected a profile dump"); dallocx(p, 0); dallocx(q, 0); dallocx(r, 0); dallocx(s, 0); } TEST_END int main(void) { return test_no_reentrancy( test_gdump); } redis-8.0.2/deps/jemalloc/test/unit/prof_gdump.sh000066400000000000000000000001711501533116600220040ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:false,prof_gdump:true" fi redis-8.0.2/deps/jemalloc/test/unit/prof_hook.c000066400000000000000000000115241501533116600214440ustar00rootroot00000000000000#include "test/jemalloc_test.h" const char *dump_filename = "/dev/null"; prof_backtrace_hook_t default_hook; bool mock_bt_hook_called = false; bool mock_dump_hook_called = false; void mock_bt_hook(void **vec, unsigned *len, unsigned max_len) { *len = max_len; for (unsigned i = 0; i < max_len; ++i) { vec[i] = (void *)((uintptr_t)i); } mock_bt_hook_called = true; } void mock_bt_augmenting_hook(void **vec, unsigned *len, unsigned max_len) { default_hook(vec, len, max_len); expect_u_gt(*len, 0, "Default backtrace hook returned empty backtrace"); expect_u_lt(*len, max_len, "Default backtrace hook returned too large backtrace"); /* Add a separator between default frames and augmented */ vec[*len] = (void *)0x030303030; (*len)++; /* Add more stack frames */ for (unsigned i = 0; i < 3; ++i) { if (*len == max_len) { break; } vec[*len] = (void *)((uintptr_t)i); (*len)++; } mock_bt_hook_called = true; } void mock_dump_hook(const char *filename) { mock_dump_hook_called = true; expect_str_eq(filename, dump_filename, "Incorrect file name passed to the dump hook"); } TEST_BEGIN(test_prof_backtrace_hook_replace) { test_skip_if(!config_prof); mock_bt_hook_called = false; void *p0 = mallocx(1, 0); assert_ptr_not_null(p0, "Failed to allocate"); expect_false(mock_bt_hook_called, "Called mock hook before it's set"); prof_backtrace_hook_t null_hook = NULL; expect_d_eq(mallctl("experimental.hooks.prof_backtrace", NULL, 0, (void *)&null_hook, sizeof(null_hook)), EINVAL, "Incorrectly allowed NULL backtrace hook"); size_t default_hook_sz = sizeof(prof_backtrace_hook_t); prof_backtrace_hook_t hook = &mock_bt_hook; expect_d_eq(mallctl("experimental.hooks.prof_backtrace", (void *)&default_hook, &default_hook_sz, (void *)&hook, sizeof(hook)), 0, "Unexpected mallctl failure setting hook"); void *p1 = mallocx(1, 0); assert_ptr_not_null(p1, "Failed to allocate"); expect_true(mock_bt_hook_called, "Didn't call mock hook"); prof_backtrace_hook_t current_hook; size_t current_hook_sz = sizeof(prof_backtrace_hook_t); expect_d_eq(mallctl("experimental.hooks.prof_backtrace", (void *)¤t_hook, ¤t_hook_sz, (void *)&default_hook, sizeof(default_hook)), 0, "Unexpected mallctl failure resetting hook to default"); expect_ptr_eq(current_hook, hook, "Hook returned by mallctl is not equal to mock hook"); dallocx(p1, 0); dallocx(p0, 0); } TEST_END TEST_BEGIN(test_prof_backtrace_hook_augment) { test_skip_if(!config_prof); mock_bt_hook_called = false; void *p0 = mallocx(1, 0); assert_ptr_not_null(p0, "Failed to allocate"); expect_false(mock_bt_hook_called, "Called mock hook before it's set"); size_t default_hook_sz = sizeof(prof_backtrace_hook_t); prof_backtrace_hook_t hook = &mock_bt_augmenting_hook; expect_d_eq(mallctl("experimental.hooks.prof_backtrace", (void *)&default_hook, &default_hook_sz, (void *)&hook, sizeof(hook)), 0, "Unexpected mallctl failure setting hook"); void *p1 = mallocx(1, 0); assert_ptr_not_null(p1, "Failed to allocate"); expect_true(mock_bt_hook_called, "Didn't call mock hook"); prof_backtrace_hook_t current_hook; size_t current_hook_sz = sizeof(prof_backtrace_hook_t); expect_d_eq(mallctl("experimental.hooks.prof_backtrace", (void *)¤t_hook, ¤t_hook_sz, (void *)&default_hook, sizeof(default_hook)), 0, "Unexpected mallctl failure resetting hook to default"); expect_ptr_eq(current_hook, hook, "Hook returned by mallctl is not equal to mock hook"); dallocx(p1, 0); dallocx(p0, 0); } TEST_END TEST_BEGIN(test_prof_dump_hook) { test_skip_if(!config_prof); mock_dump_hook_called = false; expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&dump_filename, sizeof(dump_filename)), 0, "Failed to dump heap profile"); expect_false(mock_dump_hook_called, "Called dump hook before it's set"); size_t default_hook_sz = sizeof(prof_dump_hook_t); prof_dump_hook_t hook = &mock_dump_hook; expect_d_eq(mallctl("experimental.hooks.prof_dump", (void *)&default_hook, &default_hook_sz, (void *)&hook, sizeof(hook)), 0, "Unexpected mallctl failure setting hook"); expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&dump_filename, sizeof(dump_filename)), 0, "Failed to dump heap profile"); expect_true(mock_dump_hook_called, "Didn't call mock hook"); prof_dump_hook_t current_hook; size_t current_hook_sz = sizeof(prof_dump_hook_t); expect_d_eq(mallctl("experimental.hooks.prof_dump", (void *)¤t_hook, ¤t_hook_sz, (void *)&default_hook, sizeof(default_hook)), 0, "Unexpected mallctl failure resetting hook to default"); expect_ptr_eq(current_hook, hook, "Hook returned by mallctl is not equal to mock hook"); } TEST_END int main(void) { return test( test_prof_backtrace_hook_replace, test_prof_backtrace_hook_augment, test_prof_dump_hook); } redis-8.0.2/deps/jemalloc/test/unit/prof_hook.sh000066400000000000000000000001711501533116600216300ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_idump.c000066400000000000000000000024401501533116600216170ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_sys.h" #define TEST_PREFIX "test_prefix" static bool did_prof_dump_open; static int prof_dump_open_file_intercept(const char *filename, int mode) { int fd; did_prof_dump_open = true; const char filename_prefix[] = TEST_PREFIX "."; expect_d_eq(strncmp(filename_prefix, filename, sizeof(filename_prefix) - 1), 0, "Dump file name should start with \"" TEST_PREFIX ".\""); fd = open("/dev/null", O_WRONLY); assert_d_ne(fd, -1, "Unexpected open() failure"); return fd; } TEST_BEGIN(test_idump) { bool active; void *p; const char *test_prefix = TEST_PREFIX; test_skip_if(!config_prof); active = true; expect_d_eq(mallctl("prof.prefix", NULL, NULL, (void *)&test_prefix, sizeof(test_prefix)), 0, "Unexpected mallctl failure while overwriting dump prefix"); expect_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active, sizeof(active)), 0, "Unexpected mallctl failure while activating profiling"); prof_dump_open_file = prof_dump_open_file_intercept; did_prof_dump_open = false; p = mallocx(1, 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); dallocx(p, 0); expect_true(did_prof_dump_open, "Expected a profile dump"); } TEST_END int main(void) { return test( test_idump); } redis-8.0.2/deps/jemalloc/test/unit/prof_idump.sh000066400000000000000000000003171501533116600220100ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="tcache:false" if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="${MALLOC_CONF},prof:true,prof_accum:true,prof_active:false,lg_prof_sample:0,lg_prof_interval:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_log.c000066400000000000000000000067441501533116600212750ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_log.h" #define N_PARAM 100 #define N_THREADS 10 static void expect_rep() { expect_b_eq(prof_log_rep_check(), false, "Rep check failed"); } static void expect_log_empty() { expect_zu_eq(prof_log_bt_count(), 0, "The log has backtraces; it isn't empty"); expect_zu_eq(prof_log_thr_count(), 0, "The log has threads; it isn't empty"); expect_zu_eq(prof_log_alloc_count(), 0, "The log has allocations; it isn't empty"); } void *buf[N_PARAM]; static void f() { int i; for (i = 0; i < N_PARAM; i++) { buf[i] = malloc(100); } for (i = 0; i < N_PARAM; i++) { free(buf[i]); } } TEST_BEGIN(test_prof_log_many_logs) { int i; test_skip_if(!config_prof); for (i = 0; i < N_PARAM; i++) { expect_b_eq(prof_log_is_logging(), false, "Logging shouldn't have started yet"); expect_d_eq(mallctl("prof.log_start", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure when starting logging"); expect_b_eq(prof_log_is_logging(), true, "Logging should be started by now"); expect_log_empty(); expect_rep(); f(); expect_zu_eq(prof_log_thr_count(), 1, "Wrong thread count"); expect_rep(); expect_b_eq(prof_log_is_logging(), true, "Logging should still be on"); expect_d_eq(mallctl("prof.log_stop", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure when stopping logging"); expect_b_eq(prof_log_is_logging(), false, "Logging should have turned off"); } } TEST_END thd_t thr_buf[N_THREADS]; static void *f_thread(void *unused) { int i; for (i = 0; i < N_PARAM; i++) { void *p = malloc(100); memset(p, 100, 1); free(p); } return NULL; } TEST_BEGIN(test_prof_log_many_threads) { test_skip_if(!config_prof); int i; expect_d_eq(mallctl("prof.log_start", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure when starting logging"); for (i = 0; i < N_THREADS; i++) { thd_create(&thr_buf[i], &f_thread, NULL); } for (i = 0; i < N_THREADS; i++) { thd_join(thr_buf[i], NULL); } expect_zu_eq(prof_log_thr_count(), N_THREADS, "Wrong number of thread entries"); expect_rep(); expect_d_eq(mallctl("prof.log_stop", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure when stopping logging"); } TEST_END static void f3() { void *p = malloc(100); free(p); } static void f1() { void *p = malloc(100); f3(); free(p); } static void f2() { void *p = malloc(100); free(p); } TEST_BEGIN(test_prof_log_many_traces) { test_skip_if(!config_prof); expect_d_eq(mallctl("prof.log_start", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure when starting logging"); int i; expect_rep(); expect_log_empty(); for (i = 0; i < N_PARAM; i++) { expect_rep(); f1(); expect_rep(); f2(); expect_rep(); f3(); expect_rep(); } /* * There should be 8 total backtraces: two for malloc/free in f1(), two * for malloc/free in f2(), two for malloc/free in f3(), and then two * for malloc/free in f1()'s call to f3(). However compiler * optimizations such as loop unrolling might generate more call sites. * So >= 8 traces are expected. */ expect_zu_ge(prof_log_bt_count(), 8, "Expect at least 8 backtraces given sample workload"); expect_d_eq(mallctl("prof.log_stop", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure when stopping logging"); } TEST_END int main(void) { if (config_prof) { prof_log_dummy_set(true); } return test_no_reentrancy( test_prof_log_many_logs, test_prof_log_many_traces, test_prof_log_many_threads); } redis-8.0.2/deps/jemalloc/test/unit/prof_log.sh000066400000000000000000000001701501533116600214500ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_mdump.c000066400000000000000000000141161501533116600216260ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_sys.h" static const char *test_filename = "test_filename"; static bool did_prof_dump_open; static int prof_dump_open_file_intercept(const char *filename, int mode) { int fd; did_prof_dump_open = true; /* * Stronger than a strcmp() - verifying that we internally directly use * the caller supplied char pointer. */ expect_ptr_eq(filename, test_filename, "Dump file name should be \"%s\"", test_filename); fd = open("/dev/null", O_WRONLY); assert_d_ne(fd, -1, "Unexpected open() failure"); return fd; } TEST_BEGIN(test_mdump_normal) { test_skip_if(!config_prof); prof_dump_open_file_t *open_file_orig = prof_dump_open_file; void *p = mallocx(1, 0); assert_ptr_not_null(p, "Unexpected mallocx() failure"); prof_dump_open_file = prof_dump_open_file_intercept; did_prof_dump_open = false; expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&test_filename, sizeof(test_filename)), 0, "Unexpected mallctl failure while dumping"); expect_true(did_prof_dump_open, "Expected a profile dump"); dallocx(p, 0); prof_dump_open_file = open_file_orig; } TEST_END static int prof_dump_open_file_error(const char *filename, int mode) { return -1; } /* * In the context of test_mdump_output_error, prof_dump_write_file_count is the * total number of times prof_dump_write_file_error() is expected to be called. * In the context of test_mdump_maps_error, prof_dump_write_file_count is the * total number of times prof_dump_write_file_error() is expected to be called * starting from the one that contains an 'M' (beginning the "MAPPED_LIBRARIES" * header). */ static int prof_dump_write_file_count; static ssize_t prof_dump_write_file_error(int fd, const void *s, size_t len) { --prof_dump_write_file_count; expect_d_ge(prof_dump_write_file_count, 0, "Write is called after error occurs"); if (prof_dump_write_file_count == 0) { return -1; } else { /* * Any non-negative number indicates success, and for * simplicity we just use 0. When prof_dump_write_file_count * is positive, it means that we haven't reached the write that * we want to fail; when prof_dump_write_file_count is * negative, it means that we've already violated the * expect_d_ge(prof_dump_write_file_count, 0) statement above, * but instead of aborting, we continue the rest of the test, * and we indicate that all the writes after the failed write * are successful. */ return 0; } } static void expect_write_failure(int count) { prof_dump_write_file_count = count; expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&test_filename, sizeof(test_filename)), EFAULT, "Dump should err"); expect_d_eq(prof_dump_write_file_count, 0, "Dumping stopped after a wrong number of writes"); } TEST_BEGIN(test_mdump_output_error) { test_skip_if(!config_prof); test_skip_if(!config_debug); prof_dump_open_file_t *open_file_orig = prof_dump_open_file; prof_dump_write_file_t *write_file_orig = prof_dump_write_file; prof_dump_write_file = prof_dump_write_file_error; void *p = mallocx(1, 0); assert_ptr_not_null(p, "Unexpected mallocx() failure"); /* * When opening the dump file fails, there shouldn't be any write, and * mallctl() should return failure. */ prof_dump_open_file = prof_dump_open_file_error; expect_write_failure(0); /* * When the n-th write fails, there shouldn't be any more write, and * mallctl() should return failure. */ prof_dump_open_file = prof_dump_open_file_intercept; expect_write_failure(1); /* First write fails. */ expect_write_failure(2); /* Second write fails. */ dallocx(p, 0); prof_dump_open_file = open_file_orig; prof_dump_write_file = write_file_orig; } TEST_END static int prof_dump_open_maps_error() { return -1; } static bool started_piping_maps_file; static ssize_t prof_dump_write_maps_file_error(int fd, const void *s, size_t len) { /* The main dump doesn't contain any capital 'M'. */ if (!started_piping_maps_file && strchr(s, 'M') != NULL) { started_piping_maps_file = true; } if (started_piping_maps_file) { return prof_dump_write_file_error(fd, s, len); } else { /* Return success when we haven't started piping maps. */ return 0; } } static void expect_maps_write_failure(int count) { int mfd = prof_dump_open_maps(); if (mfd == -1) { /* No need to continue if we just can't find the maps file. */ return; } close(mfd); started_piping_maps_file = false; expect_write_failure(count); expect_true(started_piping_maps_file, "Should start piping maps"); } TEST_BEGIN(test_mdump_maps_error) { test_skip_if(!config_prof); test_skip_if(!config_debug); prof_dump_open_file_t *open_file_orig = prof_dump_open_file; prof_dump_write_file_t *write_file_orig = prof_dump_write_file; prof_dump_open_maps_t *open_maps_orig = prof_dump_open_maps; prof_dump_open_file = prof_dump_open_file_intercept; prof_dump_write_file = prof_dump_write_maps_file_error; void *p = mallocx(1, 0); assert_ptr_not_null(p, "Unexpected mallocx() failure"); /* * When opening the maps file fails, there shouldn't be any maps write, * and mallctl() should return success. */ prof_dump_open_maps = prof_dump_open_maps_error; started_piping_maps_file = false; prof_dump_write_file_count = 0; expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&test_filename, sizeof(test_filename)), 0, "mallctl should not fail in case of maps file opening failure"); expect_false(started_piping_maps_file, "Shouldn't start piping maps"); expect_d_eq(prof_dump_write_file_count, 0, "Dumping stopped after a wrong number of writes"); /* * When the n-th maps write fails (given that we are able to find the * maps file), there shouldn't be any more maps write, and mallctl() * should return failure. */ prof_dump_open_maps = open_maps_orig; expect_maps_write_failure(1); /* First write fails. */ expect_maps_write_failure(2); /* Second write fails. */ dallocx(p, 0); prof_dump_open_file = open_file_orig; prof_dump_write_file = write_file_orig; } TEST_END int main(void) { return test( test_mdump_normal, test_mdump_output_error, test_mdump_maps_error); } redis-8.0.2/deps/jemalloc/test/unit/prof_mdump.sh000066400000000000000000000001501501533116600220070ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_recent.c000066400000000000000000000450141501533116600217650ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_recent.h" /* As specified in the shell script */ #define OPT_ALLOC_MAX 3 /* Invariant before and after every test (when config_prof is on) */ static void confirm_prof_setup() { /* Options */ assert_true(opt_prof, "opt_prof not on"); assert_true(opt_prof_active, "opt_prof_active not on"); assert_zd_eq(opt_prof_recent_alloc_max, OPT_ALLOC_MAX, "opt_prof_recent_alloc_max not set correctly"); /* Dynamics */ assert_true(prof_active_state, "prof_active not on"); assert_zd_eq(prof_recent_alloc_max_ctl_read(), OPT_ALLOC_MAX, "prof_recent_alloc_max not set correctly"); } TEST_BEGIN(test_confirm_setup) { test_skip_if(!config_prof); confirm_prof_setup(); } TEST_END TEST_BEGIN(test_prof_recent_off) { test_skip_if(config_prof); const ssize_t past_ref = 0, future_ref = 0; const size_t len_ref = sizeof(ssize_t); ssize_t past = past_ref, future = future_ref; size_t len = len_ref; #define ASSERT_SHOULD_FAIL(opt, a, b, c, d) do { \ assert_d_eq(mallctl("experimental.prof_recent." opt, a, b, c, \ d), ENOENT, "Should return ENOENT when config_prof is off");\ assert_zd_eq(past, past_ref, "output was touched"); \ assert_zu_eq(len, len_ref, "output length was touched"); \ assert_zd_eq(future, future_ref, "input was touched"); \ } while (0) ASSERT_SHOULD_FAIL("alloc_max", NULL, NULL, NULL, 0); ASSERT_SHOULD_FAIL("alloc_max", &past, &len, NULL, 0); ASSERT_SHOULD_FAIL("alloc_max", NULL, NULL, &future, len); ASSERT_SHOULD_FAIL("alloc_max", &past, &len, &future, len); #undef ASSERT_SHOULD_FAIL } TEST_END TEST_BEGIN(test_prof_recent_on) { test_skip_if(!config_prof); ssize_t past, future; size_t len = sizeof(ssize_t); confirm_prof_setup(); assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, NULL, 0), 0, "no-op mallctl should be allowed"); confirm_prof_setup(); assert_d_eq(mallctl("experimental.prof_recent.alloc_max", &past, &len, NULL, 0), 0, "Read error"); expect_zd_eq(past, OPT_ALLOC_MAX, "Wrong read result"); future = OPT_ALLOC_MAX + 1; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, len), 0, "Write error"); future = -1; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", &past, &len, &future, len), 0, "Read/write error"); expect_zd_eq(past, OPT_ALLOC_MAX + 1, "Wrong read result"); future = -2; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", &past, &len, &future, len), EINVAL, "Invalid write should return EINVAL"); expect_zd_eq(past, OPT_ALLOC_MAX + 1, "Output should not be touched given invalid write"); future = OPT_ALLOC_MAX; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", &past, &len, &future, len), 0, "Read/write error"); expect_zd_eq(past, -1, "Wrong read result"); future = OPT_ALLOC_MAX + 2; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", &past, &len, &future, len * 2), EINVAL, "Invalid write should return EINVAL"); expect_zd_eq(past, -1, "Output should not be touched given invalid write"); confirm_prof_setup(); } TEST_END /* Reproducible sequence of request sizes */ #define NTH_REQ_SIZE(n) ((n) * 97 + 101) static void confirm_malloc(void *p) { assert_ptr_not_null(p, "malloc failed unexpectedly"); edata_t *e = emap_edata_lookup(TSDN_NULL, &arena_emap_global, p); assert_ptr_not_null(e, "NULL edata for living pointer"); prof_recent_t *n = edata_prof_recent_alloc_get_no_lock_test(e); assert_ptr_not_null(n, "Record in edata should not be NULL"); expect_ptr_not_null(n->alloc_tctx, "alloc_tctx in record should not be NULL"); expect_ptr_eq(e, prof_recent_alloc_edata_get_no_lock_test(n), "edata pointer in record is not correct"); expect_ptr_null(n->dalloc_tctx, "dalloc_tctx in record should be NULL"); } static void confirm_record_size(prof_recent_t *n, unsigned kth) { expect_zu_eq(n->size, NTH_REQ_SIZE(kth), "Recorded allocation size is wrong"); } static void confirm_record_living(prof_recent_t *n) { expect_ptr_not_null(n->alloc_tctx, "alloc_tctx in record should not be NULL"); edata_t *edata = prof_recent_alloc_edata_get_no_lock_test(n); assert_ptr_not_null(edata, "Recorded edata should not be NULL for living pointer"); expect_ptr_eq(n, edata_prof_recent_alloc_get_no_lock_test(edata), "Record in edata is not correct"); expect_ptr_null(n->dalloc_tctx, "dalloc_tctx in record should be NULL"); } static void confirm_record_released(prof_recent_t *n) { expect_ptr_not_null(n->alloc_tctx, "alloc_tctx in record should not be NULL"); expect_ptr_null(prof_recent_alloc_edata_get_no_lock_test(n), "Recorded edata should be NULL for released pointer"); expect_ptr_not_null(n->dalloc_tctx, "dalloc_tctx in record should not be NULL for released pointer"); } TEST_BEGIN(test_prof_recent_alloc) { test_skip_if(!config_prof); bool b; unsigned i, c; size_t req_size; void *p; prof_recent_t *n; ssize_t future; confirm_prof_setup(); /* * First batch of 2 * OPT_ALLOC_MAX allocations. After the * (OPT_ALLOC_MAX - 1)'th allocation the recorded allocations should * always be the last OPT_ALLOC_MAX allocations coming from here. */ for (i = 0; i < 2 * OPT_ALLOC_MAX; ++i) { req_size = NTH_REQ_SIZE(i); p = malloc(req_size); confirm_malloc(p); if (i < OPT_ALLOC_MAX - 1) { assert_false(ql_empty(&prof_recent_alloc_list), "Empty recent allocation"); free(p); /* * The recorded allocations may still include some * other allocations before the test run started, * so keep allocating without checking anything. */ continue; } c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { ++c; confirm_record_size(n, i + c - OPT_ALLOC_MAX); if (c == OPT_ALLOC_MAX) { confirm_record_living(n); } else { confirm_record_released(n); } } assert_u_eq(c, OPT_ALLOC_MAX, "Incorrect total number of allocations"); free(p); } confirm_prof_setup(); b = false; assert_d_eq(mallctl("prof.active", NULL, NULL, &b, sizeof(bool)), 0, "mallctl for turning off prof_active failed"); /* * Second batch of OPT_ALLOC_MAX allocations. Since prof_active is * turned off, this batch shouldn't be recorded. */ for (; i < 3 * OPT_ALLOC_MAX; ++i) { req_size = NTH_REQ_SIZE(i); p = malloc(req_size); assert_ptr_not_null(p, "malloc failed unexpectedly"); c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { confirm_record_size(n, c + OPT_ALLOC_MAX); confirm_record_released(n); ++c; } assert_u_eq(c, OPT_ALLOC_MAX, "Incorrect total number of allocations"); free(p); } b = true; assert_d_eq(mallctl("prof.active", NULL, NULL, &b, sizeof(bool)), 0, "mallctl for turning on prof_active failed"); confirm_prof_setup(); /* * Third batch of OPT_ALLOC_MAX allocations. Since prof_active is * turned back on, they should be recorded, and in the list of recorded * allocations they should follow the first batch rather than the * second batch. */ for (; i < 4 * OPT_ALLOC_MAX; ++i) { req_size = NTH_REQ_SIZE(i); p = malloc(req_size); confirm_malloc(p); c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { ++c; confirm_record_size(n, /* Is the allocation from the third batch? */ i + c - OPT_ALLOC_MAX >= 3 * OPT_ALLOC_MAX ? /* If yes, then it's just recorded. */ i + c - OPT_ALLOC_MAX : /* * Otherwise, it should come from the first batch * instead of the second batch. */ i + c - 2 * OPT_ALLOC_MAX); if (c == OPT_ALLOC_MAX) { confirm_record_living(n); } else { confirm_record_released(n); } } assert_u_eq(c, OPT_ALLOC_MAX, "Incorrect total number of allocations"); free(p); } /* Increasing the limit shouldn't alter the list of records. */ future = OPT_ALLOC_MAX + 1; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); confirm_record_released(n); ++c; } assert_u_eq(c, OPT_ALLOC_MAX, "Incorrect total number of allocations"); /* * Decreasing the limit shouldn't alter the list of records as long as * the new limit is still no less than the length of the list. */ future = OPT_ALLOC_MAX; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); confirm_record_released(n); ++c; } assert_u_eq(c, OPT_ALLOC_MAX, "Incorrect total number of allocations"); /* * Decreasing the limit should shorten the list of records if the new * limit is less than the length of the list. */ future = OPT_ALLOC_MAX - 1; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { ++c; confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); confirm_record_released(n); } assert_u_eq(c, OPT_ALLOC_MAX - 1, "Incorrect total number of allocations"); /* Setting to unlimited shouldn't alter the list of records. */ future = -1; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); c = 0; ql_foreach(n, &prof_recent_alloc_list, link) { ++c; confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); confirm_record_released(n); } assert_u_eq(c, OPT_ALLOC_MAX - 1, "Incorrect total number of allocations"); /* Downshift to only one record. */ future = 1; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); assert_false(ql_empty(&prof_recent_alloc_list), "Recent list is empty"); n = ql_first(&prof_recent_alloc_list); confirm_record_size(n, 4 * OPT_ALLOC_MAX - 1); confirm_record_released(n); n = ql_next(&prof_recent_alloc_list, n, link); assert_ptr_null(n, "Recent list should only contain one record"); /* Completely turn off. */ future = 0; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); assert_true(ql_empty(&prof_recent_alloc_list), "Recent list should be empty"); /* Restore the settings. */ future = OPT_ALLOC_MAX; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); assert_true(ql_empty(&prof_recent_alloc_list), "Recent list should be empty"); confirm_prof_setup(); } TEST_END #undef NTH_REQ_SIZE #define DUMP_OUT_SIZE 4096 static char dump_out[DUMP_OUT_SIZE]; static size_t dump_out_len = 0; static void test_dump_write_cb(void *not_used, const char *str) { size_t len = strlen(str); assert(dump_out_len + len < DUMP_OUT_SIZE); memcpy(dump_out + dump_out_len, str, len + 1); dump_out_len += len; } static void call_dump() { static void *in[2] = {test_dump_write_cb, NULL}; dump_out_len = 0; assert_d_eq(mallctl("experimental.prof_recent.alloc_dump", NULL, NULL, in, sizeof(in)), 0, "Dump mallctl raised error"); } typedef struct { size_t size; size_t usize; bool released; } confirm_record_t; #define DUMP_ERROR "Dump output is wrong" static void confirm_record(const char *template, const confirm_record_t *records, const size_t n_records) { static const char *types[2] = {"alloc", "dalloc"}; static char buf[64]; /* * The template string would be in the form of: * "{...,\"recent_alloc\":[]}", * and dump_out would be in the form of: * "{...,\"recent_alloc\":[...]}". * Using "- 2" serves to cut right before the ending "]}". */ assert_d_eq(memcmp(dump_out, template, strlen(template) - 2), 0, DUMP_ERROR); assert_d_eq(memcmp(dump_out + strlen(dump_out) - 2, template + strlen(template) - 2, 2), 0, DUMP_ERROR); const char *start = dump_out + strlen(template) - 2; const char *end = dump_out + strlen(dump_out) - 2; const confirm_record_t *record; for (record = records; record < records + n_records; ++record) { #define ASSERT_CHAR(c) do { \ assert_true(start < end, DUMP_ERROR); \ assert_c_eq(*start++, c, DUMP_ERROR); \ } while (0) #define ASSERT_STR(s) do { \ const size_t len = strlen(s); \ assert_true(start + len <= end, DUMP_ERROR); \ assert_d_eq(memcmp(start, s, len), 0, DUMP_ERROR); \ start += len; \ } while (0) #define ASSERT_FORMATTED_STR(s, ...) do { \ malloc_snprintf(buf, sizeof(buf), s, __VA_ARGS__); \ ASSERT_STR(buf); \ } while (0) if (record != records) { ASSERT_CHAR(','); } ASSERT_CHAR('{'); ASSERT_STR("\"size\""); ASSERT_CHAR(':'); ASSERT_FORMATTED_STR("%zu", record->size); ASSERT_CHAR(','); ASSERT_STR("\"usize\""); ASSERT_CHAR(':'); ASSERT_FORMATTED_STR("%zu", record->usize); ASSERT_CHAR(','); ASSERT_STR("\"released\""); ASSERT_CHAR(':'); ASSERT_STR(record->released ? "true" : "false"); ASSERT_CHAR(','); const char **type = types; while (true) { ASSERT_FORMATTED_STR("\"%s_thread_uid\"", *type); ASSERT_CHAR(':'); while (isdigit(*start)) { ++start; } ASSERT_CHAR(','); if (opt_prof_sys_thread_name) { ASSERT_FORMATTED_STR("\"%s_thread_name\"", *type); ASSERT_CHAR(':'); ASSERT_CHAR('"'); while (*start != '"') { ++start; } ASSERT_CHAR('"'); ASSERT_CHAR(','); } ASSERT_FORMATTED_STR("\"%s_time\"", *type); ASSERT_CHAR(':'); while (isdigit(*start)) { ++start; } ASSERT_CHAR(','); ASSERT_FORMATTED_STR("\"%s_trace\"", *type); ASSERT_CHAR(':'); ASSERT_CHAR('['); while (isdigit(*start) || *start == 'x' || (*start >= 'a' && *start <= 'f') || *start == '\"' || *start == ',') { ++start; } ASSERT_CHAR(']'); if (strcmp(*type, "dalloc") == 0) { break; } assert(strcmp(*type, "alloc") == 0); if (!record->released) { break; } ASSERT_CHAR(','); ++type; } ASSERT_CHAR('}'); #undef ASSERT_FORMATTED_STR #undef ASSERT_STR #undef ASSERT_CHAR } assert_ptr_eq(record, records + n_records, DUMP_ERROR); assert_ptr_eq(start, end, DUMP_ERROR); } TEST_BEGIN(test_prof_recent_alloc_dump) { test_skip_if(!config_prof); confirm_prof_setup(); ssize_t future; void *p, *q; confirm_record_t records[2]; assert_zu_eq(lg_prof_sample, (size_t)0, "lg_prof_sample not set correctly"); future = 0; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); call_dump(); expect_str_eq(dump_out, "{\"sample_interval\":1," "\"recent_alloc_max\":0,\"recent_alloc\":[]}", DUMP_ERROR); future = 2; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); call_dump(); const char *template = "{\"sample_interval\":1," "\"recent_alloc_max\":2,\"recent_alloc\":[]}"; expect_str_eq(dump_out, template, DUMP_ERROR); p = malloc(7); call_dump(); records[0].size = 7; records[0].usize = sz_s2u(7); records[0].released = false; confirm_record(template, records, 1); q = mallocx(17, MALLOCX_ALIGN(128)); call_dump(); records[1].size = 17; records[1].usize = sz_sa2u(17, 128); records[1].released = false; confirm_record(template, records, 2); free(q); call_dump(); records[1].released = true; confirm_record(template, records, 2); free(p); call_dump(); records[0].released = true; confirm_record(template, records, 2); future = OPT_ALLOC_MAX; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); confirm_prof_setup(); } TEST_END #undef DUMP_ERROR #undef DUMP_OUT_SIZE #define N_THREADS 8 #define N_PTRS 512 #define N_CTLS 8 #define N_ITERS 2048 #define STRESS_ALLOC_MAX 4096 typedef struct { thd_t thd; size_t id; void *ptrs[N_PTRS]; size_t count; } thd_data_t; static thd_data_t thd_data[N_THREADS]; static ssize_t test_max; static void test_write_cb(void *cbopaque, const char *str) { sleep_ns(1000 * 1000); } static void * f_thread(void *arg) { const size_t thd_id = *(size_t *)arg; thd_data_t *data_p = thd_data + thd_id; assert(data_p->id == thd_id); data_p->count = 0; uint64_t rand = (uint64_t)thd_id; tsd_t *tsd = tsd_fetch(); assert(test_max > 1); ssize_t last_max = -1; for (int i = 0; i < N_ITERS; i++) { rand = prng_range_u64(&rand, N_PTRS + N_CTLS * 5); assert(data_p->count <= N_PTRS); if (rand < data_p->count) { assert(data_p->count > 0); if (rand != data_p->count - 1) { assert(data_p->count > 1); void *temp = data_p->ptrs[rand]; data_p->ptrs[rand] = data_p->ptrs[data_p->count - 1]; data_p->ptrs[data_p->count - 1] = temp; } free(data_p->ptrs[--data_p->count]); } else if (rand < N_PTRS) { assert(data_p->count < N_PTRS); data_p->ptrs[data_p->count++] = malloc(1); } else if (rand % 5 == 0) { prof_recent_alloc_dump(tsd, test_write_cb, NULL); } else if (rand % 5 == 1) { last_max = prof_recent_alloc_max_ctl_read(); } else if (rand % 5 == 2) { last_max = prof_recent_alloc_max_ctl_write(tsd, test_max * 2); } else if (rand % 5 == 3) { last_max = prof_recent_alloc_max_ctl_write(tsd, test_max); } else { assert(rand % 5 == 4); last_max = prof_recent_alloc_max_ctl_write(tsd, test_max / 2); } assert_zd_ge(last_max, -1, "Illegal last-N max"); } while (data_p->count > 0) { free(data_p->ptrs[--data_p->count]); } return NULL; } TEST_BEGIN(test_prof_recent_stress) { test_skip_if(!config_prof); confirm_prof_setup(); test_max = OPT_ALLOC_MAX; for (size_t i = 0; i < N_THREADS; i++) { thd_data_t *data_p = thd_data + i; data_p->id = i; thd_create(&data_p->thd, &f_thread, &data_p->id); } for (size_t i = 0; i < N_THREADS; i++) { thd_data_t *data_p = thd_data + i; thd_join(data_p->thd, NULL); } test_max = STRESS_ALLOC_MAX; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &test_max, sizeof(ssize_t)), 0, "Write error"); for (size_t i = 0; i < N_THREADS; i++) { thd_data_t *data_p = thd_data + i; data_p->id = i; thd_create(&data_p->thd, &f_thread, &data_p->id); } for (size_t i = 0; i < N_THREADS; i++) { thd_data_t *data_p = thd_data + i; thd_join(data_p->thd, NULL); } test_max = OPT_ALLOC_MAX; assert_d_eq(mallctl("experimental.prof_recent.alloc_max", NULL, NULL, &test_max, sizeof(ssize_t)), 0, "Write error"); confirm_prof_setup(); } TEST_END #undef STRESS_ALLOC_MAX #undef N_ITERS #undef N_PTRS #undef N_THREADS int main(void) { return test( test_confirm_setup, test_prof_recent_off, test_prof_recent_on, test_prof_recent_alloc, test_prof_recent_alloc_dump, test_prof_recent_stress); } redis-8.0.2/deps/jemalloc/test/unit/prof_recent.sh000066400000000000000000000002201501533116600221430ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0,prof_recent_alloc_max:3" fi redis-8.0.2/deps/jemalloc/test/unit/prof_reset.c000066400000000000000000000152201501533116600216230ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_data.h" #include "jemalloc/internal/prof_sys.h" static int prof_dump_open_file_intercept(const char *filename, int mode) { int fd; fd = open("/dev/null", O_WRONLY); assert_d_ne(fd, -1, "Unexpected open() failure"); return fd; } static void set_prof_active(bool active) { expect_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active, sizeof(active)), 0, "Unexpected mallctl failure"); } static size_t get_lg_prof_sample(void) { size_t ret; size_t sz = sizeof(size_t); expect_d_eq(mallctl("prof.lg_sample", (void *)&ret, &sz, NULL, 0), 0, "Unexpected mallctl failure while reading profiling sample rate"); return ret; } static void do_prof_reset(size_t lg_prof_sample_input) { expect_d_eq(mallctl("prof.reset", NULL, NULL, (void *)&lg_prof_sample_input, sizeof(size_t)), 0, "Unexpected mallctl failure while resetting profile data"); expect_zu_eq(lg_prof_sample_input, get_lg_prof_sample(), "Expected profile sample rate change"); } TEST_BEGIN(test_prof_reset_basic) { size_t lg_prof_sample_orig, lg_prof_sample_cur, lg_prof_sample_next; size_t sz; unsigned i; test_skip_if(!config_prof); sz = sizeof(size_t); expect_d_eq(mallctl("opt.lg_prof_sample", (void *)&lg_prof_sample_orig, &sz, NULL, 0), 0, "Unexpected mallctl failure while reading profiling sample rate"); expect_zu_eq(lg_prof_sample_orig, 0, "Unexpected profiling sample rate"); lg_prof_sample_cur = get_lg_prof_sample(); expect_zu_eq(lg_prof_sample_orig, lg_prof_sample_cur, "Unexpected disagreement between \"opt.lg_prof_sample\" and " "\"prof.lg_sample\""); /* Test simple resets. */ for (i = 0; i < 2; i++) { expect_d_eq(mallctl("prof.reset", NULL, NULL, NULL, 0), 0, "Unexpected mallctl failure while resetting profile data"); lg_prof_sample_cur = get_lg_prof_sample(); expect_zu_eq(lg_prof_sample_orig, lg_prof_sample_cur, "Unexpected profile sample rate change"); } /* Test resets with prof.lg_sample changes. */ lg_prof_sample_next = 1; for (i = 0; i < 2; i++) { do_prof_reset(lg_prof_sample_next); lg_prof_sample_cur = get_lg_prof_sample(); expect_zu_eq(lg_prof_sample_cur, lg_prof_sample_next, "Expected profile sample rate change"); lg_prof_sample_next = lg_prof_sample_orig; } /* Make sure the test code restored prof.lg_sample. */ lg_prof_sample_cur = get_lg_prof_sample(); expect_zu_eq(lg_prof_sample_orig, lg_prof_sample_cur, "Unexpected disagreement between \"opt.lg_prof_sample\" and " "\"prof.lg_sample\""); } TEST_END TEST_BEGIN(test_prof_reset_cleanup) { test_skip_if(!config_prof); set_prof_active(true); expect_zu_eq(prof_bt_count(), 0, "Expected 0 backtraces"); void *p = mallocx(1, 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_zu_eq(prof_bt_count(), 1, "Expected 1 backtrace"); prof_cnt_t cnt_all; prof_cnt_all(&cnt_all); expect_u64_eq(cnt_all.curobjs, 1, "Expected 1 allocation"); expect_d_eq(mallctl("prof.reset", NULL, NULL, NULL, 0), 0, "Unexpected error while resetting heap profile data"); prof_cnt_all(&cnt_all); expect_u64_eq(cnt_all.curobjs, 0, "Expected 0 allocations"); expect_zu_eq(prof_bt_count(), 1, "Expected 1 backtrace"); dallocx(p, 0); expect_zu_eq(prof_bt_count(), 0, "Expected 0 backtraces"); set_prof_active(false); } TEST_END #define NTHREADS 4 #define NALLOCS_PER_THREAD (1U << 13) #define OBJ_RING_BUF_COUNT 1531 #define RESET_INTERVAL (1U << 10) #define DUMP_INTERVAL 3677 static void * thd_start(void *varg) { unsigned thd_ind = *(unsigned *)varg; unsigned i; void *objs[OBJ_RING_BUF_COUNT]; memset(objs, 0, sizeof(objs)); for (i = 0; i < NALLOCS_PER_THREAD; i++) { if (i % RESET_INTERVAL == 0) { expect_d_eq(mallctl("prof.reset", NULL, NULL, NULL, 0), 0, "Unexpected error while resetting heap profile " "data"); } if (i % DUMP_INTERVAL == 0) { expect_d_eq(mallctl("prof.dump", NULL, NULL, NULL, 0), 0, "Unexpected error while dumping heap profile"); } { void **pp = &objs[i % OBJ_RING_BUF_COUNT]; if (*pp != NULL) { dallocx(*pp, 0); *pp = NULL; } *pp = btalloc(1, thd_ind*NALLOCS_PER_THREAD + i); expect_ptr_not_null(*pp, "Unexpected btalloc() failure"); } } /* Clean up any remaining objects. */ for (i = 0; i < OBJ_RING_BUF_COUNT; i++) { void **pp = &objs[i % OBJ_RING_BUF_COUNT]; if (*pp != NULL) { dallocx(*pp, 0); *pp = NULL; } } return NULL; } TEST_BEGIN(test_prof_reset) { size_t lg_prof_sample_orig; thd_t thds[NTHREADS]; unsigned thd_args[NTHREADS]; unsigned i; size_t bt_count, tdata_count; test_skip_if(!config_prof); bt_count = prof_bt_count(); expect_zu_eq(bt_count, 0, "Unexpected pre-existing tdata structures"); tdata_count = prof_tdata_count(); lg_prof_sample_orig = get_lg_prof_sample(); do_prof_reset(5); set_prof_active(true); for (i = 0; i < NTHREADS; i++) { thd_args[i] = i; thd_create(&thds[i], thd_start, (void *)&thd_args[i]); } for (i = 0; i < NTHREADS; i++) { thd_join(thds[i], NULL); } expect_zu_eq(prof_bt_count(), bt_count, "Unexpected bactrace count change"); expect_zu_eq(prof_tdata_count(), tdata_count, "Unexpected remaining tdata structures"); set_prof_active(false); do_prof_reset(lg_prof_sample_orig); } TEST_END #undef NTHREADS #undef NALLOCS_PER_THREAD #undef OBJ_RING_BUF_COUNT #undef RESET_INTERVAL #undef DUMP_INTERVAL /* Test sampling at the same allocation site across resets. */ #define NITER 10 TEST_BEGIN(test_xallocx) { size_t lg_prof_sample_orig; unsigned i; void *ptrs[NITER]; test_skip_if(!config_prof); lg_prof_sample_orig = get_lg_prof_sample(); set_prof_active(true); /* Reset profiling. */ do_prof_reset(0); for (i = 0; i < NITER; i++) { void *p; size_t sz, nsz; /* Reset profiling. */ do_prof_reset(0); /* Allocate small object (which will be promoted). */ p = ptrs[i] = mallocx(1, 0); expect_ptr_not_null(p, "Unexpected mallocx() failure"); /* Reset profiling. */ do_prof_reset(0); /* Perform successful xallocx(). */ sz = sallocx(p, 0); expect_zu_eq(xallocx(p, sz, 0, 0), sz, "Unexpected xallocx() failure"); /* Perform unsuccessful xallocx(). */ nsz = nallocx(sz+1, 0); expect_zu_eq(xallocx(p, nsz, 0, 0), sz, "Unexpected xallocx() success"); } for (i = 0; i < NITER; i++) { /* dallocx. */ dallocx(ptrs[i], 0); } set_prof_active(false); do_prof_reset(lg_prof_sample_orig); } TEST_END #undef NITER int main(void) { /* Intercept dumping prior to running any tests. */ prof_dump_open_file = prof_dump_open_file_intercept; return test_no_reentrancy( test_prof_reset_basic, test_prof_reset_cleanup, test_prof_reset, test_xallocx); } redis-8.0.2/deps/jemalloc/test/unit/prof_reset.sh000066400000000000000000000002211501533116600220060ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:false,lg_prof_sample:0,prof_recent_alloc_max:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_stats.c000066400000000000000000000103361501533116600216420ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define N_PTRS 3 static void test_combinations(szind_t ind, size_t sizes_array[N_PTRS], int flags_array[N_PTRS]) { #define MALLCTL_STR_LEN 64 assert(opt_prof && opt_prof_stats); char mallctl_live_str[MALLCTL_STR_LEN]; char mallctl_accum_str[MALLCTL_STR_LEN]; if (ind < SC_NBINS) { malloc_snprintf(mallctl_live_str, MALLCTL_STR_LEN, "prof.stats.bins.%u.live", (unsigned)ind); malloc_snprintf(mallctl_accum_str, MALLCTL_STR_LEN, "prof.stats.bins.%u.accum", (unsigned)ind); } else { malloc_snprintf(mallctl_live_str, MALLCTL_STR_LEN, "prof.stats.lextents.%u.live", (unsigned)(ind - SC_NBINS)); malloc_snprintf(mallctl_accum_str, MALLCTL_STR_LEN, "prof.stats.lextents.%u.accum", (unsigned)(ind - SC_NBINS)); } size_t stats_len = 2 * sizeof(uint64_t); uint64_t live_stats_orig[2]; assert_d_eq(mallctl(mallctl_live_str, &live_stats_orig, &stats_len, NULL, 0), 0, ""); uint64_t accum_stats_orig[2]; assert_d_eq(mallctl(mallctl_accum_str, &accum_stats_orig, &stats_len, NULL, 0), 0, ""); void *ptrs[N_PTRS]; uint64_t live_req_sum = 0; uint64_t live_count = 0; uint64_t accum_req_sum = 0; uint64_t accum_count = 0; for (size_t i = 0; i < N_PTRS; ++i) { size_t sz = sizes_array[i]; int flags = flags_array[i]; void *p = mallocx(sz, flags); assert_ptr_not_null(p, "malloc() failed"); assert(TEST_MALLOC_SIZE(p) == sz_index2size(ind)); ptrs[i] = p; live_req_sum += sz; live_count++; accum_req_sum += sz; accum_count++; uint64_t live_stats[2]; assert_d_eq(mallctl(mallctl_live_str, &live_stats, &stats_len, NULL, 0), 0, ""); expect_u64_eq(live_stats[0] - live_stats_orig[0], live_req_sum, ""); expect_u64_eq(live_stats[1] - live_stats_orig[1], live_count, ""); uint64_t accum_stats[2]; assert_d_eq(mallctl(mallctl_accum_str, &accum_stats, &stats_len, NULL, 0), 0, ""); expect_u64_eq(accum_stats[0] - accum_stats_orig[0], accum_req_sum, ""); expect_u64_eq(accum_stats[1] - accum_stats_orig[1], accum_count, ""); } for (size_t i = 0; i < N_PTRS; ++i) { size_t sz = sizes_array[i]; int flags = flags_array[i]; sdallocx(ptrs[i], sz, flags); live_req_sum -= sz; live_count--; uint64_t live_stats[2]; assert_d_eq(mallctl(mallctl_live_str, &live_stats, &stats_len, NULL, 0), 0, ""); expect_u64_eq(live_stats[0] - live_stats_orig[0], live_req_sum, ""); expect_u64_eq(live_stats[1] - live_stats_orig[1], live_count, ""); uint64_t accum_stats[2]; assert_d_eq(mallctl(mallctl_accum_str, &accum_stats, &stats_len, NULL, 0), 0, ""); expect_u64_eq(accum_stats[0] - accum_stats_orig[0], accum_req_sum, ""); expect_u64_eq(accum_stats[1] - accum_stats_orig[1], accum_count, ""); } #undef MALLCTL_STR_LEN } static void test_szind_wrapper(szind_t ind) { size_t sizes_array[N_PTRS]; int flags_array[N_PTRS]; for (size_t i = 0, sz = sz_index2size(ind) - N_PTRS; i < N_PTRS; ++i, ++sz) { sizes_array[i] = sz; flags_array[i] = 0; } test_combinations(ind, sizes_array, flags_array); } TEST_BEGIN(test_prof_stats) { test_skip_if(!config_prof); test_szind_wrapper(0); test_szind_wrapper(1); test_szind_wrapper(2); test_szind_wrapper(SC_NBINS); test_szind_wrapper(SC_NBINS + 1); test_szind_wrapper(SC_NBINS + 2); } TEST_END static void test_szind_aligned_wrapper(szind_t ind, unsigned lg_align) { size_t sizes_array[N_PTRS]; int flags_array[N_PTRS]; int flags = MALLOCX_LG_ALIGN(lg_align); for (size_t i = 0, sz = sz_index2size(ind) - N_PTRS; i < N_PTRS; ++i, ++sz) { sizes_array[i] = sz; flags_array[i] = flags; } test_combinations( sz_size2index(sz_sa2u(sz_index2size(ind), 1 << lg_align)), sizes_array, flags_array); } TEST_BEGIN(test_prof_stats_aligned) { test_skip_if(!config_prof); for (szind_t ind = 0; ind < 10; ++ind) { for (unsigned lg_align = 0; lg_align < 10; ++lg_align) { test_szind_aligned_wrapper(ind, lg_align); } } for (szind_t ind = SC_NBINS - 5; ind < SC_NBINS + 5; ++ind) { for (unsigned lg_align = SC_LG_LARGE_MINCLASS - 5; lg_align < SC_LG_LARGE_MINCLASS + 5; ++lg_align) { test_szind_aligned_wrapper(ind, lg_align); } } } TEST_END int main(void) { return test( test_prof_stats, test_prof_stats_aligned); } redis-8.0.2/deps/jemalloc/test/unit/prof_stats.sh000066400000000000000000000002101501533116600220200ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0,prof_stats:true" fi redis-8.0.2/deps/jemalloc/test/unit/prof_sys_thread_name.c000066400000000000000000000044641501533116600236560ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_sys.h" static const char *test_thread_name = "test_name"; static int test_prof_sys_thread_name_read_error(char *buf, size_t limit) { return ENOSYS; } static int test_prof_sys_thread_name_read(char *buf, size_t limit) { assert(strlen(test_thread_name) < limit); strncpy(buf, test_thread_name, limit); return 0; } static int test_prof_sys_thread_name_read_clear(char *buf, size_t limit) { assert(limit > 0); buf[0] = '\0'; return 0; } TEST_BEGIN(test_prof_sys_thread_name) { test_skip_if(!config_prof); bool oldval; size_t sz = sizeof(oldval); assert_d_eq(mallctl("opt.prof_sys_thread_name", &oldval, &sz, NULL, 0), 0, "mallctl failed"); assert_true(oldval, "option was not set correctly"); const char *thread_name; sz = sizeof(thread_name); assert_d_eq(mallctl("thread.prof.name", &thread_name, &sz, NULL, 0), 0, "mallctl read for thread name should not fail"); expect_str_eq(thread_name, "", "Initial thread name should be empty"); thread_name = test_thread_name; assert_d_eq(mallctl("thread.prof.name", NULL, NULL, &thread_name, sz), ENOENT, "mallctl write for thread name should fail"); assert_ptr_eq(thread_name, test_thread_name, "Thread name should not be touched"); prof_sys_thread_name_read = test_prof_sys_thread_name_read_error; void *p = malloc(1); free(p); assert_d_eq(mallctl("thread.prof.name", &thread_name, &sz, NULL, 0), 0, "mallctl read for thread name should not fail"); assert_str_eq(thread_name, "", "Thread name should stay the same if the system call fails"); prof_sys_thread_name_read = test_prof_sys_thread_name_read; p = malloc(1); free(p); assert_d_eq(mallctl("thread.prof.name", &thread_name, &sz, NULL, 0), 0, "mallctl read for thread name should not fail"); assert_str_eq(thread_name, test_thread_name, "Thread name should be changed if the system call succeeds"); prof_sys_thread_name_read = test_prof_sys_thread_name_read_clear; p = malloc(1); free(p); assert_d_eq(mallctl("thread.prof.name", &thread_name, &sz, NULL, 0), 0, "mallctl read for thread name should not fail"); expect_str_eq(thread_name, "", "Thread name should be updated if the " "system call returns a different name"); } TEST_END int main(void) { return test( test_prof_sys_thread_name); } redis-8.0.2/deps/jemalloc/test/unit/prof_sys_thread_name.sh000066400000000000000000000002221501533116600240320ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0,prof_sys_thread_name:true" fi redis-8.0.2/deps/jemalloc/test/unit/prof_tctx.c000066400000000000000000000024331501533116600214650ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/prof_data.h" TEST_BEGIN(test_prof_realloc) { tsd_t *tsd; int flags; void *p, *q; prof_info_t prof_info_p, prof_info_q; prof_cnt_t cnt_0, cnt_1, cnt_2, cnt_3; test_skip_if(!config_prof); tsd = tsd_fetch(); flags = MALLOCX_TCACHE_NONE; prof_cnt_all(&cnt_0); p = mallocx(1024, flags); expect_ptr_not_null(p, "Unexpected mallocx() failure"); prof_info_get(tsd, p, NULL, &prof_info_p); expect_ptr_ne(prof_info_p.alloc_tctx, (prof_tctx_t *)(uintptr_t)1U, "Expected valid tctx"); prof_cnt_all(&cnt_1); expect_u64_eq(cnt_0.curobjs + 1, cnt_1.curobjs, "Allocation should have increased sample size"); q = rallocx(p, 2048, flags); expect_ptr_ne(p, q, "Expected move"); expect_ptr_not_null(p, "Unexpected rmallocx() failure"); prof_info_get(tsd, q, NULL, &prof_info_q); expect_ptr_ne(prof_info_q.alloc_tctx, (prof_tctx_t *)(uintptr_t)1U, "Expected valid tctx"); prof_cnt_all(&cnt_2); expect_u64_eq(cnt_1.curobjs, cnt_2.curobjs, "Reallocation should not have changed sample size"); dallocx(q, flags); prof_cnt_all(&cnt_3); expect_u64_eq(cnt_0.curobjs, cnt_3.curobjs, "Sample size should have returned to base level"); } TEST_END int main(void) { return test_no_reentrancy( test_prof_realloc); } redis-8.0.2/deps/jemalloc/test/unit/prof_tctx.sh000066400000000000000000000001701501533116600216510ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/prof_thread_name.c000066400000000000000000000063371501533116600227610ustar00rootroot00000000000000#include "test/jemalloc_test.h" static void mallctl_thread_name_get_impl(const char *thread_name_expected, const char *func, int line) { const char *thread_name_old; size_t sz; sz = sizeof(thread_name_old); expect_d_eq(mallctl("thread.prof.name", (void *)&thread_name_old, &sz, NULL, 0), 0, "%s():%d: Unexpected mallctl failure reading thread.prof.name", func, line); expect_str_eq(thread_name_old, thread_name_expected, "%s():%d: Unexpected thread.prof.name value", func, line); } #define mallctl_thread_name_get(a) \ mallctl_thread_name_get_impl(a, __func__, __LINE__) static void mallctl_thread_name_set_impl(const char *thread_name, const char *func, int line) { expect_d_eq(mallctl("thread.prof.name", NULL, NULL, (void *)&thread_name, sizeof(thread_name)), 0, "%s():%d: Unexpected mallctl failure writing thread.prof.name", func, line); mallctl_thread_name_get_impl(thread_name, func, line); } #define mallctl_thread_name_set(a) \ mallctl_thread_name_set_impl(a, __func__, __LINE__) TEST_BEGIN(test_prof_thread_name_validation) { const char *thread_name; test_skip_if(!config_prof); test_skip_if(opt_prof_sys_thread_name); mallctl_thread_name_get(""); mallctl_thread_name_set("hi there"); /* NULL input shouldn't be allowed. */ thread_name = NULL; expect_d_eq(mallctl("thread.prof.name", NULL, NULL, (void *)&thread_name, sizeof(thread_name)), EFAULT, "Unexpected mallctl result writing \"%s\" to thread.prof.name", thread_name); /* '\n' shouldn't be allowed. */ thread_name = "hi\nthere"; expect_d_eq(mallctl("thread.prof.name", NULL, NULL, (void *)&thread_name, sizeof(thread_name)), EFAULT, "Unexpected mallctl result writing \"%s\" to thread.prof.name", thread_name); /* Simultaneous read/write shouldn't be allowed. */ { const char *thread_name_old; size_t sz; sz = sizeof(thread_name_old); expect_d_eq(mallctl("thread.prof.name", (void *)&thread_name_old, &sz, (void *)&thread_name, sizeof(thread_name)), EPERM, "Unexpected mallctl result writing \"%s\" to " "thread.prof.name", thread_name); } mallctl_thread_name_set(""); } TEST_END #define NTHREADS 4 #define NRESET 25 static void * thd_start(void *varg) { unsigned thd_ind = *(unsigned *)varg; char thread_name[16] = ""; unsigned i; malloc_snprintf(thread_name, sizeof(thread_name), "thread %u", thd_ind); mallctl_thread_name_get(""); mallctl_thread_name_set(thread_name); for (i = 0; i < NRESET; i++) { expect_d_eq(mallctl("prof.reset", NULL, NULL, NULL, 0), 0, "Unexpected error while resetting heap profile data"); mallctl_thread_name_get(thread_name); } mallctl_thread_name_set(thread_name); mallctl_thread_name_set(""); return NULL; } TEST_BEGIN(test_prof_thread_name_threaded) { test_skip_if(!config_prof); test_skip_if(opt_prof_sys_thread_name); thd_t thds[NTHREADS]; unsigned thd_args[NTHREADS]; unsigned i; for (i = 0; i < NTHREADS; i++) { thd_args[i] = i; thd_create(&thds[i], thd_start, (void *)&thd_args[i]); } for (i = 0; i < NTHREADS; i++) { thd_join(thds[i], NULL); } } TEST_END #undef NTHREADS #undef NRESET int main(void) { return test( test_prof_thread_name_validation, test_prof_thread_name_threaded); } redis-8.0.2/deps/jemalloc/test/unit/prof_thread_name.sh000066400000000000000000000001501501533116600231340ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:false" fi redis-8.0.2/deps/jemalloc/test/unit/psset.c000066400000000000000000000540661501533116600206240ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/psset.h" #define PAGESLAB_ADDR ((void *)(1234 * HUGEPAGE)) #define PAGESLAB_AGE 5678 #define ALLOC_ARENA_IND 111 #define ALLOC_ESN 222 static void edata_init_test(edata_t *edata) { memset(edata, 0, sizeof(*edata)); edata_arena_ind_set(edata, ALLOC_ARENA_IND); edata_esn_set(edata, ALLOC_ESN); } static void test_psset_fake_purge(hpdata_t *ps) { hpdata_purge_state_t purge_state; hpdata_alloc_allowed_set(ps, false); hpdata_purge_begin(ps, &purge_state); void *addr; size_t size; while (hpdata_purge_next(ps, &purge_state, &addr, &size)) { } hpdata_purge_end(ps, &purge_state); hpdata_alloc_allowed_set(ps, true); } static void test_psset_alloc_new(psset_t *psset, hpdata_t *ps, edata_t *r_edata, size_t size) { hpdata_assert_empty(ps); test_psset_fake_purge(ps); psset_insert(psset, ps); psset_update_begin(psset, ps); void *addr = hpdata_reserve_alloc(ps, size); edata_init(r_edata, edata_arena_ind_get(r_edata), addr, size, /* slab */ false, SC_NSIZES, /* sn */ 0, extent_state_active, /* zeroed */ false, /* committed */ true, EXTENT_PAI_HPA, EXTENT_NOT_HEAD); edata_ps_set(r_edata, ps); psset_update_end(psset, ps); } static bool test_psset_alloc_reuse(psset_t *psset, edata_t *r_edata, size_t size) { hpdata_t *ps = psset_pick_alloc(psset, size); if (ps == NULL) { return true; } psset_update_begin(psset, ps); void *addr = hpdata_reserve_alloc(ps, size); edata_init(r_edata, edata_arena_ind_get(r_edata), addr, size, /* slab */ false, SC_NSIZES, /* sn */ 0, extent_state_active, /* zeroed */ false, /* committed */ true, EXTENT_PAI_HPA, EXTENT_NOT_HEAD); edata_ps_set(r_edata, ps); psset_update_end(psset, ps); return false; } static hpdata_t * test_psset_dalloc(psset_t *psset, edata_t *edata) { hpdata_t *ps = edata_ps_get(edata); psset_update_begin(psset, ps); hpdata_unreserve(ps, edata_addr_get(edata), edata_size_get(edata)); psset_update_end(psset, ps); if (hpdata_empty(ps)) { psset_remove(psset, ps); return ps; } else { return NULL; } } static void edata_expect(edata_t *edata, size_t page_offset, size_t page_cnt) { /* * Note that allocations should get the arena ind of their home * arena, *not* the arena ind of the pageslab allocator. */ expect_u_eq(ALLOC_ARENA_IND, edata_arena_ind_get(edata), "Arena ind changed"); expect_ptr_eq( (void *)((uintptr_t)PAGESLAB_ADDR + (page_offset << LG_PAGE)), edata_addr_get(edata), "Didn't allocate in order"); expect_zu_eq(page_cnt << LG_PAGE, edata_size_get(edata), ""); expect_false(edata_slab_get(edata), ""); expect_u_eq(SC_NSIZES, edata_szind_get_maybe_invalid(edata), ""); expect_u64_eq(0, edata_sn_get(edata), ""); expect_d_eq(edata_state_get(edata), extent_state_active, ""); expect_false(edata_zeroed_get(edata), ""); expect_true(edata_committed_get(edata), ""); expect_d_eq(EXTENT_PAI_HPA, edata_pai_get(edata), ""); expect_false(edata_is_head_get(edata), ""); } TEST_BEGIN(test_empty) { bool err; hpdata_t pageslab; hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE); edata_t alloc; edata_init_test(&alloc); psset_t psset; psset_init(&psset); /* Empty psset should return fail allocations. */ err = test_psset_alloc_reuse(&psset, &alloc, PAGE); expect_true(err, "Empty psset succeeded in an allocation."); } TEST_END TEST_BEGIN(test_fill) { bool err; hpdata_t pageslab; hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE); edata_t alloc[HUGEPAGE_PAGES]; psset_t psset; psset_init(&psset); edata_init_test(&alloc[0]); test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE); for (size_t i = 1; i < HUGEPAGE_PAGES; i++) { edata_init_test(&alloc[i]); err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE); expect_false(err, "Nonempty psset failed page allocation."); } for (size_t i = 0; i < HUGEPAGE_PAGES; i++) { edata_t *edata = &alloc[i]; edata_expect(edata, i, 1); } /* The pageslab, and thus psset, should now have no allocations. */ edata_t extra_alloc; edata_init_test(&extra_alloc); err = test_psset_alloc_reuse(&psset, &extra_alloc, PAGE); expect_true(err, "Alloc succeeded even though psset should be empty"); } TEST_END TEST_BEGIN(test_reuse) { bool err; hpdata_t *ps; hpdata_t pageslab; hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE); edata_t alloc[HUGEPAGE_PAGES]; psset_t psset; psset_init(&psset); edata_init_test(&alloc[0]); test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE); for (size_t i = 1; i < HUGEPAGE_PAGES; i++) { edata_init_test(&alloc[i]); err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE); expect_false(err, "Nonempty psset failed page allocation."); } /* Free odd indices. */ for (size_t i = 0; i < HUGEPAGE_PAGES; i ++) { if (i % 2 == 0) { continue; } ps = test_psset_dalloc(&psset, &alloc[i]); expect_ptr_null(ps, "Nonempty pageslab evicted"); } /* Realloc into them. */ for (size_t i = 0; i < HUGEPAGE_PAGES; i++) { if (i % 2 == 0) { continue; } err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE); expect_false(err, "Nonempty psset failed page allocation."); edata_expect(&alloc[i], i, 1); } /* Now, free the pages at indices 0 or 1 mod 2. */ for (size_t i = 0; i < HUGEPAGE_PAGES; i++) { if (i % 4 > 1) { continue; } ps = test_psset_dalloc(&psset, &alloc[i]); expect_ptr_null(ps, "Nonempty pageslab evicted"); } /* And realloc 2-page allocations into them. */ for (size_t i = 0; i < HUGEPAGE_PAGES; i++) { if (i % 4 != 0) { continue; } err = test_psset_alloc_reuse(&psset, &alloc[i], 2 * PAGE); expect_false(err, "Nonempty psset failed page allocation."); edata_expect(&alloc[i], i, 2); } /* Free all the 2-page allocations. */ for (size_t i = 0; i < HUGEPAGE_PAGES; i++) { if (i % 4 != 0) { continue; } ps = test_psset_dalloc(&psset, &alloc[i]); expect_ptr_null(ps, "Nonempty pageslab evicted"); } /* * Free up a 1-page hole next to a 2-page hole, but somewhere in the * middle of the pageslab. Index 11 should be right before such a hole * (since 12 % 4 == 0). */ size_t index_of_3 = 11; ps = test_psset_dalloc(&psset, &alloc[index_of_3]); expect_ptr_null(ps, "Nonempty pageslab evicted"); err = test_psset_alloc_reuse(&psset, &alloc[index_of_3], 3 * PAGE); expect_false(err, "Should have been able to find alloc."); edata_expect(&alloc[index_of_3], index_of_3, 3); /* * Free up a 4-page hole at the end. Recall that the pages at offsets 0 * and 1 mod 4 were freed above, so we just have to free the last * allocations. */ ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]); expect_ptr_null(ps, "Nonempty pageslab evicted"); ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 2]); expect_ptr_null(ps, "Nonempty pageslab evicted"); /* Make sure we can satisfy an allocation at the very end of a slab. */ size_t index_of_4 = HUGEPAGE_PAGES - 4; err = test_psset_alloc_reuse(&psset, &alloc[index_of_4], 4 * PAGE); expect_false(err, "Should have been able to find alloc."); edata_expect(&alloc[index_of_4], index_of_4, 4); } TEST_END TEST_BEGIN(test_evict) { bool err; hpdata_t *ps; hpdata_t pageslab; hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE); edata_t alloc[HUGEPAGE_PAGES]; psset_t psset; psset_init(&psset); /* Alloc the whole slab. */ edata_init_test(&alloc[0]); test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE); for (size_t i = 1; i < HUGEPAGE_PAGES; i++) { edata_init_test(&alloc[i]); err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE); expect_false(err, "Unxpected allocation failure"); } /* Dealloc the whole slab, going forwards. */ for (size_t i = 0; i < HUGEPAGE_PAGES - 1; i++) { ps = test_psset_dalloc(&psset, &alloc[i]); expect_ptr_null(ps, "Nonempty pageslab evicted"); } ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]); expect_ptr_eq(&pageslab, ps, "Empty pageslab not evicted."); err = test_psset_alloc_reuse(&psset, &alloc[0], PAGE); expect_true(err, "psset should be empty."); } TEST_END TEST_BEGIN(test_multi_pageslab) { bool err; hpdata_t *ps; hpdata_t pageslab[2]; hpdata_init(&pageslab[0], PAGESLAB_ADDR, PAGESLAB_AGE); hpdata_init(&pageslab[1], (void *)((uintptr_t)PAGESLAB_ADDR + HUGEPAGE), PAGESLAB_AGE + 1); edata_t alloc[2][HUGEPAGE_PAGES]; psset_t psset; psset_init(&psset); /* Insert both slabs. */ edata_init_test(&alloc[0][0]); test_psset_alloc_new(&psset, &pageslab[0], &alloc[0][0], PAGE); edata_init_test(&alloc[1][0]); test_psset_alloc_new(&psset, &pageslab[1], &alloc[1][0], PAGE); /* Fill them both up; make sure we do so in first-fit order. */ for (size_t i = 0; i < 2; i++) { for (size_t j = 1; j < HUGEPAGE_PAGES; j++) { edata_init_test(&alloc[i][j]); err = test_psset_alloc_reuse(&psset, &alloc[i][j], PAGE); expect_false(err, "Nonempty psset failed page allocation."); assert_ptr_eq(&pageslab[i], edata_ps_get(&alloc[i][j]), "Didn't pick pageslabs in first-fit"); } } /* * Free up a 2-page hole in the earlier slab, and a 1-page one in the * later one. We should still pick the later one. */ ps = test_psset_dalloc(&psset, &alloc[0][0]); expect_ptr_null(ps, "Unexpected eviction"); ps = test_psset_dalloc(&psset, &alloc[0][1]); expect_ptr_null(ps, "Unexpected eviction"); ps = test_psset_dalloc(&psset, &alloc[1][0]); expect_ptr_null(ps, "Unexpected eviction"); err = test_psset_alloc_reuse(&psset, &alloc[0][0], PAGE); expect_ptr_eq(&pageslab[1], edata_ps_get(&alloc[0][0]), "Should have picked the fuller pageslab"); /* * Now both slabs have 1-page holes. Free up a second one in the later * slab. */ ps = test_psset_dalloc(&psset, &alloc[1][1]); expect_ptr_null(ps, "Unexpected eviction"); /* * We should be able to allocate a 2-page object, even though an earlier * size class is nonempty. */ err = test_psset_alloc_reuse(&psset, &alloc[1][0], 2 * PAGE); expect_false(err, "Allocation should have succeeded"); } TEST_END static void stats_expect_empty(psset_bin_stats_t *stats) { assert_zu_eq(0, stats->npageslabs, "Supposedly empty bin had positive npageslabs"); expect_zu_eq(0, stats->nactive, "Unexpected nonempty bin" "Supposedly empty bin had positive nactive"); } static void stats_expect(psset_t *psset, size_t nactive) { if (nactive == HUGEPAGE_PAGES) { expect_zu_eq(1, psset->stats.full_slabs[0].npageslabs, "Expected a full slab"); expect_zu_eq(HUGEPAGE_PAGES, psset->stats.full_slabs[0].nactive, "Should have exactly filled the bin"); } else { stats_expect_empty(&psset->stats.full_slabs[0]); } size_t ninactive = HUGEPAGE_PAGES - nactive; pszind_t nonempty_pind = PSSET_NPSIZES; if (ninactive != 0 && ninactive < HUGEPAGE_PAGES) { nonempty_pind = sz_psz2ind(sz_psz_quantize_floor( ninactive << LG_PAGE)); } for (pszind_t i = 0; i < PSSET_NPSIZES; i++) { if (i == nonempty_pind) { assert_zu_eq(1, psset->stats.nonfull_slabs[i][0].npageslabs, "Should have found a slab"); expect_zu_eq(nactive, psset->stats.nonfull_slabs[i][0].nactive, "Mismatch in active pages"); } else { stats_expect_empty(&psset->stats.nonfull_slabs[i][0]); } } expect_zu_eq(nactive, psset_nactive(psset), ""); } TEST_BEGIN(test_stats) { bool err; hpdata_t pageslab; hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE); edata_t alloc[HUGEPAGE_PAGES]; psset_t psset; psset_init(&psset); stats_expect(&psset, 0); edata_init_test(&alloc[0]); test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE); for (size_t i = 1; i < HUGEPAGE_PAGES; i++) { stats_expect(&psset, i); edata_init_test(&alloc[i]); err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE); expect_false(err, "Nonempty psset failed page allocation."); } stats_expect(&psset, HUGEPAGE_PAGES); hpdata_t *ps; for (ssize_t i = HUGEPAGE_PAGES - 1; i >= 0; i--) { ps = test_psset_dalloc(&psset, &alloc[i]); expect_true((ps == NULL) == (i != 0), "test_psset_dalloc should only evict a slab on the last " "free"); stats_expect(&psset, i); } test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE); stats_expect(&psset, 1); psset_update_begin(&psset, &pageslab); stats_expect(&psset, 0); psset_update_end(&psset, &pageslab); stats_expect(&psset, 1); } TEST_END /* * Fills in and inserts two pageslabs, with the first better than the second, * and each fully allocated (into the allocations in allocs and worse_allocs, * each of which should be HUGEPAGE_PAGES long), except for a single free page * at the end. * * (There's nothing magic about these numbers; it's just useful to share the * setup between the oldest fit and the insert/remove test). */ static void init_test_pageslabs(psset_t *psset, hpdata_t *pageslab, hpdata_t *worse_pageslab, edata_t *alloc, edata_t *worse_alloc) { bool err; hpdata_init(pageslab, (void *)(10 * HUGEPAGE), PAGESLAB_AGE); /* * This pageslab would be better from an address-first-fit POV, but * worse from an age POV. */ hpdata_init(worse_pageslab, (void *)(9 * HUGEPAGE), PAGESLAB_AGE + 1); psset_init(psset); edata_init_test(&alloc[0]); test_psset_alloc_new(psset, pageslab, &alloc[0], PAGE); for (size_t i = 1; i < HUGEPAGE_PAGES; i++) { edata_init_test(&alloc[i]); err = test_psset_alloc_reuse(psset, &alloc[i], PAGE); expect_false(err, "Nonempty psset failed page allocation."); expect_ptr_eq(pageslab, edata_ps_get(&alloc[i]), "Allocated from the wrong pageslab"); } edata_init_test(&worse_alloc[0]); test_psset_alloc_new(psset, worse_pageslab, &worse_alloc[0], PAGE); expect_ptr_eq(worse_pageslab, edata_ps_get(&worse_alloc[0]), "Allocated from the wrong pageslab"); /* * Make the two pssets otherwise indistinguishable; all full except for * a single page. */ for (size_t i = 1; i < HUGEPAGE_PAGES - 1; i++) { edata_init_test(&worse_alloc[i]); err = test_psset_alloc_reuse(psset, &alloc[i], PAGE); expect_false(err, "Nonempty psset failed page allocation."); expect_ptr_eq(worse_pageslab, edata_ps_get(&alloc[i]), "Allocated from the wrong pageslab"); } /* Deallocate the last page from the older pageslab. */ hpdata_t *evicted = test_psset_dalloc(psset, &alloc[HUGEPAGE_PAGES - 1]); expect_ptr_null(evicted, "Unexpected eviction"); } TEST_BEGIN(test_oldest_fit) { bool err; edata_t alloc[HUGEPAGE_PAGES]; edata_t worse_alloc[HUGEPAGE_PAGES]; hpdata_t pageslab; hpdata_t worse_pageslab; psset_t psset; init_test_pageslabs(&psset, &pageslab, &worse_pageslab, alloc, worse_alloc); /* The edata should come from the better pageslab. */ edata_t test_edata; edata_init_test(&test_edata); err = test_psset_alloc_reuse(&psset, &test_edata, PAGE); expect_false(err, "Nonempty psset failed page allocation"); expect_ptr_eq(&pageslab, edata_ps_get(&test_edata), "Allocated from the wrong pageslab"); } TEST_END TEST_BEGIN(test_insert_remove) { bool err; hpdata_t *ps; edata_t alloc[HUGEPAGE_PAGES]; edata_t worse_alloc[HUGEPAGE_PAGES]; hpdata_t pageslab; hpdata_t worse_pageslab; psset_t psset; init_test_pageslabs(&psset, &pageslab, &worse_pageslab, alloc, worse_alloc); /* Remove better; should still be able to alloc from worse. */ psset_update_begin(&psset, &pageslab); err = test_psset_alloc_reuse(&psset, &worse_alloc[HUGEPAGE_PAGES - 1], PAGE); expect_false(err, "Removal should still leave an empty page"); expect_ptr_eq(&worse_pageslab, edata_ps_get(&worse_alloc[HUGEPAGE_PAGES - 1]), "Allocated out of wrong ps"); /* * After deallocating the previous alloc and reinserting better, it * should be preferred for future allocations. */ ps = test_psset_dalloc(&psset, &worse_alloc[HUGEPAGE_PAGES - 1]); expect_ptr_null(ps, "Incorrect eviction of nonempty pageslab"); psset_update_end(&psset, &pageslab); err = test_psset_alloc_reuse(&psset, &alloc[HUGEPAGE_PAGES - 1], PAGE); expect_false(err, "psset should be nonempty"); expect_ptr_eq(&pageslab, edata_ps_get(&alloc[HUGEPAGE_PAGES - 1]), "Removal/reinsertion shouldn't change ordering"); /* * After deallocating and removing both, allocations should fail. */ ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]); expect_ptr_null(ps, "Incorrect eviction"); psset_update_begin(&psset, &pageslab); psset_update_begin(&psset, &worse_pageslab); err = test_psset_alloc_reuse(&psset, &alloc[HUGEPAGE_PAGES - 1], PAGE); expect_true(err, "psset should be empty, but an alloc succeeded"); } TEST_END TEST_BEGIN(test_purge_prefers_nonhuge) { /* * All else being equal, we should prefer purging non-huge pages over * huge ones for non-empty extents. */ /* Nothing magic about this constant. */ enum { NHP = 23, }; hpdata_t *hpdata; psset_t psset; psset_init(&psset); hpdata_t hpdata_huge[NHP]; uintptr_t huge_begin = (uintptr_t)&hpdata_huge[0]; uintptr_t huge_end = (uintptr_t)&hpdata_huge[NHP]; hpdata_t hpdata_nonhuge[NHP]; uintptr_t nonhuge_begin = (uintptr_t)&hpdata_nonhuge[0]; uintptr_t nonhuge_end = (uintptr_t)&hpdata_nonhuge[NHP]; for (size_t i = 0; i < NHP; i++) { hpdata_init(&hpdata_huge[i], (void *)((10 + i) * HUGEPAGE), 123 + i); psset_insert(&psset, &hpdata_huge[i]); hpdata_init(&hpdata_nonhuge[i], (void *)((10 + NHP + i) * HUGEPAGE), 456 + i); psset_insert(&psset, &hpdata_nonhuge[i]); } for (int i = 0; i < 2 * NHP; i++) { hpdata = psset_pick_alloc(&psset, HUGEPAGE * 3 / 4); psset_update_begin(&psset, hpdata); void *ptr; ptr = hpdata_reserve_alloc(hpdata, HUGEPAGE * 3 / 4); /* Ignore the first alloc, which will stick around. */ (void)ptr; /* * The second alloc is to dirty the pages; free it immediately * after allocating. */ ptr = hpdata_reserve_alloc(hpdata, HUGEPAGE / 4); hpdata_unreserve(hpdata, ptr, HUGEPAGE / 4); if (huge_begin <= (uintptr_t)hpdata && (uintptr_t)hpdata < huge_end) { hpdata_hugify(hpdata); } hpdata_purge_allowed_set(hpdata, true); psset_update_end(&psset, hpdata); } /* * We've got a bunch of 1/8th dirty hpdatas. It should give us all the * non-huge ones to purge, then all the huge ones, then refuse to purge * further. */ for (int i = 0; i < NHP; i++) { hpdata = psset_pick_purge(&psset); assert_true(nonhuge_begin <= (uintptr_t)hpdata && (uintptr_t)hpdata < nonhuge_end, ""); psset_update_begin(&psset, hpdata); test_psset_fake_purge(hpdata); hpdata_purge_allowed_set(hpdata, false); psset_update_end(&psset, hpdata); } for (int i = 0; i < NHP; i++) { hpdata = psset_pick_purge(&psset); expect_true(huge_begin <= (uintptr_t)hpdata && (uintptr_t)hpdata < huge_end, ""); psset_update_begin(&psset, hpdata); hpdata_dehugify(hpdata); test_psset_fake_purge(hpdata); hpdata_purge_allowed_set(hpdata, false); psset_update_end(&psset, hpdata); } } TEST_END TEST_BEGIN(test_purge_prefers_empty) { void *ptr; psset_t psset; psset_init(&psset); hpdata_t hpdata_empty; hpdata_t hpdata_nonempty; hpdata_init(&hpdata_empty, (void *)(10 * HUGEPAGE), 123); psset_insert(&psset, &hpdata_empty); hpdata_init(&hpdata_nonempty, (void *)(11 * HUGEPAGE), 456); psset_insert(&psset, &hpdata_nonempty); psset_update_begin(&psset, &hpdata_empty); ptr = hpdata_reserve_alloc(&hpdata_empty, PAGE); expect_ptr_eq(hpdata_addr_get(&hpdata_empty), ptr, ""); hpdata_unreserve(&hpdata_empty, ptr, PAGE); hpdata_purge_allowed_set(&hpdata_empty, true); psset_update_end(&psset, &hpdata_empty); psset_update_begin(&psset, &hpdata_nonempty); ptr = hpdata_reserve_alloc(&hpdata_nonempty, 10 * PAGE); expect_ptr_eq(hpdata_addr_get(&hpdata_nonempty), ptr, ""); hpdata_unreserve(&hpdata_nonempty, ptr, 9 * PAGE); hpdata_purge_allowed_set(&hpdata_nonempty, true); psset_update_end(&psset, &hpdata_nonempty); /* * The nonempty slab has 9 dirty pages, while the empty one has only 1. * We should still pick the empty one for purging. */ hpdata_t *to_purge = psset_pick_purge(&psset); expect_ptr_eq(&hpdata_empty, to_purge, ""); } TEST_END TEST_BEGIN(test_purge_prefers_empty_huge) { void *ptr; psset_t psset; psset_init(&psset); enum {NHP = 10 }; hpdata_t hpdata_huge[NHP]; hpdata_t hpdata_nonhuge[NHP]; uintptr_t cur_addr = 100 * HUGEPAGE; uint64_t cur_age = 123; for (int i = 0; i < NHP; i++) { hpdata_init(&hpdata_huge[i], (void *)cur_addr, cur_age); cur_addr += HUGEPAGE; cur_age++; psset_insert(&psset, &hpdata_huge[i]); hpdata_init(&hpdata_nonhuge[i], (void *)cur_addr, cur_age); cur_addr += HUGEPAGE; cur_age++; psset_insert(&psset, &hpdata_nonhuge[i]); /* * Make the hpdata_huge[i] fully dirty, empty, purgable, and * huge. */ psset_update_begin(&psset, &hpdata_huge[i]); ptr = hpdata_reserve_alloc(&hpdata_huge[i], HUGEPAGE); expect_ptr_eq(hpdata_addr_get(&hpdata_huge[i]), ptr, ""); hpdata_hugify(&hpdata_huge[i]); hpdata_unreserve(&hpdata_huge[i], ptr, HUGEPAGE); hpdata_purge_allowed_set(&hpdata_huge[i], true); psset_update_end(&psset, &hpdata_huge[i]); /* * Make hpdata_nonhuge[i] fully dirty, empty, purgable, and * non-huge. */ psset_update_begin(&psset, &hpdata_nonhuge[i]); ptr = hpdata_reserve_alloc(&hpdata_nonhuge[i], HUGEPAGE); expect_ptr_eq(hpdata_addr_get(&hpdata_nonhuge[i]), ptr, ""); hpdata_unreserve(&hpdata_nonhuge[i], ptr, HUGEPAGE); hpdata_purge_allowed_set(&hpdata_nonhuge[i], true); psset_update_end(&psset, &hpdata_nonhuge[i]); } /* * We have a bunch of empty slabs, half huge, half nonhuge, inserted in * alternating order. We should pop all the huge ones before popping * any of the non-huge ones for purging. */ for (int i = 0; i < NHP; i++) { hpdata_t *to_purge = psset_pick_purge(&psset); expect_ptr_eq(&hpdata_huge[i], to_purge, ""); psset_update_begin(&psset, to_purge); hpdata_purge_allowed_set(to_purge, false); psset_update_end(&psset, to_purge); } for (int i = 0; i < NHP; i++) { hpdata_t *to_purge = psset_pick_purge(&psset); expect_ptr_eq(&hpdata_nonhuge[i], to_purge, ""); psset_update_begin(&psset, to_purge); hpdata_purge_allowed_set(to_purge, false); psset_update_end(&psset, to_purge); } } TEST_END int main(void) { return test_no_reentrancy( test_empty, test_fill, test_reuse, test_evict, test_multi_pageslab, test_stats, test_oldest_fit, test_insert_remove, test_purge_prefers_nonhuge, test_purge_prefers_empty, test_purge_prefers_empty_huge); } redis-8.0.2/deps/jemalloc/test/unit/ql.c000066400000000000000000000163541501533116600201000ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/ql.h" /* Number of ring entries, in [2..26]. */ #define NENTRIES 9 typedef struct list_s list_t; typedef ql_head(list_t) list_head_t; struct list_s { ql_elm(list_t) link; char id; }; static void test_empty_list(list_head_t *head) { list_t *t; unsigned i; expect_true(ql_empty(head), "Unexpected element for empty list"); expect_ptr_null(ql_first(head), "Unexpected element for empty list"); expect_ptr_null(ql_last(head, link), "Unexpected element for empty list"); i = 0; ql_foreach(t, head, link) { i++; } expect_u_eq(i, 0, "Unexpected element for empty list"); i = 0; ql_reverse_foreach(t, head, link) { i++; } expect_u_eq(i, 0, "Unexpected element for empty list"); } TEST_BEGIN(test_ql_empty) { list_head_t head; ql_new(&head); test_empty_list(&head); } TEST_END static void init_entries(list_t *entries, unsigned nentries) { unsigned i; for (i = 0; i < nentries; i++) { entries[i].id = 'a' + i; ql_elm_new(&entries[i], link); } } static void test_entries_list(list_head_t *head, list_t *entries, unsigned nentries) { list_t *t; unsigned i; expect_false(ql_empty(head), "List should not be empty"); expect_c_eq(ql_first(head)->id, entries[0].id, "Element id mismatch"); expect_c_eq(ql_last(head, link)->id, entries[nentries-1].id, "Element id mismatch"); i = 0; ql_foreach(t, head, link) { expect_c_eq(t->id, entries[i].id, "Element id mismatch"); i++; } i = 0; ql_reverse_foreach(t, head, link) { expect_c_eq(t->id, entries[nentries-i-1].id, "Element id mismatch"); i++; } for (i = 0; i < nentries-1; i++) { t = ql_next(head, &entries[i], link); expect_c_eq(t->id, entries[i+1].id, "Element id mismatch"); } expect_ptr_null(ql_next(head, &entries[nentries-1], link), "Unexpected element"); expect_ptr_null(ql_prev(head, &entries[0], link), "Unexpected element"); for (i = 1; i < nentries; i++) { t = ql_prev(head, &entries[i], link); expect_c_eq(t->id, entries[i-1].id, "Element id mismatch"); } } TEST_BEGIN(test_ql_tail_insert) { list_head_t head; list_t entries[NENTRIES]; unsigned i; ql_new(&head); init_entries(entries, sizeof(entries)/sizeof(list_t)); for (i = 0; i < NENTRIES; i++) { ql_tail_insert(&head, &entries[i], link); } test_entries_list(&head, entries, NENTRIES); } TEST_END TEST_BEGIN(test_ql_tail_remove) { list_head_t head; list_t entries[NENTRIES]; unsigned i; ql_new(&head); init_entries(entries, sizeof(entries)/sizeof(list_t)); for (i = 0; i < NENTRIES; i++) { ql_tail_insert(&head, &entries[i], link); } for (i = 0; i < NENTRIES; i++) { test_entries_list(&head, entries, NENTRIES-i); ql_tail_remove(&head, list_t, link); } test_empty_list(&head); } TEST_END TEST_BEGIN(test_ql_head_insert) { list_head_t head; list_t entries[NENTRIES]; unsigned i; ql_new(&head); init_entries(entries, sizeof(entries)/sizeof(list_t)); for (i = 0; i < NENTRIES; i++) { ql_head_insert(&head, &entries[NENTRIES-i-1], link); } test_entries_list(&head, entries, NENTRIES); } TEST_END TEST_BEGIN(test_ql_head_remove) { list_head_t head; list_t entries[NENTRIES]; unsigned i; ql_new(&head); init_entries(entries, sizeof(entries)/sizeof(list_t)); for (i = 0; i < NENTRIES; i++) { ql_head_insert(&head, &entries[NENTRIES-i-1], link); } for (i = 0; i < NENTRIES; i++) { test_entries_list(&head, &entries[i], NENTRIES-i); ql_head_remove(&head, list_t, link); } test_empty_list(&head); } TEST_END TEST_BEGIN(test_ql_insert) { list_head_t head; list_t entries[8]; list_t *a, *b, *c, *d, *e, *f, *g, *h; ql_new(&head); init_entries(entries, sizeof(entries)/sizeof(list_t)); a = &entries[0]; b = &entries[1]; c = &entries[2]; d = &entries[3]; e = &entries[4]; f = &entries[5]; g = &entries[6]; h = &entries[7]; /* * ql_remove(), ql_before_insert(), and ql_after_insert() are used * internally by other macros that are already tested, so there's no * need to test them completely. However, insertion/deletion from the * middle of lists is not otherwise tested; do so here. */ ql_tail_insert(&head, f, link); ql_before_insert(&head, f, b, link); ql_before_insert(&head, f, c, link); ql_after_insert(f, h, link); ql_after_insert(f, g, link); ql_before_insert(&head, b, a, link); ql_after_insert(c, d, link); ql_before_insert(&head, f, e, link); test_entries_list(&head, entries, sizeof(entries)/sizeof(list_t)); } TEST_END static void test_concat_split_entries(list_t *entries, unsigned nentries_a, unsigned nentries_b) { init_entries(entries, nentries_a + nentries_b); list_head_t head_a; ql_new(&head_a); for (unsigned i = 0; i < nentries_a; i++) { ql_tail_insert(&head_a, &entries[i], link); } if (nentries_a == 0) { test_empty_list(&head_a); } else { test_entries_list(&head_a, entries, nentries_a); } list_head_t head_b; ql_new(&head_b); for (unsigned i = 0; i < nentries_b; i++) { ql_tail_insert(&head_b, &entries[nentries_a + i], link); } if (nentries_b == 0) { test_empty_list(&head_b); } else { test_entries_list(&head_b, entries + nentries_a, nentries_b); } ql_concat(&head_a, &head_b, link); if (nentries_a + nentries_b == 0) { test_empty_list(&head_a); } else { test_entries_list(&head_a, entries, nentries_a + nentries_b); } test_empty_list(&head_b); if (nentries_b == 0) { return; } list_head_t head_c; ql_split(&head_a, &entries[nentries_a], &head_c, link); if (nentries_a == 0) { test_empty_list(&head_a); } else { test_entries_list(&head_a, entries, nentries_a); } test_entries_list(&head_c, entries + nentries_a, nentries_b); } TEST_BEGIN(test_ql_concat_split) { list_t entries[NENTRIES]; test_concat_split_entries(entries, 0, 0); test_concat_split_entries(entries, 0, 1); test_concat_split_entries(entries, 1, 0); test_concat_split_entries(entries, 0, NENTRIES); test_concat_split_entries(entries, 1, NENTRIES - 1); test_concat_split_entries(entries, NENTRIES / 2, NENTRIES - NENTRIES / 2); test_concat_split_entries(entries, NENTRIES - 1, 1); test_concat_split_entries(entries, NENTRIES, 0); } TEST_END TEST_BEGIN(test_ql_rotate) { list_head_t head; list_t entries[NENTRIES]; unsigned i; ql_new(&head); init_entries(entries, sizeof(entries)/sizeof(list_t)); for (i = 0; i < NENTRIES; i++) { ql_tail_insert(&head, &entries[i], link); } char head_id = ql_first(&head)->id; for (i = 0; i < NENTRIES; i++) { assert_c_eq(ql_first(&head)->id, head_id, ""); ql_rotate(&head, link); assert_c_eq(ql_last(&head, link)->id, head_id, ""); head_id++; } test_entries_list(&head, entries, NENTRIES); } TEST_END TEST_BEGIN(test_ql_move) { list_head_t head_dest, head_src; list_t entries[NENTRIES]; unsigned i; ql_new(&head_src); ql_move(&head_dest, &head_src); test_empty_list(&head_src); test_empty_list(&head_dest); init_entries(entries, sizeof(entries)/sizeof(list_t)); for (i = 0; i < NENTRIES; i++) { ql_tail_insert(&head_src, &entries[i], link); } ql_move(&head_dest, &head_src); test_empty_list(&head_src); test_entries_list(&head_dest, entries, NENTRIES); } TEST_END int main(void) { return test( test_ql_empty, test_ql_tail_insert, test_ql_tail_remove, test_ql_head_insert, test_ql_head_remove, test_ql_insert, test_ql_concat_split, test_ql_rotate, test_ql_move); } redis-8.0.2/deps/jemalloc/test/unit/qr.c000066400000000000000000000121501501533116600200740ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/qr.h" /* Number of ring entries, in [2..26]. */ #define NENTRIES 9 /* Split index, in [1..NENTRIES). */ #define SPLIT_INDEX 5 typedef struct ring_s ring_t; struct ring_s { qr(ring_t) link; char id; }; static void init_entries(ring_t *entries) { unsigned i; for (i = 0; i < NENTRIES; i++) { qr_new(&entries[i], link); entries[i].id = 'a' + i; } } static void test_independent_entries(ring_t *entries) { ring_t *t; unsigned i, j; for (i = 0; i < NENTRIES; i++) { j = 0; qr_foreach(t, &entries[i], link) { j++; } expect_u_eq(j, 1, "Iteration over single-element ring should visit precisely " "one element"); } for (i = 0; i < NENTRIES; i++) { j = 0; qr_reverse_foreach(t, &entries[i], link) { j++; } expect_u_eq(j, 1, "Iteration over single-element ring should visit precisely " "one element"); } for (i = 0; i < NENTRIES; i++) { t = qr_next(&entries[i], link); expect_ptr_eq(t, &entries[i], "Next element in single-element ring should be same as " "current element"); } for (i = 0; i < NENTRIES; i++) { t = qr_prev(&entries[i], link); expect_ptr_eq(t, &entries[i], "Previous element in single-element ring should be same as " "current element"); } } TEST_BEGIN(test_qr_one) { ring_t entries[NENTRIES]; init_entries(entries); test_independent_entries(entries); } TEST_END static void test_entries_ring(ring_t *entries) { ring_t *t; unsigned i, j; for (i = 0; i < NENTRIES; i++) { j = 0; qr_foreach(t, &entries[i], link) { expect_c_eq(t->id, entries[(i+j) % NENTRIES].id, "Element id mismatch"); j++; } } for (i = 0; i < NENTRIES; i++) { j = 0; qr_reverse_foreach(t, &entries[i], link) { expect_c_eq(t->id, entries[(NENTRIES+i-j-1) % NENTRIES].id, "Element id mismatch"); j++; } } for (i = 0; i < NENTRIES; i++) { t = qr_next(&entries[i], link); expect_c_eq(t->id, entries[(i+1) % NENTRIES].id, "Element id mismatch"); } for (i = 0; i < NENTRIES; i++) { t = qr_prev(&entries[i], link); expect_c_eq(t->id, entries[(NENTRIES+i-1) % NENTRIES].id, "Element id mismatch"); } } TEST_BEGIN(test_qr_after_insert) { ring_t entries[NENTRIES]; unsigned i; init_entries(entries); for (i = 1; i < NENTRIES; i++) { qr_after_insert(&entries[i - 1], &entries[i], link); } test_entries_ring(entries); } TEST_END TEST_BEGIN(test_qr_remove) { ring_t entries[NENTRIES]; ring_t *t; unsigned i, j; init_entries(entries); for (i = 1; i < NENTRIES; i++) { qr_after_insert(&entries[i - 1], &entries[i], link); } for (i = 0; i < NENTRIES; i++) { j = 0; qr_foreach(t, &entries[i], link) { expect_c_eq(t->id, entries[i+j].id, "Element id mismatch"); j++; } j = 0; qr_reverse_foreach(t, &entries[i], link) { expect_c_eq(t->id, entries[NENTRIES - 1 - j].id, "Element id mismatch"); j++; } qr_remove(&entries[i], link); } test_independent_entries(entries); } TEST_END TEST_BEGIN(test_qr_before_insert) { ring_t entries[NENTRIES]; ring_t *t; unsigned i, j; init_entries(entries); for (i = 1; i < NENTRIES; i++) { qr_before_insert(&entries[i - 1], &entries[i], link); } for (i = 0; i < NENTRIES; i++) { j = 0; qr_foreach(t, &entries[i], link) { expect_c_eq(t->id, entries[(NENTRIES+i-j) % NENTRIES].id, "Element id mismatch"); j++; } } for (i = 0; i < NENTRIES; i++) { j = 0; qr_reverse_foreach(t, &entries[i], link) { expect_c_eq(t->id, entries[(i+j+1) % NENTRIES].id, "Element id mismatch"); j++; } } for (i = 0; i < NENTRIES; i++) { t = qr_next(&entries[i], link); expect_c_eq(t->id, entries[(NENTRIES+i-1) % NENTRIES].id, "Element id mismatch"); } for (i = 0; i < NENTRIES; i++) { t = qr_prev(&entries[i], link); expect_c_eq(t->id, entries[(i+1) % NENTRIES].id, "Element id mismatch"); } } TEST_END static void test_split_entries(ring_t *entries) { ring_t *t; unsigned i, j; for (i = 0; i < NENTRIES; i++) { j = 0; qr_foreach(t, &entries[i], link) { if (i < SPLIT_INDEX) { expect_c_eq(t->id, entries[(i+j) % SPLIT_INDEX].id, "Element id mismatch"); } else { expect_c_eq(t->id, entries[(i+j-SPLIT_INDEX) % (NENTRIES-SPLIT_INDEX) + SPLIT_INDEX].id, "Element id mismatch"); } j++; } } } TEST_BEGIN(test_qr_meld_split) { ring_t entries[NENTRIES]; unsigned i; init_entries(entries); for (i = 1; i < NENTRIES; i++) { qr_after_insert(&entries[i - 1], &entries[i], link); } qr_split(&entries[0], &entries[SPLIT_INDEX], link); test_split_entries(entries); qr_meld(&entries[0], &entries[SPLIT_INDEX], link); test_entries_ring(entries); qr_meld(&entries[0], &entries[SPLIT_INDEX], link); test_split_entries(entries); qr_split(&entries[0], &entries[SPLIT_INDEX], link); test_entries_ring(entries); qr_split(&entries[0], &entries[0], link); test_entries_ring(entries); qr_meld(&entries[0], &entries[0], link); test_entries_ring(entries); } TEST_END int main(void) { return test( test_qr_one, test_qr_after_insert, test_qr_remove, test_qr_before_insert, test_qr_meld_split); } redis-8.0.2/deps/jemalloc/test/unit/rb.c000066400000000000000000000715501501533116600200660ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include #include "jemalloc/internal/rb.h" #define rbtn_black_height(a_type, a_field, a_rbt, r_height) do { \ a_type *rbp_bh_t; \ for (rbp_bh_t = (a_rbt)->rbt_root, (r_height) = 0; rbp_bh_t != \ NULL; rbp_bh_t = rbtn_left_get(a_type, a_field, \ rbp_bh_t)) { \ if (!rbtn_red_get(a_type, a_field, rbp_bh_t)) { \ (r_height)++; \ } \ } \ } while (0) static bool summarize_always_returns_true = false; typedef struct node_s node_t; struct node_s { #define NODE_MAGIC 0x9823af7e uint32_t magic; rb_node(node_t) link; /* Order used by nodes. */ uint64_t key; /* * Our made-up summary property is "specialness", with summarization * taking the max. */ uint64_t specialness; /* * Used by some of the test randomization to avoid double-removing * nodes. */ bool mid_remove; /* * To test searching functionality, we want to temporarily weaken the * ordering to allow non-equal nodes that nevertheless compare equal. */ bool allow_duplicates; /* * In check_consistency, it's handy to know a node's rank in the tree; * this tracks it (but only there; not all tests use this). */ int rank; int filtered_rank; /* * Replicate the internal structure of the tree, to make sure the * implementation doesn't miss any updates. */ const node_t *summary_lchild; const node_t *summary_rchild; uint64_t summary_max_specialness; }; static int node_cmp(const node_t *a, const node_t *b) { int ret; expect_u32_eq(a->magic, NODE_MAGIC, "Bad magic"); expect_u32_eq(b->magic, NODE_MAGIC, "Bad magic"); ret = (a->key > b->key) - (a->key < b->key); if (ret == 0 && !a->allow_duplicates) { /* * Duplicates are not allowed in the tree, so force an * arbitrary ordering for non-identical items with equal keys, * unless the user is searching and wants to allow the * duplicate. */ ret = (((uintptr_t)a) > ((uintptr_t)b)) - (((uintptr_t)a) < ((uintptr_t)b)); } return ret; } static uint64_t node_subtree_specialness(node_t *n, const node_t *lchild, const node_t *rchild) { uint64_t subtree_specialness = n->specialness; if (lchild != NULL && lchild->summary_max_specialness > subtree_specialness) { subtree_specialness = lchild->summary_max_specialness; } if (rchild != NULL && rchild->summary_max_specialness > subtree_specialness) { subtree_specialness = rchild->summary_max_specialness; } return subtree_specialness; } static bool node_summarize(node_t *a, const node_t *lchild, const node_t *rchild) { uint64_t new_summary_max_specialness = node_subtree_specialness( a, lchild, rchild); bool changed = (a->summary_lchild != lchild) || (a->summary_rchild != rchild) || (new_summary_max_specialness != a->summary_max_specialness); a->summary_max_specialness = new_summary_max_specialness; a->summary_lchild = lchild; a->summary_rchild = rchild; return changed || summarize_always_returns_true; } typedef rb_tree(node_t) tree_t; rb_summarized_proto(static, tree_, tree_t, node_t); rb_summarized_gen(static, tree_, tree_t, node_t, link, node_cmp, node_summarize); static bool specialness_filter_node(void *ctx, node_t *node) { uint64_t specialness = *(uint64_t *)ctx; return node->specialness >= specialness; } static bool specialness_filter_subtree(void *ctx, node_t *node) { uint64_t specialness = *(uint64_t *)ctx; return node->summary_max_specialness >= specialness; } static node_t * tree_iterate_cb(tree_t *tree, node_t *node, void *data) { unsigned *i = (unsigned *)data; node_t *search_node; expect_u32_eq(node->magic, NODE_MAGIC, "Bad magic"); /* Test rb_search(). */ search_node = tree_search(tree, node); expect_ptr_eq(search_node, node, "tree_search() returned unexpected node"); /* Test rb_nsearch(). */ search_node = tree_nsearch(tree, node); expect_ptr_eq(search_node, node, "tree_nsearch() returned unexpected node"); /* Test rb_psearch(). */ search_node = tree_psearch(tree, node); expect_ptr_eq(search_node, node, "tree_psearch() returned unexpected node"); (*i)++; return NULL; } TEST_BEGIN(test_rb_empty) { tree_t tree; node_t key; tree_new(&tree); expect_true(tree_empty(&tree), "Tree should be empty"); expect_ptr_null(tree_first(&tree), "Unexpected node"); expect_ptr_null(tree_last(&tree), "Unexpected node"); key.key = 0; key.magic = NODE_MAGIC; expect_ptr_null(tree_search(&tree, &key), "Unexpected node"); key.key = 0; key.magic = NODE_MAGIC; expect_ptr_null(tree_nsearch(&tree, &key), "Unexpected node"); key.key = 0; key.magic = NODE_MAGIC; expect_ptr_null(tree_psearch(&tree, &key), "Unexpected node"); unsigned nodes = 0; tree_iter_filtered(&tree, NULL, &tree_iterate_cb, &nodes, &specialness_filter_node, &specialness_filter_subtree, NULL); expect_u_eq(0, nodes, ""); nodes = 0; tree_reverse_iter_filtered(&tree, NULL, &tree_iterate_cb, &nodes, &specialness_filter_node, &specialness_filter_subtree, NULL); expect_u_eq(0, nodes, ""); expect_ptr_null(tree_first_filtered(&tree, &specialness_filter_node, &specialness_filter_subtree, NULL), ""); expect_ptr_null(tree_last_filtered(&tree, &specialness_filter_node, &specialness_filter_subtree, NULL), ""); key.key = 0; key.magic = NODE_MAGIC; expect_ptr_null(tree_search_filtered(&tree, &key, &specialness_filter_node, &specialness_filter_subtree, NULL), ""); expect_ptr_null(tree_nsearch_filtered(&tree, &key, &specialness_filter_node, &specialness_filter_subtree, NULL), ""); expect_ptr_null(tree_psearch_filtered(&tree, &key, &specialness_filter_node, &specialness_filter_subtree, NULL), ""); } TEST_END static unsigned tree_recurse(node_t *node, unsigned black_height, unsigned black_depth) { unsigned ret = 0; node_t *left_node; node_t *right_node; if (node == NULL) { return ret; } left_node = rbtn_left_get(node_t, link, node); right_node = rbtn_right_get(node_t, link, node); expect_ptr_eq(left_node, node->summary_lchild, "summary missed a tree update"); expect_ptr_eq(right_node, node->summary_rchild, "summary missed a tree update"); uint64_t expected_subtree_specialness = node_subtree_specialness(node, left_node, right_node); expect_u64_eq(expected_subtree_specialness, node->summary_max_specialness, "Incorrect summary"); if (!rbtn_red_get(node_t, link, node)) { black_depth++; } /* Red nodes must be interleaved with black nodes. */ if (rbtn_red_get(node_t, link, node)) { if (left_node != NULL) { expect_false(rbtn_red_get(node_t, link, left_node), "Node should be black"); } if (right_node != NULL) { expect_false(rbtn_red_get(node_t, link, right_node), "Node should be black"); } } /* Self. */ expect_u32_eq(node->magic, NODE_MAGIC, "Bad magic"); /* Left subtree. */ if (left_node != NULL) { ret += tree_recurse(left_node, black_height, black_depth); } else { ret += (black_depth != black_height); } /* Right subtree. */ if (right_node != NULL) { ret += tree_recurse(right_node, black_height, black_depth); } else { ret += (black_depth != black_height); } return ret; } static unsigned tree_iterate(tree_t *tree) { unsigned i; i = 0; tree_iter(tree, NULL, tree_iterate_cb, (void *)&i); return i; } static unsigned tree_iterate_reverse(tree_t *tree) { unsigned i; i = 0; tree_reverse_iter(tree, NULL, tree_iterate_cb, (void *)&i); return i; } static void node_remove(tree_t *tree, node_t *node, unsigned nnodes) { node_t *search_node; unsigned black_height, imbalances; tree_remove(tree, node); /* Test rb_nsearch(). */ search_node = tree_nsearch(tree, node); if (search_node != NULL) { expect_u64_ge(search_node->key, node->key, "Key ordering error"); } /* Test rb_psearch(). */ search_node = tree_psearch(tree, node); if (search_node != NULL) { expect_u64_le(search_node->key, node->key, "Key ordering error"); } node->magic = 0; rbtn_black_height(node_t, link, tree, black_height); imbalances = tree_recurse(tree->rbt_root, black_height, 0); expect_u_eq(imbalances, 0, "Tree is unbalanced"); expect_u_eq(tree_iterate(tree), nnodes-1, "Unexpected node iteration count"); expect_u_eq(tree_iterate_reverse(tree), nnodes-1, "Unexpected node iteration count"); } static node_t * remove_iterate_cb(tree_t *tree, node_t *node, void *data) { unsigned *nnodes = (unsigned *)data; node_t *ret = tree_next(tree, node); node_remove(tree, node, *nnodes); return ret; } static node_t * remove_reverse_iterate_cb(tree_t *tree, node_t *node, void *data) { unsigned *nnodes = (unsigned *)data; node_t *ret = tree_prev(tree, node); node_remove(tree, node, *nnodes); return ret; } static void destroy_cb(node_t *node, void *data) { unsigned *nnodes = (unsigned *)data; expect_u_gt(*nnodes, 0, "Destruction removed too many nodes"); (*nnodes)--; } TEST_BEGIN(test_rb_random) { enum { NNODES = 25, NBAGS = 500, SEED = 42 }; sfmt_t *sfmt; uint64_t bag[NNODES]; tree_t tree; node_t nodes[NNODES]; unsigned i, j, k, black_height, imbalances; sfmt = init_gen_rand(SEED); for (i = 0; i < NBAGS; i++) { switch (i) { case 0: /* Insert in order. */ for (j = 0; j < NNODES; j++) { bag[j] = j; } break; case 1: /* Insert in reverse order. */ for (j = 0; j < NNODES; j++) { bag[j] = NNODES - j - 1; } break; default: for (j = 0; j < NNODES; j++) { bag[j] = gen_rand64_range(sfmt, NNODES); } } /* * We alternate test behavior with a period of 2 here, and a * period of 5 down below, so there's no cycle in which certain * combinations get omitted. */ summarize_always_returns_true = (i % 2 == 0); for (j = 1; j <= NNODES; j++) { /* Initialize tree and nodes. */ tree_new(&tree); for (k = 0; k < j; k++) { nodes[k].magic = NODE_MAGIC; nodes[k].key = bag[k]; nodes[k].specialness = gen_rand64_range(sfmt, NNODES); nodes[k].mid_remove = false; nodes[k].allow_duplicates = false; nodes[k].summary_lchild = NULL; nodes[k].summary_rchild = NULL; nodes[k].summary_max_specialness = 0; } /* Insert nodes. */ for (k = 0; k < j; k++) { tree_insert(&tree, &nodes[k]); rbtn_black_height(node_t, link, &tree, black_height); imbalances = tree_recurse(tree.rbt_root, black_height, 0); expect_u_eq(imbalances, 0, "Tree is unbalanced"); expect_u_eq(tree_iterate(&tree), k+1, "Unexpected node iteration count"); expect_u_eq(tree_iterate_reverse(&tree), k+1, "Unexpected node iteration count"); expect_false(tree_empty(&tree), "Tree should not be empty"); expect_ptr_not_null(tree_first(&tree), "Tree should not be empty"); expect_ptr_not_null(tree_last(&tree), "Tree should not be empty"); tree_next(&tree, &nodes[k]); tree_prev(&tree, &nodes[k]); } /* Remove nodes. */ switch (i % 5) { case 0: for (k = 0; k < j; k++) { node_remove(&tree, &nodes[k], j - k); } break; case 1: for (k = j; k > 0; k--) { node_remove(&tree, &nodes[k-1], k); } break; case 2: { node_t *start; unsigned nnodes = j; start = NULL; do { start = tree_iter(&tree, start, remove_iterate_cb, (void *)&nnodes); nnodes--; } while (start != NULL); expect_u_eq(nnodes, 0, "Removal terminated early"); break; } case 3: { node_t *start; unsigned nnodes = j; start = NULL; do { start = tree_reverse_iter(&tree, start, remove_reverse_iterate_cb, (void *)&nnodes); nnodes--; } while (start != NULL); expect_u_eq(nnodes, 0, "Removal terminated early"); break; } case 4: { unsigned nnodes = j; tree_destroy(&tree, destroy_cb, &nnodes); expect_u_eq(nnodes, 0, "Destruction terminated early"); break; } default: not_reached(); } } } fini_gen_rand(sfmt); } TEST_END static void expect_simple_consistency(tree_t *tree, uint64_t specialness, bool expected_empty, node_t *expected_first, node_t *expected_last) { bool empty; node_t *first; node_t *last; empty = tree_empty_filtered(tree, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_b_eq(expected_empty, empty, ""); first = tree_first_filtered(tree, &specialness_filter_node, &specialness_filter_subtree, (void *)&specialness); expect_ptr_eq(expected_first, first, ""); last = tree_last_filtered(tree, &specialness_filter_node, &specialness_filter_subtree, (void *)&specialness); expect_ptr_eq(expected_last, last, ""); } TEST_BEGIN(test_rb_filter_simple) { enum {FILTER_NODES = 10}; node_t nodes[FILTER_NODES]; for (unsigned i = 0; i < FILTER_NODES; i++) { nodes[i].magic = NODE_MAGIC; nodes[i].key = i; if (i == 0) { nodes[i].specialness = 0; } else { nodes[i].specialness = ffs_u(i); } nodes[i].mid_remove = false; nodes[i].allow_duplicates = false; nodes[i].summary_lchild = NULL; nodes[i].summary_rchild = NULL; nodes[i].summary_max_specialness = 0; } summarize_always_returns_true = false; tree_t tree; tree_new(&tree); /* Should be empty */ expect_simple_consistency(&tree, /* specialness */ 0, /* empty */ true, /* first */ NULL, /* last */ NULL); /* Fill in just the odd nodes. */ for (int i = 1; i < FILTER_NODES; i += 2) { tree_insert(&tree, &nodes[i]); } /* A search for an odd node should succeed. */ expect_simple_consistency(&tree, /* specialness */ 0, /* empty */ false, /* first */ &nodes[1], /* last */ &nodes[9]); /* But a search for an even one should fail. */ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ true, /* first */ NULL, /* last */ NULL); /* Now we add an even. */ tree_insert(&tree, &nodes[4]); expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false, /* first */ &nodes[4], /* last */ &nodes[4]); /* A smaller even, and a larger even. */ tree_insert(&tree, &nodes[2]); tree_insert(&tree, &nodes[8]); /* * A first-search (resp. last-search) for an even should switch to the * lower (higher) one, now that it's been added. */ expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false, /* first */ &nodes[2], /* last */ &nodes[8]); /* * If we remove 2, a first-search we should go back to 4, while a * last-search should remain unchanged. */ tree_remove(&tree, &nodes[2]); expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false, /* first */ &nodes[4], /* last */ &nodes[8]); /* Reinsert 2, then find it again. */ tree_insert(&tree, &nodes[2]); expect_simple_consistency(&tree, /* specialness */ 1, /* empty */ false, /* first */ &nodes[2], /* last */ &nodes[8]); /* Searching for a multiple of 4 should not have changed. */ expect_simple_consistency(&tree, /* specialness */ 2, /* empty */ false, /* first */ &nodes[4], /* last */ &nodes[8]); /* And a multiple of 8 */ expect_simple_consistency(&tree, /* specialness */ 3, /* empty */ false, /* first */ &nodes[8], /* last */ &nodes[8]); /* But not a multiple of 16 */ expect_simple_consistency(&tree, /* specialness */ 4, /* empty */ true, /* first */ NULL, /* last */ NULL); } TEST_END typedef struct iter_ctx_s iter_ctx_t; struct iter_ctx_s { int ncalls; node_t *last_node; int ncalls_max; bool forward; }; static node_t * tree_iterate_filtered_cb(tree_t *tree, node_t *node, void *arg) { iter_ctx_t *ctx = (iter_ctx_t *)arg; ctx->ncalls++; expect_u64_ge(node->specialness, 1, "Should only invoke cb on nodes that pass the filter"); if (ctx->last_node != NULL) { if (ctx->forward) { expect_d_lt(node_cmp(ctx->last_node, node), 0, "Incorrect iteration order"); } else { expect_d_gt(node_cmp(ctx->last_node, node), 0, "Incorrect iteration order"); } } ctx->last_node = node; if (ctx->ncalls == ctx->ncalls_max) { return node; } return NULL; } static int qsort_node_cmp(const void *ap, const void *bp) { node_t *a = *(node_t **)ap; node_t *b = *(node_t **)bp; return node_cmp(a, b); } #define UPDATE_TEST_MAX 100 static void check_consistency(tree_t *tree, node_t nodes[UPDATE_TEST_MAX], int nnodes) { uint64_t specialness = 1; bool empty; bool real_empty = true; node_t *first; node_t *real_first = NULL; node_t *last; node_t *real_last = NULL; for (int i = 0; i < nnodes; i++) { if (nodes[i].specialness >= specialness) { real_empty = false; if (real_first == NULL || node_cmp(&nodes[i], real_first) < 0) { real_first = &nodes[i]; } if (real_last == NULL || node_cmp(&nodes[i], real_last) > 0) { real_last = &nodes[i]; } } } empty = tree_empty_filtered(tree, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_b_eq(real_empty, empty, ""); first = tree_first_filtered(tree, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_first, first, ""); last = tree_last_filtered(tree, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_last, last, ""); for (int i = 0; i < nnodes; i++) { node_t *next_filtered; node_t *real_next_filtered = NULL; node_t *prev_filtered; node_t *real_prev_filtered = NULL; for (int j = 0; j < nnodes; j++) { if (nodes[j].specialness < specialness) { continue; } if (node_cmp(&nodes[j], &nodes[i]) < 0 && (real_prev_filtered == NULL || node_cmp(&nodes[j], real_prev_filtered) > 0)) { real_prev_filtered = &nodes[j]; } if (node_cmp(&nodes[j], &nodes[i]) > 0 && (real_next_filtered == NULL || node_cmp(&nodes[j], real_next_filtered) < 0)) { real_next_filtered = &nodes[j]; } } next_filtered = tree_next_filtered(tree, &nodes[i], &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_next_filtered, next_filtered, ""); prev_filtered = tree_prev_filtered(tree, &nodes[i], &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_prev_filtered, prev_filtered, ""); node_t *search_filtered; node_t *real_search_filtered; node_t *nsearch_filtered; node_t *real_nsearch_filtered; node_t *psearch_filtered; node_t *real_psearch_filtered; /* * search, nsearch, psearch from a node before nodes[i] in the * ordering. */ node_t before; before.magic = NODE_MAGIC; before.key = nodes[i].key - 1; before.allow_duplicates = false; real_search_filtered = NULL; search_filtered = tree_search_filtered(tree, &before, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_search_filtered, search_filtered, ""); real_nsearch_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : real_next_filtered); nsearch_filtered = tree_nsearch_filtered(tree, &before, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, ""); real_psearch_filtered = real_prev_filtered; psearch_filtered = tree_psearch_filtered(tree, &before, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_psearch_filtered, psearch_filtered, ""); /* search, nsearch, psearch from nodes[i] */ real_search_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : NULL); search_filtered = tree_search_filtered(tree, &nodes[i], &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_search_filtered, search_filtered, ""); real_nsearch_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : real_next_filtered); nsearch_filtered = tree_nsearch_filtered(tree, &nodes[i], &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, ""); real_psearch_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : real_prev_filtered); psearch_filtered = tree_psearch_filtered(tree, &nodes[i], &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_psearch_filtered, psearch_filtered, ""); /* * search, nsearch, psearch from a node equivalent to but * distinct from nodes[i]. */ node_t equiv; equiv.magic = NODE_MAGIC; equiv.key = nodes[i].key; equiv.allow_duplicates = true; real_search_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : NULL); search_filtered = tree_search_filtered(tree, &equiv, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_search_filtered, search_filtered, ""); real_nsearch_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : real_next_filtered); nsearch_filtered = tree_nsearch_filtered(tree, &equiv, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, ""); real_psearch_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : real_prev_filtered); psearch_filtered = tree_psearch_filtered(tree, &equiv, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_psearch_filtered, psearch_filtered, ""); /* * search, nsearch, psearch from a node after nodes[i] in the * ordering. */ node_t after; after.magic = NODE_MAGIC; after.key = nodes[i].key + 1; after.allow_duplicates = false; real_search_filtered = NULL; search_filtered = tree_search_filtered(tree, &after, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_search_filtered, search_filtered, ""); real_nsearch_filtered = real_next_filtered; nsearch_filtered = tree_nsearch_filtered(tree, &after, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_nsearch_filtered, nsearch_filtered, ""); real_psearch_filtered = (nodes[i].specialness >= specialness ? &nodes[i] : real_prev_filtered); psearch_filtered = tree_psearch_filtered(tree, &after, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(real_psearch_filtered, psearch_filtered, ""); } /* Filtered iteration test setup. */ int nspecial = 0; node_t *sorted_nodes[UPDATE_TEST_MAX]; node_t *sorted_filtered_nodes[UPDATE_TEST_MAX]; for (int i = 0; i < nnodes; i++) { sorted_nodes[i] = &nodes[i]; } qsort(sorted_nodes, nnodes, sizeof(node_t *), &qsort_node_cmp); for (int i = 0; i < nnodes; i++) { sorted_nodes[i]->rank = i; sorted_nodes[i]->filtered_rank = nspecial; if (sorted_nodes[i]->specialness >= 1) { sorted_filtered_nodes[nspecial] = sorted_nodes[i]; nspecial++; } } node_t *iter_result; iter_ctx_t ctx; ctx.ncalls = 0; ctx.last_node = NULL; ctx.ncalls_max = INT_MAX; ctx.forward = true; /* Filtered forward iteration from the beginning. */ iter_result = tree_iter_filtered(tree, NULL, &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_null(iter_result, ""); expect_d_eq(nspecial, ctx.ncalls, ""); /* Filtered forward iteration from a starting point. */ for (int i = 0; i < nnodes; i++) { ctx.ncalls = 0; ctx.last_node = NULL; iter_result = tree_iter_filtered(tree, &nodes[i], &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_null(iter_result, ""); expect_d_eq(nspecial - nodes[i].filtered_rank, ctx.ncalls, ""); } /* Filtered forward iteration from the beginning, with stopping */ for (int i = 0; i < nspecial; i++) { ctx.ncalls = 0; ctx.last_node = NULL; ctx.ncalls_max = i + 1; iter_result = tree_iter_filtered(tree, NULL, &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(sorted_filtered_nodes[i], iter_result, ""); expect_d_eq(ctx.ncalls, i + 1, ""); } /* Filtered forward iteration from a starting point, with stopping. */ for (int i = 0; i < nnodes; i++) { for (int j = 0; j < nspecial - nodes[i].filtered_rank; j++) { ctx.ncalls = 0; ctx.last_node = NULL; ctx.ncalls_max = j + 1; iter_result = tree_iter_filtered(tree, &nodes[i], &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_d_eq(j + 1, ctx.ncalls, ""); expect_ptr_eq(sorted_filtered_nodes[ nodes[i].filtered_rank + j], iter_result, ""); } } /* Backwards iteration. */ ctx.ncalls = 0; ctx.last_node = NULL; ctx.ncalls_max = INT_MAX; ctx.forward = false; /* Filtered backward iteration from the end. */ iter_result = tree_reverse_iter_filtered(tree, NULL, &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_null(iter_result, ""); expect_d_eq(nspecial, ctx.ncalls, ""); /* Filtered backward iteration from a starting point. */ for (int i = 0; i < nnodes; i++) { ctx.ncalls = 0; ctx.last_node = NULL; iter_result = tree_reverse_iter_filtered(tree, &nodes[i], &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_null(iter_result, ""); int surplus_rank = (nodes[i].specialness >= 1 ? 1 : 0); expect_d_eq(nodes[i].filtered_rank + surplus_rank, ctx.ncalls, ""); } /* Filtered backward iteration from the end, with stopping */ for (int i = 0; i < nspecial; i++) { ctx.ncalls = 0; ctx.last_node = NULL; ctx.ncalls_max = i + 1; iter_result = tree_reverse_iter_filtered(tree, NULL, &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_ptr_eq(sorted_filtered_nodes[nspecial - i - 1], iter_result, ""); expect_d_eq(ctx.ncalls, i + 1, ""); } /* Filtered backward iteration from a starting point, with stopping. */ for (int i = 0; i < nnodes; i++) { int surplus_rank = (nodes[i].specialness >= 1 ? 1 : 0); for (int j = 0; j < nodes[i].filtered_rank + surplus_rank; j++) { ctx.ncalls = 0; ctx.last_node = NULL; ctx.ncalls_max = j + 1; iter_result = tree_reverse_iter_filtered(tree, &nodes[i], &tree_iterate_filtered_cb, &ctx, &specialness_filter_node, &specialness_filter_subtree, &specialness); expect_d_eq(j + 1, ctx.ncalls, ""); expect_ptr_eq(sorted_filtered_nodes[ nodes[i].filtered_rank - j - 1 + surplus_rank], iter_result, ""); } } } static void do_update_search_test(int nnodes, int ntrees, int nremovals, int nupdates) { node_t nodes[UPDATE_TEST_MAX]; assert(nnodes <= UPDATE_TEST_MAX); sfmt_t *sfmt = init_gen_rand(12345); for (int i = 0; i < ntrees; i++) { tree_t tree; tree_new(&tree); for (int j = 0; j < nnodes; j++) { nodes[j].magic = NODE_MAGIC; /* * In consistency checking, we increment or decrement a * key and assume that the result is not a key in the * tree. This isn't a *real* concern with 64-bit keys * and a good PRNG, but why not be correct anyways? */ nodes[j].key = 2 * gen_rand64(sfmt); nodes[j].specialness = 0; nodes[j].mid_remove = false; nodes[j].allow_duplicates = false; nodes[j].summary_lchild = NULL; nodes[j].summary_rchild = NULL; nodes[j].summary_max_specialness = 0; tree_insert(&tree, &nodes[j]); } for (int j = 0; j < nremovals; j++) { int victim = (int)gen_rand64_range(sfmt, nnodes); if (!nodes[victim].mid_remove) { tree_remove(&tree, &nodes[victim]); nodes[victim].mid_remove = true; } } for (int j = 0; j < nnodes; j++) { if (nodes[j].mid_remove) { nodes[j].mid_remove = false; nodes[j].key = 2 * gen_rand64(sfmt); tree_insert(&tree, &nodes[j]); } } for (int j = 0; j < nupdates; j++) { uint32_t ind = gen_rand32_range(sfmt, nnodes); nodes[ind].specialness = 1 - nodes[ind].specialness; tree_update_summaries(&tree, &nodes[ind]); check_consistency(&tree, nodes, nnodes); } } } TEST_BEGIN(test_rb_update_search) { summarize_always_returns_true = false; do_update_search_test(2, 100, 3, 50); do_update_search_test(5, 100, 3, 50); do_update_search_test(12, 100, 5, 1000); do_update_search_test(100, 1, 50, 500); } TEST_END typedef rb_tree(node_t) unsummarized_tree_t; rb_gen(static UNUSED, unsummarized_tree_, unsummarized_tree_t, node_t, link, node_cmp); static node_t * unsummarized_tree_iterate_cb(unsummarized_tree_t *tree, node_t *node, void *data) { unsigned *i = (unsigned *)data; (*i)++; return NULL; } /* * The unsummarized and summarized funtionality is implemented via the same * functions; we don't really need to do much more than test that we can exclude * the filtered functionality without anything breaking. */ TEST_BEGIN(test_rb_unsummarized) { unsummarized_tree_t tree; unsummarized_tree_new(&tree); unsigned nnodes = 0; unsummarized_tree_iter(&tree, NULL, &unsummarized_tree_iterate_cb, &nnodes); expect_u_eq(0, nnodes, ""); } TEST_END int main(void) { return test_no_reentrancy( test_rb_empty, test_rb_random, test_rb_filter_simple, test_rb_update_search, test_rb_unsummarized); } redis-8.0.2/deps/jemalloc/test/unit/retained.c000066400000000000000000000120131501533116600212430ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/spin.h" static unsigned arena_ind; static size_t sz; static size_t esz; #define NEPOCHS 8 #define PER_THD_NALLOCS 1 static atomic_u_t epoch; static atomic_u_t nfinished; static unsigned do_arena_create(extent_hooks_t *h) { unsigned new_arena_ind; size_t ind_sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&new_arena_ind, &ind_sz, (void *)(h != NULL ? &h : NULL), (h != NULL ? sizeof(h) : 0)), 0, "Unexpected mallctl() failure"); return new_arena_ind; } static void do_arena_destroy(unsigned ind) { size_t mib[3]; size_t miblen; miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0, "Unexpected mallctlnametomib() failure"); mib[1] = (size_t)ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib() failure"); } static void do_refresh(void) { uint64_t refresh_epoch = 1; expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&refresh_epoch, sizeof(refresh_epoch)), 0, "Unexpected mallctl() failure"); } static size_t do_get_size_impl(const char *cmd, unsigned ind) { size_t mib[4]; size_t miblen = sizeof(mib) / sizeof(size_t); size_t z = sizeof(size_t); expect_d_eq(mallctlnametomib(cmd, mib, &miblen), 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd); mib[2] = ind; size_t size; expect_d_eq(mallctlbymib(mib, miblen, (void *)&size, &z, NULL, 0), 0, "Unexpected mallctlbymib([\"%s\"], ...) failure", cmd); return size; } static size_t do_get_active(unsigned ind) { return do_get_size_impl("stats.arenas.0.pactive", ind) * PAGE; } static size_t do_get_mapped(unsigned ind) { return do_get_size_impl("stats.arenas.0.mapped", ind); } static void * thd_start(void *arg) { for (unsigned next_epoch = 1; next_epoch < NEPOCHS; next_epoch++) { /* Busy-wait for next epoch. */ unsigned cur_epoch; spin_t spinner = SPIN_INITIALIZER; while ((cur_epoch = atomic_load_u(&epoch, ATOMIC_ACQUIRE)) != next_epoch) { spin_adaptive(&spinner); } expect_u_eq(cur_epoch, next_epoch, "Unexpected epoch"); /* * Allocate. The main thread will reset the arena, so there's * no need to deallocate. */ for (unsigned i = 0; i < PER_THD_NALLOCS; i++) { void *p = mallocx(sz, MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE ); expect_ptr_not_null(p, "Unexpected mallocx() failure\n"); } /* Let the main thread know we've finished this iteration. */ atomic_fetch_add_u(&nfinished, 1, ATOMIC_RELEASE); } return NULL; } TEST_BEGIN(test_retained) { test_skip_if(!config_stats); test_skip_if(opt_hpa); arena_ind = do_arena_create(NULL); sz = nallocx(HUGEPAGE, 0); size_t guard_sz = san_guard_enabled() ? SAN_PAGE_GUARDS_SIZE : 0; esz = sz + sz_large_pad + guard_sz; atomic_store_u(&epoch, 0, ATOMIC_RELAXED); unsigned nthreads = ncpus * 2; if (LG_SIZEOF_PTR < 3 && nthreads > 16) { nthreads = 16; /* 32-bit platform could run out of vaddr. */ } VARIABLE_ARRAY(thd_t, threads, nthreads); for (unsigned i = 0; i < nthreads; i++) { thd_create(&threads[i], thd_start, NULL); } for (unsigned e = 1; e < NEPOCHS; e++) { atomic_store_u(&nfinished, 0, ATOMIC_RELEASE); atomic_store_u(&epoch, e, ATOMIC_RELEASE); /* Wait for threads to finish allocating. */ spin_t spinner = SPIN_INITIALIZER; while (atomic_load_u(&nfinished, ATOMIC_ACQUIRE) < nthreads) { spin_adaptive(&spinner); } /* * Assert that retained is no more than the sum of size classes * that should have been used to satisfy the worker threads' * requests, discounting per growth fragmentation. */ do_refresh(); size_t allocated = (esz - guard_sz) * nthreads * PER_THD_NALLOCS; size_t active = do_get_active(arena_ind); expect_zu_le(allocated, active, "Unexpected active memory"); size_t mapped = do_get_mapped(arena_ind); expect_zu_le(active, mapped, "Unexpected mapped memory"); arena_t *arena = arena_get(tsdn_fetch(), arena_ind, false); size_t usable = 0; size_t fragmented = 0; for (pszind_t pind = sz_psz2ind(HUGEPAGE); pind < arena->pa_shard.pac.exp_grow.next; pind++) { size_t psz = sz_pind2sz(pind); size_t psz_fragmented = psz % esz; size_t psz_usable = psz - psz_fragmented; /* * Only consider size classes that wouldn't be skipped. */ if (psz_usable > 0) { expect_zu_lt(usable, allocated, "Excessive retained memory " "(%#zx[+%#zx] > %#zx)", usable, psz_usable, allocated); fragmented += psz_fragmented; usable += psz_usable; } } /* * Clean up arena. Destroying and recreating the arena * is simpler that specifying extent hooks that deallocate * (rather than retaining) during reset. */ do_arena_destroy(arena_ind); expect_u_eq(do_arena_create(NULL), arena_ind, "Unexpected arena index"); } for (unsigned i = 0; i < nthreads; i++) { thd_join(threads[i], NULL); } do_arena_destroy(arena_ind); } TEST_END int main(void) { return test( test_retained); } redis-8.0.2/deps/jemalloc/test/unit/rtree.c000066400000000000000000000234621501533116600206030ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/rtree.h" #define INVALID_ARENA_IND ((1U << MALLOCX_ARENA_BITS) - 1) /* Potentially too large to safely place on the stack. */ rtree_t test_rtree; TEST_BEGIN(test_rtree_read_empty) { tsdn_t *tsdn; tsdn = tsdn_fetch(); base_t *base = base_new(tsdn, 0, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new failure"); rtree_t *rtree = &test_rtree; rtree_ctx_t rtree_ctx; rtree_ctx_data_init(&rtree_ctx); expect_false(rtree_new(rtree, base, false), "Unexpected rtree_new() failure"); rtree_contents_t contents; expect_true(rtree_read_independent(tsdn, rtree, &rtree_ctx, PAGE, &contents), "rtree_read_independent() should fail on empty rtree."); base_delete(tsdn, base); } TEST_END #undef NTHREADS #undef NITERS #undef SEED static edata_t * alloc_edata(void) { void *ret = mallocx(sizeof(edata_t), MALLOCX_ALIGN(EDATA_ALIGNMENT)); assert_ptr_not_null(ret, "Unexpected mallocx() failure"); return ret; } TEST_BEGIN(test_rtree_extrema) { edata_t *edata_a, *edata_b; edata_a = alloc_edata(); edata_b = alloc_edata(); edata_init(edata_a, INVALID_ARENA_IND, NULL, SC_LARGE_MINCLASS, false, sz_size2index(SC_LARGE_MINCLASS), 0, extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); edata_init(edata_b, INVALID_ARENA_IND, NULL, 0, false, SC_NSIZES, 0, extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); tsdn_t *tsdn = tsdn_fetch(); base_t *base = base_new(tsdn, 0, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new failure"); rtree_t *rtree = &test_rtree; rtree_ctx_t rtree_ctx; rtree_ctx_data_init(&rtree_ctx); expect_false(rtree_new(rtree, base, false), "Unexpected rtree_new() failure"); rtree_contents_t contents_a; contents_a.edata = edata_a; contents_a.metadata.szind = edata_szind_get(edata_a); contents_a.metadata.slab = edata_slab_get(edata_a); contents_a.metadata.is_head = edata_is_head_get(edata_a); contents_a.metadata.state = edata_state_get(edata_a); expect_false(rtree_write(tsdn, rtree, &rtree_ctx, PAGE, contents_a), "Unexpected rtree_write() failure"); expect_false(rtree_write(tsdn, rtree, &rtree_ctx, PAGE, contents_a), "Unexpected rtree_write() failure"); rtree_contents_t read_contents_a = rtree_read(tsdn, rtree, &rtree_ctx, PAGE); expect_true(contents_a.edata == read_contents_a.edata && contents_a.metadata.szind == read_contents_a.metadata.szind && contents_a.metadata.slab == read_contents_a.metadata.slab && contents_a.metadata.is_head == read_contents_a.metadata.is_head && contents_a.metadata.state == read_contents_a.metadata.state, "rtree_read() should return previously set value"); rtree_contents_t contents_b; contents_b.edata = edata_b; contents_b.metadata.szind = edata_szind_get_maybe_invalid(edata_b); contents_b.metadata.slab = edata_slab_get(edata_b); contents_b.metadata.is_head = edata_is_head_get(edata_b); contents_b.metadata.state = edata_state_get(edata_b); expect_false(rtree_write(tsdn, rtree, &rtree_ctx, ~((uintptr_t)0), contents_b), "Unexpected rtree_write() failure"); rtree_contents_t read_contents_b = rtree_read(tsdn, rtree, &rtree_ctx, ~((uintptr_t)0)); assert_true(contents_b.edata == read_contents_b.edata && contents_b.metadata.szind == read_contents_b.metadata.szind && contents_b.metadata.slab == read_contents_b.metadata.slab && contents_b.metadata.is_head == read_contents_b.metadata.is_head && contents_b.metadata.state == read_contents_b.metadata.state, "rtree_read() should return previously set value"); base_delete(tsdn, base); } TEST_END TEST_BEGIN(test_rtree_bits) { tsdn_t *tsdn = tsdn_fetch(); base_t *base = base_new(tsdn, 0, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new failure"); uintptr_t keys[] = {PAGE, PAGE + 1, PAGE + (((uintptr_t)1) << LG_PAGE) - 1}; edata_t *edata_c = alloc_edata(); edata_init(edata_c, INVALID_ARENA_IND, NULL, 0, false, SC_NSIZES, 0, extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); rtree_t *rtree = &test_rtree; rtree_ctx_t rtree_ctx; rtree_ctx_data_init(&rtree_ctx); expect_false(rtree_new(rtree, base, false), "Unexpected rtree_new() failure"); for (unsigned i = 0; i < sizeof(keys)/sizeof(uintptr_t); i++) { rtree_contents_t contents; contents.edata = edata_c; contents.metadata.szind = SC_NSIZES; contents.metadata.slab = false; contents.metadata.is_head = false; contents.metadata.state = extent_state_active; expect_false(rtree_write(tsdn, rtree, &rtree_ctx, keys[i], contents), "Unexpected rtree_write() failure"); for (unsigned j = 0; j < sizeof(keys)/sizeof(uintptr_t); j++) { expect_ptr_eq(rtree_read(tsdn, rtree, &rtree_ctx, keys[j]).edata, edata_c, "rtree_edata_read() should return previously set " "value and ignore insignificant key bits; i=%u, " "j=%u, set key=%#"FMTxPTR", get key=%#"FMTxPTR, i, j, keys[i], keys[j]); } expect_ptr_null(rtree_read(tsdn, rtree, &rtree_ctx, (((uintptr_t)2) << LG_PAGE)).edata, "Only leftmost rtree leaf should be set; i=%u", i); rtree_clear(tsdn, rtree, &rtree_ctx, keys[i]); } base_delete(tsdn, base); } TEST_END TEST_BEGIN(test_rtree_random) { #define NSET 16 #define SEED 42 sfmt_t *sfmt = init_gen_rand(SEED); tsdn_t *tsdn = tsdn_fetch(); base_t *base = base_new(tsdn, 0, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new failure"); uintptr_t keys[NSET]; rtree_t *rtree = &test_rtree; rtree_ctx_t rtree_ctx; rtree_ctx_data_init(&rtree_ctx); edata_t *edata_d = alloc_edata(); edata_init(edata_d, INVALID_ARENA_IND, NULL, 0, false, SC_NSIZES, 0, extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); expect_false(rtree_new(rtree, base, false), "Unexpected rtree_new() failure"); for (unsigned i = 0; i < NSET; i++) { keys[i] = (uintptr_t)gen_rand64(sfmt); rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, &rtree_ctx, keys[i], false, true); expect_ptr_not_null(elm, "Unexpected rtree_leaf_elm_lookup() failure"); rtree_contents_t contents; contents.edata = edata_d; contents.metadata.szind = SC_NSIZES; contents.metadata.slab = false; contents.metadata.is_head = false; contents.metadata.state = edata_state_get(edata_d); rtree_leaf_elm_write(tsdn, rtree, elm, contents); expect_ptr_eq(rtree_read(tsdn, rtree, &rtree_ctx, keys[i]).edata, edata_d, "rtree_edata_read() should return previously set value"); } for (unsigned i = 0; i < NSET; i++) { expect_ptr_eq(rtree_read(tsdn, rtree, &rtree_ctx, keys[i]).edata, edata_d, "rtree_edata_read() should return previously set value, " "i=%u", i); } for (unsigned i = 0; i < NSET; i++) { rtree_clear(tsdn, rtree, &rtree_ctx, keys[i]); expect_ptr_null(rtree_read(tsdn, rtree, &rtree_ctx, keys[i]).edata, "rtree_edata_read() should return previously set value"); } for (unsigned i = 0; i < NSET; i++) { expect_ptr_null(rtree_read(tsdn, rtree, &rtree_ctx, keys[i]).edata, "rtree_edata_read() should return previously set value"); } base_delete(tsdn, base); fini_gen_rand(sfmt); #undef NSET #undef SEED } TEST_END static void test_rtree_range_write(tsdn_t *tsdn, rtree_t *rtree, uintptr_t start, uintptr_t end) { rtree_ctx_t rtree_ctx; rtree_ctx_data_init(&rtree_ctx); edata_t *edata_e = alloc_edata(); edata_init(edata_e, INVALID_ARENA_IND, NULL, 0, false, SC_NSIZES, 0, extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); rtree_contents_t contents; contents.edata = edata_e; contents.metadata.szind = SC_NSIZES; contents.metadata.slab = false; contents.metadata.is_head = false; contents.metadata.state = extent_state_active; expect_false(rtree_write(tsdn, rtree, &rtree_ctx, start, contents), "Unexpected rtree_write() failure"); expect_false(rtree_write(tsdn, rtree, &rtree_ctx, end, contents), "Unexpected rtree_write() failure"); rtree_write_range(tsdn, rtree, &rtree_ctx, start, end, contents); for (uintptr_t i = 0; i < ((end - start) >> LG_PAGE); i++) { expect_ptr_eq(rtree_read(tsdn, rtree, &rtree_ctx, start + (i << LG_PAGE)).edata, edata_e, "rtree_edata_read() should return previously set value"); } rtree_clear_range(tsdn, rtree, &rtree_ctx, start, end); rtree_leaf_elm_t *elm; for (uintptr_t i = 0; i < ((end - start) >> LG_PAGE); i++) { elm = rtree_leaf_elm_lookup(tsdn, rtree, &rtree_ctx, start + (i << LG_PAGE), false, false); expect_ptr_not_null(elm, "Should have been initialized."); expect_ptr_null(rtree_leaf_elm_read(tsdn, rtree, elm, false).edata, "Should have been cleared."); } } TEST_BEGIN(test_rtree_range) { tsdn_t *tsdn = tsdn_fetch(); base_t *base = base_new(tsdn, 0, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); expect_ptr_not_null(base, "Unexpected base_new failure"); rtree_t *rtree = &test_rtree; expect_false(rtree_new(rtree, base, false), "Unexpected rtree_new() failure"); /* Not crossing rtree node boundary first. */ uintptr_t start = ZU(1) << rtree_leaf_maskbits(); uintptr_t end = start + (ZU(100) << LG_PAGE); test_rtree_range_write(tsdn, rtree, start, end); /* Crossing rtree node boundary. */ start = (ZU(1) << rtree_leaf_maskbits()) - (ZU(10) << LG_PAGE); end = start + (ZU(100) << LG_PAGE); assert_ptr_ne((void *)rtree_leafkey(start), (void *)rtree_leafkey(end), "The range should span across two rtree nodes"); test_rtree_range_write(tsdn, rtree, start, end); base_delete(tsdn, base); } TEST_END int main(void) { return test( test_rtree_read_empty, test_rtree_extrema, test_rtree_bits, test_rtree_random, test_rtree_range); } redis-8.0.2/deps/jemalloc/test/unit/safety_check.c000066400000000000000000000070771501533116600221160ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/safety_check.h" /* * Note that we get called through safety_check.sh, which turns on sampling for * everything. */ bool fake_abort_called; void fake_abort(const char *message) { (void)message; fake_abort_called = true; } static void buffer_overflow_write(char *ptr, size_t size) { /* Avoid overflow warnings. */ volatile size_t idx = size; ptr[idx] = 0; } TEST_BEGIN(test_malloc_free_overflow) { test_skip_if(!config_prof); test_skip_if(!config_opt_safety_checks); safety_check_set_abort(&fake_abort); /* Buffer overflow! */ char* ptr = malloc(128); buffer_overflow_write(ptr, 128); free(ptr); safety_check_set_abort(NULL); expect_b_eq(fake_abort_called, true, "Redzone check didn't fire."); fake_abort_called = false; } TEST_END TEST_BEGIN(test_mallocx_dallocx_overflow) { test_skip_if(!config_prof); test_skip_if(!config_opt_safety_checks); safety_check_set_abort(&fake_abort); /* Buffer overflow! */ char* ptr = mallocx(128, 0); buffer_overflow_write(ptr, 128); dallocx(ptr, 0); safety_check_set_abort(NULL); expect_b_eq(fake_abort_called, true, "Redzone check didn't fire."); fake_abort_called = false; } TEST_END TEST_BEGIN(test_malloc_sdallocx_overflow) { test_skip_if(!config_prof); test_skip_if(!config_opt_safety_checks); safety_check_set_abort(&fake_abort); /* Buffer overflow! */ char* ptr = malloc(128); buffer_overflow_write(ptr, 128); sdallocx(ptr, 128, 0); safety_check_set_abort(NULL); expect_b_eq(fake_abort_called, true, "Redzone check didn't fire."); fake_abort_called = false; } TEST_END TEST_BEGIN(test_realloc_overflow) { test_skip_if(!config_prof); test_skip_if(!config_opt_safety_checks); safety_check_set_abort(&fake_abort); /* Buffer overflow! */ char* ptr = malloc(128); buffer_overflow_write(ptr, 128); ptr = realloc(ptr, 129); safety_check_set_abort(NULL); free(ptr); expect_b_eq(fake_abort_called, true, "Redzone check didn't fire."); fake_abort_called = false; } TEST_END TEST_BEGIN(test_rallocx_overflow) { test_skip_if(!config_prof); test_skip_if(!config_opt_safety_checks); safety_check_set_abort(&fake_abort); /* Buffer overflow! */ char* ptr = malloc(128); buffer_overflow_write(ptr, 128); ptr = rallocx(ptr, 129, 0); safety_check_set_abort(NULL); free(ptr); expect_b_eq(fake_abort_called, true, "Redzone check didn't fire."); fake_abort_called = false; } TEST_END TEST_BEGIN(test_xallocx_overflow) { test_skip_if(!config_prof); test_skip_if(!config_opt_safety_checks); safety_check_set_abort(&fake_abort); /* Buffer overflow! */ char* ptr = malloc(128); buffer_overflow_write(ptr, 128); size_t result = xallocx(ptr, 129, 0, 0); expect_zu_eq(result, 128, ""); free(ptr); expect_b_eq(fake_abort_called, true, "Redzone check didn't fire."); fake_abort_called = false; safety_check_set_abort(NULL); } TEST_END TEST_BEGIN(test_realloc_no_overflow) { char* ptr = malloc(128); ptr = realloc(ptr, 256); ptr[128] = 0; ptr[255] = 0; free(ptr); ptr = malloc(128); ptr = realloc(ptr, 64); ptr[63] = 0; ptr[0] = 0; free(ptr); } TEST_END TEST_BEGIN(test_rallocx_no_overflow) { char* ptr = malloc(128); ptr = rallocx(ptr, 256, 0); ptr[128] = 0; ptr[255] = 0; free(ptr); ptr = malloc(128); ptr = rallocx(ptr, 64, 0); ptr[63] = 0; ptr[0] = 0; free(ptr); } TEST_END int main(void) { return test( test_malloc_free_overflow, test_mallocx_dallocx_overflow, test_malloc_sdallocx_overflow, test_realloc_overflow, test_rallocx_overflow, test_xallocx_overflow, test_realloc_no_overflow, test_rallocx_no_overflow); } redis-8.0.2/deps/jemalloc/test/unit/safety_check.sh000066400000000000000000000001701501533116600222710ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/san.c000066400000000000000000000136331501533116600202420ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/arena_util.h" #include "test/san.h" #include "jemalloc/internal/san.h" static void verify_extent_guarded(tsdn_t *tsdn, void *ptr) { expect_true(extent_is_guarded(tsdn, ptr), "All extents should be guarded."); } #define MAX_SMALL_ALLOCATIONS 4096 void *small_alloc[MAX_SMALL_ALLOCATIONS]; /* * This test allocates page sized slabs and checks that every two slabs have * at least one page in between them. That page is supposed to be the guard * page. */ TEST_BEGIN(test_guarded_small) { test_skip_if(opt_prof); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); unsigned npages = 16, pages_found = 0, ends_found = 0; VARIABLE_ARRAY(uintptr_t, pages, npages); /* Allocate to get sanitized pointers. */ size_t slab_sz = PAGE; size_t sz = slab_sz / 8; unsigned n_alloc = 0; while (n_alloc < MAX_SMALL_ALLOCATIONS) { void *ptr = malloc(sz); expect_ptr_not_null(ptr, "Unexpected malloc() failure"); small_alloc[n_alloc] = ptr; verify_extent_guarded(tsdn, ptr); if ((uintptr_t)ptr % PAGE == 0) { assert_u_lt(pages_found, npages, "Unexpectedly large number of page aligned allocs"); pages[pages_found++] = (uintptr_t)ptr; } if (((uintptr_t)ptr + (uintptr_t)sz) % PAGE == 0) { ends_found++; } n_alloc++; if (pages_found == npages && ends_found == npages) { break; } } /* Should found the ptrs being checked for overflow and underflow. */ expect_u_eq(pages_found, npages, "Could not found the expected pages."); expect_u_eq(ends_found, npages, "Could not found the expected pages."); /* Verify the pages are not continuous, i.e. separated by guards. */ for (unsigned i = 0; i < npages - 1; i++) { for (unsigned j = i + 1; j < npages; j++) { uintptr_t ptr_diff = pages[i] > pages[j] ? pages[i] - pages[j] : pages[j] - pages[i]; expect_zu_ge((size_t)ptr_diff, slab_sz + PAGE, "There should be at least one pages between " "guarded slabs"); } } for (unsigned i = 0; i < n_alloc + 1; i++) { free(small_alloc[i]); } } TEST_END TEST_BEGIN(test_guarded_large) { tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); unsigned nlarge = 32; VARIABLE_ARRAY(uintptr_t, large, nlarge); /* Allocate to get sanitized pointers. */ size_t large_sz = SC_LARGE_MINCLASS; for (unsigned i = 0; i < nlarge; i++) { void *ptr = malloc(large_sz); verify_extent_guarded(tsdn, ptr); expect_ptr_not_null(ptr, "Unexpected malloc() failure"); large[i] = (uintptr_t)ptr; } /* Verify the pages are not continuous, i.e. separated by guards. */ for (unsigned i = 0; i < nlarge; i++) { for (unsigned j = i + 1; j < nlarge; j++) { uintptr_t ptr_diff = large[i] > large[j] ? large[i] - large[j] : large[j] - large[i]; expect_zu_ge((size_t)ptr_diff, large_sz + 2 * PAGE, "There should be at least two pages between " " guarded large allocations"); } } for (unsigned i = 0; i < nlarge; i++) { free((void *)large[i]); } } TEST_END static void verify_pdirty(unsigned arena_ind, uint64_t expected) { uint64_t pdirty = get_arena_pdirty(arena_ind); expect_u64_eq(pdirty, expected / PAGE, "Unexpected dirty page amount."); } static void verify_pmuzzy(unsigned arena_ind, uint64_t expected) { uint64_t pmuzzy = get_arena_pmuzzy(arena_ind); expect_u64_eq(pmuzzy, expected / PAGE, "Unexpected muzzy page amount."); } TEST_BEGIN(test_guarded_decay) { unsigned arena_ind = do_arena_create(-1, -1); do_decay(arena_ind); do_purge(arena_ind); verify_pdirty(arena_ind, 0); verify_pmuzzy(arena_ind, 0); /* Verify that guarded extents as dirty. */ size_t sz1 = PAGE, sz2 = PAGE * 2; /* W/o maps_coalesce, guarded extents are unguarded eagerly. */ size_t add_guard_size = maps_coalesce ? 0 : SAN_PAGE_GUARDS_SIZE; generate_dirty(arena_ind, sz1); verify_pdirty(arena_ind, sz1 + add_guard_size); verify_pmuzzy(arena_ind, 0); /* Should reuse the first extent. */ generate_dirty(arena_ind, sz1); verify_pdirty(arena_ind, sz1 + add_guard_size); verify_pmuzzy(arena_ind, 0); /* Should not reuse; expect new dirty pages. */ generate_dirty(arena_ind, sz2); verify_pdirty(arena_ind, sz1 + sz2 + 2 * add_guard_size); verify_pmuzzy(arena_ind, 0); tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; /* Should reuse dirty extents for the two mallocx. */ void *p1 = do_mallocx(sz1, flags); verify_extent_guarded(tsdn, p1); verify_pdirty(arena_ind, sz2 + add_guard_size); void *p2 = do_mallocx(sz2, flags); verify_extent_guarded(tsdn, p2); verify_pdirty(arena_ind, 0); verify_pmuzzy(arena_ind, 0); dallocx(p1, flags); verify_pdirty(arena_ind, sz1 + add_guard_size); dallocx(p2, flags); verify_pdirty(arena_ind, sz1 + sz2 + 2 * add_guard_size); verify_pmuzzy(arena_ind, 0); do_purge(arena_ind); verify_pdirty(arena_ind, 0); verify_pmuzzy(arena_ind, 0); if (config_stats) { expect_u64_eq(get_arena_npurge(arena_ind), 1, "Expected purging to occur"); expect_u64_eq(get_arena_dirty_npurge(arena_ind), 1, "Expected purging to occur"); expect_u64_eq(get_arena_dirty_purged(arena_ind), (sz1 + sz2 + 2 * add_guard_size) / PAGE, "Expected purging to occur"); expect_u64_eq(get_arena_muzzy_npurge(arena_ind), 0, "Expected purging to occur"); } if (opt_retain) { /* * With retain, guarded extents are not mergable and will be * cached in ecache_retained. They should be reused. */ void *new_p1 = do_mallocx(sz1, flags); verify_extent_guarded(tsdn, p1); expect_ptr_eq(p1, new_p1, "Expect to reuse p1"); void *new_p2 = do_mallocx(sz2, flags); verify_extent_guarded(tsdn, p2); expect_ptr_eq(p2, new_p2, "Expect to reuse p2"); dallocx(new_p1, flags); verify_pdirty(arena_ind, sz1 + add_guard_size); dallocx(new_p2, flags); verify_pdirty(arena_ind, sz1 + sz2 + 2 * add_guard_size); verify_pmuzzy(arena_ind, 0); } do_arena_destroy(arena_ind); } TEST_END int main(void) { return test( test_guarded_small, test_guarded_large, test_guarded_decay); } redis-8.0.2/deps/jemalloc/test/unit/san.sh000066400000000000000000000001041501533116600204170ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="san_guard_large:1,san_guard_small:1" redis-8.0.2/deps/jemalloc/test/unit/san_bump.c000066400000000000000000000071611501533116600212640ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/arena_util.h" #include "jemalloc/internal/arena_structs.h" #include "jemalloc/internal/san_bump.h" TEST_BEGIN(test_san_bump_alloc) { test_skip_if(!maps_coalesce || !opt_retain); tsdn_t *tsdn = tsdn_fetch(); san_bump_alloc_t sba; san_bump_alloc_init(&sba); unsigned arena_ind = do_arena_create(0, 0); assert_u_ne(arena_ind, UINT_MAX, "Failed to create an arena"); arena_t *arena = arena_get(tsdn, arena_ind, false); pac_t *pac = &arena->pa_shard.pac; size_t alloc_size = PAGE * 16; size_t alloc_n = alloc_size / sizeof(unsigned); edata_t* edata = san_bump_alloc(tsdn, &sba, pac, pac_ehooks_get(pac), alloc_size, /* zero */ false); expect_ptr_not_null(edata, "Failed to allocate edata"); expect_u_eq(edata_arena_ind_get(edata), arena_ind, "Edata was assigned an incorrect arena id"); expect_zu_eq(edata_size_get(edata), alloc_size, "Allocated edata of incorrect size"); expect_false(edata_slab_get(edata), "Bump allocator incorrectly assigned 'slab' to true"); expect_true(edata_committed_get(edata), "Edata is not committed"); void *ptr = edata_addr_get(edata); expect_ptr_not_null(ptr, "Edata was assigned an invalid address"); /* Test that memory is allocated; no guard pages are misplaced */ for (unsigned i = 0; i < alloc_n; ++i) { ((unsigned *)ptr)[i] = 1; } size_t alloc_size2 = PAGE * 28; size_t alloc_n2 = alloc_size / sizeof(unsigned); edata_t *edata2 = san_bump_alloc(tsdn, &sba, pac, pac_ehooks_get(pac), alloc_size2, /* zero */ true); expect_ptr_not_null(edata2, "Failed to allocate edata"); expect_u_eq(edata_arena_ind_get(edata2), arena_ind, "Edata was assigned an incorrect arena id"); expect_zu_eq(edata_size_get(edata2), alloc_size2, "Allocated edata of incorrect size"); expect_false(edata_slab_get(edata2), "Bump allocator incorrectly assigned 'slab' to true"); expect_true(edata_committed_get(edata2), "Edata is not committed"); void *ptr2 = edata_addr_get(edata2); expect_ptr_not_null(ptr, "Edata was assigned an invalid address"); uintptr_t ptrdiff = ptr2 > ptr ? (uintptr_t)ptr2 - (uintptr_t)ptr : (uintptr_t)ptr - (uintptr_t)ptr2; size_t between_allocs = (size_t)ptrdiff - alloc_size; expect_zu_ge(between_allocs, PAGE, "Guard page between allocs is missing"); for (unsigned i = 0; i < alloc_n2; ++i) { expect_u_eq(((unsigned *)ptr2)[i], 0, "Memory is not zeroed"); } } TEST_END TEST_BEGIN(test_large_alloc_size) { test_skip_if(!maps_coalesce || !opt_retain); tsdn_t *tsdn = tsdn_fetch(); san_bump_alloc_t sba; san_bump_alloc_init(&sba); unsigned arena_ind = do_arena_create(0, 0); assert_u_ne(arena_ind, UINT_MAX, "Failed to create an arena"); arena_t *arena = arena_get(tsdn, arena_ind, false); pac_t *pac = &arena->pa_shard.pac; size_t alloc_size = SBA_RETAINED_ALLOC_SIZE * 2; edata_t* edata = san_bump_alloc(tsdn, &sba, pac, pac_ehooks_get(pac), alloc_size, /* zero */ false); expect_u_eq(edata_arena_ind_get(edata), arena_ind, "Edata was assigned an incorrect arena id"); expect_zu_eq(edata_size_get(edata), alloc_size, "Allocated edata of incorrect size"); expect_false(edata_slab_get(edata), "Bump allocator incorrectly assigned 'slab' to true"); expect_true(edata_committed_get(edata), "Edata is not committed"); void *ptr = edata_addr_get(edata); expect_ptr_not_null(ptr, "Edata was assigned an invalid address"); /* Test that memory is allocated; no guard pages are misplaced */ for (unsigned i = 0; i < alloc_size / PAGE; ++i) { *((char *)ptr + PAGE * i) = 1; } } TEST_END int main(void) { return test( test_san_bump_alloc, test_large_alloc_size); } redis-8.0.2/deps/jemalloc/test/unit/sc.c000066400000000000000000000016421501533116600200630ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_update_slab_size) { sc_data_t data; memset(&data, 0, sizeof(data)); sc_data_init(&data); sc_t *tiny = &data.sc[0]; size_t tiny_size = (ZU(1) << tiny->lg_base) + (ZU(tiny->ndelta) << tiny->lg_delta); size_t pgs_too_big = (tiny_size * BITMAP_MAXBITS + PAGE - 1) / PAGE + 1; sc_data_update_slab_size(&data, tiny_size, tiny_size, (int)pgs_too_big); expect_zu_lt((size_t)tiny->pgs, pgs_too_big, "Allowed excessive pages"); sc_data_update_slab_size(&data, 1, 10 * PAGE, 1); for (int i = 0; i < data.nbins; i++) { sc_t *sc = &data.sc[i]; size_t reg_size = (ZU(1) << sc->lg_base) + (ZU(sc->ndelta) << sc->lg_delta); if (reg_size <= PAGE) { expect_d_eq(sc->pgs, 1, "Ignored valid page size hint"); } else { expect_d_gt(sc->pgs, 1, "Allowed invalid page size hint"); } } } TEST_END int main(void) { return test( test_update_slab_size); } redis-8.0.2/deps/jemalloc/test/unit/sec.c000066400000000000000000000504461501533116600202360ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/sec.h" typedef struct pai_test_allocator_s pai_test_allocator_t; struct pai_test_allocator_s { pai_t pai; bool alloc_fail; size_t alloc_count; size_t alloc_batch_count; size_t dalloc_count; size_t dalloc_batch_count; /* * We use a simple bump allocator as the implementation. This isn't * *really* correct, since we may allow expansion into a subsequent * allocation, but it's not like the SEC is really examining the * pointers it gets back; this is mostly just helpful for debugging. */ uintptr_t next_ptr; size_t expand_count; bool expand_return_value; size_t shrink_count; bool shrink_return_value; }; static void test_sec_init(sec_t *sec, pai_t *fallback, size_t nshards, size_t max_alloc, size_t max_bytes) { sec_opts_t opts; opts.nshards = 1; opts.max_alloc = max_alloc; opts.max_bytes = max_bytes; /* * Just choose reasonable defaults for these; most tests don't care so * long as they're something reasonable. */ opts.bytes_after_flush = max_bytes / 2; opts.batch_fill_extra = 4; /* * We end up leaking this base, but that's fine; this test is * short-running, and SECs are arena-scoped in reality. */ base_t *base = base_new(TSDN_NULL, /* ind */ 123, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); bool err = sec_init(TSDN_NULL, sec, base, fallback, &opts); assert_false(err, "Unexpected initialization failure"); assert_u_ge(sec->npsizes, 0, "Zero size classes allowed for caching"); } static inline edata_t * pai_test_allocator_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, bool guarded, bool frequent_reuse, bool *deferred_work_generated) { assert(!guarded); pai_test_allocator_t *ta = (pai_test_allocator_t *)self; if (ta->alloc_fail) { return NULL; } edata_t *edata = malloc(sizeof(edata_t)); assert_ptr_not_null(edata, ""); ta->next_ptr += alignment - 1; edata_init(edata, /* arena_ind */ 0, (void *)(ta->next_ptr & ~(alignment - 1)), size, /* slab */ false, /* szind */ 0, /* sn */ 1, extent_state_active, /* zero */ zero, /* comitted */ true, /* ranged */ false, EXTENT_NOT_HEAD); ta->next_ptr += size; ta->alloc_count++; return edata; } static inline size_t pai_test_allocator_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated) { pai_test_allocator_t *ta = (pai_test_allocator_t *)self; if (ta->alloc_fail) { return 0; } for (size_t i = 0; i < nallocs; i++) { edata_t *edata = malloc(sizeof(edata_t)); assert_ptr_not_null(edata, ""); edata_init(edata, /* arena_ind */ 0, (void *)ta->next_ptr, size, /* slab */ false, /* szind */ 0, /* sn */ 1, extent_state_active, /* zero */ false, /* comitted */ true, /* ranged */ false, EXTENT_NOT_HEAD); ta->next_ptr += size; ta->alloc_batch_count++; edata_list_active_append(results, edata); } return nallocs; } static bool pai_test_allocator_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated) { pai_test_allocator_t *ta = (pai_test_allocator_t *)self; ta->expand_count++; return ta->expand_return_value; } static bool pai_test_allocator_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, size_t new_size, bool *deferred_work_generated) { pai_test_allocator_t *ta = (pai_test_allocator_t *)self; ta->shrink_count++; return ta->shrink_return_value; } static void pai_test_allocator_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, bool *deferred_work_generated) { pai_test_allocator_t *ta = (pai_test_allocator_t *)self; ta->dalloc_count++; free(edata); } static void pai_test_allocator_dalloc_batch(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, bool *deferred_work_generated) { pai_test_allocator_t *ta = (pai_test_allocator_t *)self; edata_t *edata; while ((edata = edata_list_active_first(list)) != NULL) { edata_list_active_remove(list, edata); ta->dalloc_batch_count++; free(edata); } } static inline void pai_test_allocator_init(pai_test_allocator_t *ta) { ta->alloc_fail = false; ta->alloc_count = 0; ta->alloc_batch_count = 0; ta->dalloc_count = 0; ta->dalloc_batch_count = 0; /* Just don't start the edata at 0. */ ta->next_ptr = 10 * PAGE; ta->expand_count = 0; ta->expand_return_value = false; ta->shrink_count = 0; ta->shrink_return_value = false; ta->pai.alloc = &pai_test_allocator_alloc; ta->pai.alloc_batch = &pai_test_allocator_alloc_batch; ta->pai.expand = &pai_test_allocator_expand; ta->pai.shrink = &pai_test_allocator_shrink; ta->pai.dalloc = &pai_test_allocator_dalloc; ta->pai.dalloc_batch = &pai_test_allocator_dalloc_batch; } TEST_BEGIN(test_reuse) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* * We can't use the "real" tsd, since we malloc within the test * allocator hooks; we'd get lock inversion crashes. Eventually, we * should have a way to mock tsds, but for now just don't do any * lock-order checking. */ tsdn_t *tsdn = TSDN_NULL; /* * 11 allocs apiece of 1-PAGE and 2-PAGE objects means that we should be * able to get to 33 pages in the cache before triggering a flush. We * set the flush liimt to twice this amount, to avoid accidentally * triggering a flush caused by the batch-allocation down the cache fill * pathway disrupting ordering. */ enum { NALLOCS = 11 }; edata_t *one_page[NALLOCS]; edata_t *two_page[NALLOCS]; bool deferred_work_generated = false; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 2 * PAGE, /* max_bytes */ 2 * (NALLOCS * PAGE + NALLOCS * 2 * PAGE)); for (int i = 0; i < NALLOCS; i++) { one_page[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(one_page[i], "Unexpected alloc failure"); two_page[i] = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(one_page[i], "Unexpected alloc failure"); } expect_zu_eq(0, ta.alloc_count, "Should be using batch allocs"); size_t max_allocs = ta.alloc_count + ta.alloc_batch_count; expect_zu_le(2 * NALLOCS, max_allocs, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of allocations"); /* * Free in a different order than we allocated, to make sure free-list * separation works correctly. */ for (int i = NALLOCS - 1; i >= 0; i--) { pai_dalloc(tsdn, &sec.pai, one_page[i], &deferred_work_generated); } for (int i = NALLOCS - 1; i >= 0; i--) { pai_dalloc(tsdn, &sec.pai, two_page[i], &deferred_work_generated); } expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of allocations"); /* * Check that the n'th most recent deallocated extent is returned for * the n'th alloc request of a given size. */ for (int i = 0; i < NALLOCS; i++) { edata_t *alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); edata_t *alloc2 = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_eq(one_page[i], alloc1, "Got unexpected allocation"); expect_ptr_eq(two_page[i], alloc2, "Got unexpected allocation"); } expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of allocations"); } TEST_END TEST_BEGIN(test_auto_flush) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; /* * 10-allocs apiece of 1-PAGE and 2-PAGE objects means that we should be * able to get to 30 pages in the cache before triggering a flush. The * choice of NALLOCS here is chosen to match the batch allocation * default (4 extra + 1 == 5; so 10 allocations leaves the cache exactly * empty, even in the presence of batch allocation on fill). * Eventually, once our allocation batching strategies become smarter, * this should change. */ enum { NALLOCS = 10 }; edata_t *extra_alloc; edata_t *allocs[NALLOCS]; bool deferred_work_generated = false; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE, /* max_bytes */ NALLOCS * PAGE); for (int i = 0; i < NALLOCS; i++) { allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(allocs[i], "Unexpected alloc failure"); } extra_alloc = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(extra_alloc, "Unexpected alloc failure"); size_t max_allocs = ta.alloc_count + ta.alloc_batch_count; expect_zu_le(NALLOCS + 1, max_allocs, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of allocations"); /* Free until the SEC is full, but should not have flushed yet. */ for (int i = 0; i < NALLOCS; i++) { pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated); } expect_zu_le(NALLOCS + 1, max_allocs, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of allocations"); /* * Free the extra allocation; this should trigger a flush. The internal * flushing logic is allowed to get complicated; for now, we rely on our * whitebox knowledge of the fact that the SEC flushes bins in their * entirety when it decides to do so, and it has only one bin active * right now. */ pai_dalloc(tsdn, &sec.pai, extra_alloc, &deferred_work_generated); expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of (non-batch) deallocations"); expect_zu_eq(NALLOCS + 1, ta.dalloc_batch_count, "Incorrect number of batch deallocations"); } TEST_END /* * A disable and a flush are *almost* equivalent; the only difference is what * happens afterwards; disabling disallows all future caching as well. */ static void do_disable_flush_test(bool is_disable) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; enum { NALLOCS = 11 }; edata_t *allocs[NALLOCS]; bool deferred_work_generated = false; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE, /* max_bytes */ NALLOCS * PAGE); for (int i = 0; i < NALLOCS; i++) { allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(allocs[i], "Unexpected alloc failure"); } /* Free all but the last aloc. */ for (int i = 0; i < NALLOCS - 1; i++) { pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated); } size_t max_allocs = ta.alloc_count + ta.alloc_batch_count; expect_zu_le(NALLOCS, max_allocs, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of allocations"); if (is_disable) { sec_disable(tsdn, &sec); } else { sec_flush(tsdn, &sec); } expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count, "Incorrect number of allocations"); expect_zu_eq(0, ta.dalloc_count, "Incorrect number of (non-batch) deallocations"); expect_zu_le(NALLOCS - 1, ta.dalloc_batch_count, "Incorrect number of batch deallocations"); size_t old_dalloc_batch_count = ta.dalloc_batch_count; /* * If we free into a disabled SEC, it should forward to the fallback. * Otherwise, the SEC should accept the allocation. */ pai_dalloc(tsdn, &sec.pai, allocs[NALLOCS - 1], &deferred_work_generated); expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count, "Incorrect number of allocations"); expect_zu_eq(is_disable ? 1 : 0, ta.dalloc_count, "Incorrect number of (non-batch) deallocations"); expect_zu_eq(old_dalloc_batch_count, ta.dalloc_batch_count, "Incorrect number of batch deallocations"); } TEST_BEGIN(test_disable) { do_disable_flush_test(/* is_disable */ true); } TEST_END TEST_BEGIN(test_flush) { do_disable_flush_test(/* is_disable */ false); } TEST_END TEST_BEGIN(test_max_alloc_respected) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; size_t max_alloc = 2 * PAGE; size_t attempted_alloc = 3 * PAGE; bool deferred_work_generated = false; test_sec_init(&sec, &ta.pai, /* nshards */ 1, max_alloc, /* max_bytes */ 1000 * PAGE); for (size_t i = 0; i < 100; i++) { expect_zu_eq(i, ta.alloc_count, "Incorrect number of allocations"); expect_zu_eq(i, ta.dalloc_count, "Incorrect number of deallocations"); edata_t *edata = pai_alloc(tsdn, &sec.pai, attempted_alloc, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(edata, "Unexpected alloc failure"); expect_zu_eq(i + 1, ta.alloc_count, "Incorrect number of allocations"); expect_zu_eq(i, ta.dalloc_count, "Incorrect number of deallocations"); pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated); } } TEST_END TEST_BEGIN(test_expand_shrink_delegate) { /* * Expand and shrink shouldn't affect sec state; they should just * delegate to the fallback PAI. */ pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; bool deferred_work_generated = false; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 10 * PAGE, /* max_bytes */ 1000 * PAGE); edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_ptr_not_null(edata, "Unexpected alloc failure"); bool err = pai_expand(tsdn, &sec.pai, edata, PAGE, 4 * PAGE, /* zero */ false, &deferred_work_generated); expect_false(err, "Unexpected expand failure"); expect_zu_eq(1, ta.expand_count, ""); ta.expand_return_value = true; err = pai_expand(tsdn, &sec.pai, edata, 4 * PAGE, 3 * PAGE, /* zero */ false, &deferred_work_generated); expect_true(err, "Unexpected expand success"); expect_zu_eq(2, ta.expand_count, ""); err = pai_shrink(tsdn, &sec.pai, edata, 4 * PAGE, 2 * PAGE, &deferred_work_generated); expect_false(err, "Unexpected shrink failure"); expect_zu_eq(1, ta.shrink_count, ""); ta.shrink_return_value = true; err = pai_shrink(tsdn, &sec.pai, edata, 2 * PAGE, PAGE, &deferred_work_generated); expect_true(err, "Unexpected shrink success"); expect_zu_eq(2, ta.shrink_count, ""); } TEST_END TEST_BEGIN(test_nshards_0) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; base_t *base = base_new(TSDN_NULL, /* ind */ 123, &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); sec_opts_t opts = SEC_OPTS_DEFAULT; opts.nshards = 0; sec_init(TSDN_NULL, &sec, base, &ta.pai, &opts); bool deferred_work_generated = false; edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated); /* Both operations should have gone directly to the fallback. */ expect_zu_eq(1, ta.alloc_count, ""); expect_zu_eq(1, ta.dalloc_count, ""); } TEST_END static void expect_stats_pages(tsdn_t *tsdn, sec_t *sec, size_t npages) { sec_stats_t stats; /* * Check that the stats merging accumulates rather than overwrites by * putting some (made up) data there to begin with. */ stats.bytes = 123; sec_stats_merge(tsdn, sec, &stats); assert_zu_le(npages * PAGE + 123, stats.bytes, ""); } TEST_BEGIN(test_stats_simple) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; enum { NITERS = 100, FLUSH_PAGES = 20, }; bool deferred_work_generated = false; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE, /* max_bytes */ FLUSH_PAGES * PAGE); edata_t *allocs[FLUSH_PAGES]; for (size_t i = 0; i < FLUSH_PAGES; i++) { allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_stats_pages(tsdn, &sec, 0); } /* Increase and decrease, without flushing. */ for (size_t i = 0; i < NITERS; i++) { for (size_t j = 0; j < FLUSH_PAGES / 2; j++) { pai_dalloc(tsdn, &sec.pai, allocs[j], &deferred_work_generated); expect_stats_pages(tsdn, &sec, j + 1); } for (size_t j = 0; j < FLUSH_PAGES / 2; j++) { allocs[j] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_stats_pages(tsdn, &sec, FLUSH_PAGES / 2 - j - 1); } } } TEST_END TEST_BEGIN(test_stats_auto_flush) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; enum { FLUSH_PAGES = 10, }; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE, /* max_bytes */ FLUSH_PAGES * PAGE); edata_t *extra_alloc0; edata_t *extra_alloc1; edata_t *allocs[2 * FLUSH_PAGES]; bool deferred_work_generated = false; extra_alloc0 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); extra_alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); for (size_t i = 0; i < 2 * FLUSH_PAGES; i++) { allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); } for (size_t i = 0; i < FLUSH_PAGES; i++) { pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated); } pai_dalloc(tsdn, &sec.pai, extra_alloc0, &deferred_work_generated); /* Flush the remaining pages; stats should still work. */ for (size_t i = 0; i < FLUSH_PAGES; i++) { pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES + i], &deferred_work_generated); } pai_dalloc(tsdn, &sec.pai, extra_alloc1, &deferred_work_generated); expect_stats_pages(tsdn, &sec, ta.alloc_count + ta.alloc_batch_count - ta.dalloc_count - ta.dalloc_batch_count); } TEST_END TEST_BEGIN(test_stats_manual_flush) { pai_test_allocator_t ta; pai_test_allocator_init(&ta); sec_t sec; /* See the note above -- we can't use the real tsd. */ tsdn_t *tsdn = TSDN_NULL; enum { FLUSH_PAGES = 10, }; test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE, /* max_bytes */ FLUSH_PAGES * PAGE); bool deferred_work_generated = false; edata_t *allocs[FLUSH_PAGES]; for (size_t i = 0; i < FLUSH_PAGES; i++) { allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false, /* guarded */ false, /* frequent_reuse */ false, &deferred_work_generated); expect_stats_pages(tsdn, &sec, 0); } /* Dalloc the first half of the allocations. */ for (size_t i = 0; i < FLUSH_PAGES / 2; i++) { pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated); expect_stats_pages(tsdn, &sec, i + 1); } sec_flush(tsdn, &sec); expect_stats_pages(tsdn, &sec, 0); /* Flush the remaining pages. */ for (size_t i = 0; i < FLUSH_PAGES / 2; i++) { pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES / 2 + i], &deferred_work_generated); expect_stats_pages(tsdn, &sec, i + 1); } sec_disable(tsdn, &sec); expect_stats_pages(tsdn, &sec, 0); } TEST_END int main(void) { return test( test_reuse, test_auto_flush, test_disable, test_flush, test_max_alloc_respected, test_expand_shrink_delegate, test_nshards_0, test_stats_simple, test_stats_auto_flush, test_stats_manual_flush); } redis-8.0.2/deps/jemalloc/test/unit/seq.c000066400000000000000000000036051501533116600202470ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/seq.h" typedef struct data_s data_t; struct data_s { int arr[10]; }; static void set_data(data_t *data, int num) { for (int i = 0; i < 10; i++) { data->arr[i] = num; } } static void expect_data(data_t *data) { int num = data->arr[0]; for (int i = 0; i < 10; i++) { expect_d_eq(num, data->arr[i], "Data consistency error"); } } seq_define(data_t, data) typedef struct thd_data_s thd_data_t; struct thd_data_s { seq_data_t data; }; static void * seq_reader_thd(void *arg) { thd_data_t *thd_data = (thd_data_t *)arg; int iter = 0; data_t local_data; while (iter < 1000 * 1000 - 1) { bool success = seq_try_load_data(&local_data, &thd_data->data); if (success) { expect_data(&local_data); expect_d_le(iter, local_data.arr[0], "Seq read went back in time."); iter = local_data.arr[0]; } } return NULL; } static void * seq_writer_thd(void *arg) { thd_data_t *thd_data = (thd_data_t *)arg; data_t local_data; memset(&local_data, 0, sizeof(local_data)); for (int i = 0; i < 1000 * 1000; i++) { set_data(&local_data, i); seq_store_data(&thd_data->data, &local_data); } return NULL; } TEST_BEGIN(test_seq_threaded) { thd_data_t thd_data; memset(&thd_data, 0, sizeof(thd_data)); thd_t reader; thd_t writer; thd_create(&reader, seq_reader_thd, &thd_data); thd_create(&writer, seq_writer_thd, &thd_data); thd_join(reader, NULL); thd_join(writer, NULL); } TEST_END TEST_BEGIN(test_seq_simple) { data_t data; seq_data_t seq; memset(&seq, 0, sizeof(seq)); for (int i = 0; i < 1000 * 1000; i++) { set_data(&data, i); seq_store_data(&seq, &data); set_data(&data, 0); bool success = seq_try_load_data(&data, &seq); expect_b_eq(success, true, "Failed non-racing read"); expect_data(&data); } } TEST_END int main(void) { return test_no_reentrancy( test_seq_simple, test_seq_threaded); } redis-8.0.2/deps/jemalloc/test/unit/size_check.c000066400000000000000000000034341501533116600215660ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/safety_check.h" bool fake_abort_called; void fake_abort(const char *message) { (void)message; fake_abort_called = true; } #define SMALL_SIZE1 SC_SMALL_MAXCLASS #define SMALL_SIZE2 (SC_SMALL_MAXCLASS / 2) #define LARGE_SIZE1 SC_LARGE_MINCLASS #define LARGE_SIZE2 (LARGE_SIZE1 * 2) void * test_invalid_size_pre(size_t sz) { safety_check_set_abort(&fake_abort); fake_abort_called = false; void *ptr = malloc(sz); assert_ptr_not_null(ptr, "Unexpected failure"); return ptr; } void test_invalid_size_post(void) { expect_true(fake_abort_called, "Safety check didn't fire"); safety_check_set_abort(NULL); } TEST_BEGIN(test_invalid_size_sdallocx) { test_skip_if(!config_opt_size_checks); void *ptr = test_invalid_size_pre(SMALL_SIZE1); sdallocx(ptr, SMALL_SIZE2, 0); test_invalid_size_post(); ptr = test_invalid_size_pre(LARGE_SIZE1); sdallocx(ptr, LARGE_SIZE2, 0); test_invalid_size_post(); } TEST_END TEST_BEGIN(test_invalid_size_sdallocx_nonzero_flag) { test_skip_if(!config_opt_size_checks); void *ptr = test_invalid_size_pre(SMALL_SIZE1); sdallocx(ptr, SMALL_SIZE2, MALLOCX_TCACHE_NONE); test_invalid_size_post(); ptr = test_invalid_size_pre(LARGE_SIZE1); sdallocx(ptr, LARGE_SIZE2, MALLOCX_TCACHE_NONE); test_invalid_size_post(); } TEST_END TEST_BEGIN(test_invalid_size_sdallocx_noflags) { test_skip_if(!config_opt_size_checks); void *ptr = test_invalid_size_pre(SMALL_SIZE1); je_sdallocx_noflags(ptr, SMALL_SIZE2); test_invalid_size_post(); ptr = test_invalid_size_pre(LARGE_SIZE1); je_sdallocx_noflags(ptr, LARGE_SIZE2); test_invalid_size_post(); } TEST_END int main(void) { return test( test_invalid_size_sdallocx, test_invalid_size_sdallocx_nonzero_flag, test_invalid_size_sdallocx_noflags); } redis-8.0.2/deps/jemalloc/test/unit/size_check.sh000066400000000000000000000001271501533116600217520ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:false" fi redis-8.0.2/deps/jemalloc/test/unit/size_classes.c000066400000000000000000000156061501533116600221520ustar00rootroot00000000000000#include "test/jemalloc_test.h" static size_t get_max_size_class(void) { unsigned nlextents; size_t mib[4]; size_t sz, miblen, max_size_class; sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.nlextents", (void *)&nlextents, &sz, NULL, 0), 0, "Unexpected mallctl() error"); miblen = sizeof(mib) / sizeof(size_t); expect_d_eq(mallctlnametomib("arenas.lextent.0.size", mib, &miblen), 0, "Unexpected mallctlnametomib() error"); mib[2] = nlextents - 1; sz = sizeof(size_t); expect_d_eq(mallctlbymib(mib, miblen, (void *)&max_size_class, &sz, NULL, 0), 0, "Unexpected mallctlbymib() error"); return max_size_class; } TEST_BEGIN(test_size_classes) { size_t size_class, max_size_class; szind_t index, max_index; max_size_class = get_max_size_class(); max_index = sz_size2index(max_size_class); for (index = 0, size_class = sz_index2size(index); index < max_index || size_class < max_size_class; index++, size_class = sz_index2size(index)) { expect_true(index < max_index, "Loop conditionals should be equivalent; index=%u, " "size_class=%zu (%#zx)", index, size_class, size_class); expect_true(size_class < max_size_class, "Loop conditionals should be equivalent; index=%u, " "size_class=%zu (%#zx)", index, size_class, size_class); expect_u_eq(index, sz_size2index(size_class), "sz_size2index() does not reverse sz_index2size(): index=%u" " --> size_class=%zu --> index=%u --> size_class=%zu", index, size_class, sz_size2index(size_class), sz_index2size(sz_size2index(size_class))); expect_zu_eq(size_class, sz_index2size(sz_size2index(size_class)), "sz_index2size() does not reverse sz_size2index(): index=%u" " --> size_class=%zu --> index=%u --> size_class=%zu", index, size_class, sz_size2index(size_class), sz_index2size(sz_size2index(size_class))); expect_u_eq(index+1, sz_size2index(size_class+1), "Next size_class does not round up properly"); expect_zu_eq(size_class, (index > 0) ? sz_s2u(sz_index2size(index-1)+1) : sz_s2u(1), "sz_s2u() does not round up to size class"); expect_zu_eq(size_class, sz_s2u(size_class-1), "sz_s2u() does not round up to size class"); expect_zu_eq(size_class, sz_s2u(size_class), "sz_s2u() does not compute same size class"); expect_zu_eq(sz_s2u(size_class+1), sz_index2size(index+1), "sz_s2u() does not round up to next size class"); } expect_u_eq(index, sz_size2index(sz_index2size(index)), "sz_size2index() does not reverse sz_index2size()"); expect_zu_eq(max_size_class, sz_index2size( sz_size2index(max_size_class)), "sz_index2size() does not reverse sz_size2index()"); expect_zu_eq(size_class, sz_s2u(sz_index2size(index-1)+1), "sz_s2u() does not round up to size class"); expect_zu_eq(size_class, sz_s2u(size_class-1), "sz_s2u() does not round up to size class"); expect_zu_eq(size_class, sz_s2u(size_class), "sz_s2u() does not compute same size class"); } TEST_END TEST_BEGIN(test_psize_classes) { size_t size_class, max_psz; pszind_t pind, max_pind; max_psz = get_max_size_class() + PAGE; max_pind = sz_psz2ind(max_psz); for (pind = 0, size_class = sz_pind2sz(pind); pind < max_pind || size_class < max_psz; pind++, size_class = sz_pind2sz(pind)) { expect_true(pind < max_pind, "Loop conditionals should be equivalent; pind=%u, " "size_class=%zu (%#zx)", pind, size_class, size_class); expect_true(size_class < max_psz, "Loop conditionals should be equivalent; pind=%u, " "size_class=%zu (%#zx)", pind, size_class, size_class); expect_u_eq(pind, sz_psz2ind(size_class), "sz_psz2ind() does not reverse sz_pind2sz(): pind=%u -->" " size_class=%zu --> pind=%u --> size_class=%zu", pind, size_class, sz_psz2ind(size_class), sz_pind2sz(sz_psz2ind(size_class))); expect_zu_eq(size_class, sz_pind2sz(sz_psz2ind(size_class)), "sz_pind2sz() does not reverse sz_psz2ind(): pind=%u -->" " size_class=%zu --> pind=%u --> size_class=%zu", pind, size_class, sz_psz2ind(size_class), sz_pind2sz(sz_psz2ind(size_class))); if (size_class == SC_LARGE_MAXCLASS) { expect_u_eq(SC_NPSIZES, sz_psz2ind(size_class + 1), "Next size_class does not round up properly"); } else { expect_u_eq(pind + 1, sz_psz2ind(size_class + 1), "Next size_class does not round up properly"); } expect_zu_eq(size_class, (pind > 0) ? sz_psz2u(sz_pind2sz(pind-1)+1) : sz_psz2u(1), "sz_psz2u() does not round up to size class"); expect_zu_eq(size_class, sz_psz2u(size_class-1), "sz_psz2u() does not round up to size class"); expect_zu_eq(size_class, sz_psz2u(size_class), "sz_psz2u() does not compute same size class"); expect_zu_eq(sz_psz2u(size_class+1), sz_pind2sz(pind+1), "sz_psz2u() does not round up to next size class"); } expect_u_eq(pind, sz_psz2ind(sz_pind2sz(pind)), "sz_psz2ind() does not reverse sz_pind2sz()"); expect_zu_eq(max_psz, sz_pind2sz(sz_psz2ind(max_psz)), "sz_pind2sz() does not reverse sz_psz2ind()"); expect_zu_eq(size_class, sz_psz2u(sz_pind2sz(pind-1)+1), "sz_psz2u() does not round up to size class"); expect_zu_eq(size_class, sz_psz2u(size_class-1), "sz_psz2u() does not round up to size class"); expect_zu_eq(size_class, sz_psz2u(size_class), "sz_psz2u() does not compute same size class"); } TEST_END TEST_BEGIN(test_overflow) { size_t max_size_class, max_psz; max_size_class = get_max_size_class(); max_psz = max_size_class + PAGE; expect_u_eq(sz_size2index(max_size_class+1), SC_NSIZES, "sz_size2index() should return NSIZES on overflow"); expect_u_eq(sz_size2index(ZU(PTRDIFF_MAX)+1), SC_NSIZES, "sz_size2index() should return NSIZES on overflow"); expect_u_eq(sz_size2index(SIZE_T_MAX), SC_NSIZES, "sz_size2index() should return NSIZES on overflow"); expect_zu_eq(sz_s2u(max_size_class+1), 0, "sz_s2u() should return 0 for unsupported size"); expect_zu_eq(sz_s2u(ZU(PTRDIFF_MAX)+1), 0, "sz_s2u() should return 0 for unsupported size"); expect_zu_eq(sz_s2u(SIZE_T_MAX), 0, "sz_s2u() should return 0 on overflow"); expect_u_eq(sz_psz2ind(max_size_class+1), SC_NPSIZES, "sz_psz2ind() should return NPSIZES on overflow"); expect_u_eq(sz_psz2ind(ZU(PTRDIFF_MAX)+1), SC_NPSIZES, "sz_psz2ind() should return NPSIZES on overflow"); expect_u_eq(sz_psz2ind(SIZE_T_MAX), SC_NPSIZES, "sz_psz2ind() should return NPSIZES on overflow"); expect_zu_eq(sz_psz2u(max_size_class+1), max_psz, "sz_psz2u() should return (LARGE_MAXCLASS + PAGE) for unsupported" " size"); expect_zu_eq(sz_psz2u(ZU(PTRDIFF_MAX)+1), max_psz, "sz_psz2u() should return (LARGE_MAXCLASS + PAGE) for unsupported " "size"); expect_zu_eq(sz_psz2u(SIZE_T_MAX), max_psz, "sz_psz2u() should return (LARGE_MAXCLASS + PAGE) on overflow"); } TEST_END int main(void) { return test( test_size_classes, test_psize_classes, test_overflow); } redis-8.0.2/deps/jemalloc/test/unit/slab.c000066400000000000000000000021551501533116600203770ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define INVALID_ARENA_IND ((1U << MALLOCX_ARENA_BITS) - 1) TEST_BEGIN(test_arena_slab_regind) { szind_t binind; for (binind = 0; binind < SC_NBINS; binind++) { size_t regind; edata_t slab; const bin_info_t *bin_info = &bin_infos[binind]; edata_init(&slab, INVALID_ARENA_IND, mallocx(bin_info->slab_size, MALLOCX_LG_ALIGN(LG_PAGE)), bin_info->slab_size, true, binind, 0, extent_state_active, false, true, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); expect_ptr_not_null(edata_addr_get(&slab), "Unexpected malloc() failure"); arena_dalloc_bin_locked_info_t dalloc_info; arena_dalloc_bin_locked_begin(&dalloc_info, binind); for (regind = 0; regind < bin_info->nregs; regind++) { void *reg = (void *)((uintptr_t)edata_addr_get(&slab) + (bin_info->reg_size * regind)); expect_zu_eq(arena_slab_regind(&dalloc_info, binind, &slab, reg), regind, "Incorrect region index computed for size %zu", bin_info->reg_size); } free(edata_addr_get(&slab)); } } TEST_END int main(void) { return test( test_arena_slab_regind); } redis-8.0.2/deps/jemalloc/test/unit/smoothstep.c000066400000000000000000000052541501533116600216660ustar00rootroot00000000000000#include "test/jemalloc_test.h" static const uint64_t smoothstep_tab[] = { #define STEP(step, h, x, y) \ h, SMOOTHSTEP #undef STEP }; TEST_BEGIN(test_smoothstep_integral) { uint64_t sum, min, max; unsigned i; /* * The integral of smoothstep in the [0..1] range equals 1/2. Verify * that the fixed point representation's integral is no more than * rounding error distant from 1/2. Regarding rounding, each table * element is rounded down to the nearest fixed point value, so the * integral may be off by as much as SMOOTHSTEP_NSTEPS ulps. */ sum = 0; for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) { sum += smoothstep_tab[i]; } max = (KQU(1) << (SMOOTHSTEP_BFP-1)) * (SMOOTHSTEP_NSTEPS+1); min = max - SMOOTHSTEP_NSTEPS; expect_u64_ge(sum, min, "Integral too small, even accounting for truncation"); expect_u64_le(sum, max, "Integral exceeds 1/2"); if (false) { malloc_printf("%"FMTu64" ulps under 1/2 (limit %d)\n", max - sum, SMOOTHSTEP_NSTEPS); } } TEST_END TEST_BEGIN(test_smoothstep_monotonic) { uint64_t prev_h; unsigned i; /* * The smoothstep function is monotonic in [0..1], i.e. its slope is * non-negative. In practice we want to parametrize table generation * such that piecewise slope is greater than zero, but do not require * that here. */ prev_h = 0; for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) { uint64_t h = smoothstep_tab[i]; expect_u64_ge(h, prev_h, "Piecewise non-monotonic, i=%u", i); prev_h = h; } expect_u64_eq(smoothstep_tab[SMOOTHSTEP_NSTEPS-1], (KQU(1) << SMOOTHSTEP_BFP), "Last step must equal 1"); } TEST_END TEST_BEGIN(test_smoothstep_slope) { uint64_t prev_h, prev_delta; unsigned i; /* * The smoothstep slope strictly increases until x=0.5, and then * strictly decreases until x=1.0. Verify the slightly weaker * requirement of monotonicity, so that inadequate table precision does * not cause false test failures. */ prev_h = 0; prev_delta = 0; for (i = 0; i < SMOOTHSTEP_NSTEPS / 2 + SMOOTHSTEP_NSTEPS % 2; i++) { uint64_t h = smoothstep_tab[i]; uint64_t delta = h - prev_h; expect_u64_ge(delta, prev_delta, "Slope must monotonically increase in 0.0 <= x <= 0.5, " "i=%u", i); prev_h = h; prev_delta = delta; } prev_h = KQU(1) << SMOOTHSTEP_BFP; prev_delta = 0; for (i = SMOOTHSTEP_NSTEPS-1; i >= SMOOTHSTEP_NSTEPS / 2; i--) { uint64_t h = smoothstep_tab[i]; uint64_t delta = prev_h - h; expect_u64_ge(delta, prev_delta, "Slope must monotonically decrease in 0.5 <= x <= 1.0, " "i=%u", i); prev_h = h; prev_delta = delta; } } TEST_END int main(void) { return test( test_smoothstep_integral, test_smoothstep_monotonic, test_smoothstep_slope); } redis-8.0.2/deps/jemalloc/test/unit/spin.c000066400000000000000000000004051501533116600204230ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/spin.h" TEST_BEGIN(test_spin) { spin_t spinner = SPIN_INITIALIZER; for (unsigned i = 0; i < 100; i++) { spin_adaptive(&spinner); } } TEST_END int main(void) { return test( test_spin); } redis-8.0.2/deps/jemalloc/test/unit/stats.c000066400000000000000000000344001501533116600206120ustar00rootroot00000000000000#include "test/jemalloc_test.h" #define STRINGIFY_HELPER(x) #x #define STRINGIFY(x) STRINGIFY_HELPER(x) TEST_BEGIN(test_stats_summary) { size_t sz, allocated, active, resident, mapped; int expected = config_stats ? 0 : ENOENT; sz = sizeof(size_t); expect_d_eq(mallctl("stats.allocated", (void *)&allocated, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.active", (void *)&active, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.resident", (void *)&resident, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.mapped", (void *)&mapped, &sz, NULL, 0), expected, "Unexpected mallctl() result"); if (config_stats) { expect_zu_le(allocated, active, "allocated should be no larger than active"); expect_zu_lt(active, resident, "active should be less than resident"); expect_zu_lt(active, mapped, "active should be less than mapped"); } } TEST_END TEST_BEGIN(test_stats_large) { void *p; uint64_t epoch; size_t allocated; uint64_t nmalloc, ndalloc, nrequests; size_t sz; int expected = config_stats ? 0 : ENOENT; p = mallocx(SC_SMALL_MAXCLASS + 1, MALLOCX_ARENA(0)); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); sz = sizeof(size_t); expect_d_eq(mallctl("stats.arenas.0.large.allocated", (void *)&allocated, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(uint64_t); expect_d_eq(mallctl("stats.arenas.0.large.nmalloc", (void *)&nmalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.large.ndalloc", (void *)&ndalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.large.nrequests", (void *)&nrequests, &sz, NULL, 0), expected, "Unexpected mallctl() result"); if (config_stats) { expect_zu_gt(allocated, 0, "allocated should be greater than zero"); expect_u64_ge(nmalloc, ndalloc, "nmalloc should be at least as large as ndalloc"); expect_u64_le(nmalloc, nrequests, "nmalloc should no larger than nrequests"); } dallocx(p, 0); } TEST_END TEST_BEGIN(test_stats_arenas_summary) { void *little, *large; uint64_t epoch; size_t sz; int expected = config_stats ? 0 : ENOENT; size_t mapped; uint64_t dirty_npurge, dirty_nmadvise, dirty_purged; uint64_t muzzy_npurge, muzzy_nmadvise, muzzy_purged; little = mallocx(SC_SMALL_MAXCLASS, MALLOCX_ARENA(0)); expect_ptr_not_null(little, "Unexpected mallocx() failure"); large = mallocx((1U << SC_LG_LARGE_MINCLASS), MALLOCX_ARENA(0)); expect_ptr_not_null(large, "Unexpected mallocx() failure"); dallocx(little, 0); dallocx(large, 0); expect_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result"); expect_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0, "Unexpected mallctl() failure"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); sz = sizeof(size_t); expect_d_eq(mallctl("stats.arenas.0.mapped", (void *)&mapped, &sz, NULL, 0), expected, "Unexepected mallctl() result"); sz = sizeof(uint64_t); expect_d_eq(mallctl("stats.arenas.0.dirty_npurge", (void *)&dirty_npurge, &sz, NULL, 0), expected, "Unexepected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.dirty_nmadvise", (void *)&dirty_nmadvise, &sz, NULL, 0), expected, "Unexepected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.dirty_purged", (void *)&dirty_purged, &sz, NULL, 0), expected, "Unexepected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.muzzy_npurge", (void *)&muzzy_npurge, &sz, NULL, 0), expected, "Unexepected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.muzzy_nmadvise", (void *)&muzzy_nmadvise, &sz, NULL, 0), expected, "Unexepected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.muzzy_purged", (void *)&muzzy_purged, &sz, NULL, 0), expected, "Unexepected mallctl() result"); if (config_stats) { if (!is_background_thread_enabled() && !opt_hpa) { expect_u64_gt(dirty_npurge + muzzy_npurge, 0, "At least one purge should have occurred"); } expect_u64_le(dirty_nmadvise, dirty_purged, "dirty_nmadvise should be no greater than dirty_purged"); expect_u64_le(muzzy_nmadvise, muzzy_purged, "muzzy_nmadvise should be no greater than muzzy_purged"); } } TEST_END void * thd_start(void *arg) { return NULL; } static void no_lazy_lock(void) { thd_t thd; thd_create(&thd, thd_start, NULL); thd_join(thd, NULL); } TEST_BEGIN(test_stats_arenas_small) { void *p; size_t sz, allocated; uint64_t epoch, nmalloc, ndalloc, nrequests; int expected = config_stats ? 0 : ENOENT; no_lazy_lock(); /* Lazy locking would dodge tcache testing. */ p = mallocx(SC_SMALL_MAXCLASS, MALLOCX_ARENA(0)); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); sz = sizeof(size_t); expect_d_eq(mallctl("stats.arenas.0.small.allocated", (void *)&allocated, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(uint64_t); expect_d_eq(mallctl("stats.arenas.0.small.nmalloc", (void *)&nmalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.small.ndalloc", (void *)&ndalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.small.nrequests", (void *)&nrequests, &sz, NULL, 0), expected, "Unexpected mallctl() result"); if (config_stats) { expect_zu_gt(allocated, 0, "allocated should be greater than zero"); expect_u64_gt(nmalloc, 0, "nmalloc should be no greater than zero"); expect_u64_ge(nmalloc, ndalloc, "nmalloc should be at least as large as ndalloc"); expect_u64_gt(nrequests, 0, "nrequests should be greater than zero"); } dallocx(p, 0); } TEST_END TEST_BEGIN(test_stats_arenas_large) { void *p; size_t sz, allocated; uint64_t epoch, nmalloc, ndalloc; int expected = config_stats ? 0 : ENOENT; p = mallocx((1U << SC_LG_LARGE_MINCLASS), MALLOCX_ARENA(0)); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); sz = sizeof(size_t); expect_d_eq(mallctl("stats.arenas.0.large.allocated", (void *)&allocated, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(uint64_t); expect_d_eq(mallctl("stats.arenas.0.large.nmalloc", (void *)&nmalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.large.ndalloc", (void *)&ndalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); if (config_stats) { expect_zu_gt(allocated, 0, "allocated should be greater than zero"); expect_u64_gt(nmalloc, 0, "nmalloc should be greater than zero"); expect_u64_ge(nmalloc, ndalloc, "nmalloc should be at least as large as ndalloc"); } dallocx(p, 0); } TEST_END static void gen_mallctl_str(char *cmd, char *name, unsigned arena_ind) { sprintf(cmd, "stats.arenas.%u.bins.0.%s", arena_ind, name); } TEST_BEGIN(test_stats_arenas_bins) { void *p; size_t sz, curslabs, curregs, nonfull_slabs; uint64_t epoch, nmalloc, ndalloc, nrequests, nfills, nflushes; uint64_t nslabs, nreslabs; int expected = config_stats ? 0 : ENOENT; /* Make sure allocation below isn't satisfied by tcache. */ expect_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result"); unsigned arena_ind, old_arena_ind; sz = sizeof(unsigned); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Arena creation failure"); sz = sizeof(arena_ind); expect_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz, (void *)&arena_ind, sizeof(arena_ind)), 0, "Unexpected mallctl() failure"); p = malloc(bin_infos[0].reg_size); expect_ptr_not_null(p, "Unexpected malloc() failure"); expect_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); char cmd[128]; sz = sizeof(uint64_t); gen_mallctl_str(cmd, "nmalloc", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nmalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); gen_mallctl_str(cmd, "ndalloc", arena_ind); expect_d_eq(mallctl(cmd, (void *)&ndalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); gen_mallctl_str(cmd, "nrequests", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nrequests, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(size_t); gen_mallctl_str(cmd, "curregs", arena_ind); expect_d_eq(mallctl(cmd, (void *)&curregs, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(uint64_t); gen_mallctl_str(cmd, "nfills", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nfills, &sz, NULL, 0), expected, "Unexpected mallctl() result"); gen_mallctl_str(cmd, "nflushes", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nflushes, &sz, NULL, 0), expected, "Unexpected mallctl() result"); gen_mallctl_str(cmd, "nslabs", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nslabs, &sz, NULL, 0), expected, "Unexpected mallctl() result"); gen_mallctl_str(cmd, "nreslabs", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nreslabs, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(size_t); gen_mallctl_str(cmd, "curslabs", arena_ind); expect_d_eq(mallctl(cmd, (void *)&curslabs, &sz, NULL, 0), expected, "Unexpected mallctl() result"); gen_mallctl_str(cmd, "nonfull_slabs", arena_ind); expect_d_eq(mallctl(cmd, (void *)&nonfull_slabs, &sz, NULL, 0), expected, "Unexpected mallctl() result"); if (config_stats) { expect_u64_gt(nmalloc, 0, "nmalloc should be greater than zero"); expect_u64_ge(nmalloc, ndalloc, "nmalloc should be at least as large as ndalloc"); expect_u64_gt(nrequests, 0, "nrequests should be greater than zero"); expect_zu_gt(curregs, 0, "allocated should be greater than zero"); if (opt_tcache) { expect_u64_gt(nfills, 0, "At least one fill should have occurred"); expect_u64_gt(nflushes, 0, "At least one flush should have occurred"); } expect_u64_gt(nslabs, 0, "At least one slab should have been allocated"); expect_zu_gt(curslabs, 0, "At least one slab should be currently allocated"); expect_zu_eq(nonfull_slabs, 0, "slabs_nonfull should be empty"); } dallocx(p, 0); } TEST_END TEST_BEGIN(test_stats_arenas_lextents) { void *p; uint64_t epoch, nmalloc, ndalloc; size_t curlextents, sz, hsize; int expected = config_stats ? 0 : ENOENT; sz = sizeof(size_t); expect_d_eq(mallctl("arenas.lextent.0.size", (void *)&hsize, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); p = mallocx(hsize, MALLOCX_ARENA(0)); expect_ptr_not_null(p, "Unexpected mallocx() failure"); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); sz = sizeof(uint64_t); expect_d_eq(mallctl("stats.arenas.0.lextents.0.nmalloc", (void *)&nmalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); expect_d_eq(mallctl("stats.arenas.0.lextents.0.ndalloc", (void *)&ndalloc, &sz, NULL, 0), expected, "Unexpected mallctl() result"); sz = sizeof(size_t); expect_d_eq(mallctl("stats.arenas.0.lextents.0.curlextents", (void *)&curlextents, &sz, NULL, 0), expected, "Unexpected mallctl() result"); if (config_stats) { expect_u64_gt(nmalloc, 0, "nmalloc should be greater than zero"); expect_u64_ge(nmalloc, ndalloc, "nmalloc should be at least as large as ndalloc"); expect_u64_gt(curlextents, 0, "At least one extent should be currently allocated"); } dallocx(p, 0); } TEST_END static void test_tcache_bytes_for_usize(size_t usize) { uint64_t epoch; size_t tcache_bytes, tcache_stashed_bytes; size_t sz = sizeof(tcache_bytes); void *ptr = mallocx(usize, 0); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); assert_d_eq(mallctl( "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL) ".tcache_bytes", &tcache_bytes, &sz, NULL, 0), 0, "Unexpected mallctl failure"); assert_d_eq(mallctl( "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL) ".tcache_stashed_bytes", &tcache_stashed_bytes, &sz, NULL, 0), 0, "Unexpected mallctl failure"); size_t tcache_bytes_before = tcache_bytes + tcache_stashed_bytes; dallocx(ptr, 0); expect_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); assert_d_eq(mallctl( "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL) ".tcache_bytes", &tcache_bytes, &sz, NULL, 0), 0, "Unexpected mallctl failure"); assert_d_eq(mallctl( "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL) ".tcache_stashed_bytes", &tcache_stashed_bytes, &sz, NULL, 0), 0, "Unexpected mallctl failure"); size_t tcache_bytes_after = tcache_bytes + tcache_stashed_bytes; assert_zu_eq(tcache_bytes_after - tcache_bytes_before, usize, "Incorrectly attributed a free"); } TEST_BEGIN(test_stats_tcache_bytes_small) { test_skip_if(!config_stats); test_skip_if(!opt_tcache); test_skip_if(opt_tcache_max < SC_SMALL_MAXCLASS); test_tcache_bytes_for_usize(SC_SMALL_MAXCLASS); } TEST_END TEST_BEGIN(test_stats_tcache_bytes_large) { test_skip_if(!config_stats); test_skip_if(!opt_tcache); test_skip_if(opt_tcache_max < SC_LARGE_MINCLASS); test_tcache_bytes_for_usize(SC_LARGE_MINCLASS); } TEST_END int main(void) { return test_no_reentrancy( test_stats_summary, test_stats_large, test_stats_arenas_summary, test_stats_arenas_small, test_stats_arenas_large, test_stats_arenas_bins, test_stats_arenas_lextents, test_stats_tcache_bytes_small, test_stats_tcache_bytes_large); } redis-8.0.2/deps/jemalloc/test/unit/stats_print.c000066400000000000000000000550511501533116600220330ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/util.h" typedef enum { TOKEN_TYPE_NONE, TOKEN_TYPE_ERROR, TOKEN_TYPE_EOI, TOKEN_TYPE_NULL, TOKEN_TYPE_FALSE, TOKEN_TYPE_TRUE, TOKEN_TYPE_LBRACKET, TOKEN_TYPE_RBRACKET, TOKEN_TYPE_LBRACE, TOKEN_TYPE_RBRACE, TOKEN_TYPE_COLON, TOKEN_TYPE_COMMA, TOKEN_TYPE_STRING, TOKEN_TYPE_NUMBER } token_type_t; typedef struct parser_s parser_t; typedef struct { parser_t *parser; token_type_t token_type; size_t pos; size_t len; size_t line; size_t col; } token_t; struct parser_s { bool verbose; char *buf; /* '\0'-terminated. */ size_t len; /* Number of characters preceding '\0' in buf. */ size_t pos; size_t line; size_t col; token_t token; }; static void token_init(token_t *token, parser_t *parser, token_type_t token_type, size_t pos, size_t len, size_t line, size_t col) { token->parser = parser; token->token_type = token_type; token->pos = pos; token->len = len; token->line = line; token->col = col; } static void token_error(token_t *token) { if (!token->parser->verbose) { return; } switch (token->token_type) { case TOKEN_TYPE_NONE: not_reached(); case TOKEN_TYPE_ERROR: malloc_printf("%zu:%zu: Unexpected character in token: ", token->line, token->col); break; default: malloc_printf("%zu:%zu: Unexpected token: ", token->line, token->col); break; } UNUSED ssize_t err = malloc_write_fd(STDERR_FILENO, &token->parser->buf[token->pos], token->len); malloc_printf("\n"); } static void parser_init(parser_t *parser, bool verbose) { parser->verbose = verbose; parser->buf = NULL; parser->len = 0; parser->pos = 0; parser->line = 1; parser->col = 0; } static void parser_fini(parser_t *parser) { if (parser->buf != NULL) { dallocx(parser->buf, MALLOCX_TCACHE_NONE); } } static bool parser_append(parser_t *parser, const char *str) { size_t len = strlen(str); char *buf = (parser->buf == NULL) ? mallocx(len + 1, MALLOCX_TCACHE_NONE) : rallocx(parser->buf, parser->len + len + 1, MALLOCX_TCACHE_NONE); if (buf == NULL) { return true; } memcpy(&buf[parser->len], str, len + 1); parser->buf = buf; parser->len += len; return false; } static bool parser_tokenize(parser_t *parser) { enum { STATE_START, STATE_EOI, STATE_N, STATE_NU, STATE_NUL, STATE_NULL, STATE_F, STATE_FA, STATE_FAL, STATE_FALS, STATE_FALSE, STATE_T, STATE_TR, STATE_TRU, STATE_TRUE, STATE_LBRACKET, STATE_RBRACKET, STATE_LBRACE, STATE_RBRACE, STATE_COLON, STATE_COMMA, STATE_CHARS, STATE_CHAR_ESCAPE, STATE_CHAR_U, STATE_CHAR_UD, STATE_CHAR_UDD, STATE_CHAR_UDDD, STATE_STRING, STATE_MINUS, STATE_LEADING_ZERO, STATE_DIGITS, STATE_DECIMAL, STATE_FRAC_DIGITS, STATE_EXP, STATE_EXP_SIGN, STATE_EXP_DIGITS, STATE_ACCEPT } state = STATE_START; size_t token_pos JEMALLOC_CC_SILENCE_INIT(0); size_t token_line JEMALLOC_CC_SILENCE_INIT(1); size_t token_col JEMALLOC_CC_SILENCE_INIT(0); expect_zu_le(parser->pos, parser->len, "Position is past end of buffer"); while (state != STATE_ACCEPT) { char c = parser->buf[parser->pos]; switch (state) { case STATE_START: token_pos = parser->pos; token_line = parser->line; token_col = parser->col; switch (c) { case ' ': case '\b': case '\n': case '\r': case '\t': break; case '\0': state = STATE_EOI; break; case 'n': state = STATE_N; break; case 'f': state = STATE_F; break; case 't': state = STATE_T; break; case '[': state = STATE_LBRACKET; break; case ']': state = STATE_RBRACKET; break; case '{': state = STATE_LBRACE; break; case '}': state = STATE_RBRACE; break; case ':': state = STATE_COLON; break; case ',': state = STATE_COMMA; break; case '"': state = STATE_CHARS; break; case '-': state = STATE_MINUS; break; case '0': state = STATE_LEADING_ZERO; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = STATE_DIGITS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_EOI: token_init(&parser->token, parser, TOKEN_TYPE_EOI, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_N: switch (c) { case 'u': state = STATE_NU; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_NU: switch (c) { case 'l': state = STATE_NUL; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_NUL: switch (c) { case 'l': state = STATE_NULL; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_NULL: switch (c) { case ' ': case '\b': case '\n': case '\r': case '\t': case '\0': case '[': case ']': case '{': case '}': case ':': case ',': break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } token_init(&parser->token, parser, TOKEN_TYPE_NULL, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_F: switch (c) { case 'a': state = STATE_FA; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_FA: switch (c) { case 'l': state = STATE_FAL; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_FAL: switch (c) { case 's': state = STATE_FALS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_FALS: switch (c) { case 'e': state = STATE_FALSE; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_FALSE: switch (c) { case ' ': case '\b': case '\n': case '\r': case '\t': case '\0': case '[': case ']': case '{': case '}': case ':': case ',': break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } token_init(&parser->token, parser, TOKEN_TYPE_FALSE, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_T: switch (c) { case 'r': state = STATE_TR; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_TR: switch (c) { case 'u': state = STATE_TRU; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_TRU: switch (c) { case 'e': state = STATE_TRUE; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_TRUE: switch (c) { case ' ': case '\b': case '\n': case '\r': case '\t': case '\0': case '[': case ']': case '{': case '}': case ':': case ',': break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } token_init(&parser->token, parser, TOKEN_TYPE_TRUE, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_LBRACKET: token_init(&parser->token, parser, TOKEN_TYPE_LBRACKET, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_RBRACKET: token_init(&parser->token, parser, TOKEN_TYPE_RBRACKET, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_LBRACE: token_init(&parser->token, parser, TOKEN_TYPE_LBRACE, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_RBRACE: token_init(&parser->token, parser, TOKEN_TYPE_RBRACE, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_COLON: token_init(&parser->token, parser, TOKEN_TYPE_COLON, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_COMMA: token_init(&parser->token, parser, TOKEN_TYPE_COMMA, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_CHARS: switch (c) { case '\\': state = STATE_CHAR_ESCAPE; break; case '"': state = STATE_STRING; break; case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; default: break; } break; case STATE_CHAR_ESCAPE: switch (c) { case '"': case '\\': case '/': case 'b': case 'n': case 'r': case 't': state = STATE_CHARS; break; case 'u': state = STATE_CHAR_U; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_CHAR_U: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': state = STATE_CHAR_UD; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_CHAR_UD: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': state = STATE_CHAR_UDD; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_CHAR_UDD: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': state = STATE_CHAR_UDDD; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_CHAR_UDDD: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': state = STATE_CHARS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_STRING: token_init(&parser->token, parser, TOKEN_TYPE_STRING, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; case STATE_MINUS: switch (c) { case '0': state = STATE_LEADING_ZERO; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = STATE_DIGITS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_LEADING_ZERO: switch (c) { case '.': state = STATE_DECIMAL; break; default: token_init(&parser->token, parser, TOKEN_TYPE_NUMBER, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; } break; case STATE_DIGITS: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; case '.': state = STATE_DECIMAL; break; default: token_init(&parser->token, parser, TOKEN_TYPE_NUMBER, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; } break; case STATE_DECIMAL: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = STATE_FRAC_DIGITS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_FRAC_DIGITS: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; case 'e': case 'E': state = STATE_EXP; break; default: token_init(&parser->token, parser, TOKEN_TYPE_NUMBER, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; } break; case STATE_EXP: switch (c) { case '-': case '+': state = STATE_EXP_SIGN; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = STATE_EXP_DIGITS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_EXP_SIGN: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': state = STATE_EXP_DIGITS; break; default: token_init(&parser->token, parser, TOKEN_TYPE_ERROR, token_pos, parser->pos + 1 - token_pos, token_line, token_col); return true; } break; case STATE_EXP_DIGITS: switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; default: token_init(&parser->token, parser, TOKEN_TYPE_NUMBER, token_pos, parser->pos - token_pos, token_line, token_col); state = STATE_ACCEPT; break; } break; default: not_reached(); } if (state != STATE_ACCEPT) { if (c == '\n') { parser->line++; parser->col = 0; } else { parser->col++; } parser->pos++; } } return false; } static bool parser_parse_array(parser_t *parser); static bool parser_parse_object(parser_t *parser); static bool parser_parse_value(parser_t *parser) { switch (parser->token.token_type) { case TOKEN_TYPE_NULL: case TOKEN_TYPE_FALSE: case TOKEN_TYPE_TRUE: case TOKEN_TYPE_STRING: case TOKEN_TYPE_NUMBER: return false; case TOKEN_TYPE_LBRACE: return parser_parse_object(parser); case TOKEN_TYPE_LBRACKET: return parser_parse_array(parser); default: return true; } not_reached(); } static bool parser_parse_pair(parser_t *parser) { expect_d_eq(parser->token.token_type, TOKEN_TYPE_STRING, "Pair should start with string"); if (parser_tokenize(parser)) { return true; } switch (parser->token.token_type) { case TOKEN_TYPE_COLON: if (parser_tokenize(parser)) { return true; } return parser_parse_value(parser); default: return true; } } static bool parser_parse_values(parser_t *parser) { if (parser_parse_value(parser)) { return true; } while (true) { if (parser_tokenize(parser)) { return true; } switch (parser->token.token_type) { case TOKEN_TYPE_COMMA: if (parser_tokenize(parser)) { return true; } if (parser_parse_value(parser)) { return true; } break; case TOKEN_TYPE_RBRACKET: return false; default: return true; } } } static bool parser_parse_array(parser_t *parser) { expect_d_eq(parser->token.token_type, TOKEN_TYPE_LBRACKET, "Array should start with ["); if (parser_tokenize(parser)) { return true; } switch (parser->token.token_type) { case TOKEN_TYPE_RBRACKET: return false; default: return parser_parse_values(parser); } not_reached(); } static bool parser_parse_pairs(parser_t *parser) { expect_d_eq(parser->token.token_type, TOKEN_TYPE_STRING, "Object should start with string"); if (parser_parse_pair(parser)) { return true; } while (true) { if (parser_tokenize(parser)) { return true; } switch (parser->token.token_type) { case TOKEN_TYPE_COMMA: if (parser_tokenize(parser)) { return true; } switch (parser->token.token_type) { case TOKEN_TYPE_STRING: if (parser_parse_pair(parser)) { return true; } break; default: return true; } break; case TOKEN_TYPE_RBRACE: return false; default: return true; } } } static bool parser_parse_object(parser_t *parser) { expect_d_eq(parser->token.token_type, TOKEN_TYPE_LBRACE, "Object should start with {"); if (parser_tokenize(parser)) { return true; } switch (parser->token.token_type) { case TOKEN_TYPE_STRING: return parser_parse_pairs(parser); case TOKEN_TYPE_RBRACE: return false; default: return true; } not_reached(); } static bool parser_parse(parser_t *parser) { if (parser_tokenize(parser)) { goto label_error; } if (parser_parse_value(parser)) { goto label_error; } if (parser_tokenize(parser)) { goto label_error; } switch (parser->token.token_type) { case TOKEN_TYPE_EOI: return false; default: goto label_error; } not_reached(); label_error: token_error(&parser->token); return true; } TEST_BEGIN(test_json_parser) { size_t i; const char *invalid_inputs[] = { /* Tokenizer error case tests. */ "{ \"string\": X }", "{ \"string\": nXll }", "{ \"string\": nuXl }", "{ \"string\": nulX }", "{ \"string\": nullX }", "{ \"string\": fXlse }", "{ \"string\": faXse }", "{ \"string\": falXe }", "{ \"string\": falsX }", "{ \"string\": falseX }", "{ \"string\": tXue }", "{ \"string\": trXe }", "{ \"string\": truX }", "{ \"string\": trueX }", "{ \"string\": \"\n\" }", "{ \"string\": \"\\z\" }", "{ \"string\": \"\\uX000\" }", "{ \"string\": \"\\u0X00\" }", "{ \"string\": \"\\u00X0\" }", "{ \"string\": \"\\u000X\" }", "{ \"string\": -X }", "{ \"string\": 0.X }", "{ \"string\": 0.0eX }", "{ \"string\": 0.0e+X }", /* Parser error test cases. */ "{\"string\": }", "{\"string\" }", "{\"string\": [ 0 }", "{\"string\": {\"a\":0, 1 } }", "{\"string\": {\"a\":0: } }", "{", "{}{", }; const char *valid_inputs[] = { /* Token tests. */ "null", "false", "true", "{}", "{\"a\": 0}", "[]", "[0, 1]", "0", "1", "10", "-10", "10.23", "10.23e4", "10.23e-4", "10.23e+4", "10.23E4", "10.23E-4", "10.23E+4", "-10.23", "-10.23e4", "-10.23e-4", "-10.23e+4", "-10.23E4", "-10.23E-4", "-10.23E+4", "\"value\"", "\" \\\" \\/ \\b \\n \\r \\t \\u0abc \\u1DEF \"", /* Parser test with various nesting. */ "{\"a\":null, \"b\":[1,[{\"c\":2},3]], \"d\":{\"e\":true}}", }; for (i = 0; i < sizeof(invalid_inputs)/sizeof(const char *); i++) { const char *input = invalid_inputs[i]; parser_t parser; parser_init(&parser, false); expect_false(parser_append(&parser, input), "Unexpected input appending failure"); expect_true(parser_parse(&parser), "Unexpected parse success for input: %s", input); parser_fini(&parser); } for (i = 0; i < sizeof(valid_inputs)/sizeof(const char *); i++) { const char *input = valid_inputs[i]; parser_t parser; parser_init(&parser, true); expect_false(parser_append(&parser, input), "Unexpected input appending failure"); expect_false(parser_parse(&parser), "Unexpected parse error for input: %s", input); parser_fini(&parser); } } TEST_END void write_cb(void *opaque, const char *str) { parser_t *parser = (parser_t *)opaque; if (parser_append(parser, str)) { test_fail("Unexpected input appending failure"); } } TEST_BEGIN(test_stats_print_json) { const char *opts[] = { "J", "Jg", "Jm", "Jd", "Jmd", "Jgd", "Jgm", "Jgmd", "Ja", "Jb", "Jl", "Jx", "Jbl", "Jal", "Jab", "Jabl", "Jax", "Jbx", "Jlx", "Jablx", "Jgmdablx", }; unsigned arena_ind, i; for (i = 0; i < 3; i++) { unsigned j; switch (i) { case 0: break; case 1: { size_t sz = sizeof(arena_ind); expect_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0), 0, "Unexpected mallctl failure"); break; } case 2: { size_t mib[3]; size_t miblen = sizeof(mib)/sizeof(size_t); expect_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0, "Unexpected mallctlnametomib failure"); mib[1] = arena_ind; expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0, "Unexpected mallctlbymib failure"); break; } default: not_reached(); } for (j = 0; j < sizeof(opts)/sizeof(const char *); j++) { parser_t parser; parser_init(&parser, true); malloc_stats_print(write_cb, (void *)&parser, opts[j]); expect_false(parser_parse(&parser), "Unexpected parse error, opts=\"%s\"", opts[j]); parser_fini(&parser); } } } TEST_END int main(void) { return test( test_json_parser, test_stats_print_json); } redis-8.0.2/deps/jemalloc/test/unit/sz.c000066400000000000000000000037531501533116600201170ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_sz_psz2ind) { /* * Testing page size classes which reside prior to the regular group * with all size classes divisible by page size. * For x86_64 Linux, it's 4096, 8192, 12288, 16384, with corresponding * pszind 0, 1, 2 and 3. */ for (size_t i = 0; i < SC_NGROUP; i++) { for (size_t psz = i * PAGE + 1; psz <= (i + 1) * PAGE; psz++) { pszind_t ind = sz_psz2ind(psz); expect_zu_eq(ind, i, "Got %u as sz_psz2ind of %zu", ind, psz); } } sc_data_t data; memset(&data, 0, sizeof(data)); sc_data_init(&data); /* * 'base' is the base of the first regular group with all size classes * divisible by page size. * For x86_64 Linux, it's 16384, and base_ind is 36. */ size_t base_psz = 1 << (SC_LG_NGROUP + LG_PAGE); size_t base_ind = 0; while (base_ind < SC_NSIZES && reg_size_compute(data.sc[base_ind].lg_base, data.sc[base_ind].lg_delta, data.sc[base_ind].ndelta) < base_psz) { base_ind++; } expect_zu_eq( reg_size_compute(data.sc[base_ind].lg_base, data.sc[base_ind].lg_delta, data.sc[base_ind].ndelta), base_psz, "Size class equal to %zu not found", base_psz); /* * Test different sizes falling into groups after the 'base'. The * increment is PAGE / 3 for the execution speed purpose. */ base_ind -= SC_NGROUP; for (size_t psz = base_psz; psz <= 64 * 1024 * 1024; psz += PAGE / 3) { pszind_t ind = sz_psz2ind(psz); sc_t gt_sc = data.sc[ind + base_ind]; expect_zu_gt(psz, reg_size_compute(gt_sc.lg_base, gt_sc.lg_delta, gt_sc.ndelta), "Got %u as sz_psz2ind of %zu", ind, psz); sc_t le_sc = data.sc[ind + base_ind + 1]; expect_zu_le(psz, reg_size_compute(le_sc.lg_base, le_sc.lg_delta, le_sc.ndelta), "Got %u as sz_psz2ind of %zu", ind, psz); } pszind_t max_ind = sz_psz2ind(SC_LARGE_MAXCLASS + 1); expect_lu_eq(max_ind, SC_NPSIZES, "Got %u as sz_psz2ind of %llu", max_ind, SC_LARGE_MAXCLASS); } TEST_END int main(void) { return test(test_sz_psz2ind); } redis-8.0.2/deps/jemalloc/test/unit/tcache_max.c000066400000000000000000000077531501533116600215630ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/san.h" const char *malloc_conf = TEST_SAN_UAF_ALIGN_DISABLE; enum { alloc_option_start = 0, use_malloc = 0, use_mallocx, alloc_option_end }; enum { dalloc_option_start = 0, use_free = 0, use_dallocx, use_sdallocx, dalloc_option_end }; static unsigned alloc_option, dalloc_option; static size_t tcache_max; static void * alloc_func(size_t sz) { void *ret; switch (alloc_option) { case use_malloc: ret = malloc(sz); break; case use_mallocx: ret = mallocx(sz, 0); break; default: unreachable(); } expect_ptr_not_null(ret, "Unexpected malloc / mallocx failure"); return ret; } static void dalloc_func(void *ptr, size_t sz) { switch (dalloc_option) { case use_free: free(ptr); break; case use_dallocx: dallocx(ptr, 0); break; case use_sdallocx: sdallocx(ptr, sz, 0); break; default: unreachable(); } } static size_t tcache_bytes_read(void) { uint64_t epoch; assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); size_t tcache_bytes; size_t sz = sizeof(tcache_bytes); assert_d_eq(mallctl( "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL) ".tcache_bytes", &tcache_bytes, &sz, NULL, 0), 0, "Unexpected mallctl failure"); return tcache_bytes; } static void tcache_bytes_check_update(size_t *prev, ssize_t diff) { size_t tcache_bytes = tcache_bytes_read(); expect_zu_eq(tcache_bytes, *prev + diff, "tcache bytes not expected"); *prev += diff; } static void test_tcache_bytes_alloc(size_t alloc_size) { expect_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), 0, "Unexpected tcache flush failure"); size_t usize = sz_s2u(alloc_size); /* No change is expected if usize is outside of tcache_max range. */ bool cached = (usize <= tcache_max); ssize_t diff = cached ? usize : 0; void *ptr1 = alloc_func(alloc_size); void *ptr2 = alloc_func(alloc_size); size_t bytes = tcache_bytes_read(); dalloc_func(ptr2, alloc_size); /* Expect tcache_bytes increase after dalloc */ tcache_bytes_check_update(&bytes, diff); dalloc_func(ptr1, alloc_size); /* Expect tcache_bytes increase again */ tcache_bytes_check_update(&bytes, diff); void *ptr3 = alloc_func(alloc_size); if (cached) { expect_ptr_eq(ptr1, ptr3, "Unexpected cached ptr"); } /* Expect tcache_bytes decrease after alloc */ tcache_bytes_check_update(&bytes, -diff); void *ptr4 = alloc_func(alloc_size); if (cached) { expect_ptr_eq(ptr2, ptr4, "Unexpected cached ptr"); } /* Expect tcache_bytes decrease again */ tcache_bytes_check_update(&bytes, -diff); dalloc_func(ptr3, alloc_size); tcache_bytes_check_update(&bytes, diff); dalloc_func(ptr4, alloc_size); tcache_bytes_check_update(&bytes, diff); } static void test_tcache_max_impl(void) { size_t sz; sz = sizeof(tcache_max); assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); /* opt.tcache_max set to 1024 in tcache_max.sh */ expect_zu_eq(tcache_max, 1024, "tcache_max not expected"); test_tcache_bytes_alloc(1); test_tcache_bytes_alloc(tcache_max - 1); test_tcache_bytes_alloc(tcache_max); test_tcache_bytes_alloc(tcache_max + 1); test_tcache_bytes_alloc(PAGE - 1); test_tcache_bytes_alloc(PAGE); test_tcache_bytes_alloc(PAGE + 1); size_t large; sz = sizeof(large); assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); test_tcache_bytes_alloc(large - 1); test_tcache_bytes_alloc(large); test_tcache_bytes_alloc(large + 1); } TEST_BEGIN(test_tcache_max) { test_skip_if(!config_stats); test_skip_if(!opt_tcache); test_skip_if(opt_prof); test_skip_if(san_uaf_detection_enabled()); for (alloc_option = alloc_option_start; alloc_option < alloc_option_end; alloc_option++) { for (dalloc_option = dalloc_option_start; dalloc_option < dalloc_option_end; dalloc_option++) { test_tcache_max_impl(); } } } TEST_END int main(void) { return test(test_tcache_max); } redis-8.0.2/deps/jemalloc/test/unit/tcache_max.sh000066400000000000000000000000601501533116600217330ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="tcache_max:1024" redis-8.0.2/deps/jemalloc/test/unit/test_hooks.c000066400000000000000000000014041501533116600216340ustar00rootroot00000000000000#include "test/jemalloc_test.h" static bool hook_called = false; static void hook() { hook_called = true; } static int func_to_hook(int arg1, int arg2) { return arg1 + arg2; } #define func_to_hook JEMALLOC_TEST_HOOK(func_to_hook, test_hooks_libc_hook) TEST_BEGIN(unhooked_call) { test_hooks_libc_hook = NULL; hook_called = false; expect_d_eq(3, func_to_hook(1, 2), "Hooking changed return value."); expect_false(hook_called, "Nulling out hook didn't take."); } TEST_END TEST_BEGIN(hooked_call) { test_hooks_libc_hook = &hook; hook_called = false; expect_d_eq(3, func_to_hook(1, 2), "Hooking changed return value."); expect_true(hook_called, "Hook should have executed."); } TEST_END int main(void) { return test( unhooked_call, hooked_call); } redis-8.0.2/deps/jemalloc/test/unit/thread_event.c000066400000000000000000000014721501533116600221270ustar00rootroot00000000000000#include "test/jemalloc_test.h" TEST_BEGIN(test_next_event_fast) { tsd_t *tsd = tsd_fetch(); te_ctx_t ctx; te_ctx_get(tsd, &ctx, true); te_ctx_last_event_set(&ctx, 0); te_ctx_current_bytes_set(&ctx, TE_NEXT_EVENT_FAST_MAX - 8U); te_ctx_next_event_set(tsd, &ctx, TE_NEXT_EVENT_FAST_MAX); #define E(event, condition, is_alloc) \ if (is_alloc && condition) { \ event##_event_wait_set(tsd, TE_NEXT_EVENT_FAST_MAX); \ } ITERATE_OVER_ALL_EVENTS #undef E /* Test next_event_fast rolling back to 0. */ void *p = malloc(16U); assert_ptr_not_null(p, "malloc() failed"); free(p); /* Test next_event_fast resuming to be equal to next_event. */ void *q = malloc(SC_LOOKUP_MAXCLASS); assert_ptr_not_null(q, "malloc() failed"); free(q); } TEST_END int main(void) { return test( test_next_event_fast); } redis-8.0.2/deps/jemalloc/test/unit/thread_event.sh000066400000000000000000000001471501533116600223150ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_prof}" = "x1" ] ; then export MALLOC_CONF="prof:true,lg_prof_sample:0" fi redis-8.0.2/deps/jemalloc/test/unit/ticker.c000066400000000000000000000053451501533116600207430ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "jemalloc/internal/ticker.h" TEST_BEGIN(test_ticker_tick) { #define NREPS 2 #define NTICKS 3 ticker_t ticker; int32_t i, j; ticker_init(&ticker, NTICKS); for (i = 0; i < NREPS; i++) { for (j = 0; j < NTICKS; j++) { expect_u_eq(ticker_read(&ticker), NTICKS - j, "Unexpected ticker value (i=%d, j=%d)", i, j); expect_false(ticker_tick(&ticker), "Unexpected ticker fire (i=%d, j=%d)", i, j); } expect_u32_eq(ticker_read(&ticker), 0, "Expected ticker depletion"); expect_true(ticker_tick(&ticker), "Expected ticker fire (i=%d)", i); expect_u32_eq(ticker_read(&ticker), NTICKS, "Expected ticker reset"); } #undef NTICKS } TEST_END TEST_BEGIN(test_ticker_ticks) { #define NTICKS 3 ticker_t ticker; ticker_init(&ticker, NTICKS); expect_u_eq(ticker_read(&ticker), NTICKS, "Unexpected ticker value"); expect_false(ticker_ticks(&ticker, NTICKS), "Unexpected ticker fire"); expect_u_eq(ticker_read(&ticker), 0, "Unexpected ticker value"); expect_true(ticker_ticks(&ticker, NTICKS), "Expected ticker fire"); expect_u_eq(ticker_read(&ticker), NTICKS, "Unexpected ticker value"); expect_true(ticker_ticks(&ticker, NTICKS + 1), "Expected ticker fire"); expect_u_eq(ticker_read(&ticker), NTICKS, "Unexpected ticker value"); #undef NTICKS } TEST_END TEST_BEGIN(test_ticker_copy) { #define NTICKS 3 ticker_t ta, tb; ticker_init(&ta, NTICKS); ticker_copy(&tb, &ta); expect_u_eq(ticker_read(&tb), NTICKS, "Unexpected ticker value"); expect_true(ticker_ticks(&tb, NTICKS + 1), "Expected ticker fire"); expect_u_eq(ticker_read(&tb), NTICKS, "Unexpected ticker value"); ticker_tick(&ta); ticker_copy(&tb, &ta); expect_u_eq(ticker_read(&tb), NTICKS - 1, "Unexpected ticker value"); expect_true(ticker_ticks(&tb, NTICKS), "Expected ticker fire"); expect_u_eq(ticker_read(&tb), NTICKS, "Unexpected ticker value"); #undef NTICKS } TEST_END TEST_BEGIN(test_ticker_geom) { const int32_t ticks = 100; const uint64_t niters = 100 * 1000; ticker_geom_t ticker; ticker_geom_init(&ticker, ticks); uint64_t total_ticks = 0; /* Just some random constant. */ uint64_t prng_state = 0x343219f93496db9fULL; for (uint64_t i = 0; i < niters; i++) { while(!ticker_geom_tick(&ticker, &prng_state)) { total_ticks++; } } /* * In fact, with this choice of random seed and the PRNG implementation * used at the time this was tested, total_ticks is 95.1% of the * expected ticks. */ expect_u64_ge(total_ticks , niters * ticks * 9 / 10, "Mean off by > 10%%"); expect_u64_le(total_ticks , niters * ticks * 11 / 10, "Mean off by > 10%%"); } TEST_END int main(void) { return test( test_ticker_tick, test_ticker_ticks, test_ticker_copy, test_ticker_geom); } redis-8.0.2/deps/jemalloc/test/unit/tsd.c000066400000000000000000000160211501533116600202450ustar00rootroot00000000000000#include "test/jemalloc_test.h" /* * If we're e.g. in debug mode, we *never* enter the fast path, and so shouldn't * be asserting that we're on one. */ static bool originally_fast; static int data_cleanup_count; void data_cleanup(int *data) { if (data_cleanup_count == 0) { expect_x_eq(*data, MALLOC_TSD_TEST_DATA_INIT, "Argument passed into cleanup function should match tsd " "value"); } ++data_cleanup_count; /* * Allocate during cleanup for two rounds, in order to assure that * jemalloc's internal tsd reinitialization happens. */ bool reincarnate = false; switch (*data) { case MALLOC_TSD_TEST_DATA_INIT: *data = 1; reincarnate = true; break; case 1: *data = 2; reincarnate = true; break; case 2: return; default: not_reached(); } if (reincarnate) { void *p = mallocx(1, 0); expect_ptr_not_null(p, "Unexpeced mallocx() failure"); dallocx(p, 0); } } static void * thd_start(void *arg) { int d = (int)(uintptr_t)arg; void *p; /* * Test free before tsd init -- the free fast path (which does not * explicitly check for NULL) has to tolerate this case, and fall back * to free_default. */ free(NULL); tsd_t *tsd = tsd_fetch(); expect_x_eq(tsd_test_data_get(tsd), MALLOC_TSD_TEST_DATA_INIT, "Initial tsd get should return initialization value"); p = malloc(1); expect_ptr_not_null(p, "Unexpected malloc() failure"); tsd_test_data_set(tsd, d); expect_x_eq(tsd_test_data_get(tsd), d, "After tsd set, tsd get should return value that was set"); d = 0; expect_x_eq(tsd_test_data_get(tsd), (int)(uintptr_t)arg, "Resetting local data should have no effect on tsd"); tsd_test_callback_set(tsd, &data_cleanup); free(p); return NULL; } TEST_BEGIN(test_tsd_main_thread) { thd_start((void *)(uintptr_t)0xa5f3e329); } TEST_END TEST_BEGIN(test_tsd_sub_thread) { thd_t thd; data_cleanup_count = 0; thd_create(&thd, thd_start, (void *)MALLOC_TSD_TEST_DATA_INIT); thd_join(thd, NULL); /* * We reincarnate twice in the data cleanup, so it should execute at * least 3 times. */ expect_x_ge(data_cleanup_count, 3, "Cleanup function should have executed multiple times."); } TEST_END static void * thd_start_reincarnated(void *arg) { tsd_t *tsd = tsd_fetch(); assert(tsd); void *p = malloc(1); expect_ptr_not_null(p, "Unexpected malloc() failure"); /* Manually trigger reincarnation. */ expect_ptr_not_null(tsd_arena_get(tsd), "Should have tsd arena set."); tsd_cleanup((void *)tsd); expect_ptr_null(*tsd_arenap_get_unsafe(tsd), "TSD arena should have been cleared."); expect_u_eq(tsd_state_get(tsd), tsd_state_purgatory, "TSD state should be purgatory\n"); free(p); expect_u_eq(tsd_state_get(tsd), tsd_state_reincarnated, "TSD state should be reincarnated\n"); p = mallocx(1, MALLOCX_TCACHE_NONE); expect_ptr_not_null(p, "Unexpected malloc() failure"); expect_ptr_null(*tsd_arenap_get_unsafe(tsd), "Should not have tsd arena set after reincarnation."); free(p); tsd_cleanup((void *)tsd); expect_ptr_null(*tsd_arenap_get_unsafe(tsd), "TSD arena should have been cleared after 2nd cleanup."); return NULL; } TEST_BEGIN(test_tsd_reincarnation) { thd_t thd; thd_create(&thd, thd_start_reincarnated, NULL); thd_join(thd, NULL); } TEST_END typedef struct { atomic_u32_t phase; atomic_b_t error; } global_slow_data_t; static void * thd_start_global_slow(void *arg) { /* PHASE 0 */ global_slow_data_t *data = (global_slow_data_t *)arg; free(mallocx(1, 0)); tsd_t *tsd = tsd_fetch(); /* * No global slowness has happened yet; there was an error if we were * originally fast but aren't now. */ atomic_store_b(&data->error, originally_fast && !tsd_fast(tsd), ATOMIC_SEQ_CST); atomic_store_u32(&data->phase, 1, ATOMIC_SEQ_CST); /* PHASE 2 */ while (atomic_load_u32(&data->phase, ATOMIC_SEQ_CST) != 2) { } free(mallocx(1, 0)); atomic_store_b(&data->error, tsd_fast(tsd), ATOMIC_SEQ_CST); atomic_store_u32(&data->phase, 3, ATOMIC_SEQ_CST); /* PHASE 4 */ while (atomic_load_u32(&data->phase, ATOMIC_SEQ_CST) != 4) { } free(mallocx(1, 0)); atomic_store_b(&data->error, tsd_fast(tsd), ATOMIC_SEQ_CST); atomic_store_u32(&data->phase, 5, ATOMIC_SEQ_CST); /* PHASE 6 */ while (atomic_load_u32(&data->phase, ATOMIC_SEQ_CST) != 6) { } free(mallocx(1, 0)); /* Only one decrement so far. */ atomic_store_b(&data->error, tsd_fast(tsd), ATOMIC_SEQ_CST); atomic_store_u32(&data->phase, 7, ATOMIC_SEQ_CST); /* PHASE 8 */ while (atomic_load_u32(&data->phase, ATOMIC_SEQ_CST) != 8) { } free(mallocx(1, 0)); /* * Both decrements happened; we should be fast again (if we ever * were) */ atomic_store_b(&data->error, originally_fast && !tsd_fast(tsd), ATOMIC_SEQ_CST); atomic_store_u32(&data->phase, 9, ATOMIC_SEQ_CST); return NULL; } TEST_BEGIN(test_tsd_global_slow) { global_slow_data_t data = {ATOMIC_INIT(0), ATOMIC_INIT(false)}; /* * Note that the "mallocx" here (vs. malloc) is important, since the * compiler is allowed to optimize away free(malloc(1)) but not * free(mallocx(1)). */ free(mallocx(1, 0)); tsd_t *tsd = tsd_fetch(); originally_fast = tsd_fast(tsd); thd_t thd; thd_create(&thd, thd_start_global_slow, (void *)&data.phase); /* PHASE 1 */ while (atomic_load_u32(&data.phase, ATOMIC_SEQ_CST) != 1) { /* * We don't have a portable condvar/semaphore mechanism. * Spin-wait. */ } expect_false(atomic_load_b(&data.error, ATOMIC_SEQ_CST), ""); tsd_global_slow_inc(tsd_tsdn(tsd)); free(mallocx(1, 0)); expect_false(tsd_fast(tsd), ""); atomic_store_u32(&data.phase, 2, ATOMIC_SEQ_CST); /* PHASE 3 */ while (atomic_load_u32(&data.phase, ATOMIC_SEQ_CST) != 3) { } expect_false(atomic_load_b(&data.error, ATOMIC_SEQ_CST), ""); /* Increase again, so that we can test multiple fast/slow changes. */ tsd_global_slow_inc(tsd_tsdn(tsd)); atomic_store_u32(&data.phase, 4, ATOMIC_SEQ_CST); free(mallocx(1, 0)); expect_false(tsd_fast(tsd), ""); /* PHASE 5 */ while (atomic_load_u32(&data.phase, ATOMIC_SEQ_CST) != 5) { } expect_false(atomic_load_b(&data.error, ATOMIC_SEQ_CST), ""); tsd_global_slow_dec(tsd_tsdn(tsd)); atomic_store_u32(&data.phase, 6, ATOMIC_SEQ_CST); /* We only decreased once; things should still be slow. */ free(mallocx(1, 0)); expect_false(tsd_fast(tsd), ""); /* PHASE 7 */ while (atomic_load_u32(&data.phase, ATOMIC_SEQ_CST) != 7) { } expect_false(atomic_load_b(&data.error, ATOMIC_SEQ_CST), ""); tsd_global_slow_dec(tsd_tsdn(tsd)); atomic_store_u32(&data.phase, 8, ATOMIC_SEQ_CST); /* We incremented and then decremented twice; we should be fast now. */ free(mallocx(1, 0)); expect_true(!originally_fast || tsd_fast(tsd), ""); /* PHASE 9 */ while (atomic_load_u32(&data.phase, ATOMIC_SEQ_CST) != 9) { } expect_false(atomic_load_b(&data.error, ATOMIC_SEQ_CST), ""); thd_join(thd, NULL); } TEST_END int main(void) { /* Ensure tsd bootstrapped. */ if (nallocx(1, 0) == 0) { malloc_printf("Initialization error"); return test_status_fail; } return test_no_reentrancy( test_tsd_main_thread, test_tsd_sub_thread, test_tsd_reincarnation, test_tsd_global_slow); } redis-8.0.2/deps/jemalloc/test/unit/uaf.c000066400000000000000000000171611501533116600202340ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include "test/arena_util.h" #include "test/san.h" #include "jemalloc/internal/cache_bin.h" #include "jemalloc/internal/san.h" #include "jemalloc/internal/safety_check.h" const char *malloc_conf = TEST_SAN_UAF_ALIGN_ENABLE; static size_t san_uaf_align; static bool fake_abort_called; void fake_abort(const char *message) { (void)message; fake_abort_called = true; } static void test_write_after_free_pre(void) { safety_check_set_abort(&fake_abort); fake_abort_called = false; } static void test_write_after_free_post(void) { assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), 0, "Unexpected tcache flush failure"); expect_true(fake_abort_called, "Use-after-free check didn't fire."); safety_check_set_abort(NULL); } static bool uaf_detection_enabled(void) { if (!config_uaf_detection || !san_uaf_detection_enabled()) { return false; } ssize_t lg_san_uaf_align; size_t sz = sizeof(lg_san_uaf_align); assert_d_eq(mallctl("opt.lg_san_uaf_align", &lg_san_uaf_align, &sz, NULL, 0), 0, "Unexpected mallctl failure"); if (lg_san_uaf_align < 0) { return false; } assert_zd_ge(lg_san_uaf_align, LG_PAGE, "san_uaf_align out of range"); san_uaf_align = (size_t)1 << lg_san_uaf_align; bool tcache_enabled; sz = sizeof(tcache_enabled); assert_d_eq(mallctl("thread.tcache.enabled", &tcache_enabled, &sz, NULL, 0), 0, "Unexpected mallctl failure"); if (!tcache_enabled) { return false; } return true; } static size_t read_tcache_stashed_bytes(unsigned arena_ind) { if (!config_stats) { return 0; } uint64_t epoch; assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)), 0, "Unexpected mallctl() failure"); size_t tcache_stashed_bytes; size_t sz = sizeof(tcache_stashed_bytes); assert_d_eq(mallctl( "stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL) ".tcache_stashed_bytes", &tcache_stashed_bytes, &sz, NULL, 0), 0, "Unexpected mallctl failure"); return tcache_stashed_bytes; } static void test_use_after_free(size_t alloc_size, bool write_after_free) { void *ptr = (void *)(uintptr_t)san_uaf_align; assert_true(cache_bin_nonfast_aligned(ptr), "Wrong alignment"); ptr = (void *)((uintptr_t)123 * (uintptr_t)san_uaf_align); assert_true(cache_bin_nonfast_aligned(ptr), "Wrong alignment"); ptr = (void *)((uintptr_t)san_uaf_align + 1); assert_false(cache_bin_nonfast_aligned(ptr), "Wrong alignment"); /* * Disable purging (-1) so that all dirty pages remain committed, to * make use-after-free tolerable. */ unsigned arena_ind = do_arena_create(-1, -1); int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; size_t n_max = san_uaf_align * 2; void **items = mallocx(n_max * sizeof(void *), flags); assert_ptr_not_null(items, "Unexpected mallocx failure"); bool found = false; size_t iter = 0; char magic = 's'; assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), 0, "Unexpected tcache flush failure"); while (!found) { ptr = mallocx(alloc_size, flags); assert_ptr_not_null(ptr, "Unexpected mallocx failure"); found = cache_bin_nonfast_aligned(ptr); *(char *)ptr = magic; items[iter] = ptr; assert_zu_lt(iter++, n_max, "No aligned ptr found"); } if (write_after_free) { test_write_after_free_pre(); } bool junked = false; while (iter-- != 0) { char *volatile mem = items[iter]; assert_c_eq(*mem, magic, "Unexpected memory content"); size_t stashed_before = read_tcache_stashed_bytes(arena_ind); free(mem); if (*mem != magic) { junked = true; assert_c_eq(*mem, (char)uaf_detect_junk, "Unexpected junk-filling bytes"); if (write_after_free) { *(char *)mem = magic + 1; } size_t stashed_after = read_tcache_stashed_bytes( arena_ind); /* * An edge case is the deallocation above triggering the * tcache GC event, in which case the stashed pointers * may get flushed immediately, before returning from * free(). Treat these cases as checked already. */ if (stashed_after <= stashed_before) { fake_abort_called = true; } } /* Flush tcache (including stashed). */ assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0), 0, "Unexpected tcache flush failure"); } expect_true(junked, "Aligned ptr not junked"); if (write_after_free) { test_write_after_free_post(); } dallocx(items, flags); do_arena_destroy(arena_ind); } TEST_BEGIN(test_read_after_free) { test_skip_if(!uaf_detection_enabled()); test_use_after_free(sizeof(void *), /* write_after_free */ false); test_use_after_free(sizeof(void *) + 1, /* write_after_free */ false); test_use_after_free(16, /* write_after_free */ false); test_use_after_free(20, /* write_after_free */ false); test_use_after_free(32, /* write_after_free */ false); test_use_after_free(33, /* write_after_free */ false); test_use_after_free(48, /* write_after_free */ false); test_use_after_free(64, /* write_after_free */ false); test_use_after_free(65, /* write_after_free */ false); test_use_after_free(129, /* write_after_free */ false); test_use_after_free(255, /* write_after_free */ false); test_use_after_free(256, /* write_after_free */ false); } TEST_END TEST_BEGIN(test_write_after_free) { test_skip_if(!uaf_detection_enabled()); test_use_after_free(sizeof(void *), /* write_after_free */ true); test_use_after_free(sizeof(void *) + 1, /* write_after_free */ true); test_use_after_free(16, /* write_after_free */ true); test_use_after_free(20, /* write_after_free */ true); test_use_after_free(32, /* write_after_free */ true); test_use_after_free(33, /* write_after_free */ true); test_use_after_free(48, /* write_after_free */ true); test_use_after_free(64, /* write_after_free */ true); test_use_after_free(65, /* write_after_free */ true); test_use_after_free(129, /* write_after_free */ true); test_use_after_free(255, /* write_after_free */ true); test_use_after_free(256, /* write_after_free */ true); } TEST_END static bool check_allocated_intact(void **allocated, size_t n_alloc) { for (unsigned i = 0; i < n_alloc; i++) { void *ptr = *(void **)allocated[i]; bool found = false; for (unsigned j = 0; j < n_alloc; j++) { if (ptr == allocated[j]) { found = true; break; } } if (!found) { return false; } } return true; } TEST_BEGIN(test_use_after_free_integration) { test_skip_if(!uaf_detection_enabled()); unsigned arena_ind = do_arena_create(-1, -1); int flags = MALLOCX_ARENA(arena_ind); size_t n_alloc = san_uaf_align * 2; void **allocated = mallocx(n_alloc * sizeof(void *), flags); assert_ptr_not_null(allocated, "Unexpected mallocx failure"); for (unsigned i = 0; i < n_alloc; i++) { allocated[i] = mallocx(sizeof(void *) * 8, flags); assert_ptr_not_null(allocated[i], "Unexpected mallocx failure"); if (i > 0) { /* Emulate a circular list. */ *(void **)allocated[i] = allocated[i - 1]; } } *(void **)allocated[0] = allocated[n_alloc - 1]; expect_true(check_allocated_intact(allocated, n_alloc), "Allocated data corrupted"); for (unsigned i = 0; i < n_alloc; i++) { free(allocated[i]); } /* Read-after-free */ expect_false(check_allocated_intact(allocated, n_alloc), "Junk-filling not detected"); test_write_after_free_pre(); for (unsigned i = 0; i < n_alloc; i++) { allocated[i] = mallocx(sizeof(void *), flags); assert_ptr_not_null(allocated[i], "Unexpected mallocx failure"); *(void **)allocated[i] = (void *)(uintptr_t)i; } /* Write-after-free */ for (unsigned i = 0; i < n_alloc; i++) { free(allocated[i]); *(void **)allocated[i] = NULL; } test_write_after_free_post(); } TEST_END int main(void) { return test( test_read_after_free, test_write_after_free, test_use_after_free_integration); } redis-8.0.2/deps/jemalloc/test/unit/witness.c000066400000000000000000000176421501533116600211610ustar00rootroot00000000000000#include "test/jemalloc_test.h" static witness_lock_error_t *witness_lock_error_orig; static witness_owner_error_t *witness_owner_error_orig; static witness_not_owner_error_t *witness_not_owner_error_orig; static witness_depth_error_t *witness_depth_error_orig; static bool saw_lock_error; static bool saw_owner_error; static bool saw_not_owner_error; static bool saw_depth_error; static void witness_lock_error_intercept(const witness_list_t *witnesses, const witness_t *witness) { saw_lock_error = true; } static void witness_owner_error_intercept(const witness_t *witness) { saw_owner_error = true; } static void witness_not_owner_error_intercept(const witness_t *witness) { saw_not_owner_error = true; } static void witness_depth_error_intercept(const witness_list_t *witnesses, witness_rank_t rank_inclusive, unsigned depth) { saw_depth_error = true; } static int witness_comp(const witness_t *a, void *oa, const witness_t *b, void *ob) { expect_u_eq(a->rank, b->rank, "Witnesses should have equal rank"); assert(oa == (void *)a); assert(ob == (void *)b); return strcmp(a->name, b->name); } static int witness_comp_reverse(const witness_t *a, void *oa, const witness_t *b, void *ob) { expect_u_eq(a->rank, b->rank, "Witnesses should have equal rank"); assert(oa == (void *)a); assert(ob == (void *)b); return -strcmp(a->name, b->name); } TEST_BEGIN(test_witness) { witness_t a, b; witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER }; test_skip_if(!config_debug); witness_assert_lockless(&witness_tsdn); witness_assert_depth(&witness_tsdn, 0); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 0); witness_init(&a, "a", 1, NULL, NULL); witness_assert_not_owner(&witness_tsdn, &a); witness_lock(&witness_tsdn, &a); witness_assert_owner(&witness_tsdn, &a); witness_assert_depth(&witness_tsdn, 1); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 1); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)2U, 0); witness_init(&b, "b", 2, NULL, NULL); witness_assert_not_owner(&witness_tsdn, &b); witness_lock(&witness_tsdn, &b); witness_assert_owner(&witness_tsdn, &b); witness_assert_depth(&witness_tsdn, 2); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 2); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)2U, 1); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)3U, 0); witness_unlock(&witness_tsdn, &a); witness_assert_depth(&witness_tsdn, 1); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 1); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)2U, 1); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)3U, 0); witness_unlock(&witness_tsdn, &b); witness_assert_lockless(&witness_tsdn); witness_assert_depth(&witness_tsdn, 0); witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 0); } TEST_END TEST_BEGIN(test_witness_comp) { witness_t a, b, c, d; witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER }; test_skip_if(!config_debug); witness_assert_lockless(&witness_tsdn); witness_init(&a, "a", 1, witness_comp, &a); witness_assert_not_owner(&witness_tsdn, &a); witness_lock(&witness_tsdn, &a); witness_assert_owner(&witness_tsdn, &a); witness_assert_depth(&witness_tsdn, 1); witness_init(&b, "b", 1, witness_comp, &b); witness_assert_not_owner(&witness_tsdn, &b); witness_lock(&witness_tsdn, &b); witness_assert_owner(&witness_tsdn, &b); witness_assert_depth(&witness_tsdn, 2); witness_unlock(&witness_tsdn, &b); witness_assert_depth(&witness_tsdn, 1); witness_lock_error_orig = witness_lock_error; witness_lock_error = witness_lock_error_intercept; saw_lock_error = false; witness_init(&c, "c", 1, witness_comp_reverse, &c); witness_assert_not_owner(&witness_tsdn, &c); expect_false(saw_lock_error, "Unexpected witness lock error"); witness_lock(&witness_tsdn, &c); expect_true(saw_lock_error, "Expected witness lock error"); witness_unlock(&witness_tsdn, &c); witness_assert_depth(&witness_tsdn, 1); saw_lock_error = false; witness_init(&d, "d", 1, NULL, NULL); witness_assert_not_owner(&witness_tsdn, &d); expect_false(saw_lock_error, "Unexpected witness lock error"); witness_lock(&witness_tsdn, &d); expect_true(saw_lock_error, "Expected witness lock error"); witness_unlock(&witness_tsdn, &d); witness_assert_depth(&witness_tsdn, 1); witness_unlock(&witness_tsdn, &a); witness_assert_lockless(&witness_tsdn); witness_lock_error = witness_lock_error_orig; } TEST_END TEST_BEGIN(test_witness_reversal) { witness_t a, b; witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER }; test_skip_if(!config_debug); witness_lock_error_orig = witness_lock_error; witness_lock_error = witness_lock_error_intercept; saw_lock_error = false; witness_assert_lockless(&witness_tsdn); witness_init(&a, "a", 1, NULL, NULL); witness_init(&b, "b", 2, NULL, NULL); witness_lock(&witness_tsdn, &b); witness_assert_depth(&witness_tsdn, 1); expect_false(saw_lock_error, "Unexpected witness lock error"); witness_lock(&witness_tsdn, &a); expect_true(saw_lock_error, "Expected witness lock error"); witness_unlock(&witness_tsdn, &a); witness_assert_depth(&witness_tsdn, 1); witness_unlock(&witness_tsdn, &b); witness_assert_lockless(&witness_tsdn); witness_lock_error = witness_lock_error_orig; } TEST_END TEST_BEGIN(test_witness_recursive) { witness_t a; witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER }; test_skip_if(!config_debug); witness_not_owner_error_orig = witness_not_owner_error; witness_not_owner_error = witness_not_owner_error_intercept; saw_not_owner_error = false; witness_lock_error_orig = witness_lock_error; witness_lock_error = witness_lock_error_intercept; saw_lock_error = false; witness_assert_lockless(&witness_tsdn); witness_init(&a, "a", 1, NULL, NULL); witness_lock(&witness_tsdn, &a); expect_false(saw_lock_error, "Unexpected witness lock error"); expect_false(saw_not_owner_error, "Unexpected witness not owner error"); witness_lock(&witness_tsdn, &a); expect_true(saw_lock_error, "Expected witness lock error"); expect_true(saw_not_owner_error, "Expected witness not owner error"); witness_unlock(&witness_tsdn, &a); witness_assert_lockless(&witness_tsdn); witness_owner_error = witness_owner_error_orig; witness_lock_error = witness_lock_error_orig; } TEST_END TEST_BEGIN(test_witness_unlock_not_owned) { witness_t a; witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER }; test_skip_if(!config_debug); witness_owner_error_orig = witness_owner_error; witness_owner_error = witness_owner_error_intercept; saw_owner_error = false; witness_assert_lockless(&witness_tsdn); witness_init(&a, "a", 1, NULL, NULL); expect_false(saw_owner_error, "Unexpected owner error"); witness_unlock(&witness_tsdn, &a); expect_true(saw_owner_error, "Expected owner error"); witness_assert_lockless(&witness_tsdn); witness_owner_error = witness_owner_error_orig; } TEST_END TEST_BEGIN(test_witness_depth) { witness_t a; witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER }; test_skip_if(!config_debug); witness_depth_error_orig = witness_depth_error; witness_depth_error = witness_depth_error_intercept; saw_depth_error = false; witness_assert_lockless(&witness_tsdn); witness_assert_depth(&witness_tsdn, 0); witness_init(&a, "a", 1, NULL, NULL); expect_false(saw_depth_error, "Unexpected depth error"); witness_assert_lockless(&witness_tsdn); witness_assert_depth(&witness_tsdn, 0); witness_lock(&witness_tsdn, &a); witness_assert_lockless(&witness_tsdn); witness_assert_depth(&witness_tsdn, 0); expect_true(saw_depth_error, "Expected depth error"); witness_unlock(&witness_tsdn, &a); witness_assert_lockless(&witness_tsdn); witness_assert_depth(&witness_tsdn, 0); witness_depth_error = witness_depth_error_orig; } TEST_END int main(void) { return test( test_witness, test_witness_comp, test_witness_reversal, test_witness_recursive, test_witness_unlock_not_owned, test_witness_depth); } redis-8.0.2/deps/jemalloc/test/unit/zero.c000066400000000000000000000024011501533116600204270ustar00rootroot00000000000000#include "test/jemalloc_test.h" static void test_zero(size_t sz_min, size_t sz_max) { uint8_t *s; size_t sz_prev, sz, i; #define MAGIC ((uint8_t)0x61) sz_prev = 0; s = (uint8_t *)mallocx(sz_min, 0); expect_ptr_not_null((void *)s, "Unexpected mallocx() failure"); for (sz = sallocx(s, 0); sz <= sz_max; sz_prev = sz, sz = sallocx(s, 0)) { if (sz_prev > 0) { expect_u_eq(s[0], MAGIC, "Previously allocated byte %zu/%zu is corrupted", ZU(0), sz_prev); expect_u_eq(s[sz_prev-1], MAGIC, "Previously allocated byte %zu/%zu is corrupted", sz_prev-1, sz_prev); } for (i = sz_prev; i < sz; i++) { expect_u_eq(s[i], 0x0, "Newly allocated byte %zu/%zu isn't zero-filled", i, sz); s[i] = MAGIC; } if (xallocx(s, sz+1, 0, 0) == sz) { s = (uint8_t *)rallocx(s, sz+1, 0); expect_ptr_not_null((void *)s, "Unexpected rallocx() failure"); } } dallocx(s, 0); #undef MAGIC } TEST_BEGIN(test_zero_small) { test_skip_if(!config_fill); test_zero(1, SC_SMALL_MAXCLASS - 1); } TEST_END TEST_BEGIN(test_zero_large) { test_skip_if(!config_fill); test_zero(SC_SMALL_MAXCLASS + 1, 1U << (SC_LG_LARGE_MINCLASS + 1)); } TEST_END int main(void) { return test( test_zero_small, test_zero_large); } redis-8.0.2/deps/jemalloc/test/unit/zero.sh000066400000000000000000000001551501533116600206230ustar00rootroot00000000000000#!/bin/sh if [ "x${enable_fill}" = "x1" ] ; then export MALLOC_CONF="abort:false,junk:false,zero:true" fi redis-8.0.2/deps/jemalloc/test/unit/zero_realloc_abort.c000066400000000000000000000007501501533116600233240ustar00rootroot00000000000000#include "test/jemalloc_test.h" #include static bool abort_called = false; void set_abort_called() { abort_called = true; }; TEST_BEGIN(test_realloc_abort) { abort_called = false; safety_check_set_abort(&set_abort_called); void *ptr = mallocx(42, 0); expect_ptr_not_null(ptr, "Unexpected mallocx error"); ptr = realloc(ptr, 0); expect_true(abort_called, "Realloc with zero size didn't abort"); } TEST_END int main(void) { return test( test_realloc_abort); } redis-8.0.2/deps/jemalloc/test/unit/zero_realloc_abort.sh000066400000000000000000000000631501533116600235110ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="zero_realloc:abort" redis-8.0.2/deps/jemalloc/test/unit/zero_realloc_alloc.c000066400000000000000000000022071501533116600233060ustar00rootroot00000000000000#include "test/jemalloc_test.h" static uint64_t allocated() { if (!config_stats) { return 0; } uint64_t allocated; size_t sz = sizeof(allocated); expect_d_eq(mallctl("thread.allocated", (void *)&allocated, &sz, NULL, 0), 0, "Unexpected mallctl failure"); return allocated; } static uint64_t deallocated() { if (!config_stats) { return 0; } uint64_t deallocated; size_t sz = sizeof(deallocated); expect_d_eq(mallctl("thread.deallocated", (void *)&deallocated, &sz, NULL, 0), 0, "Unexpected mallctl failure"); return deallocated; } TEST_BEGIN(test_realloc_alloc) { void *ptr = mallocx(1, 0); expect_ptr_not_null(ptr, "Unexpected mallocx error"); uint64_t allocated_before = allocated(); uint64_t deallocated_before = deallocated(); ptr = realloc(ptr, 0); uint64_t allocated_after = allocated(); uint64_t deallocated_after = deallocated(); if (config_stats) { expect_u64_lt(allocated_before, allocated_after, "Unexpected stats change"); expect_u64_lt(deallocated_before, deallocated_after, "Unexpected stats change"); } dallocx(ptr, 0); } TEST_END int main(void) { return test( test_realloc_alloc); } redis-8.0.2/deps/jemalloc/test/unit/zero_realloc_alloc.sh000066400000000000000000000000631501533116600234740ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="zero_realloc:alloc" redis-8.0.2/deps/jemalloc/test/unit/zero_realloc_free.c000066400000000000000000000013661501533116600231420ustar00rootroot00000000000000#include "test/jemalloc_test.h" static uint64_t deallocated() { if (!config_stats) { return 0; } uint64_t deallocated; size_t sz = sizeof(deallocated); expect_d_eq(mallctl("thread.deallocated", (void *)&deallocated, &sz, NULL, 0), 0, "Unexpected mallctl failure"); return deallocated; } TEST_BEGIN(test_realloc_free) { void *ptr = mallocx(42, 0); expect_ptr_not_null(ptr, "Unexpected mallocx error"); uint64_t deallocated_before = deallocated(); ptr = realloc(ptr, 0); uint64_t deallocated_after = deallocated(); expect_ptr_null(ptr, "Realloc didn't free"); if (config_stats) { expect_u64_gt(deallocated_after, deallocated_before, "Realloc didn't free"); } } TEST_END int main(void) { return test( test_realloc_free); } redis-8.0.2/deps/jemalloc/test/unit/zero_realloc_free.sh000066400000000000000000000000621501533116600233220ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="zero_realloc:free" redis-8.0.2/deps/jemalloc/test/unit/zero_reallocs.c000066400000000000000000000016371501533116600223250ustar00rootroot00000000000000#include "test/jemalloc_test.h" static size_t zero_reallocs() { if (!config_stats) { return 0; } size_t count = 12345; size_t sz = sizeof(count); expect_d_eq(mallctl("stats.zero_reallocs", (void *)&count, &sz, NULL, 0), 0, "Unexpected mallctl failure"); return count; } TEST_BEGIN(test_zero_reallocs) { test_skip_if(!config_stats); for (size_t i = 0; i < 100; ++i) { void *ptr = mallocx(i * i + 1, 0); expect_ptr_not_null(ptr, "Unexpected mallocx error"); size_t count = zero_reallocs(); expect_zu_eq(i, count, "Incorrect zero realloc count"); ptr = realloc(ptr, 0); expect_ptr_null(ptr, "Realloc didn't free"); count = zero_reallocs(); expect_zu_eq(i + 1, count, "Realloc didn't adjust count"); } } TEST_END int main(void) { /* * We expect explicit counts; reentrant tests run multiple times, so * counts leak across runs. */ return test_no_reentrancy( test_zero_reallocs); } redis-8.0.2/deps/jemalloc/test/unit/zero_reallocs.sh000066400000000000000000000000621501533116600225040ustar00rootroot00000000000000#!/bin/sh export MALLOC_CONF="zero_realloc:free" redis-8.0.2/deps/linenoise/000077500000000000000000000000001501533116600155505ustar00rootroot00000000000000redis-8.0.2/deps/linenoise/.gitignore000066400000000000000000000000451501533116600175370ustar00rootroot00000000000000linenoise_example *.dSYM history.txt redis-8.0.2/deps/linenoise/Makefile000066400000000000000000000004731501533116600172140ustar00rootroot00000000000000STD= WARN= -Wall OPT= -Os R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) R_LDFLAGS= $(LDFLAGS) DEBUG= -g R_CC=$(CC) $(R_CFLAGS) R_LD=$(CC) $(R_LDFLAGS) linenoise.o: linenoise.h linenoise.c linenoise_example: linenoise.o example.o $(R_LD) -o $@ $^ .c.o: $(R_CC) -c $< clean: rm -f linenoise_example *.o redis-8.0.2/deps/linenoise/README.markdown000066400000000000000000000246261501533116600202630ustar00rootroot00000000000000# Linenoise A minimal, zero-config, BSD licensed, readline replacement used in Redis, MongoDB, and Android. * Single and multi line editing mode with the usual key bindings implemented. * History handling. * Completion. * Hints (suggestions at the right of the prompt as you type). * About 1,100 lines of BSD license source code. * Only uses a subset of VT100 escapes (ANSI.SYS compatible). ## Can a line editing library be 20k lines of code? Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing? So what usually happens is either: * Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh). * Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance). The result is a pollution of binaries without line editing support. So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not. ## Terminals, in 2010. Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no VT220 specific sequences are used anymore. The library is currently about 1100 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software. ## Tested with... * Linux text only console ($TERM = linux) * Linux KDE terminal application ($TERM = xterm) * Linux xterm ($TERM = xterm) * Linux Buildroot ($TERM = vt100) * Mac OS X iTerm ($TERM = xterm) * Mac OS X default Terminal.app ($TERM = xterm) * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) * IBM AIX 6.1 * FreeBSD xterm ($TERM = xterm) * ANSI.SYS * Emacs comint mode ($TERM = dumb) Please test it everywhere you can and report back! ## Let's push this forward! Patches should be provided in the respect of Linenoise sensibility for small easy to understand code. Send feedbacks to antirez at gmail # The API Linenoise is very easy to use, and reading the example shipped with the library should get you up to speed ASAP. Here is a list of API calls and how to use them. char *linenoise(const char *prompt); This is the main Linenoise call: it shows the user a prompt with line editing and history capabilities. The prompt you specify is used as a prompt, that is, it will be printed to the left of the cursor. The library returns a buffer with the line composed by the user, or NULL on end of file or when there is an out of memory condition. When a tty is detected (the user is actually typing into a terminal session) the maximum editable line length is `LINENOISE_MAX_LINE`. When instead the standard input is not a tty, which happens every time you redirect a file to a program, or use it in an Unix pipeline, there are no limits to the length of the line that can be returned. The returned line should be freed with the `free()` standard system call. However sometimes it could happen that your program uses a different dynamic allocation library, so you may also used `linenoiseFree` to make sure the line is freed with the same allocator it was created. The canonical loop used by a program using Linenoise will be something like this: while((line = linenoise("hello> ")) != NULL) { printf("You wrote: %s\n", line); linenoiseFree(line); /* Or just free(line) if you use libc malloc. */ } ## Single line VS multi line editing By default, Linenoise uses single line editing, that is, a single row on the screen will be used, and as the user types more, the text will scroll towards left to make room. This works if your program is one where the user is unlikely to write a lot of text, otherwise multi line editing, where multiple screens rows are used, can be a lot more comfortable. In order to enable multi line editing use the following API call: linenoiseSetMultiLine(1); You can disable it using `0` as argument. ## History Linenoise supporst history, so that the user does not have to retype again and again the same things, but can use the down and up arrows in order to search and re-edit already inserted lines of text. The followings are the history API calls: int linenoiseHistoryAdd(const char *line, int is_sensitive); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); Use `linenoiseHistoryAdd` every time you want to add a new element to the top of the history (it will be the first the user will see when using the up arrow). Note that for history to work, you have to set a length for the history (which is zero by default, so history will be disabled if you don't set a proper one). This is accomplished using the `linenoiseHistorySetMaxLen` function. Linenoise has direct support for persisting the history into an history file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do just that. Both functions return -1 on error and 0 on success. ## Mask mode Sometimes it is useful to allow the user to type passwords or other secrets that should not be displayed. For such situations linenoise supports a "mask mode" that will just replace the characters the user is typing with `*` characters, like in the following example: $ ./linenoise_example hello> get mykey echo: 'get mykey' hello> /mask hello> ********* You can enable and disable mask mode using the following two functions: void linenoiseMaskModeEnable(void); void linenoiseMaskModeDisable(void); ## Completion Linenoise supports completion, which is the ability to complete the user input when she or he presses the `` key. In order to use completion, you need to register a completion callback, which is called every time the user presses ``. Your callback will return a list of items that are completions for the current string. The following is an example of registering a completion callback: linenoiseSetCompletionCallback(completion); The completion must be a function returning `void` and getting as input a `const char` pointer, which is the line the user has typed so far, and a `linenoiseCompletions` object pointer, which is used as argument of `linenoiseAddCompletion` in order to add completions inside the callback. An example will make it more clear: void completion(const char *buf, linenoiseCompletions *lc) { if (buf[0] == 'h') { linenoiseAddCompletion(lc,"hello"); linenoiseAddCompletion(lc,"hello there"); } } Basically in your completion callback, you inspect the input, and return a list of items that are good completions by using `linenoiseAddCompletion`. If you want to test the completion feature, compile the example program with `make`, run it, type `h` and press ``. ## Hints Linenoise has a feature called *hints* which is very useful when you use Linenoise in order to implement a REPL (Read Eval Print Loop) for a program that accepts commands and arguments, but may also be useful in other conditions. The feature shows, on the right of the cursor, as the user types, hints that may be useful. The hints can be displayed using a different color compared to the color the user is typing, and can also be bold. For example as the user starts to type `"git remote add"`, with hints it's possible to show on the right of the prompt a string ` `. The feature works similarly to the history feature, using a callback. To register the callback we use: linenoiseSetHintsCallback(hints); The callback itself is implemented like this: char *hints(const char *buf, int *color, int *bold) { if (!strcasecmp(buf,"git remote add")) { *color = 35; *bold = 0; return " "; } return NULL; } The callback function returns the string that should be displayed or NULL if no hint is available for the text the user currently typed. The returned string will be trimmed as needed depending on the number of columns available on the screen. It is possible to return a string allocated in dynamic way, by also registering a function to deallocate the hint string once used: void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); The free hint callback will just receive the pointer and free the string as needed (depending on how the hits callback allocated it). As you can see in the example above, a `color` (in xterm color terminal codes) can be provided together with a `bold` attribute. If no color is set, the current terminal foreground color is used. If no bold attribute is set, non-bold text is printed. Color codes are: red = 31 green = 32 yellow = 33 blue = 34 magenta = 35 cyan = 36 white = 37; ## Screen handling Sometimes you may want to clear the screen as a result of something the user typed. You can do this by calling the following function: void linenoiseClearScreen(void); ## Related projects * [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language. * [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift. redis-8.0.2/deps/linenoise/example.c000066400000000000000000000050611501533116600173510ustar00rootroot00000000000000#include #include #include #include "linenoise.h" void completion(const char *buf, linenoiseCompletions *lc) { if (buf[0] == 'h') { linenoiseAddCompletion(lc,"hello"); linenoiseAddCompletion(lc,"hello there"); } } char *hints(const char *buf, int *color, int *bold) { if (!strcasecmp(buf,"hello")) { *color = 35; *bold = 0; return " World"; } return NULL; } int main(int argc, char **argv) { char *line; char *prgname = argv[0]; /* Parse options, with --multiline we enable multi line editing. */ while(argc > 1) { argc--; argv++; if (!strcmp(*argv,"--multiline")) { linenoiseSetMultiLine(1); printf("Multi-line mode enabled.\n"); } else if (!strcmp(*argv,"--keycodes")) { linenoisePrintKeyCodes(); exit(0); } else { fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname); exit(1); } } /* Set the completion callback. This will be called every time the * user uses the key. */ linenoiseSetCompletionCallback(completion); linenoiseSetHintsCallback(hints); /* Load history from file. The history file is just a plain text file * where entries are separated by newlines. */ linenoiseHistoryLoad("history.txt"); /* Load the history at startup */ /* Now this is the main loop of the typical linenoise-based application. * The call to linenoise() will block as long as the user types something * and presses enter. * * The typed string is returned as a malloc() allocated string by * linenoise, so the user needs to free() it. */ while((line = linenoise("hello> ")) != NULL) { /* Do something with the string. */ if (line[0] != '\0' && line[0] != '/') { printf("echo: '%s'\n", line); linenoiseHistoryAdd(line); /* Add to the history. */ linenoiseHistorySave("history.txt"); /* Save the history on disk. */ } else if (!strncmp(line,"/historylen",11)) { /* The "/historylen" command will change the history len. */ int len = atoi(line+11); linenoiseHistorySetMaxLen(len); } else if (!strncmp(line, "/mask", 5)) { linenoiseMaskModeEnable(); } else if (!strncmp(line, "/unmask", 7)) { linenoiseMaskModeDisable(); } else if (line[0] == '/') { printf("Unreconized command: %s\n", line); } free(line); } return 0; } redis-8.0.2/deps/linenoise/linenoise.c000066400000000000000000001400601501533116600177020ustar00rootroot00000000000000/* linenoise.c -- guerrilla line editing library against the idea that a * line editing lib needs to be 20,000 lines of C code. * * You can find the latest source code at: * * http://github.com/antirez/linenoise * * Does a number of crazy assumptions that happen to be true in 99.9999% of * the 2010 UNIX computers around. * * ------------------------------------------------------------------------ * * Copyright (c) 2010-2016, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 HOLDERS 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. * * ------------------------------------------------------------------------ * * References: * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html * * Todo list: * - Filter bogus Ctrl+ combinations. * - Win32 support * * Bloat: * - History search like Ctrl+r in readline? * * List of escape sequences used by this program, we do everything just * with three sequences. In order to be so cheap we may have some * flickering effect with some slow terminal, but the lesser sequences * the more compatible. * * EL (Erase Line) * Sequence: ESC [ n K * Effect: if n is 0 or missing, clear from cursor to end of line * Effect: if n is 1, clear from beginning of line to cursor * Effect: if n is 2, clear entire line * * CUF (CUrsor Forward) * Sequence: ESC [ n C * Effect: moves cursor forward n chars * * CUB (CUrsor Backward) * Sequence: ESC [ n D * Effect: moves cursor backward n chars * * The following is used to get the terminal width if getting * the width with the TIOCGWINSZ ioctl fails * * DSR (Device Status Report) * Sequence: ESC [ 6 n * Effect: reports the current cusor position as ESC [ n ; m R * where n is the row and m is the column * * When multi line mode is enabled, we also use an additional escape * sequence. However multi line editing is disabled by default. * * CUU (Cursor Up) * Sequence: ESC [ n A * Effect: moves cursor up of n chars. * * CUD (Cursor Down) * Sequence: ESC [ n B * Effect: moves cursor down of n chars. * * When linenoiseClearScreen() is called, two additional escape sequences * are used in order to clear the screen and position the cursor at home * position. * * CUP (Cursor position) * Sequence: ESC [ H * Effect: moves the cursor to upper left corner * * ED (Erase display) * Sequence: ESC [ 2 J * Effect: clear the whole screen * */ #define _DEFAULT_SOURCE /* For fchmod() */ #define _BSD_SOURCE /* For fchmod() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "linenoise.h" #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 #define LINENOISE_MAX_LINE 4096 static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; static linenoiseCompletionCallback *completionCallback = NULL; static linenoiseHintsCallback *hintsCallback = NULL; static linenoiseFreeHintsCallback *freeHintsCallback = NULL; static struct termios orig_termios; /* In order to restore at exit.*/ static int maskmode = 0; /* Show "***" instead of input. For passwords. */ static int rawmode = 0; /* For atexit() function to check if restore is needed*/ static int mlmode = 0; /* Multi line mode. Default is single line. */ static int atexit_registered = 0; /* Register atexit just 1 time. */ static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; static int history_len = 0; static char **history = NULL; static int *history_sensitive = NULL; /* An array records whether each line in * history is sensitive. */ static int reverse_search_mode_enabled = 0; static int reverse_search_direction = 0; /* 1 means forward, -1 means backward. */ static int cycle_to_next_search = 0; /* indicates whether to continue the search with CTRL+S or CTRL+R. */ static char search_result[LINENOISE_MAX_LINE]; static char search_result_friendly[LINENOISE_MAX_LINE]; static int search_result_history_index = 0; static int search_result_start_offset = 0; static int ignore_once_hint = 0; /* Flag to ignore hint once, preventing it from interfering * with search results right after exiting search mode. */ /* The linenoiseState structure represents the state during line editing. * We pass this state to functions implementing specific editing * functionalities. */ struct linenoiseState { int ifd; /* Terminal stdin file descriptor. */ int ofd; /* Terminal stdout file descriptor. */ char *buf; /* Edited line buffer. */ size_t buflen; /* Edited line buffer size. */ const char *origin_prompt; /* Original prompt, used to restore when exiting search mode. */ const char *prompt; /* Prompt to display. */ size_t plen; /* Prompt length. */ size_t pos; /* Current cursor position. */ size_t oldpos; /* Previous refresh cursor position. */ size_t len; /* Current edited line length. */ size_t cols; /* Number of columns in terminal. */ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ int history_index; /* The history index we are currently editing. */ }; typedef struct { int len; /* Length of the result string. */ char *result; /* Search result string. */ int search_term_index; /* Position of the search term in the history record. */ int search_term_len; /* Length of the search term. */ } linenoiseHistorySearchResult; enum KEY_ACTION{ KEY_NULL = 0, /* NULL */ CTRL_A = 1, /* Ctrl+a */ CTRL_B = 2, /* Ctrl-b */ CTRL_C = 3, /* Ctrl-c */ CTRL_D = 4, /* Ctrl-d */ CTRL_E = 5, /* Ctrl-e */ CTRL_F = 6, /* Ctrl-f */ CTRL_G = 7, /* Ctrl-g */ CTRL_H = 8, /* Ctrl-h */ TAB = 9, /* Tab */ NL = 10, /* Enter typed before raw mode was enabled */ CTRL_K = 11, /* Ctrl+k */ CTRL_L = 12, /* Ctrl+l */ ENTER = 13, /* Enter */ CTRL_N = 14, /* Ctrl-n */ CTRL_P = 16, /* Ctrl-p */ CTRL_R = 18, /* Ctrl-r */ CTRL_S = 19, /* Ctrl-s */ CTRL_T = 20, /* Ctrl-t */ CTRL_U = 21, /* Ctrl+u */ CTRL_W = 23, /* Ctrl+w */ ESC = 27, /* Escape */ BACKSPACE = 127 /* Backspace */ }; static void linenoiseAtExit(void); int linenoiseHistoryAdd(const char *line, int is_sensitive); static void refreshLine(struct linenoiseState *l); static void refreshSearchResult(struct linenoiseState *ls); static inline void resetSearchResult(void) { memset(search_result, 0, sizeof(search_result)); memset(search_result_friendly, 0, sizeof(search_result_friendly)); } /* Debugging macro. */ #if 0 FILE *lndebug_fp = NULL; #define lndebug(...) \ do { \ if (lndebug_fp == NULL) { \ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ fprintf(lndebug_fp, \ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ (int)l->maxrows,old_rows); \ } \ fprintf(lndebug_fp, ", " __VA_ARGS__); \ fflush(lndebug_fp); \ } while (0) #else #define lndebug(fmt, ...) #endif /* ======================= Low level terminal handling ====================== */ /* Enable "mask mode". When it is enabled, instead of the input that * the user is typing, the terminal will just display a corresponding * number of asterisks, like "****". This is useful for passwords and other * secrets that should not be displayed. */ void linenoiseMaskModeEnable(void) { maskmode = 1; } /* Disable mask mode. */ void linenoiseMaskModeDisable(void) { maskmode = 0; } /* Set if to use or not the multi line mode. */ void linenoiseSetMultiLine(int ml) { mlmode = ml; } #define REVERSE_SEARCH_PROMPT(direction) ((direction) == -1 ? "(reverse-i-search): " : "(i-search): ") /* Enables the reverse search mode and refreshes the prompt. */ static void enableReverseSearchMode(struct linenoiseState *l) { assert(reverse_search_mode_enabled != 1); reverse_search_mode_enabled = 1; l->origin_prompt = l->prompt; l->prompt = REVERSE_SEARCH_PROMPT(reverse_search_direction); refreshLine(l); } /* This function disables the reverse search mode and returns the terminal to its original state. * If the 'discard' parameter is true, it discards the user's input search keyword and search result. * Otherwise, it copies the search result into 'buf', If there is no search result, it copies the * input search keyword instead. */ static void disableReverseSearchMode(struct linenoiseState *l, char *buf, size_t buflen, int discard) { if (discard) { buf[0] = '\0'; l->pos = l->len = 0; } else { ignore_once_hint = 1; if (strlen(search_result)) { strncpy(buf, search_result, buflen); buf[buflen-1] = '\0'; l->pos = l->len = strlen(buf); } } /* Reset the state to non-search state. */ reverse_search_mode_enabled = 0; l->prompt = l->origin_prompt; resetSearchResult(); refreshLine(l); } /* Return true if the terminal name is in the list of terminals we know are * not able to understand basic escape sequences. */ static int isUnsupportedTerm(void) { char *term = getenv("TERM"); int j; if (term == NULL) return 0; for (j = 0; unsupported_term[j]; j++) if (!strcasecmp(term,unsupported_term[j])) return 1; return 0; } /* Raw mode: 1960's magic. */ static int enableRawMode(int fd) { if (getenv("FAKETTY_WITH_PROMPT") != NULL) { return 0; } struct termios raw; if (!isatty(STDIN_FILENO)) goto fatal; if (!atexit_registered) { atexit(linenoiseAtExit); atexit_registered = 1; } if (tcgetattr(fd,&orig_termios) == -1) goto fatal; raw = orig_termios; /* modify the original mode */ /* input modes: no break, no CR to NL, no parity check, no strip char, * no start/stop output control. */ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /* output modes - disable post processing */ raw.c_oflag &= ~(OPOST); /* control modes - set 8 bit chars */ raw.c_cflag |= (CS8); /* local modes - choing off, canonical off, no extended functions, * no signal chars (^Z,^C) */ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); /* control chars - set return condition: min number of bytes and timer. * We want read to return every single byte, without timeout. */ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ /* put terminal in raw mode */ if (tcsetattr(fd,TCSANOW,&raw) < 0) goto fatal; rawmode = 1; return 0; fatal: errno = ENOTTY; return -1; } static void disableRawMode(int fd) { /* Don't even check the return value as it's too late. */ if (rawmode && tcsetattr(fd,TCSANOW,&orig_termios) != -1) rawmode = 0; } /* Use the ESC [6n escape sequence to query the horizontal cursor position * and return it. On error -1 is returned, on success the position of the * cursor. */ static int getCursorPosition(int ifd, int ofd) { char buf[32]; int cols, rows; unsigned int i = 0; /* Report cursor location */ if (write(ofd, "\x1b[6n", 4) != 4) return -1; /* Read the response: ESC [ rows ; cols R */ while (i < sizeof(buf)-1) { if (read(ifd,buf+i,1) != 1) break; if (buf[i] == 'R') break; i++; } buf[i] = '\0'; /* Parse it. */ if (buf[0] != ESC || buf[1] != '[') return -1; if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; return cols; } /* Try to get the number of columns in the current terminal, or assume 80 * if it fails. */ static int getColumns(int ifd, int ofd) { if (getenv("FAKETTY_WITH_PROMPT") != NULL) { goto failed; } struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { /* ioctl() failed. Try to query the terminal itself. */ int start, cols; /* Get the initial position so we can restore it later. */ start = getCursorPosition(ifd,ofd); if (start == -1) goto failed; /* Go to right margin and get position. */ if (write(ofd,"\x1b[999C",6) != 6) goto failed; cols = getCursorPosition(ifd,ofd); if (cols == -1) goto failed; /* Restore position. */ if (cols > start) { char seq[32]; snprintf(seq,32,"\x1b[%dD",cols-start); if (write(ofd,seq,strlen(seq)) == -1) { /* Can't recover... */ } } return cols; } else { return ws.ws_col; } failed: return 80; } /* Clear the screen. Used to handle ctrl+l */ void linenoiseClearScreen(void) { if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { /* nothing to do, just to avoid warning. */ } } /* Beep, used for completion when there is nothing to complete or when all * the choices were already shown. */ static void linenoiseBeep(void) { fprintf(stderr, "\x7"); fflush(stderr); } /* ============================== Completion ================================ */ /* Free a list of completion option populated by linenoiseAddCompletion(). */ static void freeCompletions(linenoiseCompletions *lc) { size_t i; for (i = 0; i < lc->len; i++) free(lc->cvec[i]); if (lc->cvec != NULL) free(lc->cvec); } /* This is an helper function for linenoiseEdit() and is called when the * user types the key in order to complete the string currently in the * input. * * The state of the editing is encapsulated into the pointed linenoiseState * structure as described in the structure definition. */ static int completeLine(struct linenoiseState *ls) { linenoiseCompletions lc = { 0, NULL }; int nread, nwritten; char c = 0; completionCallback(ls->buf,&lc); if (lc.len == 0) { linenoiseBeep(); } else { size_t stop = 0, i = 0; while(!stop) { /* Show completion or original buffer */ if (i < lc.len) { struct linenoiseState saved = *ls; ls->len = ls->pos = strlen(lc.cvec[i]); ls->buf = lc.cvec[i]; refreshLine(ls); ls->len = saved.len; ls->pos = saved.pos; ls->buf = saved.buf; } else { refreshLine(ls); } nread = read(ls->ifd,&c,1); if (nread <= 0) { freeCompletions(&lc); return -1; } switch(c) { case 9: /* tab */ i = (i+1) % (lc.len+1); if (i == lc.len) linenoiseBeep(); break; case 27: /* escape */ /* Re-show original buffer */ if (i < lc.len) refreshLine(ls); stop = 1; break; default: /* Update buffer and return */ if (i < lc.len) { nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); ls->len = ls->pos = nwritten; } stop = 1; break; } } } freeCompletions(&lc); return c; /* Return last read character */ } /* Register a callback function to be called for tab-completion. */ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { completionCallback = fn; } /* Register a hits function to be called to show hits to the user at the * right of the prompt. */ void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { hintsCallback = fn; } /* Register a function to free the hints returned by the hints callback * registered with linenoiseSetHintsCallback(). */ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { freeHintsCallback = fn; } /* This function is used by the callback function registered by the user * in order to add completion options given the input string when the * user typed . See the example.c source code for a very easy to * understand example. */ void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { size_t len = strlen(str); char *copy, **cvec; copy = malloc(len+1); if (copy == NULL) return; memcpy(copy,str,len+1); cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); if (cvec == NULL) { free(copy); return; } lc->cvec = cvec; lc->cvec[lc->len++] = copy; } /* =========================== Line editing ================================= */ /* We define a very simple "append buffer" structure, that is an heap * allocated string where we can append to. This is useful in order to * write all the escape sequences in a buffer and flush them to the standard * output in a single call, to avoid flickering effects. */ struct abuf { char *b; int len; }; static void abInit(struct abuf *ab) { ab->b = NULL; ab->len = 0; } static void abAppend(struct abuf *ab, const char *s, int len) { char *new = realloc(ab->b,ab->len+len); if (new == NULL) return; memcpy(new+ab->len,s,len); ab->b = new; ab->len += len; } static void abFree(struct abuf *ab) { free(ab->b); } /* Helper of refreshSingleLine() and refreshMultiLine() to show hints * to the right of the prompt. */ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { char seq[64]; /* Show hits when not in reverse search mode and not instructed to ignore once. */ if (reverse_search_mode_enabled || ignore_once_hint) { ignore_once_hint = 0; return; } if (hintsCallback && plen+l->len < l->cols) { int color = -1, bold = 0; char *hint = hintsCallback(l->buf,&color,&bold); if (hint) { int hintlen = strlen(hint); int hintmaxlen = l->cols-(plen+l->len); if (hintlen > hintmaxlen) hintlen = hintmaxlen; if (bold == 1 && color == -1) color = 37; if (color != -1 || bold != 0) snprintf(seq,64,"\033[%d;%d;49m",bold,color); else seq[0] = '\0'; abAppend(ab,seq,strlen(seq)); abAppend(ab,hint,hintlen); if (color != -1 || bold != 0) abAppend(ab,"\033[0m",4); /* Call the function to free the hint returned. */ if (freeHintsCallback) freeHintsCallback(hint); } } } /* Single line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ static void refreshSingleLine(struct linenoiseState *l) { char seq[64]; size_t plen = strlen(l->prompt); int fd = l->ofd; char *buf = l->buf; size_t len = l->len; size_t pos = l->pos; struct abuf ab; while((plen+pos) >= l->cols) { buf++; len--; pos--; } while (plen+len > l->cols) { len--; } abInit(&ab); /* Cursor to left edge */ snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); /* Write the prompt and the current buffer content */ abAppend(&ab,l->prompt,strlen(l->prompt)); if (maskmode == 1) { while (len--) abAppend(&ab,"*",1); } else { abAppend(&ab,buf,len); } /* Show hits if any. */ refreshShowHints(&ab,l,plen); /* Erase to right */ snprintf(seq,64,"\x1b[0K"); abAppend(&ab,seq,strlen(seq)); /* Move cursor to original position. */ snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); abAppend(&ab,seq,strlen(seq)); if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); } /* Multi line low level line refresh. * * Rewrite the currently edited line accordingly to the buffer content, * cursor position, and number of columns of the terminal. */ static void refreshMultiLine(struct linenoiseState *l) { char seq[64]; int plen = strlen(l->prompt); int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ int rpos2; /* rpos after refresh. */ int col; /* colum position, zero-based. */ int old_rows = l->maxrows; int fd = l->ofd, j; struct abuf ab; /* Update maxrows if needed. */ if (rows > (int)l->maxrows) l->maxrows = rows; /* First step: clear all the lines used before. To do so start by * going to the last row. */ abInit(&ab); if (old_rows-rpos > 0) { lndebug("go down %d", old_rows-rpos); snprintf(seq,64,"\x1b[%dB", old_rows-rpos); abAppend(&ab,seq,strlen(seq)); } /* Now for every row clear it, go up. */ for (j = 0; j < old_rows-1; j++) { lndebug("clear+up"); snprintf(seq,64,"\r\x1b[0K\x1b[1A"); abAppend(&ab,seq,strlen(seq)); } /* Clean the top line. */ lndebug("clear"); snprintf(seq,64,"\r\x1b[0K"); abAppend(&ab,seq,strlen(seq)); /* Write the prompt and the current buffer content */ abAppend(&ab,l->prompt,strlen(l->prompt)); if (maskmode == 1) { unsigned int i; for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); } else { refreshSearchResult(l); if (strlen(search_result) > 0) { abAppend(&ab, search_result_friendly, strlen(search_result_friendly)); } else { abAppend(&ab,l->buf,l->len); } } /* Show hits if any. */ refreshShowHints(&ab,l,plen); /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the prompt to the first column. */ if (l->pos && l->pos == l->len && (l->pos+plen) % l->cols == 0) { lndebug(""); abAppend(&ab,"\n",1); snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); rows++; if (rows > (int)l->maxrows) l->maxrows = rows; } /* Move cursor to right position. */ rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ lndebug("rpos2 %d", rpos2); /* Go up till we reach the expected position. */ if (rows-rpos2 > 0) { lndebug("go-up %d", rows-rpos2); snprintf(seq,64,"\x1b[%dA", rows-rpos2); abAppend(&ab,seq,strlen(seq)); } /* Set column. */ col = (plen+(int)l->pos) % (int)l->cols; if (strlen(search_result) > 0) { col += search_result_start_offset; } lndebug("set col %d", 1+col); if (col) snprintf(seq,64,"\r\x1b[%dC", col); else snprintf(seq,64,"\r"); abAppend(&ab,seq,strlen(seq)); lndebug("\n"); l->oldpos = l->pos; if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ abFree(&ab); } /* Calls the two low level functions refreshSingleLine() or * refreshMultiLine() according to the selected mode. */ static void refreshLine(struct linenoiseState *l) { if (mlmode) refreshMultiLine(l); else refreshSingleLine(l); } /* Insert the character 'c' at cursor current position. * * On error writing to the terminal -1 is returned, otherwise 0. */ int linenoiseEditInsert(struct linenoiseState *l, char c) { if (l->len < l->buflen) { if (l->len == l->pos) { l->buf[l->pos] = c; l->pos++; l->len++; l->buf[l->len] = '\0'; if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { /* Avoid a full update of the line in the * trivial case. */ char d = (maskmode==1) ? '*' : c; if (write(l->ofd,&d,1) == -1) return -1; } else { refreshLine(l); } } else { memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); l->buf[l->pos] = c; l->len++; l->pos++; l->buf[l->len] = '\0'; refreshLine(l); } } return 0; } /* Move cursor on the left. */ void linenoiseEditMoveLeft(struct linenoiseState *l) { if (l->pos > 0) { l->pos--; refreshLine(l); } } /* Move cursor on the right. */ void linenoiseEditMoveRight(struct linenoiseState *l) { if (l->pos != l->len) { l->pos++; refreshLine(l); } } /* Move cursor to the start of the line. */ void linenoiseEditMoveHome(struct linenoiseState *l) { if (l->pos != 0) { l->pos = 0; refreshLine(l); } } /* Move cursor to the end of the line. */ void linenoiseEditMoveEnd(struct linenoiseState *l) { if (l->pos != l->len) { l->pos = l->len; refreshLine(l); } } /* Substitute the currently edited line with the next or previous history * entry as specified by 'dir'. */ #define LINENOISE_HISTORY_NEXT 0 #define LINENOISE_HISTORY_PREV 1 void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { if (history_len > 1) { /* Update the current history entry before to * overwrite it with the next one. */ free(history[history_len - 1 - l->history_index]); history[history_len - 1 - l->history_index] = strdup(l->buf); /* Show the new entry */ l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; if (l->history_index < 0) { l->history_index = 0; return; } else if (l->history_index >= history_len) { l->history_index = history_len-1; return; } strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); l->buf[l->buflen-1] = '\0'; l->len = l->pos = strlen(l->buf); refreshLine(l); } } /* Delete the character at the right of the cursor without altering the cursor * position. Basically this is what happens with the "Delete" keyboard key. */ void linenoiseEditDelete(struct linenoiseState *l) { if (l->len > 0 && l->pos < l->len) { memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); l->len--; l->buf[l->len] = '\0'; refreshLine(l); } } /* Backspace implementation. */ void linenoiseEditBackspace(struct linenoiseState *l) { if (l->pos > 0 && l->len > 0) { memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); l->pos--; l->len--; l->buf[l->len] = '\0'; refreshLine(l); } } /* Delete the previous word, maintaining the cursor at the start of the * current word. */ void linenoiseEditDeletePrevWord(struct linenoiseState *l) { size_t old_pos = l->pos; size_t diff; while (l->pos > 0 && l->buf[l->pos-1] == ' ') l->pos--; while (l->pos > 0 && l->buf[l->pos-1] != ' ') l->pos--; diff = old_pos - l->pos; memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); l->len -= diff; refreshLine(l); } /* This function is the core of the line editing capability of linenoise. * It expects 'fd' to be already in "raw mode" so that every key pressed * will be returned ASAP to read(). * * The resulting string is put into 'buf' when the user type enter, or * when ctrl+d is typed. * * The function returns the length of the current buffer. */ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) { struct linenoiseState l; /* Populate the linenoise state that we pass to functions implementing * specific editing functionalities. */ l.ifd = stdin_fd; l.ofd = stdout_fd; l.buf = buf; l.buflen = buflen; l.prompt = prompt; l.plen = strlen(prompt); l.oldpos = l.pos = 0; l.len = 0; l.cols = getColumns(stdin_fd, stdout_fd); l.maxrows = 0; l.history_index = 0; /* Buffer starts empty. */ l.buf[0] = '\0'; l.buflen--; /* Make sure there is always space for the nulterm */ /* The latest history entry is always our current buffer, that * initially is just an empty string. */ linenoiseHistoryAdd("", 0); if (write(l.ofd,prompt,l.plen) == -1) return -1; while(1) { char c; int nread; char seq[3]; nread = read(l.ifd,&c,1); if (nread <= 0) return l.len; /* Only autocomplete when the callback is set. It returns < 0 when * there was an error reading from fd. Otherwise it will return the * character that should be handled next. */ if (c == TAB && completionCallback != NULL && !reverse_search_mode_enabled) { c = completeLine(&l); /* Return on errors */ if (c < 0) return l.len; /* Read next character when 0 */ if (c == 0) continue; } switch(c) { case NL: /* enter, typed before raw mode was enabled */ break; case TAB: if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 0); break; case ENTER: /* enter */ history_len--; free(history[history_len]); if (mlmode) linenoiseEditMoveEnd(&l); if (hintsCallback) { /* Force a refresh without hints to leave the previous * line as the user typed it after a newline. */ linenoiseHintsCallback *hc = hintsCallback; hintsCallback = NULL; refreshLine(&l); hintsCallback = hc; } if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 0); return (int)l.len; case CTRL_C: /* ctrl-c */ if (reverse_search_mode_enabled) { disableReverseSearchMode(&l, buf, buflen, 1); break; } errno = EAGAIN; return -1; case BACKSPACE: /* backspace */ case 8: /* ctrl-h */ linenoiseEditBackspace(&l); break; case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the line is empty, act as end-of-file. */ if (l.len > 0) { linenoiseEditDelete(&l); } else { history_len--; free(history[history_len]); return -1; } break; case CTRL_T: /* ctrl-t, swaps current character with previous. */ if (l.pos > 0 && l.pos < l.len) { int aux = buf[l.pos-1]; buf[l.pos-1] = buf[l.pos]; buf[l.pos] = aux; if (l.pos != l.len-1) l.pos++; refreshLine(&l); } break; case CTRL_B: /* ctrl-b */ linenoiseEditMoveLeft(&l); break; case CTRL_F: /* ctrl-f */ linenoiseEditMoveRight(&l); break; case CTRL_P: /* ctrl-p */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); break; case CTRL_R: case CTRL_S: reverse_search_direction = c == CTRL_R ? -1 : 1; if (reverse_search_mode_enabled) { /* cycle search results */ cycle_to_next_search = 1; l.prompt = REVERSE_SEARCH_PROMPT(reverse_search_direction); refreshLine(&l); break; } buf[0] = '\0'; l.pos = l.len = 0; enableReverseSearchMode(&l); break; case CTRL_G: if (reverse_search_mode_enabled) disableReverseSearchMode(&l, buf, buflen, 1); break; case CTRL_N: /* ctrl-n */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); break; case ESC: /* escape sequence */ /* Read the next two bytes representing the escape sequence. * Use two calls to handle slow terminals returning the two * chars at different times. */ if (read(l.ifd,seq,1) == -1) break; if (read(l.ifd,seq+1,1) == -1) break; if (reverse_search_mode_enabled) { disableReverseSearchMode(&l, buf, buflen, 1); break; } /* 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; } } } else { switch(seq[1]) { case 'A': /* Up */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); break; case 'B': /* Down */ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); break; case 'C': /* Right */ linenoiseEditMoveRight(&l); break; case 'D': /* Left */ linenoiseEditMoveLeft(&l); break; case 'H': /* Home */ linenoiseEditMoveHome(&l); break; case 'F': /* End*/ linenoiseEditMoveEnd(&l); break; } } } /* ESC O sequences. */ else if (seq[0] == 'O') { switch(seq[1]) { case 'H': /* Home */ linenoiseEditMoveHome(&l); break; case 'F': /* End*/ linenoiseEditMoveEnd(&l); break; } } break; default: if (linenoiseEditInsert(&l,c)) return -1; break; case CTRL_U: /* Ctrl+u, delete the whole line. */ buf[0] = '\0'; l.pos = l.len = 0; refreshLine(&l); break; case CTRL_K: /* Ctrl+k, delete from current to end of line. */ buf[l.pos] = '\0'; l.len = l.pos; refreshLine(&l); break; case CTRL_A: /* Ctrl+a, go to the start of the line */ linenoiseEditMoveHome(&l); break; case CTRL_E: /* ctrl+e, go to the end of the line */ linenoiseEditMoveEnd(&l); break; case CTRL_L: /* ctrl+l, clear screen */ linenoiseClearScreen(); refreshLine(&l); break; case CTRL_W: /* ctrl+w, delete previous word */ linenoiseEditDeletePrevWord(&l); break; } } return l.len; } /* This special mode is used by linenoise in order to print scan codes * on screen for debugging / development purposes. It is implemented * by the linenoise_example program using the --keycodes option. */ void linenoisePrintKeyCodes(void) { char quit[4]; printf("Linenoise key codes debugging mode.\n" "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); if (enableRawMode(STDIN_FILENO) == -1) return; memset(quit,' ',4); while(1) { char c; int nread; nread = read(STDIN_FILENO,&c,1); if (nread <= 0) continue; memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ if (memcmp(quit,"quit",sizeof(quit)) == 0) break; printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, (int)c); printf("\r"); /* Go left edge manually, we are in raw mode. */ fflush(stdout); } disableRawMode(STDIN_FILENO); } /* This function calls the line editing function linenoiseEdit() using * the STDIN file descriptor set in raw mode. */ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { int count; if (buflen == 0) { errno = EINVAL; return -1; } if (enableRawMode(STDIN_FILENO) == -1) return -1; count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); disableRawMode(STDIN_FILENO); printf("\n"); return count; } /* This function is called when linenoise() is called with the standard * input file descriptor not attached to a TTY. So for example when the * program using linenoise is called in pipe or with a file redirected * to its standard input. In this case, we want to be able to return the * line regardless of its length (by default we are limited to 4k). */ static char *linenoiseNoTTY(void) { char *line = NULL; size_t len = 0, maxlen = 0; while(1) { if (len == maxlen) { if (maxlen == 0) maxlen = 16; maxlen *= 2; char *oldval = line; line = realloc(line,maxlen); if (line == NULL) { if (oldval) free(oldval); return NULL; } } int c = fgetc(stdin); if (c == EOF || c == '\n') { if (c == EOF && len == 0) { free(line); return NULL; } else { line[len] = '\0'; return line; } } else { line[len] = c; len++; } } } /* The high level function that is the main API of the linenoise library. * This function checks if the terminal has basic capabilities, just checking * for a blacklist of stupid terminals, and later either calls the line * editing function or uses dummy fgets() so that you will be able to type * something even in the most desperate of the conditions. */ char *linenoise(const char *prompt) { char buf[LINENOISE_MAX_LINE] = {0}; int count; if (getenv("FAKETTY_WITH_PROMPT") == NULL && !isatty(STDIN_FILENO)) { /* Not a tty: read from file / pipe. In this mode we don't want any * limit to the line size, so we call a function to handle that. */ return linenoiseNoTTY(); } else if (getenv("FAKETTY_WITH_PROMPT") == NULL && isUnsupportedTerm()) { size_t len; printf("%s",prompt); fflush(stdout); if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; len = strlen(buf); while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { len--; buf[len] = '\0'; } return strdup(buf); } else { count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); if (count == -1) return NULL; return strdup(buf); } } /* This is just a wrapper the user may want to call in order to make sure * the linenoise returned buffer is freed with the same allocator it was * created with. Useful when the main program is using an alternative * allocator. */ void linenoiseFree(void *ptr) { free(ptr); } /* ================================ History ================================= */ /* Free the history, but does not reset it. Only used when we have to * exit() to avoid memory leaks are reported by valgrind & co. */ static void freeHistory(void) { if (history) { int j; for (j = 0; j < history_len; j++) free(history[j]); free(history); free(history_sensitive); } } /* At exit we'll try to fix the terminal to the initial conditions. */ static void linenoiseAtExit(void) { disableRawMode(STDIN_FILENO); freeHistory(); } /* This is the API call to add a new entry in the linenoise history. * It uses a fixed array of char pointers that are shifted (memmoved) * when the history max length is reached in order to remove the older * entry and make room for the new one, so it is not exactly suitable for huge * histories, but will work well for a few hundred of entries. * * Using a circular buffer is smarter, but a bit more complex to handle. */ int linenoiseHistoryAdd(const char *line, int is_sensitive) { char *linecopy; if (history_max_len == 0) return 0; /* Initialization on first call. */ if (history == NULL) { history = malloc(sizeof(char*)*history_max_len); if (history == NULL) return 0; history_sensitive = malloc(sizeof(int)*history_max_len); if (history_sensitive == NULL) { free(history); history = NULL; return 0; } memset(history,0,(sizeof(char*)*history_max_len)); memset(history_sensitive,0,(sizeof(int)*history_max_len)); } /* Don't add duplicated lines. */ if (history_len && !strcmp(history[history_len-1], line)) return 0; /* Add an heap allocated copy of the line in the history. * If we reached the max length, remove the older line. */ linecopy = strdup(line); if (!linecopy) return 0; if (history_len == history_max_len) { free(history[0]); memmove(history,history+1,sizeof(char*)*(history_max_len-1)); memmove(history_sensitive,history_sensitive+1,sizeof(int)*(history_max_len-1)); history_len--; } history[history_len] = linecopy; history_sensitive[history_len] = is_sensitive; history_len++; return 1; } /* Set the maximum length for the history. This function can be called even * if there is already some history, the function will make sure to retain * just the latest 'len' elements if the new history length value is smaller * than the amount of items already inside the history. */ int linenoiseHistorySetMaxLen(int len) { char **new; int *new_sensitive; if (len < 1) return 0; if (history) { int tocopy = history_len; new = malloc(sizeof(char*)*len); if (new == NULL) return 0; new_sensitive = malloc(sizeof(int)*len); if (new_sensitive == NULL) { free(new); return 0; } /* If we can't copy everything, free the elements we'll not use. */ if (len < tocopy) { int j; for (j = 0; j < tocopy-len; j++) free(history[j]); tocopy = len; } memset(new,0,sizeof(char*)*len); memset(new_sensitive,0,sizeof(int)*len); memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); memcpy(new_sensitive,history_sensitive+(history_len-tocopy), sizeof(int)*tocopy); free(history); free(history_sensitive); history = new; history_sensitive = new_sensitive; } history_max_len = len; if (history_len > history_max_len) history_len = history_max_len; return 1; } /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int linenoiseHistorySave(const char *filename) { mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); FILE *fp; int j; fp = fopen(filename,"w"); umask(old_umask); if (fp == NULL) return -1; fchmod(fileno(fp),S_IRUSR|S_IWUSR); for (j = 0; j < history_len; j++) if (!history_sensitive[j]) fprintf(fp,"%s\n",history[j]); fclose(fp); return 0; } /* Load the history from the specified file. If the file does not exist * zero is returned and no operation is performed. * * If the file exists and the operation succeeded 0 is returned, otherwise * on error -1 is returned. */ int linenoiseHistoryLoad(const char *filename) { FILE *fp = fopen(filename,"r"); char buf[LINENOISE_MAX_LINE]; if (fp == NULL) return -1; while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { char *p; p = strchr(buf,'\r'); if (!p) p = strchr(buf,'\n'); if (p) *p = '\0'; linenoiseHistoryAdd(buf, 0); } fclose(fp); return 0; } /* This function updates the search index based on the direction of the search. * Returns 0 if the beginning or end of the history is reached, otherwise, returns 1. */ static int setNextSearchIndex(int *i) { if (reverse_search_direction == 1) { if (*i == history_len-1) return 0; *i = *i + 1; } else { if (*i <= 0) return 0; *i = *i - 1; } return 1; } linenoiseHistorySearchResult searchInHistory(char *search_term) { linenoiseHistorySearchResult result = {0}; if (!history_len || !strlen(search_term)) return result; int i = cycle_to_next_search ? search_result_history_index : (reverse_search_direction == -1 ? history_len-1 : 0); while (1) { char *found = strstr(history[i], search_term); /* check if we found the same string at another index when cycling, this would be annoying to cycle through * as it might appear that cycling isn't working */ int strings_are_the_same = cycle_to_next_search && strcmp(history[i], history[search_result_history_index]) == 0; if (found && !strings_are_the_same) { int haystack_index = found - history[i]; result.result = history[i]; result.len = strlen(history[i]); result.search_term_index = haystack_index; result.search_term_len = strlen(search_term); search_result_history_index = i; break; } /* Exit if reached the end. */ if (!setNextSearchIndex(&i)) break; } return result; } static void refreshSearchResult(struct linenoiseState *ls) { if (!reverse_search_mode_enabled) { return; } linenoiseHistorySearchResult sr = searchInHistory(ls->buf); int found = sr.result && sr.len; /* If the search term has not changed and we are cycling to the next search result * (using CTRL+R or CTRL+S), there is no need to reset the old search result. */ if (!cycle_to_next_search || found) resetSearchResult(); cycle_to_next_search = 0; if (found) { char *bold = "\x1B[1m"; char *normal = "\x1B[0m"; int size_needed = sr.search_term_index + sr.search_term_len + sr.len - (sr.search_term_index+sr.search_term_len) + sizeof(normal) + sizeof(bold) + sizeof(normal); if (size_needed > sizeof(search_result_friendly) - 1) { return; } /* Allocate memory for the prefix, match, and suffix strings, one extra byte for `\0`. */ char *prefix = calloc(sizeof(char), sr.search_term_index + 1); char *match = calloc(sizeof(char), sr.search_term_len + 1); char *suffix = calloc(sizeof(char), sr.len - (sr.search_term_index+sr.search_term_len) + 1); memcpy(prefix, sr.result, sr.search_term_index); memcpy(match, sr.result + sr.search_term_index, sr.search_term_len); memcpy(suffix, sr.result + sr.search_term_index + sr.search_term_len, sr.len - (sr.search_term_index+sr.search_term_len)); sprintf(search_result, "%s%s%s", prefix, match, suffix); sprintf(search_result_friendly, "%s%s%s%s%s%s", normal, prefix, bold, match, normal, suffix); free(prefix); free(match); free(suffix); search_result_start_offset = sr.search_term_index; } } redis-8.0.2/deps/linenoise/linenoise.h000066400000000000000000000055441501533116600177160ustar00rootroot00000000000000/* linenoise.h -- VERSION 1.0 * * Guerrilla line editing library against the idea that a line editing lib * needs to be 20,000 lines of C code. * * See linenoise.c for more information. * * ------------------------------------------------------------------------ * * Copyright (c) 2010-2014, Salvatore Sanfilippo * Copyright (c) 2010-2013, Pieter Noordhuis * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 HOLDERS 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. */ #ifndef __LINENOISE_H #define __LINENOISE_H #ifdef __cplusplus extern "C" { #endif typedef struct linenoiseCompletions { size_t len; char **cvec; } linenoiseCompletions; typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); typedef void(linenoiseFreeHintsCallback)(void *); void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); void linenoiseSetHintsCallback(linenoiseHintsCallback *); void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); void linenoiseAddCompletion(linenoiseCompletions *, const char *); char *linenoise(const char *prompt); void linenoiseFree(void *ptr); int linenoiseHistoryAdd(const char *line, int is_sensitive); int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySave(const char *filename); int linenoiseHistoryLoad(const char *filename); void linenoiseClearScreen(void); void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); void linenoiseMaskModeEnable(void); void linenoiseMaskModeDisable(void); #ifdef __cplusplus } #endif #endif /* __LINENOISE_H */ redis-8.0.2/deps/lua/000077500000000000000000000000001501533116600143445ustar00rootroot00000000000000redis-8.0.2/deps/lua/COPYRIGHT000066400000000000000000000027701501533116600156450ustar00rootroot00000000000000Lua License ----------- Lua is licensed under the terms of the MIT license reproduced below. This means that Lua is free software and can be used for both academic and commercial purposes at absolutely no cost. For details and rationale, see http://www.lua.org/license.html . =============================================================================== Copyright (C) 1994-2012 Lua.org, PUC-Rio. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =============================================================================== (end of COPYRIGHT) redis-8.0.2/deps/lua/HISTORY000066400000000000000000000173431501533116600154400ustar00rootroot00000000000000HISTORY for Lua 5.1 * Changes from version 5.0 to 5.1 ------------------------------- Language: + new module system. + new semantics for control variables of fors. + new semantics for setn/getn. + new syntax/semantics for varargs. + new long strings and comments. + new `mod' operator (`%') + new length operator #t + metatables for all types API: + new functions: lua_createtable, lua_get(set)field, lua_push(to)integer. + user supplies memory allocator (lua_open becomes lua_newstate). + luaopen_* functions must be called through Lua. Implementation: + new configuration scheme via luaconf.h. + incremental garbage collection. + better handling of end-of-line in the lexer. + fully reentrant parser (new Lua function `load') + better support for 64-bit machines. + native loadlib support for Mac OS X. + standard distribution in only one library (lualib.a merged into lua.a) * Changes from version 4.0 to 5.0 ------------------------------- Language: + lexical scoping. + Lua coroutines. + standard libraries now packaged in tables. + tags replaced by metatables and tag methods replaced by metamethods, stored in metatables. + proper tail calls. + each function can have its own global table, which can be shared. + new __newindex metamethod, called when we insert a new key into a table. + new block comments: --[[ ... ]]. + new generic for. + new weak tables. + new boolean type. + new syntax "local function". + (f()) returns the first value returned by f. + {f()} fills a table with all values returned by f. + \n ignored in [[\n . + fixed and-or priorities. + more general syntax for function definition (e.g. function a.x.y:f()...end). + more general syntax for function calls (e.g. (print or write)(9)). + new functions (time/date, tmpfile, unpack, require, load*, etc.). API: + chunks are loaded by using lua_load; new luaL_loadfile and luaL_loadbuffer. + introduced lightweight userdata, a simple "void*" without a metatable. + new error handling protocol: the core no longer prints error messages; all errors are reported to the caller on the stack. + new lua_atpanic for host cleanup. + new, signal-safe, hook scheme. Implementation: + new license: MIT. + new, faster, register-based virtual machine. + support for external multithreading and coroutines. + new and consistent error message format. + the core no longer needs "stdio.h" for anything (except for a single use of sprintf to convert numbers to strings). + lua.c now runs the environment variable LUA_INIT, if present. It can be "@filename", to run a file, or the chunk itself. + support for user extensions in lua.c. sample implementation given for command line editing. + new dynamic loading library, active by default on several platforms. + safe garbage-collector metamethods. + precompiled bytecodes checked for integrity (secure binary dostring). + strings are fully aligned. + position capture in string.find. + read('*l') can read lines with embedded zeros. * Changes from version 3.2 to 4.0 ------------------------------- Language: + new "break" and "for" statements (both numerical and for tables). + uniform treatment of globals: globals are now stored in a Lua table. + improved error messages. + no more '$debug': full speed *and* full debug information. + new read form: read(N) for next N bytes. + general read patterns now deprecated. (still available with -DCOMPAT_READPATTERNS.) + all return values are passed as arguments for the last function (old semantics still available with -DLUA_COMPAT_ARGRET) + garbage collection tag methods for tables now deprecated. + there is now only one tag method for order. API: + New API: fully re-entrant, simpler, and more efficient. + New debug API. Implementation: + faster than ever: cleaner virtual machine and new hashing algorithm. + non-recursive garbage-collector algorithm. + reduced memory usage for programs with many strings. + improved treatment for memory allocation errors. + improved support for 16-bit machines (we hope). + code now compiles unmodified as both ANSI C and C++. + numbers in bases other than 10 are converted using strtoul. + new -f option in Lua to support #! scripts. + luac can now combine text and binaries. * Changes from version 3.1 to 3.2 ------------------------------- + redirected all output in Lua's core to _ERRORMESSAGE and _ALERT. + increased limit on the number of constants and globals per function (from 2^16 to 2^24). + debugging info (lua_debug and hooks) moved into lua_state and new API functions provided to get and set this info. + new debug lib gives full debugging access within Lua. + new table functions "foreachi", "sort", "tinsert", "tremove", "getn". + new io functions "flush", "seek". * Changes from version 3.0 to 3.1 ------------------------------- + NEW FEATURE: anonymous functions with closures (via "upvalues"). + new syntax: - local variables in chunks. - better scope control with DO block END. - constructors can now be also written: { record-part; list-part }. - more general syntax for function calls and lvalues, e.g.: f(x).y=1 o:f(x,y):g(z) f"string" is sugar for f("string") + strings may now contain arbitrary binary data (e.g., embedded zeros). + major code re-organization and clean-up; reduced module interdependecies. + no arbitrary limits on the total number of constants and globals. + support for multiple global contexts. + better syntax error messages. + new traversal functions "foreach" and "foreachvar". + the default for numbers is now double. changing it to use floats or longs is easy. + complete debug information stored in pre-compiled chunks. + sample interpreter now prompts user when run interactively, and also handles control-C interruptions gracefully. * Changes from version 2.5 to 3.0 ------------------------------- + NEW CONCEPT: "tag methods". Tag methods replace fallbacks as the meta-mechanism for extending the semantics of Lua. Whereas fallbacks had a global nature, tag methods work on objects having the same tag (e.g., groups of tables). Existing code that uses fallbacks should work without change. + new, general syntax for constructors {[exp] = exp, ... }. + support for handling variable number of arguments in functions (varargs). + support for conditional compilation ($if ... $else ... $end). + cleaner semantics in API simplifies host code. + better support for writing libraries (auxlib.h). + better type checking and error messages in the standard library. + luac can now also undump. * Changes from version 2.4 to 2.5 ------------------------------- + io and string libraries are now based on pattern matching; the old libraries are still available for compatibility + dofile and dostring can now return values (via return statement) + better support for 16- and 64-bit machines + expanded documentation, with more examples * Changes from version 2.2 to 2.4 ------------------------------- + external compiler creates portable binary files that can be loaded faster + interface for debugging and profiling + new "getglobal" fallback + new functions for handling references to Lua objects + new functions in standard lib + only one copy of each string is stored + expanded documentation, with more examples * Changes from version 2.1 to 2.2 ------------------------------- + functions now may be declared with any "lvalue" as a name + garbage collection of functions + support for pipes * Changes from version 1.1 to 2.1 ------------------------------- + object-oriented support + fallbacks + simplified syntax for tables + many internal improvements (end of HISTORY) redis-8.0.2/deps/lua/INSTALL000066400000000000000000000074341501533116600154050ustar00rootroot00000000000000INSTALL for Lua 5.1 * Building Lua ------------ Lua is built in the src directory, but the build process can be controlled from the top-level Makefile. Building Lua on Unix systems should be very easy. First do "make" and see if your platform is listed. If so, just do "make xxx", where xxx is your platform name. The platforms currently supported are: aix ansi bsd freebsd generic linux macosx mingw posix solaris If your platform is not listed, try the closest one or posix, generic, ansi, in this order. See below for customization instructions and for instructions on how to build with other Windows compilers. If you want to check that Lua has been built correctly, do "make test" after building Lua. Also, have a look at the example programs in test. * Installing Lua -------------- Once you have built Lua, you may want to install it in an official place in your system. In this case, do "make install". The official place and the way to install files are defined in Makefile. You must have the right permissions to install files. If you want to build and install Lua in one step, do "make xxx install", where xxx is your platform name. If you want to install Lua locally, then do "make local". This will create directories bin, include, lib, man, and install Lua there as follows: bin: lua luac include: lua.h luaconf.h lualib.h lauxlib.h lua.hpp lib: liblua.a man/man1: lua.1 luac.1 These are the only directories you need for development. There are man pages for lua and luac, in both nroff and html, and a reference manual in html in doc, some sample code in test, and some useful stuff in etc. You don't need these directories for development. If you want to install Lua locally, but in some other directory, do "make install INSTALL_TOP=xxx", where xxx is your chosen directory. See below for instructions for Windows and other systems. * Customization ------------- Three things can be customized by editing a file: - Where and how to install Lua -- edit Makefile. - How to build Lua -- edit src/Makefile. - Lua features -- edit src/luaconf.h. You don't actually need to edit the Makefiles because you may set the relevant variables when invoking make. On the other hand, if you need to select some Lua features, you'll need to edit src/luaconf.h. The edited file will be the one installed, and it will be used by any Lua clients that you build, to ensure consistency. We strongly recommend that you enable dynamic loading. This is done automatically for all platforms listed above that have this feature (and also Windows). See src/luaconf.h and also src/Makefile. * Building Lua on Windows and other systems ----------------------------------------- If you're not using the usual Unix tools, then the instructions for building Lua depend on the compiler you use. You'll need to create projects (or whatever your compiler uses) for building the library, the interpreter, and the compiler, as follows: library: lapi.c lcode.c ldebug.c ldo.c ldump.c lfunc.c lgc.c llex.c lmem.c lobject.c lopcodes.c lparser.c lstate.c lstring.c ltable.c ltm.c lundump.c lvm.c lzio.c lauxlib.c lbaselib.c ldblib.c liolib.c lmathlib.c loslib.c ltablib.c lstrlib.c loadlib.c linit.c interpreter: library, lua.c compiler: library, luac.c print.c If you use Visual Studio .NET, you can use etc/luavs.bat in its "Command Prompt". If all you want is to build the Lua interpreter, you may put all .c files in a single project, except for luac.c and print.c. Or just use etc/all.c. To use Lua as a library in your own programs, you'll need to know how to create and use libraries with your compiler. As mentioned above, you may edit luaconf.h to select some features before building Lua. (end of INSTALL) redis-8.0.2/deps/lua/Makefile000066400000000000000000000071571501533116600160160ustar00rootroot00000000000000# makefile for installing Lua # see INSTALL for installation instructions # see src/Makefile and src/luaconf.h for further customization # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= # Your platform. See PLATS for possible values. PLAT= none # Where to install. The installation starts in the src and doc directories, # so take care if INSTALL_TOP is not an absolute path. INSTALL_TOP= /usr/local INSTALL_BIN= $(INSTALL_TOP)/bin INSTALL_INC= $(INSTALL_TOP)/include INSTALL_LIB= $(INSTALL_TOP)/lib INSTALL_MAN= $(INSTALL_TOP)/man/man1 # # You probably want to make INSTALL_LMOD and INSTALL_CMOD consistent with # LUA_ROOT, LUA_LDIR, and LUA_CDIR in luaconf.h (and also with etc/lua.pc). INSTALL_LMOD= $(INSTALL_TOP)/share/lua/$V INSTALL_CMOD= $(INSTALL_TOP)/lib/lua/$V # How to install. If your install program does not support "-p", then you # may have to run ranlib on the installed liblua.a (do "make ranlib"). INSTALL= install -p INSTALL_EXEC= $(INSTALL) -m 0755 INSTALL_DATA= $(INSTALL) -m 0644 # # If you don't have install you can use cp instead. # INSTALL= cp -p # INSTALL_EXEC= $(INSTALL) # INSTALL_DATA= $(INSTALL) # Utilities. MKDIR= mkdir -p RANLIB= ranlib # == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE ========= # Convenience platforms targets. PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris # What to install. TO_BIN= lua luac TO_INC= lua.h luaconf.h lualib.h lauxlib.h ../etc/lua.hpp TO_LIB= liblua.a TO_MAN= lua.1 luac.1 # Lua version and release. V= 5.1 R= 5.1.5 all: $(PLAT) $(PLATS) clean: cd src && $(MAKE) $@ test: dummy src/lua test/hello.lua install: dummy cd src && $(MKDIR) $(INSTALL_BIN) $(INSTALL_INC) $(INSTALL_LIB) $(INSTALL_MAN) $(INSTALL_LMOD) $(INSTALL_CMOD) cd src && $(INSTALL_EXEC) $(TO_BIN) $(INSTALL_BIN) cd src && $(INSTALL_DATA) $(TO_INC) $(INSTALL_INC) cd src && $(INSTALL_DATA) $(TO_LIB) $(INSTALL_LIB) cd doc && $(INSTALL_DATA) $(TO_MAN) $(INSTALL_MAN) ranlib: cd src && cd $(INSTALL_LIB) && $(RANLIB) $(TO_LIB) local: $(MAKE) install INSTALL_TOP=.. none: @echo "Please do" @echo " make PLATFORM" @echo "where PLATFORM is one of these:" @echo " $(PLATS)" @echo "See INSTALL for complete instructions." # make may get confused with test/ and INSTALL in a case-insensitive OS dummy: # echo config parameters echo: @echo "" @echo "These are the parameters currently set in src/Makefile to build Lua $R:" @echo "" @cd src && $(MAKE) -s echo @echo "" @echo "These are the parameters currently set in Makefile to install Lua $R:" @echo "" @echo "PLAT = $(PLAT)" @echo "INSTALL_TOP = $(INSTALL_TOP)" @echo "INSTALL_BIN = $(INSTALL_BIN)" @echo "INSTALL_INC = $(INSTALL_INC)" @echo "INSTALL_LIB = $(INSTALL_LIB)" @echo "INSTALL_MAN = $(INSTALL_MAN)" @echo "INSTALL_LMOD = $(INSTALL_LMOD)" @echo "INSTALL_CMOD = $(INSTALL_CMOD)" @echo "INSTALL_EXEC = $(INSTALL_EXEC)" @echo "INSTALL_DATA = $(INSTALL_DATA)" @echo "" @echo "See also src/luaconf.h ." @echo "" # echo private config parameters pecho: @echo "V = $(V)" @echo "R = $(R)" @echo "TO_BIN = $(TO_BIN)" @echo "TO_INC = $(TO_INC)" @echo "TO_LIB = $(TO_LIB)" @echo "TO_MAN = $(TO_MAN)" # echo config parameters as Lua code # uncomment the last sed expression if you want nil instead of empty strings lecho: @echo "-- installation parameters for Lua $R" @echo "VERSION = '$V'" @echo "RELEASE = '$R'" @$(MAKE) echo | grep = | sed -e 's/= /= "/' -e 's/$$/"/' #-e 's/""/nil/' @echo "-- EOF" # list targets that do not create files (but not all makes understand .PHONY) .PHONY: all $(PLATS) clean test install local none dummy echo pecho lecho # (end of Makefile) redis-8.0.2/deps/lua/README000066400000000000000000000025421501533116600152270ustar00rootroot00000000000000README for Lua 5.1 See INSTALL for installation instructions. See HISTORY for a summary of changes since the last released version. * What is Lua? ------------ Lua is a powerful, light-weight programming language designed for extending applications. Lua is also frequently used as a general-purpose, stand-alone language. Lua is free software. For complete information, visit Lua's web site at http://www.lua.org/ . For an executive summary, see http://www.lua.org/about.html . Lua has been used in many different projects around the world. For a short list, see http://www.lua.org/uses.html . * Availability ------------ Lua is freely available for both academic and commercial purposes. See COPYRIGHT and http://www.lua.org/license.html for details. Lua can be downloaded at http://www.lua.org/download.html . * Installation ------------ Lua is implemented in pure ANSI C, and compiles unmodified in all known platforms that have an ANSI C compiler. In most Unix-like platforms, simply do "make" with a suitable target. See INSTALL for detailed instructions. * Origin ------ Lua is developed at Lua.org, a laboratory of the Department of Computer Science of PUC-Rio (the Pontifical Catholic University of Rio de Janeiro in Brazil). For more information about the authors, see http://www.lua.org/authors.html . (end of README) redis-8.0.2/deps/lua/doc/000077500000000000000000000000001501533116600151115ustar00rootroot00000000000000redis-8.0.2/deps/lua/doc/contents.html000066400000000000000000000537221501533116600176450ustar00rootroot00000000000000 Lua 5.1 Reference Manual - contents

Lua 5.1 Reference Manual

The reference manual is the official definition of the Lua language. For a complete introduction to Lua programming, see the book Programming in Lua.

This manual is also available as a book:

Lua 5.1 Reference Manual
by R. Ierusalimschy, L. H. de Figueiredo, W. Celes
Lua.org, August 2006
ISBN 85-903798-3-3

Buy a copy of this book and help to support the Lua project.

start · contents · index · other versions


Copyright © 2006–2012 Lua.org, PUC-Rio. Freely available under the terms of the Lua license.

Contents

Index

Lua functions

_G
_VERSION

assert
collectgarbage
dofile
error
getfenv
getmetatable
ipairs
load
loadfile
loadstring
module
next
pairs
pcall
print
rawequal
rawget
rawset
require
select
setfenv
setmetatable
tonumber
tostring
type
unpack
xpcall

coroutine.create
coroutine.resume
coroutine.running
coroutine.status
coroutine.wrap
coroutine.yield

debug.debug
debug.getfenv
debug.gethook
debug.getinfo
debug.getlocal
debug.getmetatable
debug.getregistry
debug.getupvalue
debug.setfenv
debug.sethook
debug.setlocal
debug.setmetatable
debug.setupvalue
debug.traceback

 

file:close
file:flush
file:lines
file:read
file:seek
file:setvbuf
file:write

io.close
io.flush
io.input
io.lines
io.open
io.output
io.popen
io.read
io.stderr
io.stdin
io.stdout
io.tmpfile
io.type
io.write

math.abs
math.acos
math.asin
math.atan
math.atan2
math.ceil
math.cos
math.cosh
math.deg
math.exp
math.floor
math.fmod
math.frexp
math.huge
math.ldexp
math.log
math.log10
math.max
math.min
math.modf
math.pi
math.pow
math.rad
math.random
math.randomseed
math.sin
math.sinh
math.sqrt
math.tan
math.tanh

os.clock
os.date
os.difftime
os.execute
os.exit
os.getenv
os.remove
os.rename
os.setlocale
os.time
os.tmpname

package.cpath
package.loaded
package.loaders
package.loadlib
package.path
package.preload
package.seeall

string.byte
string.char
string.dump
string.find
string.format
string.gmatch
string.gsub
string.len
string.lower
string.match
string.rep
string.reverse
string.sub
string.upper

table.concat
table.insert
table.maxn
table.remove
table.sort

C API

lua_Alloc
lua_CFunction
lua_Debug
lua_Hook
lua_Integer
lua_Number
lua_Reader
lua_State
lua_Writer

lua_atpanic
lua_call
lua_checkstack
lua_close
lua_concat
lua_cpcall
lua_createtable
lua_dump
lua_equal
lua_error
lua_gc
lua_getallocf
lua_getfenv
lua_getfield
lua_getglobal
lua_gethook
lua_gethookcount
lua_gethookmask
lua_getinfo
lua_getlocal
lua_getmetatable
lua_getstack
lua_gettable
lua_gettop
lua_getupvalue
lua_insert
lua_isboolean
lua_iscfunction
lua_isfunction
lua_islightuserdata
lua_isnil
lua_isnone
lua_isnoneornil
lua_isnumber
lua_isstring
lua_istable
lua_isthread
lua_isuserdata
lua_lessthan
lua_load
lua_newstate
lua_newtable
lua_newthread
lua_newuserdata
lua_next
lua_objlen
lua_pcall
lua_pop
lua_pushboolean
lua_pushcclosure
lua_pushcfunction
lua_pushfstring
lua_pushinteger
lua_pushlightuserdata
lua_pushliteral
lua_pushlstring
lua_pushnil
lua_pushnumber
lua_pushstring
lua_pushthread
lua_pushvalue
lua_pushvfstring
lua_rawequal
lua_rawget
lua_rawgeti
lua_rawset
lua_rawseti
lua_register
lua_remove
lua_replace
lua_resume
lua_setallocf
lua_setfenv
lua_setfield
lua_setglobal
lua_sethook
lua_setlocal
lua_setmetatable
lua_settable
lua_settop
lua_setupvalue
lua_status
lua_toboolean
lua_tocfunction
lua_tointeger
lua_tolstring
lua_tonumber
lua_topointer
lua_tostring
lua_tothread
lua_touserdata
lua_type
lua_typename
lua_upvalueindex
lua_xmove
lua_yield

auxiliary library

luaL_Buffer
luaL_Reg

luaL_addchar
luaL_addlstring
luaL_addsize
luaL_addstring
luaL_addvalue
luaL_argcheck
luaL_argerror
luaL_buffinit
luaL_callmeta
luaL_checkany
luaL_checkint
luaL_checkinteger
luaL_checklong
luaL_checklstring
luaL_checknumber
luaL_checkoption
luaL_checkstack
luaL_checkstring
luaL_checktype
luaL_checkudata
luaL_dofile
luaL_dostring
luaL_error
luaL_getmetafield
luaL_getmetatable
luaL_gsub
luaL_loadbuffer
luaL_loadfile
luaL_loadstring
luaL_newmetatable
luaL_newstate
luaL_openlibs
luaL_optint
luaL_optinteger
luaL_optlong
luaL_optlstring
luaL_optnumber
luaL_optstring
luaL_prepbuffer
luaL_pushresult
luaL_ref
luaL_register
luaL_typename
luaL_typerror
luaL_unref
luaL_where


Last update: Mon Feb 13 18:53:32 BRST 2012 redis-8.0.2/deps/lua/doc/cover.png000066400000000000000000000063511501533116600167420ustar00rootroot00000000000000‰PNG  IHDRE^úþN&gAMA± üa8tEXtSoftwareXV Version 3.10a Rev: 12/29/94 (PNG patch 1.2)Ý.I IIDATxœí›KpÇyÇÝ3³³,°xc$@IQ/Êõ £Š•9©r¬“ËÇåª|ÐMÖÕG_tñ%/ù¸T©T"Ç©r$ÅŠhš*‹IñR$$‚ØÅ¾g¦ÛìbÀØ"¢Â¿pÀÎôtÿùúëþ¾¯¿üã)¾B[€c‡ÏöÆŸí>Û+ùHh± ôCfm8.™™E]Îõð¼¢®äÓ0~°¿«;dëlz9ÞüýáŽÛüRD^S³,dÉqÜÒ•tŽ ×™O¯h¸RV}áÀ³]Ñ绢-£;d$s³3.Z³¥¥  %‚R°;ޱ¬íJ>É¢ûOïáVº)ºa)¯e‹@µ¨q= ‰«°ŒRÇ¢tå!eiì„Ò¸Ñ0®ÇØÃýA8ˆR¾ºÖâãe Î_§P¤³•L­hŽ ®‡iàz(ãÒÝÆ¥Qz;¹z“=½Ø "aLƒT†\½ýØV#ø(†úh ÜOâ)Lc­¶«lCk´&•!•AJ<ÅlË àPpØgb†Hˆù€mqiÓ`t¥èíäÎ –I&‡ë1Ô×2‹"Mΰ'Ž«J‹Á\ŠÖ檋Ö*>v€ý쎓L¶AËÓ%™Æ¶h ‹¢5mQ:[io¡»C2ŸÆ¶ˆE‰5áxxŠ€‰Õ …Ä2‰wp÷>bÑfRØÕö¿V¼ —µ*ý«®/5ËŸj„álÕæÛjèÿˆŠÆËH4–°’OØ”‡ca dJ­QZ§ÏQºÍ6SŽ¢¨tÒñ:m3 eÎóZss’ÑI^:J$´ì–R$R\åÂu&¦YÈáº(Ó$$ÞΡ!¦» £LLL£"¾BÒ5ÆñIçøì*/mÙ0J1=Ço?åôÉt•·[$_$‘äÒ¿>Éá!^y†Á8†AÀdo?ãw« &Ø?°¡rØäz°¸”Y&Ãý„C%>‹KíGgøÅ¯¹ØÝÊß¼²r4ŒOÑá ºÛ±LŸL2Í¿ü†÷?!›_—É–Ž©]F'¸q‡Á>ú:ééàÆÙ|9É+ˆwð½o2г5óMkÎ\Á4‘§&s)þá=Fnµ0‘RîÞÝýÔS{#‘àíÛ³gÏÞ˜¿6ÎÏÿ¿ý+¦¿‹Ï®pë.R0ØÇ“ˆ5Õè"lŠOs„ýþ™¿øï2™‰ßxã¯ô£ott´H)ǽxñÖ›oþóœÓÿüÝwèiç•gP!XÈÊkÚ¸_ ntѾùg‰®Ç¯Npáz†üÉO^ë­×ãñ6Ë2 Cƒ£G‡ßyççž;Èä ÿúr„ÀH!ùäÅj¹òð™_àÜ€o6—GùølÍŽ³>|xà‡?üº¹ÜsBôô´¾õÖë¶mqy”ΠÊ9çHˆ}¤s5P­¹‘(×㜾L:[ú_Ãt‚kãµÌêšùh˜œ%•)iCC"Åh}Ê>üðüýû©Õ‡èZëS§FîÞM H$ôo÷u²­%¾­]?šK7ÈWœcßœ"[«™–!®^½óöÛï ÅJJZëÛ·g~úÓ_‹åCOqõª|nkæØ‘Z¨yÿÑ`D#å(KskªŠ{¶”R?ûÙ¿§Ó¹ÿøµ¾¾v)e>_<}úÚ›o¾súôõe½ßÅóf‰’çal\àT3/>Q.Òš™¹uX³£|¾øöÛï½ûî‰Ã‡"‘àÄDbdd<“É/5‚ùÏßèN~¾<æ­Žzôã¸Ëød7]C!´fjjnj*±t¥ŠžóE\lÿŠã®L,¡fûq\þ÷´¿†j]-¬b=ÏUéŠj Á‘ašÂöX³~¤ _DiŒòsÍØ«!0 ?àÕšÎlRïð— ñöMˆY:[}>žbäfõÚ¯å¨g?ŸöÝ*ƒ}ëÔ=6C}~ˆUtj,¬ÇßYL²éò××I,ºU9i;À#{|s±-þì¨OomÔç§ Ëôå†92\Ÿ”µB3ÐCo‡¿wC©,w#ÔÌG@8ÈÓ‡*²‚ž \ï©ÍAJ^|ÂOX;.¿=S£ã[§íºÜ¹Wžr‚]Ý<}°ÑSN3ØË“|mLÌ`ÈZ’Ulæüç“‹~(bH¾ñ<­£¤ Ú¼vœp…Ú›B<ýHù:ùX&1]öS„ «•ïü9v ¾~ÖGòê1îñCFÏ£#Vû^W'!8°›ÁÞRÙùâ•§ð—ZNÕ>„àùÇyõ˜ï%Š|ðéê¢þuPÿ|“ oø»µaðòŸðíãØÍRÒHÉ óÝW|UkÍÙ/èh©«.}SùQ·ï16Á@wib’Á>ºÛ›$—¯Ó©Ó„ƒ|ëE^;N0°ÌNB6{⾺jÀæøºÚ(ikñ]F)éíäÈ^2yfçkó¾5†ÁÝ|ï›<ó(Šìñl ]y³¡h›?o\4¡DŠÖf1]<àãϸ<ÆB¶bT~B ²·ŸäðÐ2µhÍíiN]àëÏÑ©W¨8/¥ùü¦É³GJ³\,“ý ÷3Ÿfl‚ëÜe!CÁÁ2‰„ènc¨¡>:bÕOu2y^=Ftãh§ŠPz츌Üd¨¯”g\!œ­Q O¡RbH¤D¬n©Éæ¹=;’Ÿ¶©o¡¸žÂ29²—HDŠ3WÈæ–yõ¤À4°-B6¶…i EY)þëwxªDu³v5¢>dqøh)øè,®WRȆáŠÖdrÜšÂóh ó­84Xï°«§XÛ;|¶7vøloìðÙÞøªñù#›´Ìˆ,`qûtIMEÖ7µ¬#IEND®B`‚redis-8.0.2/deps/lua/doc/logo.gif000066400000000000000000000102101501533116600165320ustar00rootroot00000000000000GIF87a€€÷rvz}~ oooqqqvvvzzz€ƒ „‚…†…‡ˆŠŠŒ‹!!Ž%%""%%‘))’,,“55—11•55™11˜::š::œ??BBŸDDžBB EE¡JJ£HH¤LL£MM¥PP§TT¦QQ¨WW«YYªZZ¬\\«^^­``¯cc±gg±ii³jj³oo·ll´qq·vv»tt¹yy»{{½¿~~½‚‚‚………ˆˆˆŽŽŽ•••›››€€¿¡¡¡¥¥¥©©©­­­³³³···¹¹¹½½½ƒƒÁ‡‡Â‰‰Ã‹‹ÅŽŽÆÇ’’È••Ê™™Ë™™ÌŸŸÏ¡¡Ï¢¢Ñ¥¥Ò©©Ó©©Ô­­Ö¯¯×±±×±±ØµµÚ»»Ý¼¼ÝÃÃÃÇÇÇÊÊÊÌÌÌÀÀßÒÒÒÕÕÕØØØÝÝÝÁÁàÇÇãÇÇäÉÉäÊÊåÎÎæÐÐçÓÓéÕÕêØØëÚÚìÝÝîáááåååààïêêêîîîââðææóèèóééôîîöñññðð÷õõõóóù÷÷ûùùùûûýÿÿÿ,€€þ' H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI¡¤›“pÖ´‰Q¡:†Ä(RJI;HGSSFAP¤È‘A™!rC+j Dš´ #:S’ Y:‰A… ±mûSNF1™Rg.Ä=2 @˜°€ ;•5ÈA)s.:’‚Ä)•ˆv 0`ƒÏŸlnp¥¬#‚‚‚rdÔDD#xЙ¶èÎ 6¿€43œ#LTFy“P‘‡¹E+¯]Ûs€2ÝQkÊ( Œ¸ñ;©òËÃþ‡gÀGΗP¢œfÉh ‚(½ oNÿ¶råØ,NiHµÀG0^sËÙ&À *92…;!†lX_mw DGU…ì7É DXŸ„ä qIRw5E¢A‡á&9¢„é¤ÐMdMDH´M(Þ‡hcYC$É#ˆÈ!ˆ~8@I$~!„ $pP |æ@ ƒcø1ä“ù1a•bR®I”ã!`È`aÐã ÆY2€qÈkD–;P€ žvæâœÊ}6jèàÇ“Ø0ß}ÖF^ipZDÇ‘Eä‡ pöhÛ2+a„r&C ¡Áª}t.G¹EbÝCŠäí |Ú,¸sâ6ê9 ’Pç¶( y0¼DÖpV¯£éëêft%AfÃÀ.KH¢‚Â?,°‡În–‚b9"•Ã|/Gž±†âAH™‘ðÂ"çÜâ}ž A#޵ÂwÜú:p;x…ë5$„ öެs•E-ÀI"ƒÂ¢;[XwÇu8¤5Ñ!K­¶€žV¯}äÅÀ[A]$Ü©¨ƒep†þG… pYCœ@ôÚ„Óçkn†:mä™ VªŠXÑÁf€2 BYzD‡3ô ,.zsƒ9PBD\ñƒ lf4y",Bscdq…f(6ñJ˜°ì袋@\ø[$zäÐ@‡¸@Bæ©Úô’ ˦=5É¢EÊ8H D“kÖ8F{R= ~}Î `»g-в1€,RöBHúú„ÍŽe¾-M‚õsŽÄ ø‘(¡!{àQÔHµm ` AcRf¡ìé¡!‚ÈF ‘&D `ðDvŸòdÐ!аéB07…¼ ªˆˆ,^l…þØ£RP¥§Ih¡~ô @r)`$ hCö@€AÑG"yXœ møàBÜÀ4Œ¢i YÁ¹(DûÀÙàï †8¢& ‰¹¼ÆF+1«›Rººh ‘KIHÀ¢+ Œ@†EÂæùJ IÅé*Á #R²WûÈðÂDBhD ,ÉÉ6¶o| ¥mÀ#ˆDxBôЪVúÑ@’Œã$ì ÂY  B yC†$B*$} ëZ¿EŸ @¯ˆþûasPJ‚áoQ|ò Η?B×Yeå†JB¤ ‰8µþJH,ùÅižóuJì&àÌ9Áö™a!r(#D|ƒé­,9|D'¨yF)_€ÃÈs¶tðˆ$ @<šGl ¤¾2d8‡†rDJ £5ƒg¥0²ôx“ÐAÿ9€=e`X£Iñ5:?iQ\<`Ê,òxá§AŠPµ©ÓòôW†Ü âT#R•9ÛÄêC „Ĭ4šáz9µ®âGbØRù¢ˆ4ò¤†$C:w:$À¦DbÊ6C^ЊöUë f¨!AA„„ݦ‚¢±€f â$„Ô!p0aÑ(G+`þÈ"(ÐÏÂÙu– ”b@„/€a =(yXØ#è.!E°TCªàÌ]rëWä‰í$fËF°ÊÕh…½ÖF:v" UxH‚£5(µ9Ñ•-m¥)5ÀÒ5Tn{Ûõ ©†…2^o'€ÞЯÁ·ÑC–h >¸@ŒÇ³ƒŽì,ÿFµ!QHÂþB6IÍ( øEú9à“EóÜà;´ÄC\ð×ohæ«Ã2B5+$T}mˆ¥üHCÆÙ‡:V²–œGj€Ð”Mâ“™0¨$8Ñ!mhîApÚfÿBø½õÉ@ m¸:OâÎ<6êt}—Ýpá<1@"Ét$Hµ ¬"BíZû-š…²–³6élg¢q€,:@|PÝ aÎFƒ€ñ #äQ!…a'±ª‚ŠÆÍìõQ´i] ±Þú;$‘ˆ}–^óV_xpm…ûÛŸ½¦èÿªÍѳ·­qÍ8K`–m[@à(m`Ñq! B¬‚þ¿ ã/Â[mÇrg&^½âÒ&qt$ ½¡ 6g)ûlõ³žò Ù×];ñ•FðpÁHAб(€ƒª„µ”½ÍÉ]•r ±Æ‚bú`QAàFkÀq72ˆ-®KMâΧüh‰ x’w²ˆ=XŸvûëA@—@m‡þêˆÆºíX½ÝZO/ ?xÁ¼ă( æ+„DÞí,d9}·«EÌvž« XÔ½ë•äIe 0Ôç¯ë]4y£Ö}'W!)„îè] ´B|§æFq± D ÀͧŽ?sü„€!¾ŸÈŒþ€¬#F“ð3ž¾ø_çÓ£738® N>ãÌ@¹ï8’õl¾HÁ q„[ ‚ˆ‹·JƱ7(hEÑ6,sIúë7)¡F0晄Vp€Ÿõ„°ƒ`ƒö[%W2Æ5fc"8‚´ät.ÕAa£€ó!DÔ@Ûàiä_¤qeG#ÑUT4é…‹P„Fx„E vDæa~ ûR¿ ºtçñ<"(ò„W´Al-á‚–[±uf²$P%~°w9W@ÁxÀwH‘vÀ{9wh“Vð6à5Pþ… \ ñ"uf欖oDd2¨g«×>6-c0(°Y<¶ °1"1°òOö*p)Њ®øŠ®¨Pˆ€À’€j ‚Ò* 2Ô5ðŠ ’ „0% (@™ÃŸBaƒnSÀ{ ãDN/%J…ÒÞø ƒà ¬(3  À›b1´$ X§]‡RPñ‡Å9çZÑ*)~Æ÷NèØ-¢ô#…r]¢Ž¾N§Œ ŠhGpl Ñ^@FY½4}b/äÁM !ª¥ŠÄ0ìU|Me=édþE$Ó>pM ‘‰~ðrÕJ¶å-&&@.\ ÖØ87{¾¤3`%0‡ds+1Hhó“ÕÕ2Ÿõ2PéK€ü3 W0•ÄuEîÕ)*…U))+ °@5ö0ÓÔF¡tÈE™O‘KÐH¡P@}ä^ ™jµH0}““ùU`10 k•å’¹Ec VUK$A€—_z `ÇT„EΑUÉ•4A„ÁÖ“<Rà¨rf“PGÉ£‚ž¹BõÁz°a"gd6“€2(¹šayImø›Fà¶"q°ŒÂc¿ô“õ("þgA™yƒ€P0i@ÛeESÒ˜Ž’»‚Yç‚O9b4»"}Ù"x@'P€‚upj¡` ‚±Ÿ¸õ2¡¢2,*ðã&îq ¿Ù•¡f%àE‰³+’]‚B%f0‰²Y…ðJ3žÓf ?€1à,À/p?€;}XV5úQ€˜%H1Z4JmpOP›7¢dÑ=Oô Ày;!Ó‘peq!F¦û PP-eKfw¤¿é.lzp`š- jù¦Ñ`o@¥¦EPOv*‘Rz††UP u¨Á§…S@©Ä¨!IàO*…PáE©AO` p\ú‘ª P ê©1{:ƒ€LS€¦“PU@Q°êzRЙʪ#4PÀG`‡Š—N¬ÂÚ¬Îú¬Ð­Òê;redis-8.0.2/deps/lua/doc/lua.1000066400000000000000000000070431501533116600157600ustar00rootroot00000000000000.\" $Id: lua.man,v 1.11 2006/01/06 16:03:34 lhf Exp $ .TH LUA 1 "$Date: 2006/01/06 16:03:34 $" .SH NAME lua \- Lua interpreter .SH SYNOPSIS .B lua [ .I options ] [ .I script [ .I args ] ] .SH DESCRIPTION .B lua is the stand-alone Lua interpreter. It loads and executes Lua programs, either in textual source form or in precompiled binary form. (Precompiled binaries are output by .BR luac , the Lua compiler.) .B lua can be used as a batch interpreter and also interactively. .LP The given .I options (see below) are executed and then the Lua program in file .I script is loaded and executed. The given .I args are available to .I script as strings in a global table named .BR arg . If these arguments contain spaces or other characters special to the shell, then they should be quoted (but note that the quotes will be removed by the shell). The arguments in .B arg start at 0, which contains the string .RI ' script '. The index of the last argument is stored in .BR arg.n . The arguments given in the command line before .IR script , including the name of the interpreter, are available in negative indices in .BR arg . .LP At the very start, before even handling the command line, .B lua executes the contents of the environment variable .BR LUA_INIT , if it is defined. If the value of .B LUA_INIT is of the form .RI '@ filename ', then .I filename is executed. Otherwise, the string is assumed to be a Lua statement and is executed. .LP Options start with .B '\-' and are described below. You can use .B "'\--'" to signal the end of options. .LP If no arguments are given, then .B "\-v \-i" is assumed when the standard input is a terminal; otherwise, .B "\-" is assumed. .LP In interactive mode, .B lua prompts the user, reads lines from the standard input, and executes them as they are read. If a line does not contain a complete statement, then a secondary prompt is displayed and lines are read until a complete statement is formed or a syntax error is found. So, one way to interrupt the reading of an incomplete statement is to force a syntax error: adding a .B ';' in the middle of a statement is a sure way of forcing a syntax error (except inside multiline strings and comments; these must be closed explicitly). If a line starts with .BR '=' , then .B lua displays the values of all the expressions in the remainder of the line. The expressions must be separated by commas. The primary prompt is the value of the global variable .BR _PROMPT , if this value is a string; otherwise, the default prompt is used. Similarly, the secondary prompt is the value of the global variable .BR _PROMPT2 . So, to change the prompts, set the corresponding variable to a string of your choice. You can do that after calling the interpreter or on the command line (but in this case you have to be careful with quotes if the prompt string contains a space; otherwise you may confuse the shell.) The default prompts are "> " and ">> ". .SH OPTIONS .TP .B \- load and execute the standard input as a file, that is, not interactively, even when the standard input is a terminal. .TP .BI \-e " stat" execute statement .IR stat . You need to quote .I stat if it contains spaces, quotes, or other characters special to the shell. .TP .B \-i enter interactive mode after .I script is executed. .TP .BI \-l " name" call .BI require(' name ') before executing .IR script . Typically used to load libraries. .TP .B \-v show version information. .SH "SEE ALSO" .BR luac (1) .br http://www.lua.org/ .SH DIAGNOSTICS Error messages should be self explanatory. .SH AUTHORS R. Ierusalimschy, L. H. de Figueiredo, and W. Celes .\" EOF redis-8.0.2/deps/lua/doc/lua.css000066400000000000000000000024321501533116600164050ustar00rootroot00000000000000body { color: #000000 ; background-color: #FFFFFF ; font-family: Helvetica, Arial, sans-serif ; text-align: justify ; margin-right: 30px ; margin-left: 30px ; } h1, h2, h3, h4 { font-family: Verdana, Geneva, sans-serif ; font-weight: normal ; font-style: italic ; } h2 { padding-top: 0.4em ; padding-bottom: 0.4em ; padding-left: 30px ; padding-right: 30px ; margin-left: -30px ; background-color: #E0E0FF ; } h3 { padding-left: 0.5em ; border-left: solid #E0E0FF 1em ; } table h3 { padding-left: 0px ; border-left: none ; } a:link { color: #000080 ; background-color: inherit ; text-decoration: none ; } a:visited { background-color: inherit ; text-decoration: none ; } a:link:hover, a:visited:hover { color: #000080 ; background-color: #E0E0FF ; } a:link:active, a:visited:active { color: #FF0000 ; } hr { border: 0 ; height: 1px ; color: #a0a0a0 ; background-color: #a0a0a0 ; } :target { background-color: #F8F8F8 ; padding: 8px ; border: solid #a0a0a0 2px ; } .footer { color: gray ; font-size: small ; } input[type=text] { border: solid #a0a0a0 2px ; border-radius: 2em ; -moz-border-radius: 2em ; background-image: url('images/search.png') ; background-repeat: no-repeat; background-position: 4px center ; padding-left: 20px ; height: 2em ; } redis-8.0.2/deps/lua/doc/lua.html000066400000000000000000000075571501533116600165760ustar00rootroot00000000000000 LUA man page

NAME

lua - Lua interpreter

SYNOPSIS

lua [ options ] [ script [ args ] ]

DESCRIPTION

lua is the stand-alone Lua interpreter. It loads and executes Lua programs, either in textual source form or in precompiled binary form. (Precompiled binaries are output by luac, the Lua compiler.) lua can be used as a batch interpreter and also interactively.

The given options (see below) are executed and then the Lua program in file script is loaded and executed. The given args are available to script as strings in a global table named arg. If these arguments contain spaces or other characters special to the shell, then they should be quoted (but note that the quotes will be removed by the shell). The arguments in arg start at 0, which contains the string 'script'. The index of the last argument is stored in arg.n. The arguments given in the command line before script, including the name of the interpreter, are available in negative indices in arg.

At the very start, before even handling the command line, lua executes the contents of the environment variable LUA_INIT, if it is defined. If the value of LUA_INIT is of the form '@filename', then filename is executed. Otherwise, the string is assumed to be a Lua statement and is executed.

Options start with '-' and are described below. You can use '--' to signal the end of options.

If no arguments are given, then "-v -i" is assumed when the standard input is a terminal; otherwise, "-" is assumed.

In interactive mode, lua prompts the user, reads lines from the standard input, and executes them as they are read. If a line does not contain a complete statement, then a secondary prompt is displayed and lines are read until a complete statement is formed or a syntax error is found. So, one way to interrupt the reading of an incomplete statement is to force a syntax error: adding a ';' in the middle of a statement is a sure way of forcing a syntax error (except inside multiline strings and comments; these must be closed explicitly). If a line starts with '=', then lua displays the values of all the expressions in the remainder of the line. The expressions must be separated by commas. The primary prompt is the value of the global variable _PROMPT, if this value is a string; otherwise, the default prompt is used. Similarly, the secondary prompt is the value of the global variable _PROMPT2. So, to change the prompts, set the corresponding variable to a string of your choice. You can do that after calling the interpreter or on the command line (but in this case you have to be careful with quotes if the prompt string contains a space; otherwise you may confuse the shell.) The default prompts are "> " and ">> ".

OPTIONS

- load and execute the standard input as a file, that is, not interactively, even when the standard input is a terminal.

-e stat execute statement stat. You need to quote stat if it contains spaces, quotes, or other characters special to the shell.

-i enter interactive mode after script is executed.

-l name call require('name') before executing script. Typically used to load libraries.

-v show version information.

SEE ALSO

luac(1)
http://www.lua.org/

DIAGNOSTICS

Error messages should be self explanatory.

AUTHORS

R. Ierusalimschy, L. H. de Figueiredo, and W. Celes redis-8.0.2/deps/lua/doc/luac.1000066400000000000000000000070411501533116600161210ustar00rootroot00000000000000.\" $Id: luac.man,v 1.28 2006/01/06 16:03:34 lhf Exp $ .TH LUAC 1 "$Date: 2006/01/06 16:03:34 $" .SH NAME luac \- Lua compiler .SH SYNOPSIS .B luac [ .I options ] [ .I filenames ] .SH DESCRIPTION .B luac is the Lua compiler. It translates programs written in the Lua programming language into binary files that can be later loaded and executed. .LP The main advantages of precompiling chunks are: faster loading, protecting source code from accidental user changes, and off-line syntax checking. .LP Pre-compiling does not imply faster execution because in Lua chunks are always compiled into bytecodes before being executed. .B luac simply allows those bytecodes to be saved in a file for later execution. .LP Pre-compiled chunks are not necessarily smaller than the corresponding source. The main goal in pre-compiling is faster loading. .LP The binary files created by .B luac are portable only among architectures with the same word size and byte order. .LP .B luac produces a single output file containing the bytecodes for all source files given. By default, the output file is named .BR luac.out , but you can change this with the .B \-o option. .LP In the command line, you can mix text files containing Lua source and binary files containing precompiled chunks. This is useful to combine several precompiled chunks, even from different (but compatible) platforms, into a single precompiled chunk. .LP You can use .B "'\-'" to indicate the standard input as a source file and .B "'\--'" to signal the end of options (that is, all remaining arguments will be treated as files even if they start with .BR "'\-'" ). .LP The internal format of the binary files produced by .B luac is likely to change when a new version of Lua is released. So, save the source files of all Lua programs that you precompile. .LP .SH OPTIONS Options must be separate. .TP .B \-l produce a listing of the compiled bytecode for Lua's virtual machine. Listing bytecodes is useful to learn about Lua's virtual machine. If no files are given, then .B luac loads .B luac.out and lists its contents. .TP .BI \-o " file" output to .IR file , instead of the default .BR luac.out . (You can use .B "'\-'" for standard output, but not on platforms that open standard output in text mode.) The output file may be a source file because all files are loaded before the output file is written. Be careful not to overwrite precious files. .TP .B \-p load files but do not generate any output file. Used mainly for syntax checking and for testing precompiled chunks: corrupted files will probably generate errors when loaded. Lua always performs a thorough integrity test on precompiled chunks. Bytecode that passes this test is completely safe, in the sense that it will not break the interpreter. However, there is no guarantee that such code does anything sensible. (None can be given, because the halting problem is unsolvable.) If no files are given, then .B luac loads .B luac.out and tests its contents. No messages are displayed if the file passes the integrity test. .TP .B \-s strip debug information before writing the output file. This saves some space in very large chunks, but if errors occur when running a stripped chunk, then the error messages may not contain the full information they usually do. For instance, line numbers and names of local variables are lost. .TP .B \-v show version information. .SH FILES .TP 15 .B luac.out default output file .SH "SEE ALSO" .BR lua (1) .br http://www.lua.org/ .SH DIAGNOSTICS Error messages should be self explanatory. .SH AUTHORS L. H. de Figueiredo, R. Ierusalimschy and W. Celes .\" EOF redis-8.0.2/deps/lua/doc/luac.html000066400000000000000000000074701501533116600167330ustar00rootroot00000000000000 LUAC man page

NAME

luac - Lua compiler

SYNOPSIS

luac [ options ] [ filenames ]

DESCRIPTION

luac is the Lua compiler. It translates programs written in the Lua programming language into binary files that can be later loaded and executed.

The main advantages of precompiling chunks are: faster loading, protecting source code from accidental user changes, and off-line syntax checking.

Precompiling does not imply faster execution because in Lua chunks are always compiled into bytecodes before being executed. luac simply allows those bytecodes to be saved in a file for later execution.

Precompiled chunks are not necessarily smaller than the corresponding source. The main goal in precompiling is faster loading.

The binary files created by luac are portable only among architectures with the same word size and byte order.

luac produces a single output file containing the bytecodes for all source files given. By default, the output file is named luac.out, but you can change this with the -o option.

In the command line, you can mix text files containing Lua source and binary files containing precompiled chunks. This is useful because several precompiled chunks, even from different (but compatible) platforms, can be combined into a single precompiled chunk.

You can use '-' to indicate the standard input as a source file and '--' to signal the end of options (that is, all remaining arguments will be treated as files even if they start with '-').

The internal format of the binary files produced by luac is likely to change when a new version of Lua is released. So, save the source files of all Lua programs that you precompile.

OPTIONS

Options must be separate.

-l produce a listing of the compiled bytecode for Lua's virtual machine. Listing bytecodes is useful to learn about Lua's virtual machine. If no files are given, then luac loads luac.out and lists its contents.

-o file output to file, instead of the default luac.out. (You can use '-' for standard output, but not on platforms that open standard output in text mode.) The output file may be a source file because all files are loaded before the output file is written. Be careful not to overwrite precious files.

-p load files but do not generate any output file. Used mainly for syntax checking and for testing precompiled chunks: corrupted files will probably generate errors when loaded. Lua always performs a thorough integrity test on precompiled chunks. Bytecode that passes this test is completely safe, in the sense that it will not break the interpreter. However, there is no guarantee that such code does anything sensible. (None can be given, because the halting problem is unsolvable.) If no files are given, then luac loads luac.out and tests its contents. No messages are displayed if the file passes the integrity test.

-s strip debug information before writing the output file. This saves some space in very large chunks, but if errors occur when running a stripped chunk, then the error messages may not contain the full information they usually do. For instance, line numbers and names of local variables are lost.

-v show version information.

FILES

luac.out default output file

SEE ALSO

lua(1)
http://www.lua.org/

DIAGNOSTICS

Error messages should be self explanatory.

AUTHORS

L. H. de Figueiredo, R. Ierusalimschy and W. Celes redis-8.0.2/deps/lua/doc/manual.css000066400000000000000000000005251501533116600171020ustar00rootroot00000000000000h3 code { font-family: inherit ; font-size: inherit ; } pre, code { font-size: 12pt ; } span.apii { float: right ; font-family: inherit ; font-style: normal ; font-size: small ; color: gray ; } p+h1, ul+h1 { padding-top: 0.4em ; padding-bottom: 0.4em ; padding-left: 30px ; margin-left: -30px ; background-color: #E0E0FF ; } redis-8.0.2/deps/lua/doc/manual.html000066400000000000000000007614311501533116600172700ustar00rootroot00000000000000 Lua 5.1 Reference Manual

Lua 5.1 Reference Manual

by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes

Copyright © 2006–2012 Lua.org, PUC-Rio. Freely available under the terms of the Lua license.


contents · index · other versions

1 - Introduction

Lua is an extension programming language designed to support general procedural programming with data description facilities. It also offers good support for object-oriented programming, functional programming, and data-driven programming. Lua is intended to be used as a powerful, light-weight scripting language for any program that needs one. Lua is implemented as a library, written in clean C (that is, in the common subset of ANSI C and C++).

Being an extension language, Lua has no notion of a "main" program: it only works embedded in a host client, called the embedding program or simply the host. This host program can invoke functions to execute a piece of Lua code, can write and read Lua variables, and can register C functions to be called by Lua code. Through the use of C functions, Lua can be augmented to cope with a wide range of different domains, thus creating customized programming languages sharing a syntactical framework. The Lua distribution includes a sample host program called lua, which uses the Lua library to offer a complete, stand-alone Lua interpreter.

Lua is free software, and is provided as usual with no guarantees, as stated in its license. The implementation described in this manual is available at Lua's official web site, www.lua.org.

Like any other reference manual, this document is dry in places. For a discussion of the decisions behind the design of Lua, see the technical papers available at Lua's web site. For a detailed introduction to programming in Lua, see Roberto's book, Programming in Lua (Second Edition).

2 - The Language

This section describes the lexis, the syntax, and the semantics of Lua. In other words, this section describes which tokens are valid, how they can be combined, and what their combinations mean.

The language constructs will be explained using the usual extended BNF notation, in which {a} means 0 or more a's, and [a] means an optional a. Non-terminals are shown like non-terminal, keywords are shown like kword, and other terminal symbols are shown like `=´. The complete syntax of Lua can be found in §8 at the end of this manual.

2.1 - Lexical Conventions

Names (also called identifiers) in Lua can be any string of letters, digits, and underscores, not beginning with a digit. This coincides with the definition of names in most languages. (The definition of letter depends on the current locale: any character considered alphabetic by the current locale can be used in an identifier.) Identifiers are used to name variables and table fields.

The following keywords are reserved and cannot be used as names:

     and       break     do        else      elseif
     end       false     for       function  if
     in        local     nil       not       or
     repeat    return    then      true      until     while

Lua is a case-sensitive language: and is a reserved word, but And and AND are two different, valid names. As a convention, names starting with an underscore followed by uppercase letters (such as _VERSION) are reserved for internal global variables used by Lua.

The following strings denote other tokens:

     +     -     *     /     %     ^     #
     ==    ~=    <=    >=    <     >     =
     (     )     {     }     [     ]
     ;     :     ,     .     ..    ...

Literal strings can be delimited by matching single or double quotes, and can contain the following C-like escape sequences: '\a' (bell), '\b' (backspace), '\f' (form feed), '\n' (newline), '\r' (carriage return), '\t' (horizontal tab), '\v' (vertical tab), '\\' (backslash), '\"' (quotation mark [double quote]), and '\'' (apostrophe [single quote]). Moreover, a backslash followed by a real newline results in a newline in the string. A character in a string can also be specified by its numerical value using the escape sequence \ddd, where ddd is a sequence of up to three decimal digits. (Note that if a numerical escape is to be followed by a digit, it must be expressed using exactly three digits.) Strings in Lua can contain any 8-bit value, including embedded zeros, which can be specified as '\0'.

Literal strings can also be defined using a long format enclosed by long brackets. We define an opening long bracket of level n as an opening square bracket followed by n equal signs followed by another opening square bracket. So, an opening long bracket of level 0 is written as [[, an opening long bracket of level 1 is written as [=[, and so on. A closing long bracket is defined similarly; for instance, a closing long bracket of level 4 is written as ]====]. A long string starts with an opening long bracket of any level and ends at the first closing long bracket of the same level. Literals in this bracketed form can run for several lines, do not interpret any escape sequences, and ignore long brackets of any other level. They can contain anything except a closing bracket of the proper level.

For convenience, when the opening long bracket is immediately followed by a newline, the newline is not included in the string. As an example, in a system using ASCII (in which 'a' is coded as 97, newline is coded as 10, and '1' is coded as 49), the five literal strings below denote the same string:

     a = 'alo\n123"'
     a = "alo\n123\""
     a = '\97lo\10\04923"'
     a = [[alo
     123"]]
     a = [==[
     alo
     123"]==]

A numerical constant can be written with an optional decimal part and an optional decimal exponent. Lua also accepts integer hexadecimal constants, by prefixing them with 0x. Examples of valid numerical constants are

     3   3.0   3.1416   314.16e-2   0.31416E1   0xff   0x56

A comment starts with a double hyphen (--) anywhere outside a string. If the text immediately after -- is not an opening long bracket, the comment is a short comment, which runs until the end of the line. Otherwise, it is a long comment, which runs until the corresponding closing long bracket. Long comments are frequently used to disable code temporarily.

2.2 - Values and Types

Lua is a dynamically typed language. This means that variables do not have types; only values do. There are no type definitions in the language. All values carry their own type.

All values in Lua are first-class values. This means that all values can be stored in variables, passed as arguments to other functions, and returned as results.

There are eight basic types in Lua: nil, boolean, number, string, function, userdata, thread, and table. Nil is the type of the value nil, whose main property is to be different from any other value; it usually represents the absence of a useful value. Boolean is the type of the values false and true. Both nil and false make a condition false; any other value makes it true. Number represents real (double-precision floating-point) numbers. (It is easy to build Lua interpreters that use other internal representations for numbers, such as single-precision float or long integers; see file luaconf.h.) String represents arrays of characters. Lua is 8-bit clean: strings can contain any 8-bit character, including embedded zeros ('\0') (see §2.1).

Lua can call (and manipulate) functions written in Lua and functions written in C (see §2.5.8).

The type userdata is provided to allow arbitrary C data to be stored in Lua variables. This type corresponds to a block of raw memory and has no pre-defined operations in Lua, except assignment and identity test. However, by using metatables, the programmer can define operations for userdata values (see §2.8). Userdata values cannot be created or modified in Lua, only through the C API. This guarantees the integrity of data owned by the host program.

The type thread represents independent threads of execution and it is used to implement coroutines (see §2.11). Do not confuse Lua threads with operating-system threads. Lua supports coroutines on all systems, even those that do not support threads.

The type table implements associative arrays, that is, arrays that can be indexed not only with numbers, but with any value (except nil). Tables can be heterogeneous; that is, they can contain values of all types (except nil). Tables are the sole data structuring mechanism in Lua; they can be used to represent ordinary arrays, symbol tables, sets, records, graphs, trees, etc. To represent records, Lua uses the field name as an index. The language supports this representation by providing a.name as syntactic sugar for a["name"]. There are several convenient ways to create tables in Lua (see §2.5.7).

Like indices, the value of a table field can be of any type (except nil). In particular, because functions are first-class values, table fields can contain functions. Thus tables can also carry methods (see §2.5.9).

Tables, functions, threads, and (full) userdata values are objects: variables do not actually contain these values, only references to them. Assignment, parameter passing, and function returns always manipulate references to such values; these operations do not imply any kind of copy.

The library function type returns a string describing the type of a given value.

2.2.1 - Coercion

Lua provides automatic conversion between string and number values at run time. Any arithmetic operation applied to a string tries to convert this string to a number, following the usual conversion rules. Conversely, whenever a number is used where a string is expected, the number is converted to a string, in a reasonable format. For complete control over how numbers are converted to strings, use the format function from the string library (see string.format).

2.3 - Variables

Variables are places that store values. There are three kinds of variables in Lua: global variables, local variables, and table fields.

A single name can denote a global variable or a local variable (or a function's formal parameter, which is a particular kind of local variable):

	var ::= Name

Name denotes identifiers, as defined in §2.1.

Any variable is assumed to be global unless explicitly declared as a local (see §2.4.7). Local variables are lexically scoped: local variables can be freely accessed by functions defined inside their scope (see §2.6).

Before the first assignment to a variable, its value is nil.

Square brackets are used to index a table:

	var ::= prefixexp `[´ exp `]´

The meaning of accesses to global variables and table fields can be changed via metatables. An access to an indexed variable t[i] is equivalent to a call gettable_event(t,i). (See §2.8 for a complete description of the gettable_event function. This function is not defined or callable in Lua. We use it here only for explanatory purposes.)

The syntax var.Name is just syntactic sugar for var["Name"]:

	var ::= prefixexp `.´ Name

All global variables live as fields in ordinary Lua tables, called environment tables or simply environments (see §2.9). Each function has its own reference to an environment, so that all global variables in this function will refer to this environment table. When a function is created, it inherits the environment from the function that created it. To get the environment table of a Lua function, you call getfenv. To replace it, you call setfenv. (You can only manipulate the environment of C functions through the debug library; (see §5.9).)

An access to a global variable x is equivalent to _env.x, which in turn is equivalent to

     gettable_event(_env, "x")

where _env is the environment of the running function. (See §2.8 for a complete description of the gettable_event function. This function is not defined or callable in Lua. Similarly, the _env variable is not defined in Lua. We use them here only for explanatory purposes.)

2.4 - Statements

Lua supports an almost conventional set of statements, similar to those in Pascal or C. This set includes assignments, control structures, function calls, and variable declarations.

2.4.1 - Chunks

The unit of execution of Lua is called a chunk. A chunk is simply a sequence of statements, which are executed sequentially. Each statement can be optionally followed by a semicolon:

	chunk ::= {stat [`;´]}

There are no empty statements and thus ';;' is not legal.

Lua handles a chunk as the body of an anonymous function with a variable number of arguments (see §2.5.9). As such, chunks can define local variables, receive arguments, and return values.

A chunk can be stored in a file or in a string inside the host program. To execute a chunk, Lua first pre-compiles the chunk into instructions for a virtual machine, and then it executes the compiled code with an interpreter for the virtual machine.

Chunks can also be pre-compiled into binary form; see program luac for details. Programs in source and compiled forms are interchangeable; Lua automatically detects the file type and acts accordingly.

2.4.2 - Blocks

A block is a list of statements; syntactically, a block is the same as a chunk:

	block ::= chunk

A block can be explicitly delimited to produce a single statement:

	stat ::= do block end

Explicit blocks are useful to control the scope of variable declarations. Explicit blocks are also sometimes used to add a return or break statement in the middle of another block (see §2.4.4).

2.4.3 - Assignment

Lua allows multiple assignments. Therefore, the syntax for assignment defines a list of variables on the left side and a list of expressions on the right side. The elements in both lists are separated by commas:

	stat ::= varlist `=´ explist
	varlist ::= var {`,´ var}
	explist ::= exp {`,´ exp}

Expressions are discussed in §2.5.

Before the assignment, the list of values is adjusted to the length of the list of variables. If there are more values than needed, the excess values are thrown away. If there are fewer values than needed, the list is extended with as many nil's as needed. If the list of expressions ends with a function call, then all values returned by that call enter the list of values, before the adjustment (except when the call is enclosed in parentheses; see §2.5).

The assignment statement first evaluates all its expressions and only then are the assignments performed. Thus the code

     i = 3
     i, a[i] = i+1, 20

sets a[3] to 20, without affecting a[4] because the i in a[i] is evaluated (to 3) before it is assigned 4. Similarly, the line

     x, y = y, x

exchanges the values of x and y, and

     x, y, z = y, z, x

cyclically permutes the values of x, y, and z.

The meaning of assignments to global variables and table fields can be changed via metatables. An assignment to an indexed variable t[i] = val is equivalent to settable_event(t,i,val). (See §2.8 for a complete description of the settable_event function. This function is not defined or callable in Lua. We use it here only for explanatory purposes.)

An assignment to a global variable x = val is equivalent to the assignment _env.x = val, which in turn is equivalent to

     settable_event(_env, "x", val)

where _env is the environment of the running function. (The _env variable is not defined in Lua. We use it here only for explanatory purposes.)

2.4.4 - Control Structures

The control structures if, while, and repeat have the usual meaning and familiar syntax:

	stat ::= while exp do block end
	stat ::= repeat block until exp
	stat ::= if exp then block {elseif exp then block} [else block] end

Lua also has a for statement, in two flavors (see §2.4.5).

The condition expression of a control structure can return any value. Both false and nil are considered false. All values different from nil and false are considered true (in particular, the number 0 and the empty string are also true).

In the repeatuntil loop, the inner block does not end at the until keyword, but only after the condition. So, the condition can refer to local variables declared inside the loop block.

The return statement is used to return values from a function or a chunk (which is just a function). Functions and chunks can return more than one value, and so the syntax for the return statement is

	stat ::= return [explist]

The break statement is used to terminate the execution of a while, repeat, or for loop, skipping to the next statement after the loop:

	stat ::= break

A break ends the innermost enclosing loop.

The return and break statements can only be written as the last statement of a block. If it is really necessary to return or break in the middle of a block, then an explicit inner block can be used, as in the idioms do return end and do break end, because now return and break are the last statements in their (inner) blocks.

2.4.5 - For Statement

The for statement has two forms: one numeric and one generic.

The numeric for loop repeats a block of code while a control variable runs through an arithmetic progression. It has the following syntax:

	stat ::= for Name `=´ exp `,´ exp [`,´ exp] do block end

The block is repeated for name starting at the value of the first exp, until it passes the second exp by steps of the third exp. More precisely, a for statement like

     for v = e1, e2, e3 do block end

is equivalent to the code:

     do
       local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
       if not (var and limit and step) then error() end
       while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do
         local v = var
         block
         var = var + step
       end
     end

Note the following:

  • All three control expressions are evaluated only once, before the loop starts. They must all result in numbers.
  • var, limit, and step are invisible variables. The names shown here are for explanatory purposes only.
  • If the third expression (the step) is absent, then a step of 1 is used.
  • You can use break to exit a for loop.
  • The loop variable v is local to the loop; you cannot use its value after the for ends or is broken. If you need this value, assign it to another variable before breaking or exiting the loop.

The generic for statement works over functions, called iterators. On each iteration, the iterator function is called to produce a new value, stopping when this new value is nil. The generic for loop has the following syntax:

	stat ::= for namelist in explist do block end
	namelist ::= Name {`,´ Name}

A for statement like

     for var_1, ···, var_n in explist do block end

is equivalent to the code:

     do
       local f, s, var = explist
       while true do
         local var_1, ···, var_n = f(s, var)
         var = var_1
         if var == nil then break end
         block
       end
     end

Note the following:

  • explist is evaluated only once. Its results are an iterator function, a state, and an initial value for the first iterator variable.
  • f, s, and var are invisible variables. The names are here for explanatory purposes only.
  • You can use break to exit a for loop.
  • The loop variables var_i are local to the loop; you cannot use their values after the for ends. If you need these values, then assign them to other variables before breaking or exiting the loop.

2.4.6 - Function Calls as Statements

To allow possible side-effects, function calls can be executed as statements:

	stat ::= functioncall

In this case, all returned values are thrown away. Function calls are explained in §2.5.8.

2.4.7 - Local Declarations

Local variables can be declared anywhere inside a block. The declaration can include an initial assignment:

	stat ::= local namelist [`=´ explist]

If present, an initial assignment has the same semantics of a multiple assignment (see §2.4.3). Otherwise, all variables are initialized with nil.

A chunk is also a block (see §2.4.1), and so local variables can be declared in a chunk outside any explicit block. The scope of such local variables extends until the end of the chunk.

The visibility rules for local variables are explained in §2.6.

2.5 - Expressions

The basic expressions in Lua are the following:

	exp ::= prefixexp
	exp ::= nil | false | true
	exp ::= Number
	exp ::= String
	exp ::= function
	exp ::= tableconstructor
	exp ::= `...´
	exp ::= exp binop exp
	exp ::= unop exp
	prefixexp ::= var | functioncall | `(´ exp `)´

Numbers and literal strings are explained in §2.1; variables are explained in §2.3; function definitions are explained in §2.5.9; function calls are explained in §2.5.8; table constructors are explained in §2.5.7. Vararg expressions, denoted by three dots ('...'), can only be used when directly inside a vararg function; they are explained in §2.5.9.

Binary operators comprise arithmetic operators (see §2.5.1), relational operators (see §2.5.2), logical operators (see §2.5.3), and the concatenation operator (see §2.5.4). Unary operators comprise the unary minus (see §2.5.1), the unary not (see §2.5.3), and the unary length operator (see §2.5.5).

Both function calls and vararg expressions can result in multiple values. If an expression is used as a statement (only possible for function calls (see §2.4.6)), then its return list is adjusted to zero elements, thus discarding all returned values. If an expression is used as the last (or the only) element of a list of expressions, then no adjustment is made (unless the call is enclosed in parentheses). In all other contexts, Lua adjusts the result list to one element, discarding all values except the first one.

Here are some examples:

     f()                -- adjusted to 0 results
     g(f(), x)          -- f() is adjusted to 1 result
     g(x, f())          -- g gets x plus all results from f()
     a,b,c = f(), x     -- f() is adjusted to 1 result (c gets nil)
     a,b = ...          -- a gets the first vararg parameter, b gets
                        -- the second (both a and b can get nil if there
                        -- is no corresponding vararg parameter)
     
     a,b,c = x, f()     -- f() is adjusted to 2 results
     a,b,c = f()        -- f() is adjusted to 3 results
     return f()         -- returns all results from f()
     return ...         -- returns all received vararg parameters
     return x,y,f()     -- returns x, y, and all results from f()
     {f()}              -- creates a list with all results from f()
     {...}              -- creates a list with all vararg parameters
     {f(), nil}         -- f() is adjusted to 1 result

Any expression enclosed in parentheses always results in only one value. Thus, (f(x,y,z)) is always a single value, even if f returns several values. (The value of (f(x,y,z)) is the first value returned by f or nil if f does not return any values.)

2.5.1 - Arithmetic Operators

Lua supports the usual arithmetic operators: the binary + (addition), - (subtraction), * (multiplication), / (division), % (modulo), and ^ (exponentiation); and unary - (negation). If the operands are numbers, or strings that can be converted to numbers (see §2.2.1), then all operations have the usual meaning. Exponentiation works for any exponent. For instance, x^(-0.5) computes the inverse of the square root of x. Modulo is defined as

     a % b == a - math.floor(a/b)*b

That is, it is the remainder of a division that rounds the quotient towards minus infinity.

2.5.2 - Relational Operators

The relational operators in Lua are

     ==    ~=    <     >     <=    >=

These operators always result in false or true.

Equality (==) first compares the type of its operands. If the types are different, then the result is false. Otherwise, the values of the operands are compared. Numbers and strings are compared in the usual way. Objects (tables, userdata, threads, and functions) are compared by reference: two objects are considered equal only if they are the same object. Every time you create a new object (a table, userdata, thread, or function), this new object is different from any previously existing object.

You can change the way that Lua compares tables and userdata by using the "eq" metamethod (see §2.8).

The conversion rules of §2.2.1 do not apply to equality comparisons. Thus, "0"==0 evaluates to false, and t[0] and t["0"] denote different entries in a table.

The operator ~= is exactly the negation of equality (==).

The order operators work as follows. If both arguments are numbers, then they are compared as such. Otherwise, if both arguments are strings, then their values are compared according to the current locale. Otherwise, Lua tries to call the "lt" or the "le" metamethod (see §2.8). A comparison a > b is translated to b < a and a >= b is translated to b <= a.

2.5.3 - Logical Operators

The logical operators in Lua are and, or, and not. Like the control structures (see §2.4.4), all logical operators consider both false and nil as false and anything else as true.

The negation operator not always returns false or true. The conjunction operator and returns its first argument if this value is false or nil; otherwise, and returns its second argument. The disjunction operator or returns its first argument if this value is different from nil and false; otherwise, or returns its second argument. Both and and or use short-cut evaluation; that is, the second operand is evaluated only if necessary. Here are some examples:

     10 or 20            --> 10
     10 or error()       --> 10
     nil or "a"          --> "a"
     nil and 10          --> nil
     false and error()   --> false
     false and nil       --> false
     false or nil        --> nil
     10 and 20           --> 20

(In this manual, --> indicates the result of the preceding expression.)

2.5.4 - Concatenation

The string concatenation operator in Lua is denoted by two dots ('..'). If both operands are strings or numbers, then they are converted to strings according to the rules mentioned in §2.2.1. Otherwise, the "concat" metamethod is called (see §2.8).

2.5.5 - The Length Operator

The length operator is denoted by the unary operator #. The length of a string is its number of bytes (that is, the usual meaning of string length when each character is one byte).

The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).

2.5.6 - Precedence

Operator precedence in Lua follows the table below, from lower to higher priority:

     or
     and
     <     >     <=    >=    ~=    ==
     ..
     +     -
     *     /     %
     not   #     - (unary)
     ^

As usual, you can use parentheses to change the precedences of an expression. The concatenation ('..') and exponentiation ('^') operators are right associative. All other binary operators are left associative.

2.5.7 - Table Constructors

Table constructors are expressions that create tables. Every time a constructor is evaluated, a new table is created. A constructor can be used to create an empty table or to create a table and initialize some of its fields. The general syntax for constructors is

	tableconstructor ::= `{´ [fieldlist] `}´
	fieldlist ::= field {fieldsep field} [fieldsep]
	field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
	fieldsep ::= `,´ | `;´

Each field of the form [exp1] = exp2 adds to the new table an entry with key exp1 and value exp2. A field of the form name = exp is equivalent to ["name"] = exp. Finally, fields of the form exp are equivalent to [i] = exp, where i are consecutive numerical integers, starting with 1. Fields in the other formats do not affect this counting. For example,

     a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }

is equivalent to

     do
       local t = {}
       t[f(1)] = g
       t[1] = "x"         -- 1st exp
       t[2] = "y"         -- 2nd exp
       t.x = 1            -- t["x"] = 1
       t[3] = f(x)        -- 3rd exp
       t[30] = 23
       t[4] = 45          -- 4th exp
       a = t
     end

If the last field in the list has the form exp and the expression is a function call or a vararg expression, then all values returned by this expression enter the list consecutively (see §2.5.8). To avoid this, enclose the function call or the vararg expression in parentheses (see §2.5).

The field list can have an optional trailing separator, as a convenience for machine-generated code.

2.5.8 - Function Calls

A function call in Lua has the following syntax:

	functioncall ::= prefixexp args

In a function call, first prefixexp and args are evaluated. If the value of prefixexp has type function, then this function is called with the given arguments. Otherwise, the prefixexp "call" metamethod is called, having as first parameter the value of prefixexp, followed by the original call arguments (see §2.8).

The form

	functioncall ::= prefixexp `:´ Name args

can be used to call "methods". A call v:name(args) is syntactic sugar for v.name(v,args), except that v is evaluated only once.

Arguments have the following syntax:

	args ::= `(´ [explist] `)´
	args ::= tableconstructor
	args ::= String

All argument expressions are evaluated before the call. A call of the form f{fields} is syntactic sugar for f({fields}); that is, the argument list is a single new table. A call of the form f'string' (or f"string" or f[[string]]) is syntactic sugar for f('string'); that is, the argument list is a single literal string.

As an exception to the free-format syntax of Lua, you cannot put a line break before the '(' in a function call. This restriction avoids some ambiguities in the language. If you write

     a = f
     (g).x(a)

Lua would see that as a single statement, a = f(g).x(a). So, if you want two statements, you must add a semi-colon between them. If you actually want to call f, you must remove the line break before (g).

A call of the form return functioncall is called a tail call. Lua implements proper tail calls (or proper tail recursion): in a tail call, the called function reuses the stack entry of the calling function. Therefore, there is no limit on the number of nested tail calls that a program can execute. However, a tail call erases any debug information about the calling function. Note that a tail call only happens with a particular syntax, where the return has one single function call as argument; this syntax makes the calling function return exactly the returns of the called function. So, none of the following examples are tail calls:

     return (f(x))        -- results adjusted to 1
     return 2 * f(x)
     return x, f(x)       -- additional results
     f(x); return         -- results discarded
     return x or f(x)     -- results adjusted to 1

2.5.9 - Function Definitions

The syntax for function definition is

	function ::= function funcbody
	funcbody ::= `(´ [parlist] `)´ block end

The following syntactic sugar simplifies function definitions:

	stat ::= function funcname funcbody
	stat ::= local function Name funcbody
	funcname ::= Name {`.´ Name} [`:´ Name]

The statement

     function f () body end

translates to

     f = function () body end

The statement

     function t.a.b.c.f () body end

translates to

     t.a.b.c.f = function () body end

The statement

     local function f () body end

translates to

     local f; f = function () body end

not to

     local f = function () body end

(This only makes a difference when the body of the function contains references to f.)

A function definition is an executable expression, whose value has type function. When Lua pre-compiles a chunk, all its function bodies are pre-compiled too. Then, whenever Lua executes the function definition, the function is instantiated (or closed). This function instance (or closure) is the final value of the expression. Different instances of the same function can refer to different external local variables and can have different environment tables.

Parameters act as local variables that are initialized with the argument values:

	parlist ::= namelist [`,´ `...´] | `...´

When a function is called, the list of arguments is adjusted to the length of the list of parameters, unless the function is a variadic or vararg function, which is indicated by three dots ('...') at the end of its parameter list. A vararg function does not adjust its argument list; instead, it collects all extra arguments and supplies them to the function through a vararg expression, which is also written as three dots. The value of this expression is a list of all actual extra arguments, similar to a function with multiple results. If a vararg expression is used inside another expression or in the middle of a list of expressions, then its return list is adjusted to one element. If the expression is used as the last element of a list of expressions, then no adjustment is made (unless that last expression is enclosed in parentheses).

As an example, consider the following definitions:

     function f(a, b) end
     function g(a, b, ...) end
     function r() return 1,2,3 end

Then, we have the following mapping from arguments to parameters and to the vararg expression:

     CALL            PARAMETERS
     
     f(3)             a=3, b=nil
     f(3, 4)          a=3, b=4
     f(3, 4, 5)       a=3, b=4
     f(r(), 10)       a=1, b=10
     f(r())           a=1, b=2
     
     g(3)             a=3, b=nil, ... -->  (nothing)
     g(3, 4)          a=3, b=4,   ... -->  (nothing)
     g(3, 4, 5, 8)    a=3, b=4,   ... -->  5  8
     g(5, r())        a=5, b=1,   ... -->  2  3

Results are returned using the return statement (see §2.4.4). If control reaches the end of a function without encountering a return statement, then the function returns with no results.

The colon syntax is used for defining methods, that is, functions that have an implicit extra parameter self. Thus, the statement

     function t.a.b.c:f (params) body end

is syntactic sugar for

     t.a.b.c.f = function (self, params) body end

2.6 - Visibility Rules

Lua is a lexically scoped language. The scope of variables begins at the first statement after their declaration and lasts until the end of the innermost block that includes the declaration. Consider the following example:

     x = 10                -- global variable
     do                    -- new block
       local x = x         -- new 'x', with value 10
       print(x)            --> 10
       x = x+1
       do                  -- another block
         local x = x+1     -- another 'x'
         print(x)          --> 12
       end
       print(x)            --> 11
     end
     print(x)              --> 10  (the global one)

Notice that, in a declaration like local x = x, the new x being declared is not in scope yet, and so the second x refers to the outside variable.

Because of the lexical scoping rules, local variables can be freely accessed by functions defined inside their scope. A local variable used by an inner function is called an upvalue, or external local variable, inside the inner function.

Notice that each execution of a local statement defines new local variables. Consider the following example:

     a = {}
     local x = 20
     for i=1,10 do
       local y = 0
       a[i] = function () y=y+1; return x+y end
     end

The loop creates ten closures (that is, ten instances of the anonymous function). Each of these closures uses a different y variable, while all of them share the same x.

2.7 - Error Handling

Because Lua is an embedded extension language, all Lua actions start from C code in the host program calling a function from the Lua library (see lua_pcall). Whenever an error occurs during Lua compilation or execution, control returns to C, which can take appropriate measures (such as printing an error message).

Lua code can explicitly generate an error by calling the error function. If you need to catch errors in Lua, you can use the pcall function.

2.8 - Metatables

Every value in Lua can have a metatable. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable. For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field "__add" in its metatable. If it finds one, Lua calls this function to perform the addition.

We call the keys in a metatable events and the values metamethods. In the previous example, the event is "add" and the metamethod is the function that performs the addition.

You can query the metatable of any value through the getmetatable function.

You can replace the metatable of tables through the setmetatable function. You cannot change the metatable of other types from Lua (except by using the debug library); you must use the C API for that.

Tables and full userdata have individual metatables (although multiple tables and userdata can share their metatables). Values of all other types share one single metatable per type; that is, there is one single metatable for all numbers, one for all strings, etc.

A metatable controls how an object behaves in arithmetic operations, order comparisons, concatenation, length operation, and indexing. A metatable also can define a function to be called when a userdata is garbage collected. For each of these operations Lua associates a specific key called an event. When Lua performs one of these operations over a value, it checks whether this value has a metatable with the corresponding event. If so, the value associated with that key (the metamethod) controls how Lua will perform the operation.

Metatables control the operations listed next. Each operation is identified by its corresponding name. The key for each operation is a string with its name prefixed by two underscores, '__'; for instance, the key for operation "add" is the string "__add". The semantics of these operations is better explained by a Lua function describing how the interpreter executes the operation.

The code shown here in Lua is only illustrative; the real behavior is hard coded in the interpreter and it is much more efficient than this simulation. All functions used in these descriptions (rawget, tonumber, etc.) are described in §5.1. In particular, to retrieve the metamethod of a given object, we use the expression

     metatable(obj)[event]

This should be read as

     rawget(getmetatable(obj) or {}, event)

That is, the access to a metamethod does not invoke other metamethods, and the access to objects with no metatables does not fail (it simply results in nil).

  • "add": the + operation.

    The function getbinhandler below defines how Lua chooses a handler for a binary operation. First, Lua tries the first operand. If its type does not define a handler for the operation, then Lua tries the second operand.

         function getbinhandler (op1, op2, event)
           return metatable(op1)[event] or metatable(op2)[event]
         end
    

    By using this function, the behavior of the op1 + op2 is

         function add_event (op1, op2)
           local o1, o2 = tonumber(op1), tonumber(op2)
           if o1 and o2 then  -- both operands are numeric?
             return o1 + o2   -- '+' here is the primitive 'add'
           else  -- at least one of the operands is not numeric
             local h = getbinhandler(op1, op2, "__add")
             if h then
               -- call the handler with both operands
               return (h(op1, op2))
             else  -- no handler available: default behavior
               error(···)
             end
           end
         end
    

  • "sub": the - operation. Behavior similar to the "add" operation.
  • "mul": the * operation. Behavior similar to the "add" operation.
  • "div": the / operation. Behavior similar to the "add" operation.
  • "mod": the % operation. Behavior similar to the "add" operation, with the operation o1 - floor(o1/o2)*o2 as the primitive operation.
  • "pow": the ^ (exponentiation) operation. Behavior similar to the "add" operation, with the function pow (from the C math library) as the primitive operation.
  • "unm": the unary - operation.
         function unm_event (op)
           local o = tonumber(op)
           if o then  -- operand is numeric?
             return -o  -- '-' here is the primitive 'unm'
           else  -- the operand is not numeric.
             -- Try to get a handler from the operand
             local h = metatable(op).__unm
             if h then
               -- call the handler with the operand
               return (h(op))
             else  -- no handler available: default behavior
               error(···)
             end
           end
         end
    

  • "concat": the .. (concatenation) operation.
         function concat_event (op1, op2)
           if (type(op1) == "string" or type(op1) == "number") and
              (type(op2) == "string" or type(op2) == "number") then
             return op1 .. op2  -- primitive string concatenation
           else
             local h = getbinhandler(op1, op2, "__concat")
             if h then
               return (h(op1, op2))
             else
               error(···)
             end
           end
         end
    

  • "len": the # operation.
         function len_event (op)
           if type(op) == "string" then
             return strlen(op)         -- primitive string length
           elseif type(op) == "table" then
             return #op                -- primitive table length
           else
             local h = metatable(op).__len
             if h then
               -- call the handler with the operand
               return (h(op))
             else  -- no handler available: default behavior
               error(···)
             end
           end
         end
    

    See §2.5.5 for a description of the length of a table.

  • "eq": the == operation. The function getcomphandler defines how Lua chooses a metamethod for comparison operators. A metamethod only is selected when both objects being compared have the same type and the same metamethod for the selected operation.
         function getcomphandler (op1, op2, event)
           if type(op1) ~= type(op2) then return nil end
           local mm1 = metatable(op1)[event]
           local mm2 = metatable(op2)[event]
           if mm1 == mm2 then return mm1 else return nil end
         end
    

    The "eq" event is defined as follows:

         function eq_event (op1, op2)
           if type(op1) ~= type(op2) then  -- different types?
             return false   -- different objects
           end
           if op1 == op2 then   -- primitive equal?
             return true   -- objects are equal
           end
           -- try metamethod
           local h = getcomphandler(op1, op2, "__eq")
           if h then
             return (h(op1, op2))
           else
             return false
           end
         end
    

    a ~= b is equivalent to not (a == b).

  • "lt": the < operation.
         function lt_event (op1, op2)
           if type(op1) == "number" and type(op2) == "number" then
             return op1 < op2   -- numeric comparison
           elseif type(op1) == "string" and type(op2) == "string" then
             return op1 < op2   -- lexicographic comparison
           else
             local h = getcomphandler(op1, op2, "__lt")
             if h then
               return (h(op1, op2))
             else
               error(···)
             end
           end
         end
    

    a > b is equivalent to b < a.

  • "le": the <= operation.
         function le_event (op1, op2)
           if type(op1) == "number" and type(op2) == "number" then
             return op1 <= op2   -- numeric comparison
           elseif type(op1) == "string" and type(op2) == "string" then
             return op1 <= op2   -- lexicographic comparison
           else
             local h = getcomphandler(op1, op2, "__le")
             if h then
               return (h(op1, op2))
             else
               h = getcomphandler(op1, op2, "__lt")
               if h then
                 return not h(op2, op1)
               else
                 error(···)
               end
             end
           end
         end
    

    a >= b is equivalent to b <= a. Note that, in the absence of a "le" metamethod, Lua tries the "lt", assuming that a <= b is equivalent to not (b < a).

  • "index": The indexing access table[key].
         function gettable_event (table, key)
           local h
           if type(table) == "table" then
             local v = rawget(table, key)
             if v ~= nil then return v end
             h = metatable(table).__index
             if h == nil then return nil end
           else
             h = metatable(table).__index
             if h == nil then
               error(···)
             end
           end
           if type(h) == "function" then
             return (h(table, key))     -- call the handler
           else return h[key]           -- or repeat operation on it
           end
         end
    

  • "newindex": The indexing assignment table[key] = value.
         function settable_event (table, key, value)
           local h
           if type(table) == "table" then
             local v = rawget(table, key)
             if v ~= nil then rawset(table, key, value); return end
             h = metatable(table).__newindex
             if h == nil then rawset(table, key, value); return end
           else
             h = metatable(table).__newindex
             if h == nil then
               error(···)
             end
           end
           if type(h) == "function" then
             h(table, key,value)           -- call the handler
           else h[key] = value             -- or repeat operation on it
           end
         end
    

  • "call": called when Lua calls a value.
         function function_event (func, ...)
           if type(func) == "function" then
             return func(...)   -- primitive call
           else
             local h = metatable(func).__call
             if h then
               return h(func, ...)
             else
               error(···)
             end
           end
         end
    

2.9 - Environments

Besides metatables, objects of types thread, function, and userdata have another table associated with them, called their environment. Like metatables, environments are regular tables and multiple objects can share the same environment.

Threads are created sharing the environment of the creating thread. Userdata and C functions are created sharing the environment of the creating C function. Non-nested Lua functions (created by loadfile, loadstring or load) are created sharing the environment of the creating thread. Nested Lua functions are created sharing the environment of the creating Lua function.

Environments associated with userdata have no meaning for Lua. It is only a convenience feature for programmers to associate a table to a userdata.

Environments associated with threads are called global environments. They are used as the default environment for threads and non-nested Lua functions created by the thread and can be directly accessed by C code (see §3.3).

The environment associated with a C function can be directly accessed by C code (see §3.3). It is used as the default environment for other C functions and userdata created by the function.

Environments associated with Lua functions are used to resolve all accesses to global variables within the function (see §2.3). They are used as the default environment for nested Lua functions created by the function.

You can change the environment of a Lua function or the running thread by calling setfenv. You can get the environment of a Lua function or the running thread by calling getfenv. To manipulate the environment of other objects (userdata, C functions, other threads) you must use the C API.

2.10 - Garbage Collection

Lua performs automatic memory management. This means that you have to worry neither about allocating memory for new objects nor about freeing it when the objects are no longer needed. Lua manages memory automatically by running a garbage collector from time to time to collect all dead objects (that is, objects that are no longer accessible from Lua). All memory used by Lua is subject to automatic management: tables, userdata, functions, threads, strings, etc.

Lua implements an incremental mark-and-sweep collector. It uses two numbers to control its garbage-collection cycles: the garbage-collector pause and the garbage-collector step multiplier. Both use percentage points as units (so that a value of 100 means an internal value of 1).

The garbage-collector pause controls how long the collector waits before starting a new cycle. Larger values make the collector less aggressive. Values smaller than 100 mean the collector will not wait to start a new cycle. A value of 200 means that the collector waits for the total memory in use to double before starting a new cycle.

The step multiplier controls the relative speed of the collector relative to memory allocation. Larger values make the collector more aggressive but also increase the size of each incremental step. Values smaller than 100 make the collector too slow and can result in the collector never finishing a cycle. The default, 200, means that the collector runs at "twice" the speed of memory allocation.

You can change these numbers by calling lua_gc in C or collectgarbage in Lua. With these functions you can also control the collector directly (e.g., stop and restart it).

2.10.1 - Garbage-Collection Metamethods

Using the C API, you can set garbage-collector metamethods for userdata (see §2.8). These metamethods are also called finalizers. Finalizers allow you to coordinate Lua's garbage collection with external resource management (such as closing files, network or database connections, or freeing your own memory).

Garbage userdata with a field __gc in their metatables are not collected immediately by the garbage collector. Instead, Lua puts them in a list. After the collection, Lua does the equivalent of the following function for each userdata in that list:

     function gc_event (udata)
       local h = metatable(udata).__gc
       if h then
         h(udata)
       end
     end

At the end of each garbage-collection cycle, the finalizers for userdata are called in reverse order of their creation, among those collected in that cycle. That is, the first finalizer to be called is the one associated with the userdata created last in the program. The userdata itself is freed only in the next garbage-collection cycle.

2.10.2 - Weak Tables

A weak table is a table whose elements are weak references. A weak reference is ignored by the garbage collector. In other words, if the only references to an object are weak references, then the garbage collector will collect this object.

A weak table can have weak keys, weak values, or both. A table with weak keys allows the collection of its keys, but prevents the collection of its values. A table with both weak keys and weak values allows the collection of both keys and values. In any case, if either the key or the value is collected, the whole pair is removed from the table. The weakness of a table is controlled by the __mode field of its metatable. If the __mode field is a string containing the character 'k', the keys in the table are weak. If __mode contains 'v', the values in the table are weak.

After you use a table as a metatable, you should not change the value of its __mode field. Otherwise, the weak behavior of the tables controlled by this metatable is undefined.

2.11 - Coroutines

Lua supports coroutines, also called collaborative multithreading. A coroutine in Lua represents an independent thread of execution. Unlike threads in multithread systems, however, a coroutine only suspends its execution by explicitly calling a yield function.

You create a coroutine with a call to coroutine.create. Its sole argument is a function that is the main function of the coroutine. The create function only creates a new coroutine and returns a handle to it (an object of type thread); it does not start the coroutine execution.

When you first call coroutine.resume, passing as its first argument a thread returned by coroutine.create, the coroutine starts its execution, at the first line of its main function. Extra arguments passed to coroutine.resume are passed on to the coroutine main function. After the coroutine starts running, it runs until it terminates or yields.

A coroutine can terminate its execution in two ways: normally, when its main function returns (explicitly or implicitly, after the last instruction); and abnormally, if there is an unprotected error. In the first case, coroutine.resume returns true, plus any values returned by the coroutine main function. In case of errors, coroutine.resume returns false plus an error message.

A coroutine yields by calling coroutine.yield. When a coroutine yields, the corresponding coroutine.resume returns immediately, even if the yield happens inside nested function calls (that is, not in the main function, but in a function directly or indirectly called by the main function). In the case of a yield, coroutine.resume also returns true, plus any values passed to coroutine.yield. The next time you resume the same coroutine, it continues its execution from the point where it yielded, with the call to coroutine.yield returning any extra arguments passed to coroutine.resume.

Like coroutine.create, the coroutine.wrap function also creates a coroutine, but instead of returning the coroutine itself, it returns a function that, when called, resumes the coroutine. Any arguments passed to this function go as extra arguments to coroutine.resume. coroutine.wrap returns all the values returned by coroutine.resume, except the first one (the boolean error code). Unlike coroutine.resume, coroutine.wrap does not catch errors; any error is propagated to the caller.

As an example, consider the following code:

     function foo (a)
       print("foo", a)
       return coroutine.yield(2*a)
     end
     
     co = coroutine.create(function (a,b)
           print("co-body", a, b)
           local r = foo(a+1)
           print("co-body", r)
           local r, s = coroutine.yield(a+b, a-b)
           print("co-body", r, s)
           return b, "end"
     end)
            
     print("main", coroutine.resume(co, 1, 10))
     print("main", coroutine.resume(co, "r"))
     print("main", coroutine.resume(co, "x", "y"))
     print("main", coroutine.resume(co, "x", "y"))

When you run it, it produces the following output:

     co-body 1       10
     foo     2
     
     main    true    4
     co-body r
     main    true    11      -9
     co-body x       y
     main    true    10      end
     main    false   cannot resume dead coroutine

3 - The Application Program Interface

This section describes the C API for Lua, that is, the set of C functions available to the host program to communicate with Lua. All API functions and related types and constants are declared in the header file lua.h.

Even when we use the term "function", any facility in the API may be provided as a macro instead. All such macros use each of their arguments exactly once (except for the first argument, which is always a Lua state), and so do not generate any hidden side-effects.

As in most C libraries, the Lua API functions do not check their arguments for validity or consistency. However, you can change this behavior by compiling Lua with a proper definition for the macro luai_apicheck, in file luaconf.h.

3.1 - The Stack

Lua uses a virtual stack to pass values to and from C. Each element in this stack represents a Lua value (nil, number, string, etc.).

Whenever Lua calls C, the called function gets a new stack, which is independent of previous stacks and of stacks of C functions that are still active. This stack initially contains any arguments to the C function and it is where the C function pushes its results to be returned to the caller (see lua_CFunction).

For convenience, most query operations in the API do not follow a strict stack discipline. Instead, they can refer to any element in the stack by using an index: A positive index represents an absolute stack position (starting at 1); a negative index represents an offset relative to the top of the stack. More specifically, if the stack has n elements, then index 1 represents the first element (that is, the element that was pushed onto the stack first) and index n represents the last element; index -1 also represents the last element (that is, the element at the top) and index -n represents the first element. We say that an index is valid if it lies between 1 and the stack top (that is, if 1 ≤ abs(index) ≤ top).

3.2 - Stack Size

When you interact with Lua API, you are responsible for ensuring consistency. In particular, you are responsible for controlling stack overflow. You can use the function lua_checkstack to grow the stack size.

Whenever Lua calls C, it ensures that at least LUA_MINSTACK stack positions are available. LUA_MINSTACK is defined as 20, so that usually you do not have to worry about stack space unless your code has loops pushing elements onto the stack.

Most query functions accept as indices any value inside the available stack space, that is, indices up to the maximum stack size you have set through lua_checkstack. Such indices are called acceptable indices. More formally, we define an acceptable index as follows:

     (index < 0 && abs(index) <= top) ||
     (index > 0 && index <= stackspace)

Note that 0 is never an acceptable index.

3.3 - Pseudo-Indices

Unless otherwise noted, any function that accepts valid indices can also be called with pseudo-indices, which represent some Lua values that are accessible to C code but which are not in the stack. Pseudo-indices are used to access the thread environment, the function environment, the registry, and the upvalues of a C function (see §3.4).

The thread environment (where global variables live) is always at pseudo-index LUA_GLOBALSINDEX. The environment of the running C function is always at pseudo-index LUA_ENVIRONINDEX.

To access and change the value of global variables, you can use regular table operations over an environment table. For instance, to access the value of a global variable, do

     lua_getfield(L, LUA_GLOBALSINDEX, varname);

3.4 - C Closures

When a C function is created, it is possible to associate some values with it, thus creating a C closure; these values are called upvalues and are accessible to the function whenever it is called (see lua_pushcclosure).

Whenever a C function is called, its upvalues are located at specific pseudo-indices. These pseudo-indices are produced by the macro lua_upvalueindex. The first value associated with a function is at position lua_upvalueindex(1), and so on. Any access to lua_upvalueindex(n), where n is greater than the number of upvalues of the current function (but not greater than 256), produces an acceptable (but invalid) index.

3.5 - Registry

Lua provides a registry, a pre-defined table that can be used by any C code to store whatever Lua value it needs to store. This table is always located at pseudo-index LUA_REGISTRYINDEX. Any C library can store data into this table, but it should take care to choose keys different from those used by other libraries, to avoid collisions. Typically, you should use as key a string containing your library name or a light userdata with the address of a C object in your code.

The integer keys in the registry are used by the reference mechanism, implemented by the auxiliary library, and therefore should not be used for other purposes.

3.6 - Error Handling in C

Internally, Lua uses the C longjmp facility to handle errors. (You can also choose to use exceptions if you use C++; see file luaconf.h.) When Lua faces any error (such as memory allocation errors, type errors, syntax errors, and runtime errors) it raises an error; that is, it does a long jump. A protected environment uses setjmp to set a recover point; any error jumps to the most recent active recover point.

Most functions in the API can throw an error, for instance due to a memory allocation error. The documentation for each function indicates whether it can throw errors.

Inside a C function you can throw an error by calling lua_error.

3.7 - Functions and Types

Here we list all functions and types from the C API in alphabetical order. Each function has an indicator like this: [-o, +p, x]

The first field, o, is how many elements the function pops from the stack. The second field, p, is how many elements the function pushes onto the stack. (Any function always pushes its results after popping its arguments.) A field in the form x|y means the function can push (or pop) x or y elements, depending on the situation; an interrogation mark '?' means that we cannot know how many elements the function pops/pushes by looking only at its arguments (e.g., they may depend on what is on the stack). The third field, x, tells whether the function may throw errors: '-' means the function never throws any error; 'm' means the function may throw an error only due to not enough memory; 'e' means the function may throw other kinds of errors; 'v' means the function may throw an error on purpose.


lua_Alloc

typedef void * (*lua_Alloc) (void *ud,
                             void *ptr,
                             size_t osize,
                             size_t nsize);

The type of the memory-allocation function used by Lua states. The allocator function must provide a functionality similar to realloc, but not exactly the same. Its arguments are ud, an opaque pointer passed to lua_newstate; ptr, a pointer to the block being allocated/reallocated/freed; osize, the original size of the block; nsize, the new size of the block. ptr is NULL if and only if osize is zero. When nsize is zero, the allocator must return NULL; if osize is not zero, it should free the block pointed to by ptr. When nsize is not zero, the allocator returns NULL if and only if it cannot fill the request. When nsize is not zero and osize is zero, the allocator should behave like malloc. When nsize and osize are not zero, the allocator behaves like realloc. Lua assumes that the allocator never fails when osize >= nsize.

Here is a simple implementation for the allocator function. It is used in the auxiliary library by luaL_newstate.

     static void *l_alloc (void *ud, void *ptr, size_t osize,
                                                size_t nsize) {
       (void)ud;  (void)osize;  /* not used */
       if (nsize == 0) {
         free(ptr);
         return NULL;
       }
       else
         return realloc(ptr, nsize);
     }

This code assumes that free(NULL) has no effect and that realloc(NULL, size) is equivalent to malloc(size). ANSI C ensures both behaviors.


lua_atpanic

[-0, +0, -]

lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf);

Sets a new panic function and returns the old one.

If an error happens outside any protected environment, Lua calls a panic function and then calls exit(EXIT_FAILURE), thus exiting the host application. Your panic function can avoid this exit by never returning (e.g., doing a long jump).

The panic function can access the error message at the top of the stack.


lua_call

[-(nargs + 1), +nresults, e]

void lua_call (lua_State *L, int nargs, int nresults);

Calls a function.

To call a function you must use the following protocol: first, the function to be called is pushed onto the stack; then, the arguments to the function are pushed in direct order; that is, the first argument is pushed first. Finally you call lua_call; nargs is the number of arguments that you pushed onto the stack. All arguments and the function value are popped from the stack when the function is called. The function results are pushed onto the stack when the function returns. The number of results is adjusted to nresults, unless nresults is LUA_MULTRET. In this case, all results from the function are pushed. Lua takes care that the returned values fit into the stack space. The function results are pushed onto the stack in direct order (the first result is pushed first), so that after the call the last result is on the top of the stack.

Any error inside the called function is propagated upwards (with a longjmp).

The following example shows how the host program can do the equivalent to this Lua code:

     a = f("how", t.x, 14)

Here it is in C:

     lua_getfield(L, LUA_GLOBALSINDEX, "f"); /* function to be called */
     lua_pushstring(L, "how");                        /* 1st argument */
     lua_getfield(L, LUA_GLOBALSINDEX, "t");   /* table to be indexed */
     lua_getfield(L, -1, "x");        /* push result of t.x (2nd arg) */
     lua_remove(L, -2);                  /* remove 't' from the stack */
     lua_pushinteger(L, 14);                          /* 3rd argument */
     lua_call(L, 3, 1);     /* call 'f' with 3 arguments and 1 result */
     lua_setfield(L, LUA_GLOBALSINDEX, "a");        /* set global 'a' */

Note that the code above is "balanced": at its end, the stack is back to its original configuration. This is considered good programming practice.


lua_CFunction

typedef int (*lua_CFunction) (lua_State *L);

Type for C functions.

In order to communicate properly with Lua, a C function must use the following protocol, which defines the way parameters and results are passed: a C function receives its arguments from Lua in its stack in direct order (the first argument is pushed first). So, when the function starts, lua_gettop(L) returns the number of arguments received by the function. The first argument (if any) is at index 1 and its last argument is at index lua_gettop(L). To return values to Lua, a C function just pushes them onto the stack, in direct order (the first result is pushed first), and returns the number of results. Any other value in the stack below the results will be properly discarded by Lua. Like a Lua function, a C function called by Lua can also return many results.

As an example, the following function receives a variable number of numerical arguments and returns their average and sum:

     static int foo (lua_State *L) {
       int n = lua_gettop(L);    /* number of arguments */
       lua_Number sum = 0;
       int i;
       for (i = 1; i <= n; i++) {
         if (!lua_isnumber(L, i)) {
           lua_pushstring(L, "incorrect argument");
           lua_error(L);
         }
         sum += lua_tonumber(L, i);
       }
       lua_pushnumber(L, sum/n);        /* first result */
       lua_pushnumber(L, sum);         /* second result */
       return 2;                   /* number of results */
     }

lua_checkstack

[-0, +0, m]

int lua_checkstack (lua_State *L, int extra);

Ensures that there are at least extra free stack slots in the stack. It returns false if it cannot grow the stack to that size. This function never shrinks the stack; if the stack is already larger than the new size, it is left unchanged.


lua_close

[-0, +0, -]

void lua_close (lua_State *L);

Destroys all objects in the given Lua state (calling the corresponding garbage-collection metamethods, if any) and frees all dynamic memory used by this state. On several platforms, you may not need to call this function, because all resources are naturally released when the host program ends. On the other hand, long-running programs, such as a daemon or a web server, might need to release states as soon as they are not needed, to avoid growing too large.


lua_concat

[-n, +1, e]

void lua_concat (lua_State *L, int n);

Concatenates the n values at the top of the stack, pops them, and leaves the result at the top. If n is 1, the result is the single value on the stack (that is, the function does nothing); if n is 0, the result is the empty string. Concatenation is performed following the usual semantics of Lua (see §2.5.4).


lua_cpcall

[-0, +(0|1), -]

int lua_cpcall (lua_State *L, lua_CFunction func, void *ud);

Calls the C function func in protected mode. func starts with only one element in its stack, a light userdata containing ud. In case of errors, lua_cpcall returns the same error codes as lua_pcall, plus the error object on the top of the stack; otherwise, it returns zero, and does not change the stack. All values returned by func are discarded.


lua_createtable

[-0, +1, m]

void lua_createtable (lua_State *L, int narr, int nrec);

Creates a new empty table and pushes it onto the stack. The new table has space pre-allocated for narr array elements and nrec non-array elements. This pre-allocation is useful when you know exactly how many elements the table will have. Otherwise you can use the function lua_newtable.


lua_dump

[-0, +0, m]

int lua_dump (lua_State *L, lua_Writer writer, void *data);

Dumps a function as a binary chunk. Receives a Lua function on the top of the stack and produces a binary chunk that, if loaded again, results in a function equivalent to the one dumped. As it produces parts of the chunk, lua_dump calls function writer (see lua_Writer) with the given data to write them.

The value returned is the error code returned by the last call to the writer; 0 means no errors.

This function does not pop the Lua function from the stack.


lua_equal

[-0, +0, e]

int lua_equal (lua_State *L, int index1, int index2);

Returns 1 if the two values in acceptable indices index1 and index2 are equal, following the semantics of the Lua == operator (that is, may call metamethods). Otherwise returns 0. Also returns 0 if any of the indices is non valid.


lua_error

[-1, +0, v]

int lua_error (lua_State *L);

Generates a Lua error. The error message (which can actually be a Lua value of any type) must be on the stack top. This function does a long jump, and therefore never returns. (see luaL_error).


lua_gc

[-0, +0, e]

int lua_gc (lua_State *L, int what, int data);

Controls the garbage collector.

This function performs several tasks, according to the value of the parameter what:

  • LUA_GCSTOP: stops the garbage collector.
  • LUA_GCRESTART: restarts the garbage collector.
  • LUA_GCCOLLECT: performs a full garbage-collection cycle.
  • LUA_GCCOUNT: returns the current amount of memory (in Kbytes) in use by Lua.
  • LUA_GCCOUNTB: returns the remainder of dividing the current amount of bytes of memory in use by Lua by 1024.
  • LUA_GCSTEP: performs an incremental step of garbage collection. The step "size" is controlled by data (larger values mean more steps) in a non-specified way. If you want to control the step size you must experimentally tune the value of data. The function returns 1 if the step finished a garbage-collection cycle.
  • LUA_GCSETPAUSE: sets data as the new value for the pause of the collector (see §2.10). The function returns the previous value of the pause.
  • LUA_GCSETSTEPMUL: sets data as the new value for the step multiplier of the collector (see §2.10). The function returns the previous value of the step multiplier.

lua_getallocf

[-0, +0, -]

lua_Alloc lua_getallocf (lua_State *L, void **ud);

Returns the memory-allocation function of a given state. If ud is not NULL, Lua stores in *ud the opaque pointer passed to lua_newstate.


lua_getfenv

[-0, +1, -]

void lua_getfenv (lua_State *L, int index);

Pushes onto the stack the environment table of the value at the given index.


lua_getfield

[-0, +1, e]

void lua_getfield (lua_State *L, int index, const char *k);

Pushes onto the stack the value t[k], where t is the value at the given valid index. As in Lua, this function may trigger a metamethod for the "index" event (see §2.8).


lua_getglobal

[-0, +1, e]

void lua_getglobal (lua_State *L, const char *name);

Pushes onto the stack the value of the global name. It is defined as a macro:

     #define lua_getglobal(L,s)  lua_getfield(L, LUA_GLOBALSINDEX, s)

lua_getmetatable

[-0, +(0|1), -]

int lua_getmetatable (lua_State *L, int index);

Pushes onto the stack the metatable of the value at the given acceptable index. If the index is not valid, or if the value does not have a metatable, the function returns 0 and pushes nothing on the stack.


lua_gettable

[-1, +1, e]

void lua_gettable (lua_State *L, int index);

Pushes onto the stack the value t[k], where t is the value at the given valid index and k is the value at the top of the stack.

This function pops the key from the stack (putting the resulting value in its place). As in Lua, this function may trigger a metamethod for the "index" event (see §2.8).


lua_gettop

[-0, +0, -]

int lua_gettop (lua_State *L);

Returns the index of the top element in the stack. Because indices start at 1, this result is equal to the number of elements in the stack (and so 0 means an empty stack).


lua_insert

[-1, +1, -]

void lua_insert (lua_State *L, int index);

Moves the top element into the given valid index, shifting up the elements above this index to open space. Cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position.


lua_Integer

typedef ptrdiff_t lua_Integer;

The type used by the Lua API to represent integral values.

By default it is a ptrdiff_t, which is usually the largest signed integral type the machine handles "comfortably".


lua_isboolean

[-0, +0, -]

int lua_isboolean (lua_State *L, int index);

Returns 1 if the value at the given acceptable index has type boolean, and 0 otherwise.


lua_iscfunction

[-0, +0, -]

int lua_iscfunction (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a C function, and 0 otherwise.


lua_isfunction

[-0, +0, -]

int lua_isfunction (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a function (either C or Lua), and 0 otherwise.


lua_islightuserdata

[-0, +0, -]

int lua_islightuserdata (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a light userdata, and 0 otherwise.


lua_isnil

[-0, +0, -]

int lua_isnil (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is nil, and 0 otherwise.


lua_isnone

[-0, +0, -]

int lua_isnone (lua_State *L, int index);

Returns 1 if the given acceptable index is not valid (that is, it refers to an element outside the current stack), and 0 otherwise.


lua_isnoneornil

[-0, +0, -]

int lua_isnoneornil (lua_State *L, int index);

Returns 1 if the given acceptable index is not valid (that is, it refers to an element outside the current stack) or if the value at this index is nil, and 0 otherwise.


lua_isnumber

[-0, +0, -]

int lua_isnumber (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a number or a string convertible to a number, and 0 otherwise.


lua_isstring

[-0, +0, -]

int lua_isstring (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a string or a number (which is always convertible to a string), and 0 otherwise.


lua_istable

[-0, +0, -]

int lua_istable (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a table, and 0 otherwise.


lua_isthread

[-0, +0, -]

int lua_isthread (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a thread, and 0 otherwise.


lua_isuserdata

[-0, +0, -]

int lua_isuserdata (lua_State *L, int index);

Returns 1 if the value at the given acceptable index is a userdata (either full or light), and 0 otherwise.


lua_lessthan

[-0, +0, e]

int lua_lessthan (lua_State *L, int index1, int index2);

Returns 1 if the value at acceptable index index1 is smaller than the value at acceptable index index2, following the semantics of the Lua < operator (that is, may call metamethods). Otherwise returns 0. Also returns 0 if any of the indices is non valid.


lua_load

[-0, +1, -]

int lua_load (lua_State *L,
              lua_Reader reader,
              void *data,
              const char *chunkname);

Loads a Lua chunk. If there are no errors, lua_load pushes the compiled chunk as a Lua function on top of the stack. Otherwise, it pushes an error message. The return values of lua_load are:

This function only loads a chunk; it does not run it.

lua_load automatically detects whether the chunk is text or binary, and loads it accordingly (see program luac).

The lua_load function uses a user-supplied reader function to read the chunk (see lua_Reader). The data argument is an opaque value passed to the reader function.

The chunkname argument gives a name to the chunk, which is used for error messages and in debug information (see §3.8).


lua_newstate

[-0, +0, -]

lua_State *lua_newstate (lua_Alloc f, void *ud);

Creates a new, independent state. Returns NULL if cannot create the state (due to lack of memory). The argument f is the allocator function; Lua does all memory allocation for this state through this function. The second argument, ud, is an opaque pointer that Lua simply passes to the allocator in every call.


lua_newtable

[-0, +1, m]

void lua_newtable (lua_State *L);

Creates a new empty table and pushes it onto the stack. It is equivalent to lua_createtable(L, 0, 0).


lua_newthread

[-0, +1, m]

lua_State *lua_newthread (lua_State *L);

Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State that represents this new thread. The new state returned by this function shares with the original state all global objects (such as tables), but has an independent execution stack.

There is no explicit function to close or to destroy a thread. Threads are subject to garbage collection, like any Lua object.


lua_newuserdata

[-0, +1, m]

void *lua_newuserdata (lua_State *L, size_t size);

This function allocates a new block of memory with the given size, pushes onto the stack a new full userdata with the block address, and returns this address.

Userdata represent C values in Lua. A full userdata represents a block of memory. It is an object (like a table): you must create it, it can have its own metatable, and you can detect when it is being collected. A full userdata is only equal to itself (under raw equality).

When Lua collects a full userdata with a gc metamethod, Lua calls the metamethod and marks the userdata as finalized. When this userdata is collected again then Lua frees its corresponding memory.


lua_next

[-1, +(2|0), e]

int lua_next (lua_State *L, int index);

Pops a key from the stack, and pushes a key-value pair from the table at the given index (the "next" pair after the given key). If there are no more elements in the table, then lua_next returns 0 (and pushes nothing).

A typical traversal looks like this:

     /* table is in the stack at index 't' */
     lua_pushnil(L);  /* first key */
     while (lua_next(L, t) != 0) {
       /* uses 'key' (at index -2) and 'value' (at index -1) */
       printf("%s - %s\n",
              lua_typename(L, lua_type(L, -2)),
              lua_typename(L, lua_type(L, -1)));
       /* removes 'value'; keeps 'key' for next iteration */
       lua_pop(L, 1);
     }

While traversing a table, do not call lua_tolstring directly on a key, unless you know that the key is actually a string. Recall that lua_tolstring changes the value at the given index; this confuses the next call to lua_next.


lua_Number

typedef double lua_Number;

The type of numbers in Lua. By default, it is double, but that can be changed in luaconf.h.

Through the configuration file you can change Lua to operate with another type for numbers (e.g., float or long).


lua_objlen

[-0, +0, -]

size_t lua_objlen (lua_State *L, int index);

Returns the "length" of the value at the given acceptable index: for strings, this is the string length; for tables, this is the result of the length operator ('#'); for userdata, this is the size of the block of memory allocated for the userdata; for other values, it is 0.


lua_pcall

[-(nargs + 1), +(nresults|1), -]

int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

Calls a function in protected mode.

Both nargs and nresults have the same meaning as in lua_call. If there are no errors during the call, lua_pcall behaves exactly like lua_call. However, if there is any error, lua_pcall catches it, pushes a single value on the stack (the error message), and returns an error code. Like lua_call, lua_pcall always removes the function and its arguments from the stack.

If errfunc is 0, then the error message returned on the stack is exactly the original error message. Otherwise, errfunc is the stack index of an error handler function. (In the current implementation, this index cannot be a pseudo-index.) In case of runtime errors, this function will be called with the error message and its return value will be the message returned on the stack by lua_pcall.

Typically, the error handler function is used to add more debug information to the error message, such as a stack traceback. Such information cannot be gathered after the return of lua_pcall, since by then the stack has unwound.

The lua_pcall function returns 0 in case of success or one of the following error codes (defined in lua.h):

  • LUA_ERRRUN: a runtime error.
  • LUA_ERRMEM: memory allocation error. For such errors, Lua does not call the error handler function.
  • LUA_ERRERR: error while running the error handler function.

lua_pop

[-n, +0, -]

void lua_pop (lua_State *L, int n);

Pops n elements from the stack.


lua_pushboolean

[-0, +1, -]

void lua_pushboolean (lua_State *L, int b);

Pushes a boolean value with value b onto the stack.


lua_pushcclosure

[-n, +1, m]

void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);

Pushes a new C closure onto the stack.

When a C function is created, it is possible to associate some values with it, thus creating a C closure (see §3.4); these values are then accessible to the function whenever it is called. To associate values with a C function, first these values should be pushed onto the stack (when there are multiple values, the first value is pushed first). Then lua_pushcclosure is called to create and push the C function onto the stack, with the argument n telling how many values should be associated with the function. lua_pushcclosure also pops these values from the stack.

The maximum value for n is 255.


lua_pushcfunction

[-0, +1, m]

void lua_pushcfunction (lua_State *L, lua_CFunction f);

Pushes a C function onto the stack. This function receives a pointer to a C function and pushes onto the stack a Lua value of type function that, when called, invokes the corresponding C function.

Any function to be registered in Lua must follow the correct protocol to receive its parameters and return its results (see lua_CFunction).

lua_pushcfunction is defined as a macro:

     #define lua_pushcfunction(L,f)  lua_pushcclosure(L,f,0)

lua_pushfstring

[-0, +1, m]

const char *lua_pushfstring (lua_State *L, const char *fmt, ...);

Pushes onto the stack a formatted string and returns a pointer to this string. It is similar to the C function sprintf, but has some important differences:

  • You do not have to allocate space for the result: the result is a Lua string and Lua takes care of memory allocation (and deallocation, through garbage collection).
  • The conversion specifiers are quite restricted. There are no flags, widths, or precisions. The conversion specifiers can only be '%%' (inserts a '%' in the string), '%s' (inserts a zero-terminated string, with no size restrictions), '%f' (inserts a lua_Number), '%p' (inserts a pointer as a hexadecimal numeral), '%d' (inserts an int), and '%c' (inserts an int as a character).

lua_pushinteger

[-0, +1, -]

void lua_pushinteger (lua_State *L, lua_Integer n);

Pushes a number with value n onto the stack.


lua_pushlightuserdata

[-0, +1, -]

void lua_pushlightuserdata (lua_State *L, void *p);

Pushes a light userdata onto the stack.

Userdata represent C values in Lua. A light userdata represents a pointer. It is a value (like a number): you do not create it, it has no individual metatable, and it is not collected (as it was never created). A light userdata is equal to "any" light userdata with the same C address.


lua_pushliteral

[-0, +1, m]

void lua_pushliteral (lua_State *L, const char *s);

This macro is equivalent to lua_pushlstring, but can be used only when s is a literal string. In these cases, it automatically provides the string length.


lua_pushlstring

[-0, +1, m]

void lua_pushlstring (lua_State *L, const char *s, size_t len);

Pushes the string pointed to by s with size len onto the stack. Lua makes (or reuses) an internal copy of the given string, so the memory at s can be freed or reused immediately after the function returns. The string can contain embedded zeros.


lua_pushnil

[-0, +1, -]

void lua_pushnil (lua_State *L);

Pushes a nil value onto the stack.


lua_pushnumber

[-0, +1, -]

void lua_pushnumber (lua_State *L, lua_Number n);

Pushes a number with value n onto the stack.


lua_pushstring

[-0, +1, m]

void lua_pushstring (lua_State *L, const char *s);

Pushes the zero-terminated string pointed to by s onto the stack. Lua makes (or reuses) an internal copy of the given string, so the memory at s can be freed or reused immediately after the function returns. The string cannot contain embedded zeros; it is assumed to end at the first zero.


lua_pushthread

[-0, +1, -]

int lua_pushthread (lua_State *L);

Pushes the thread represented by L onto the stack. Returns 1 if this thread is the main thread of its state.


lua_pushvalue

[-0, +1, -]

void lua_pushvalue (lua_State *L, int index);

Pushes a copy of the element at the given valid index onto the stack.


lua_pushvfstring

[-0, +1, m]

const char *lua_pushvfstring (lua_State *L,
                              const char *fmt,
                              va_list argp);

Equivalent to lua_pushfstring, except that it receives a va_list instead of a variable number of arguments.


lua_rawequal

[-0, +0, -]

int lua_rawequal (lua_State *L, int index1, int index2);

Returns 1 if the two values in acceptable indices index1 and index2 are primitively equal (that is, without calling metamethods). Otherwise returns 0. Also returns 0 if any of the indices are non valid.


lua_rawget

[-1, +1, -]

void lua_rawget (lua_State *L, int index);

Similar to lua_gettable, but does a raw access (i.e., without metamethods).


lua_rawgeti

[-0, +1, -]

void lua_rawgeti (lua_State *L, int index, int n);

Pushes onto the stack the value t[n], where t is the value at the given valid index. The access is raw; that is, it does not invoke metamethods.


lua_rawset

[-2, +0, m]

void lua_rawset (lua_State *L, int index);

Similar to lua_settable, but does a raw assignment (i.e., without metamethods).


lua_rawseti

[-1, +0, m]

void lua_rawseti (lua_State *L, int index, int n);

Does the equivalent of t[n] = v, where t is the value at the given valid index and v is the value at the top of the stack.

This function pops the value from the stack. The assignment is raw; that is, it does not invoke metamethods.


lua_Reader

typedef const char * (*lua_Reader) (lua_State *L,
                                    void *data,
                                    size_t *size);

The reader function used by lua_load. Every time it needs another piece of the chunk, lua_load calls the reader, passing along its data parameter. The reader must return a pointer to a block of memory with a new piece of the chunk and set size to the block size. The block must exist until the reader function is called again. To signal the end of the chunk, the reader must return NULL or set size to zero. The reader function may return pieces of any size greater than zero.


lua_register

[-0, +0, e]

void lua_register (lua_State *L,
                   const char *name,
                   lua_CFunction f);

Sets the C function f as the new value of global name. It is defined as a macro:

     #define lua_register(L,n,f) \
            (lua_pushcfunction(L, f), lua_setglobal(L, n))

lua_remove

[-1, +0, -]

void lua_remove (lua_State *L, int index);

Removes the element at the given valid index, shifting down the elements above this index to fill the gap. Cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position.


lua_replace

[-1, +0, -]

void lua_replace (lua_State *L, int index);

Moves the top element into the given position (and pops it), without shifting any element (therefore replacing the value at the given position).


lua_resume

[-?, +?, -]

int lua_resume (lua_State *L, int narg);

Starts and resumes a coroutine in a given thread.

To start a coroutine, you first create a new thread (see lua_newthread); then you push onto its stack the main function plus any arguments; then you call lua_resume, with narg being the number of arguments. This call returns when the coroutine suspends or finishes its execution. When it returns, the stack contains all values passed to lua_yield, or all values returned by the body function. lua_resume returns LUA_YIELD if the coroutine yields, 0 if the coroutine finishes its execution without errors, or an error code in case of errors (see lua_pcall). In case of errors, the stack is not unwound, so you can use the debug API over it. The error message is on the top of the stack. To restart a coroutine, you put on its stack only the values to be passed as results from yield, and then call lua_resume.


lua_setallocf

[-0, +0, -]

void lua_setallocf (lua_State *L, lua_Alloc f, void *ud);

Changes the allocator function of a given state to f with user data ud.


lua_setfenv

[-1, +0, -]

int lua_setfenv (lua_State *L, int index);

Pops a table from the stack and sets it as the new environment for the value at the given index. If the value at the given index is neither a function nor a thread nor a userdata, lua_setfenv returns 0. Otherwise it returns 1.


lua_setfield

[-1, +0, e]

void lua_setfield (lua_State *L, int index, const char *k);

Does the equivalent to t[k] = v, where t is the value at the given valid index and v is the value at the top of the stack.

This function pops the value from the stack. As in Lua, this function may trigger a metamethod for the "newindex" event (see §2.8).


lua_setglobal

[-1, +0, e]

void lua_setglobal (lua_State *L, const char *name);

Pops a value from the stack and sets it as the new value of global name. It is defined as a macro:

     #define lua_setglobal(L,s)   lua_setfield(L, LUA_GLOBALSINDEX, s)

lua_setmetatable

[-1, +0, -]

int lua_setmetatable (lua_State *L, int index);

Pops a table from the stack and sets it as the new metatable for the value at the given acceptable index.


lua_settable

[-2, +0, e]

void lua_settable (lua_State *L, int index);

Does the equivalent to t[k] = v, where t is the value at the given valid index, v is the value at the top of the stack, and k is the value just below the top.

This function pops both the key and the value from the stack. As in Lua, this function may trigger a metamethod for the "newindex" event (see §2.8).


lua_settop

[-?, +?, -]

void lua_settop (lua_State *L, int index);

Accepts any acceptable index, or 0, and sets the stack top to this index. If the new top is larger than the old one, then the new elements are filled with nil. If index is 0, then all stack elements are removed.


lua_State

typedef struct lua_State lua_State;

Opaque structure that keeps the whole state of a Lua interpreter. The Lua library is fully reentrant: it has no global variables. All information about a state is kept in this structure.

A pointer to this state must be passed as the first argument to every function in the library, except to lua_newstate, which creates a Lua state from scratch.


lua_status

[-0, +0, -]

int lua_status (lua_State *L);

Returns the status of the thread L.

The status can be 0 for a normal thread, an error code if the thread finished its execution with an error, or LUA_YIELD if the thread is suspended.


lua_toboolean

[-0, +0, -]

int lua_toboolean (lua_State *L, int index);

Converts the Lua value at the given acceptable index to a C boolean value (0 or 1). Like all tests in Lua, lua_toboolean returns 1 for any Lua value different from false and nil; otherwise it returns 0. It also returns 0 when called with a non-valid index. (If you want to accept only actual boolean values, use lua_isboolean to test the value's type.)


lua_tocfunction

[-0, +0, -]

lua_CFunction lua_tocfunction (lua_State *L, int index);

Converts a value at the given acceptable index to a C function. That value must be a C function; otherwise, returns NULL.


lua_tointeger

[-0, +0, -]

lua_Integer lua_tointeger (lua_State *L, int index);

Converts the Lua value at the given acceptable index to the signed integral type lua_Integer. The Lua value must be a number or a string convertible to a number (see §2.2.1); otherwise, lua_tointeger returns 0.

If the number is not an integer, it is truncated in some non-specified way.


lua_tolstring

[-0, +0, m]

const char *lua_tolstring (lua_State *L, int index, size_t *len);

Converts the Lua value at the given acceptable index to a C string. If len is not NULL, it also sets *len with the string length. The Lua value must be a string or a number; otherwise, the function returns NULL. If the value is a number, then lua_tolstring also changes the actual value in the stack to a string. (This change confuses lua_next when lua_tolstring is applied to keys during a table traversal.)

lua_tolstring returns a fully aligned pointer to a string inside the Lua state. This string always has a zero ('\0') after its last character (as in C), but can contain other zeros in its body. Because Lua has garbage collection, there is no guarantee that the pointer returned by lua_tolstring will be valid after the corresponding value is removed from the stack.


lua_tonumber

[-0, +0, -]

lua_Number lua_tonumber (lua_State *L, int index);

Converts the Lua value at the given acceptable index to the C type lua_Number (see lua_Number). The Lua value must be a number or a string convertible to a number (see §2.2.1); otherwise, lua_tonumber returns 0.


lua_topointer

[-0, +0, -]

const void *lua_topointer (lua_State *L, int index);

Converts the value at the given acceptable index to a generic C pointer (void*). The value can be a userdata, a table, a thread, or a function; otherwise, lua_topointer returns NULL. Different objects will give different pointers. There is no way to convert the pointer back to its original value.

Typically this function is used only for debug information.


lua_tostring

[-0, +0, m]

const char *lua_tostring (lua_State *L, int index);

Equivalent to lua_tolstring with len equal to NULL.


lua_tothread

[-0, +0, -]

lua_State *lua_tothread (lua_State *L, int index);

Converts the value at the given acceptable index to a Lua thread (represented as lua_State*). This value must be a thread; otherwise, the function returns NULL.


lua_touserdata

[-0, +0, -]

void *lua_touserdata (lua_State *L, int index);

If the value at the given acceptable index is a full userdata, returns its block address. If the value is a light userdata, returns its pointer. Otherwise, returns NULL.


lua_type

[-0, +0, -]

int lua_type (lua_State *L, int index);

Returns the type of the value in the given acceptable index, or LUA_TNONE for a non-valid index (that is, an index to an "empty" stack position). The types returned by lua_type are coded by the following constants defined in lua.h: LUA_TNIL, LUA_TNUMBER, LUA_TBOOLEAN, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, and LUA_TLIGHTUSERDATA.


lua_typename

[-0, +0, -]

const char *lua_typename  (lua_State *L, int tp);

Returns the name of the type encoded by the value tp, which must be one the values returned by lua_type.


lua_Writer

typedef int (*lua_Writer) (lua_State *L,
                           const void* p,
                           size_t sz,
                           void* ud);

The type of the writer function used by lua_dump. Every time it produces another piece of chunk, lua_dump calls the writer, passing along the buffer to be written (p), its size (sz), and the data parameter supplied to lua_dump.

The writer returns an error code: 0 means no errors; any other value means an error and stops lua_dump from calling the writer again.


lua_xmove

[-?, +?, -]

void lua_xmove (lua_State *from, lua_State *to, int n);

Exchange values between different threads of the same global state.

This function pops n values from the stack from, and pushes them onto the stack to.


lua_yield

[-?, +?, -]

int lua_yield  (lua_State *L, int nresults);

Yields a coroutine.

This function should only be called as the return expression of a C function, as follows:

     return lua_yield (L, nresults);

When a C function calls lua_yield in that way, the running coroutine suspends its execution, and the call to lua_resume that started this coroutine returns. The parameter nresults is the number of values from the stack that are passed as results to lua_resume.

3.8 - The Debug Interface

Lua has no built-in debugging facilities. Instead, it offers a special interface by means of functions and hooks. This interface allows the construction of different kinds of debuggers, profilers, and other tools that need "inside information" from the interpreter.


lua_Debug

typedef struct lua_Debug {
  int event;
  const char *name;           /* (n) */
  const char *namewhat;       /* (n) */
  const char *what;           /* (S) */
  const char *source;         /* (S) */
  int currentline;            /* (l) */
  int nups;                   /* (u) number of upvalues */
  int linedefined;            /* (S) */
  int lastlinedefined;        /* (S) */
  char short_src[LUA_IDSIZE]; /* (S) */
  /* private part */
  other fields
} lua_Debug;

A structure used to carry different pieces of information about an active function. lua_getstack fills only the private part of this structure, for later use. To fill the other fields of lua_Debug with useful information, call lua_getinfo.

The fields of lua_Debug have the following meaning:

  • source: If the function was defined in a string, then source is that string. If the function was defined in a file, then source starts with a '@' followed by the file name.
  • short_src: a "printable" version of source, to be used in error messages.
  • linedefined: the line number where the definition of the function starts.
  • lastlinedefined: the line number where the definition of the function ends.
  • what: the string "Lua" if the function is a Lua function, "C" if it is a C function, "main" if it is the main part of a chunk, and "tail" if it was a function that did a tail call. In the latter case, Lua has no other information about the function.
  • currentline: the current line where the given function is executing. When no line information is available, currentline is set to -1.
  • name: a reasonable name for the given function. Because functions in Lua are first-class values, they do not have a fixed name: some functions can be the value of multiple global variables, while others can be stored only in a table field. The lua_getinfo function checks how the function was called to find a suitable name. If it cannot find a name, then name is set to NULL.
  • namewhat: explains the name field. The value of namewhat can be "global", "local", "method", "field", "upvalue", or "" (the empty string), according to how the function was called. (Lua uses the empty string when no other option seems to apply.)
  • nups: the number of upvalues of the function.

lua_gethook

[-0, +0, -]

lua_Hook lua_gethook (lua_State *L);

Returns the current hook function.


lua_gethookcount

[-0, +0, -]

int lua_gethookcount (lua_State *L);

Returns the current hook count.


lua_gethookmask

[-0, +0, -]

int lua_gethookmask (lua_State *L);

Returns the current hook mask.


lua_getinfo

[-(0|1), +(0|1|2), m]

int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);

Returns information about a specific function or function invocation.

To get information about a function invocation, the parameter ar must be a valid activation record that was filled by a previous call to lua_getstack or given as argument to a hook (see lua_Hook).

To get information about a function you push it onto the stack and start the what string with the character '>'. (In that case, lua_getinfo pops the function in the top of the stack.) For instance, to know in which line a function f was defined, you can write the following code:

     lua_Debug ar;
     lua_getfield(L, LUA_GLOBALSINDEX, "f");  /* get global 'f' */
     lua_getinfo(L, ">S", &ar);
     printf("%d\n", ar.linedefined);

Each character in the string what selects some fields of the structure ar to be filled or a value to be pushed on the stack:

  • 'n': fills in the field name and namewhat;
  • 'S': fills in the fields source, short_src, linedefined, lastlinedefined, and what;
  • 'l': fills in the field currentline;
  • 'u': fills in the field nups;
  • 'f': pushes onto the stack the function that is running at the given level;
  • 'L': pushes onto the stack a table whose indices are the numbers of the lines that are valid on the function. (A valid line is a line with some associated code, that is, a line where you can put a break point. Non-valid lines include empty lines and comments.)

This function returns 0 on error (for instance, an invalid option in what).


lua_getlocal

[-0, +(0|1), -]

const char *lua_getlocal (lua_State *L, lua_Debug *ar, int n);

Gets information about a local variable of a given activation record. The parameter ar must be a valid activation record that was filled by a previous call to lua_getstack or given as argument to a hook (see lua_Hook). The index n selects which local variable to inspect (1 is the first parameter or active local variable, and so on, until the last active local variable). lua_getlocal pushes the variable's value onto the stack and returns its name.

Variable names starting with '(' (open parentheses) represent internal variables (loop control variables, temporaries, and C function locals).

Returns NULL (and pushes nothing) when the index is greater than the number of active local variables.


lua_getstack

[-0, +0, -]

int lua_getstack (lua_State *L, int level, lua_Debug *ar);

Get information about the interpreter runtime stack.

This function fills parts of a lua_Debug structure with an identification of the activation record of the function executing at a given level. Level 0 is the current running function, whereas level n+1 is the function that has called level n. When there are no errors, lua_getstack returns 1; when called with a level greater than the stack depth, it returns 0.


lua_getupvalue

[-0, +(0|1), -]

const char *lua_getupvalue (lua_State *L, int funcindex, int n);

Gets information about a closure's upvalue. (For Lua functions, upvalues are the external local variables that the function uses, and that are consequently included in its closure.) lua_getupvalue gets the index n of an upvalue, pushes the upvalue's value onto the stack, and returns its name. funcindex points to the closure in the stack. (Upvalues have no particular order, as they are active through the whole function. So, they are numbered in an arbitrary order.)

Returns NULL (and pushes nothing) when the index is greater than the number of upvalues. For C functions, this function uses the empty string "" as a name for all upvalues.


lua_Hook

typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);

Type for debugging hook functions.

Whenever a hook is called, its ar argument has its field event set to the specific event that triggered the hook. Lua identifies these events with the following constants: LUA_HOOKCALL, LUA_HOOKRET, LUA_HOOKTAILRET, LUA_HOOKLINE, and LUA_HOOKCOUNT. Moreover, for line events, the field currentline is also set. To get the value of any other field in ar, the hook must call lua_getinfo. For return events, event can be LUA_HOOKRET, the normal value, or LUA_HOOKTAILRET. In the latter case, Lua is simulating a return from a function that did a tail call; in this case, it is useless to call lua_getinfo.

While Lua is running a hook, it disables other calls to hooks. Therefore, if a hook calls back Lua to execute a function or a chunk, this execution occurs without any calls to hooks.


lua_sethook

[-0, +0, -]

int lua_sethook (lua_State *L, lua_Hook f, int mask, int count);

Sets the debugging hook function.

Argument f is the hook function. mask specifies on which events the hook will be called: it is formed by a bitwise or of the constants LUA_MASKCALL, LUA_MASKRET, LUA_MASKLINE, and LUA_MASKCOUNT. The count argument is only meaningful when the mask includes LUA_MASKCOUNT. For each event, the hook is called as explained below:

  • The call hook: is called when the interpreter calls a function. The hook is called just after Lua enters the new function, before the function gets its arguments.
  • The return hook: is called when the interpreter returns from a function. The hook is called just before Lua leaves the function. You have no access to the values to be returned by the function.
  • The line hook: is called when the interpreter is about to start the execution of a new line of code, or when it jumps back in the code (even to the same line). (This event only happens while Lua is executing a Lua function.)
  • The count hook: is called after the interpreter executes every count instructions. (This event only happens while Lua is executing a Lua function.)

A hook is disabled by setting mask to zero.


lua_setlocal

[-(0|1), +0, -]

const char *lua_setlocal (lua_State *L, lua_Debug *ar, int n);

Sets the value of a local variable of a given activation record. Parameters ar and n are as in lua_getlocal (see lua_getlocal). lua_setlocal assigns the value at the top of the stack to the variable and returns its name. It also pops the value from the stack.

Returns NULL (and pops nothing) when the index is greater than the number of active local variables.


lua_setupvalue

[-(0|1), +0, -]

const char *lua_setupvalue (lua_State *L, int funcindex, int n);

Sets the value of a closure's upvalue. It assigns the value at the top of the stack to the upvalue and returns its name. It also pops the value from the stack. Parameters funcindex and n are as in the lua_getupvalue (see lua_getupvalue).

Returns NULL (and pops nothing) when the index is greater than the number of upvalues.

4 - The Auxiliary Library

The auxiliary library provides several convenient functions to interface C with Lua. While the basic API provides the primitive functions for all interactions between C and Lua, the auxiliary library provides higher-level functions for some common tasks.

All functions from the auxiliary library are defined in header file lauxlib.h and have a prefix luaL_.

All functions in the auxiliary library are built on top of the basic API, and so they provide nothing that cannot be done with this API.

Several functions in the auxiliary library are used to check C function arguments. Their names are always luaL_check* or luaL_opt*. All of these functions throw an error if the check is not satisfied. Because the error message is formatted for arguments (e.g., "bad argument #1"), you should not use these functions for other stack values.

4.1 - Functions and Types

Here we list all functions and types from the auxiliary library in alphabetical order.


luaL_addchar

[-0, +0, m]

void luaL_addchar (luaL_Buffer *B, char c);

Adds the character c to the buffer B (see luaL_Buffer).


luaL_addlstring

[-0, +0, m]

void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);

Adds the string pointed to by s with length l to the buffer B (see luaL_Buffer). The string may contain embedded zeros.


luaL_addsize

[-0, +0, m]

void luaL_addsize (luaL_Buffer *B, size_t n);

Adds to the buffer B (see luaL_Buffer) a string of length n previously copied to the buffer area (see luaL_prepbuffer).


luaL_addstring

[-0, +0, m]

void luaL_addstring (luaL_Buffer *B, const char *s);

Adds the zero-terminated string pointed to by s to the buffer B (see luaL_Buffer). The string may not contain embedded zeros.


luaL_addvalue

[-1, +0, m]

void luaL_addvalue (luaL_Buffer *B);

Adds the value at the top of the stack to the buffer B (see luaL_Buffer). Pops the value.

This is the only function on string buffers that can (and must) be called with an extra element on the stack, which is the value to be added to the buffer.


luaL_argcheck

[-0, +0, v]

void luaL_argcheck (lua_State *L,
                    int cond,
                    int narg,
                    const char *extramsg);

Checks whether cond is true. If not, raises an error with the following message, where func is retrieved from the call stack:

     bad argument #<narg> to <func> (<extramsg>)

luaL_argerror

[-0, +0, v]

int luaL_argerror (lua_State *L, int narg, const char *extramsg);

Raises an error with the following message, where func is retrieved from the call stack:

     bad argument #<narg> to <func> (<extramsg>)

This function never returns, but it is an idiom to use it in C functions as return luaL_argerror(args).


luaL_Buffer

typedef struct luaL_Buffer luaL_Buffer;

Type for a string buffer.

A string buffer allows C code to build Lua strings piecemeal. Its pattern of use is as follows:

  • First you declare a variable b of type luaL_Buffer.
  • Then you initialize it with a call luaL_buffinit(L, &b).
  • Then you add string pieces to the buffer calling any of the luaL_add* functions.
  • You finish by calling luaL_pushresult(&b). This call leaves the final string on the top of the stack.

During its normal operation, a string buffer uses a variable number of stack slots. So, while using a buffer, you cannot assume that you know where the top of the stack is. You can use the stack between successive calls to buffer operations as long as that use is balanced; that is, when you call a buffer operation, the stack is at the same level it was immediately after the previous buffer operation. (The only exception to this rule is luaL_addvalue.) After calling luaL_pushresult the stack is back to its level when the buffer was initialized, plus the final string on its top.


luaL_buffinit

[-0, +0, -]

void luaL_buffinit (lua_State *L, luaL_Buffer *B);

Initializes a buffer B. This function does not allocate any space; the buffer must be declared as a variable (see luaL_Buffer).


luaL_callmeta

[-0, +(0|1), e]

int luaL_callmeta (lua_State *L, int obj, const char *e);

Calls a metamethod.

If the object at index obj has a metatable and this metatable has a field e, this function calls this field and passes the object as its only argument. In this case this function returns 1 and pushes onto the stack the value returned by the call. If there is no metatable or no metamethod, this function returns 0 (without pushing any value on the stack).


luaL_checkany

[-0, +0, v]

void luaL_checkany (lua_State *L, int narg);

Checks whether the function has an argument of any type (including nil) at position narg.


luaL_checkint

[-0, +0, v]

int luaL_checkint (lua_State *L, int narg);

Checks whether the function argument narg is a number and returns this number cast to an int.


luaL_checkinteger

[-0, +0, v]

lua_Integer luaL_checkinteger (lua_State *L, int narg);

Checks whether the function argument narg is a number and returns this number cast to a lua_Integer.


luaL_checklong

[-0, +0, v]

long luaL_checklong (lua_State *L, int narg);

Checks whether the function argument narg is a number and returns this number cast to a long.


luaL_checklstring

[-0, +0, v]

const char *luaL_checklstring (lua_State *L, int narg, size_t *l);

Checks whether the function argument narg is a string and returns this string; if l is not NULL fills *l with the string's length.

This function uses lua_tolstring to get its result, so all conversions and caveats of that function apply here.


luaL_checknumber

[-0, +0, v]

lua_Number luaL_checknumber (lua_State *L, int narg);

Checks whether the function argument narg is a number and returns this number.


luaL_checkoption

[-0, +0, v]

int luaL_checkoption (lua_State *L,
                      int narg,
                      const char *def,
                      const char *const lst[]);

Checks whether the function argument narg is a string and searches for this string in the array lst (which must be NULL-terminated). Returns the index in the array where the string was found. Raises an error if the argument is not a string or if the string cannot be found.

If def is not NULL, the function uses def as a default value when there is no argument narg or if this argument is nil.

This is a useful function for mapping strings to C enums. (The usual convention in Lua libraries is to use strings instead of numbers to select options.)


luaL_checkstack

[-0, +0, v]

void luaL_checkstack (lua_State *L, int sz, const char *msg);

Grows the stack size to top + sz elements, raising an error if the stack cannot grow to that size. msg is an additional text to go into the error message.


luaL_checkstring

[-0, +0, v]

const char *luaL_checkstring (lua_State *L, int narg);

Checks whether the function argument narg is a string and returns this string.

This function uses lua_tolstring to get its result, so all conversions and caveats of that function apply here.


luaL_checktype

[-0, +0, v]

void luaL_checktype (lua_State *L, int narg, int t);

Checks whether the function argument narg has type t. See lua_type for the encoding of types for t.


luaL_checkudata

[-0, +0, v]

void *luaL_checkudata (lua_State *L, int narg, const char *tname);

Checks whether the function argument narg is a userdata of the type tname (see luaL_newmetatable).


luaL_dofile

[-0, +?, m]

int luaL_dofile (lua_State *L, const char *filename);

Loads and runs the given file. It is defined as the following macro:

     (luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))

It returns 0 if there are no errors or 1 in case of errors.


luaL_dostring

[-0, +?, m]

int luaL_dostring (lua_State *L, const char *str);

Loads and runs the given string. It is defined as the following macro:

     (luaL_loadstring(L, str) || lua_pcall(L, 0, LUA_MULTRET, 0))

It returns 0 if there are no errors or 1 in case of errors.


luaL_error

[-0, +0, v]

int luaL_error (lua_State *L, const char *fmt, ...);

Raises an error. The error message format is given by fmt plus any extra arguments, following the same rules of lua_pushfstring. It also adds at the beginning of the message the file name and the line number where the error occurred, if this information is available.

This function never returns, but it is an idiom to use it in C functions as return luaL_error(args).


luaL_getmetafield

[-0, +(0|1), m]

int luaL_getmetafield (lua_State *L, int obj, const char *e);

Pushes onto the stack the field e from the metatable of the object at index obj. If the object does not have a metatable, or if the metatable does not have this field, returns 0 and pushes nothing.


luaL_getmetatable

[-0, +1, -]

void luaL_getmetatable (lua_State *L, const char *tname);

Pushes onto the stack the metatable associated with name tname in the registry (see luaL_newmetatable).


luaL_gsub

[-0, +1, m]

const char *luaL_gsub (lua_State *L,
                       const char *s,
                       const char *p,
                       const char *r);

Creates a copy of string s by replacing any occurrence of the string p with the string r. Pushes the resulting string on the stack and returns it.


luaL_loadbuffer

[-0, +1, m]

int luaL_loadbuffer (lua_State *L,
                     const char *buff,
                     size_t sz,
                     const char *name);

Loads a buffer as a Lua chunk. This function uses lua_load to load the chunk in the buffer pointed to by buff with size sz.

This function returns the same results as lua_load. name is the chunk name, used for debug information and error messages.


luaL_loadfile

[-0, +1, m]

int luaL_loadfile (lua_State *L, const char *filename);

Loads a file as a Lua chunk. This function uses lua_load to load the chunk in the file named filename. If filename is NULL, then it loads from the standard input. The first line in the file is ignored if it starts with a #.

This function returns the same results as lua_load, but it has an extra error code LUA_ERRFILE if it cannot open/read the file.

As lua_load, this function only loads the chunk; it does not run it.


luaL_loadstring

[-0, +1, m]

int luaL_loadstring (lua_State *L, const char *s);

Loads a string as a Lua chunk. This function uses lua_load to load the chunk in the zero-terminated string s.

This function returns the same results as lua_load.

Also as lua_load, this function only loads the chunk; it does not run it.


luaL_newmetatable

[-0, +1, m]

int luaL_newmetatable (lua_State *L, const char *tname);

If the registry already has the key tname, returns 0. Otherwise, creates a new table to be used as a metatable for userdata, adds it to the registry with key tname, and returns 1.

In both cases pushes onto the stack the final value associated with tname in the registry.


luaL_newstate

[-0, +0, -]

lua_State *luaL_newstate (void);

Creates a new Lua state. It calls lua_newstate with an allocator based on the standard C realloc function and then sets a panic function (see lua_atpanic) that prints an error message to the standard error output in case of fatal errors.

Returns the new state, or NULL if there is a memory allocation error.


luaL_openlibs

[-0, +0, m]

void luaL_openlibs (lua_State *L);

Opens all standard Lua libraries into the given state.


luaL_optint

[-0, +0, v]

int luaL_optint (lua_State *L, int narg, int d);

If the function argument narg is a number, returns this number cast to an int. If this argument is absent or is nil, returns d. Otherwise, raises an error.


luaL_optinteger

[-0, +0, v]

lua_Integer luaL_optinteger (lua_State *L,
                             int narg,
                             lua_Integer d);

If the function argument narg is a number, returns this number cast to a lua_Integer. If this argument is absent or is nil, returns d. Otherwise, raises an error.


luaL_optlong

[-0, +0, v]

long luaL_optlong (lua_State *L, int narg, long d);

If the function argument narg is a number, returns this number cast to a long. If this argument is absent or is nil, returns d. Otherwise, raises an error.


luaL_optlstring

[-0, +0, v]

const char *luaL_optlstring (lua_State *L,
                             int narg,
                             const char *d,
                             size_t *l);

If the function argument narg is a string, returns this string. If this argument is absent or is nil, returns d. Otherwise, raises an error.

If l is not NULL, fills the position *l with the results's length.


luaL_optnumber

[-0, +0, v]

lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number d);

If the function argument narg is a number, returns this number. If this argument is absent or is nil, returns d. Otherwise, raises an error.


luaL_optstring

[-0, +0, v]

const char *luaL_optstring (lua_State *L,
                            int narg,
                            const char *d);

If the function argument narg is a string, returns this string. If this argument is absent or is nil, returns d. Otherwise, raises an error.


luaL_prepbuffer

[-0, +0, -]

char *luaL_prepbuffer (luaL_Buffer *B);

Returns an address to a space of size LUAL_BUFFERSIZE where you can copy a string to be added to buffer B (see luaL_Buffer). After copying the string into this space you must call luaL_addsize with the size of the string to actually add it to the buffer.


luaL_pushresult

[-?, +1, m]

void luaL_pushresult (luaL_Buffer *B);

Finishes the use of buffer B leaving the final string on the top of the stack.


luaL_ref

[-1, +0, m]

int luaL_ref (lua_State *L, int t);

Creates and returns a reference, in the table at index t, for the object at the top of the stack (and pops the object).

A reference is a unique integer key. As long as you do not manually add integer keys into table t, luaL_ref ensures the uniqueness of the key it returns. You can retrieve an object referred by reference r by calling lua_rawgeti(L, t, r). Function luaL_unref frees a reference and its associated object.

If the object at the top of the stack is nil, luaL_ref returns the constant LUA_REFNIL. The constant LUA_NOREF is guaranteed to be different from any reference returned by luaL_ref.


luaL_Reg

typedef struct luaL_Reg {
  const char *name;
  lua_CFunction func;
} luaL_Reg;

Type for arrays of functions to be registered by luaL_register. name is the function name and func is a pointer to the function. Any array of luaL_Reg must end with an sentinel entry in which both name and func are NULL.


luaL_register

[-(0|1), +1, m]

void luaL_register (lua_State *L,
                    const char *libname,
                    const luaL_Reg *l);

Opens a library.

When called with libname equal to NULL, it simply registers all functions in the list l (see luaL_Reg) into the table on the top of the stack.

When called with a non-null libname, luaL_register creates a new table t, sets it as the value of the global variable libname, sets it as the value of package.loaded[libname], and registers on it all functions in the list l. If there is a table in package.loaded[libname] or in variable libname, reuses this table instead of creating a new one.

In any case the function leaves the table on the top of the stack.


luaL_typename

[-0, +0, -]

const char *luaL_typename (lua_State *L, int index);

Returns the name of the type of the value at the given index.


luaL_typerror

[-0, +0, v]

int luaL_typerror (lua_State *L, int narg, const char *tname);

Generates an error with a message like the following:

     location: bad argument narg to 'func' (tname expected, got rt)

where location is produced by luaL_where, func is the name of the current function, and rt is the type name of the actual argument.


luaL_unref

[-0, +0, -]

void luaL_unref (lua_State *L, int t, int ref);

Releases reference ref from the table at index t (see luaL_ref). The entry is removed from the table, so that the referred object can be collected. The reference ref is also freed to be used again.

If ref is LUA_NOREF or LUA_REFNIL, luaL_unref does nothing.


luaL_where

[-0, +1, m]

void luaL_where (lua_State *L, int lvl);

Pushes onto the stack a string identifying the current position of the control at level lvl in the call stack. Typically this string has the following format:

     chunkname:currentline:

Level 0 is the running function, level 1 is the function that called the running function, etc.

This function is used to build a prefix for error messages.

5 - Standard Libraries

The standard Lua libraries provide useful functions that are implemented directly through the C API. Some of these functions provide essential services to the language (e.g., type and getmetatable); others provide access to "outside" services (e.g., I/O); and others could be implemented in Lua itself, but are quite useful or have critical performance requirements that deserve an implementation in C (e.g., table.sort).

All libraries are implemented through the official C API and are provided as separate C modules. Currently, Lua has the following standard libraries:

  • basic library, which includes the coroutine sub-library;
  • package library;
  • string manipulation;
  • table manipulation;
  • mathematical functions (sin, log, etc.);
  • input and output;
  • operating system facilities;
  • debug facilities.

Except for the basic and package libraries, each library provides all its functions as fields of a global table or as methods of its objects.

To have access to these libraries, the C host program should call the luaL_openlibs function, which opens all standard libraries. Alternatively, it can open them individually by calling luaopen_base (for the basic library), luaopen_package (for the package library), luaopen_string (for the string library), luaopen_table (for the table library), luaopen_math (for the mathematical library), luaopen_io (for the I/O library), luaopen_os (for the Operating System library), and luaopen_debug (for the debug library). These functions are declared in lualib.h and should not be called directly: you must call them like any other Lua C function, e.g., by using lua_call.

5.1 - Basic Functions

The basic library provides some core functions to Lua. If you do not include this library in your application, you should check carefully whether you need to provide implementations for some of its facilities.


assert (v [, message])

Issues an error when the value of its argument v is false (i.e., nil or false); otherwise, returns all its arguments. message is an error message; when absent, it defaults to "assertion failed!"


collectgarbage ([opt [, arg]])

This function is a generic interface to the garbage collector. It performs different functions according to its first argument, opt:

  • "collect": performs a full garbage-collection cycle. This is the default option.
  • "stop": stops the garbage collector.
  • "restart": restarts the garbage collector.
  • "count": returns the total memory in use by Lua (in Kbytes).
  • "step": performs a garbage-collection step. The step "size" is controlled by arg (larger values mean more steps) in a non-specified way. If you want to control the step size you must experimentally tune the value of arg. Returns true if the step finished a collection cycle.
  • "setpause": sets arg as the new value for the pause of the collector (see §2.10). Returns the previous value for pause.
  • "setstepmul": sets arg as the new value for the step multiplier of the collector (see §2.10). Returns the previous value for step.


dofile ([filename])

Opens the named file and executes its contents as a Lua chunk. When called without arguments, dofile executes the contents of the standard input (stdin). Returns all values returned by the chunk. In case of errors, dofile propagates the error to its caller (that is, dofile does not run in protected mode).


error (message [, level])

Terminates the last protected function called and returns message as the error message. Function error never returns.

Usually, error adds some information about the error position at the beginning of the message. The level argument specifies how to get the error position. With level 1 (the default), the error position is where the error function was called. Level 2 points the error to where the function that called error was called; and so on. Passing a level 0 avoids the addition of error position information to the message.


_G

A global variable (not a function) that holds the global environment (that is, _G._G = _G). Lua itself does not use this variable; changing its value does not affect any environment, nor vice-versa. (Use setfenv to change environments.)


getfenv ([f])

Returns the current environment in use by the function. f can be a Lua function or a number that specifies the function at that stack level: Level 1 is the function calling getfenv. If the given function is not a Lua function, or if f is 0, getfenv returns the global environment. The default for f is 1.


getmetatable (object)

If object does not have a metatable, returns nil. Otherwise, if the object's metatable has a "__metatable" field, returns the associated value. Otherwise, returns the metatable of the given object.


ipairs (t)

Returns three values: an iterator function, the table t, and 0, so that the construction

     for i,v in ipairs(t) do body end

will iterate over the pairs (1,t[1]), (2,t[2]), ···, up to the first integer key absent from the table.


load (func [, chunkname])

Loads a chunk using function func to get its pieces. Each call to func must return a string that concatenates with previous results. A return of an empty string, nil, or no value signals the end of the chunk.

If there are no errors, returns the compiled chunk as a function; otherwise, returns nil plus the error message. The environment of the returned function is the global environment.

chunkname is used as the chunk name for error messages and debug information. When absent, it defaults to "=(load)".


loadfile ([filename])

Similar to load, but gets the chunk from file filename or from the standard input, if no file name is given.


loadstring (string [, chunkname])

Similar to load, but gets the chunk from the given string.

To load and run a given string, use the idiom

     assert(loadstring(s))()

When absent, chunkname defaults to the given string.


next (table [, index])

Allows a program to traverse all fields of a table. Its first argument is a table and its second argument is an index in this table. next returns the next index of the table and its associated value. When called with nil as its second argument, next returns an initial index and its associated value. When called with the last index, or with nil in an empty table, next returns nil. If the second argument is absent, then it is interpreted as nil. In particular, you can use next(t) to check whether a table is empty.

The order in which the indices are enumerated is not specified, even for numeric indices. (To traverse a table in numeric order, use a numerical for or the ipairs function.)

The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table. You may however modify existing fields. In particular, you may clear existing fields.


pairs (t)

Returns three values: the next function, the table t, and nil, so that the construction

     for k,v in pairs(t) do body end

will iterate over all key–value pairs of table t.

See function next for the caveats of modifying the table during its traversal.


pcall (f, arg1, ···)

Calls function f with the given arguments in protected mode. This means that any error inside f is not propagated; instead, pcall catches the error and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In such case, pcall also returns all results from the call, after this first result. In case of any error, pcall returns false plus the error message.


print (···)

Receives any number of arguments, and prints their values to stdout, using the tostring function to convert them to strings. print is not intended for formatted output, but only as a quick way to show a value, typically for debugging. For formatted output, use string.format.


rawequal (v1, v2)

Checks whether v1 is equal to v2, without invoking any metamethod. Returns a boolean.


rawget (table, index)

Gets the real value of table[index], without invoking any metamethod. table must be a table; index may be any value.


rawset (table, index, value)

Sets the real value of table[index] to value, without invoking any metamethod. table must be a table, index any value different from nil, and value any Lua value.

This function returns table.


select (index, ···)

If index is a number, returns all arguments after argument number index. Otherwise, index must be the string "#", and select returns the total number of extra arguments it received.


setfenv (f, table)

Sets the environment to be used by the given function. f can be a Lua function or a number that specifies the function at that stack level: Level 1 is the function calling setfenv. setfenv returns the given function.

As a special case, when f is 0 setfenv changes the environment of the running thread. In this case, setfenv returns no values.


setmetatable (table, metatable)

Sets the metatable for the given table. (You cannot change the metatable of other types from Lua, only from C.) If metatable is nil, removes the metatable of the given table. If the original metatable has a "__metatable" field, raises an error.

This function returns table.


tonumber (e [, base])

Tries to convert its argument to a number. If the argument is already a number or a string convertible to a number, then tonumber returns this number; otherwise, it returns nil.

An optional argument specifies the base to interpret the numeral. The base may be any integer between 2 and 36, inclusive. In bases above 10, the letter 'A' (in either upper or lower case) represents 10, 'B' represents 11, and so forth, with 'Z' representing 35. In base 10 (the default), the number can have a decimal part, as well as an optional exponent part (see §2.1). In other bases, only unsigned integers are accepted.


tostring (e)

Receives an argument of any type and converts it to a string in a reasonable format. For complete control of how numbers are converted, use string.format.

If the metatable of e has a "__tostring" field, then tostring calls the corresponding value with e as argument, and uses the result of the call as its result.


type (v)

Returns the type of its only argument, coded as a string. The possible results of this function are "nil" (a string, not the value nil), "number", "string", "boolean", "table", "function", "thread", and "userdata".


unpack (list [, i [, j]])

Returns the elements from the given table. This function is equivalent to
     return list[i], list[i+1], ···, list[j]

except that the above code can be written only for a fixed number of elements. By default, i is 1 and j is the length of the list, as defined by the length operator (see §2.5.5).


_VERSION

A global variable (not a function) that holds a string containing the current interpreter version. The current contents of this variable is "Lua 5.1".


xpcall (f, err)

This function is similar to pcall, except that you can set a new error handler.

xpcall calls function f in protected mode, using err as the error handler. Any error inside f is not propagated; instead, xpcall catches the error, calls the err function with the original error object, and returns a status code. Its first result is the status code (a boolean), which is true if the call succeeds without errors. In this case, xpcall also returns all results from the call, after this first result. In case of any error, xpcall returns false plus the result from err.

5.2 - Coroutine Manipulation

The operations related to coroutines comprise a sub-library of the basic library and come inside the table coroutine. See §2.11 for a general description of coroutines.


coroutine.create (f)

Creates a new coroutine, with body f. f must be a Lua function. Returns this new coroutine, an object with type "thread".


coroutine.resume (co [, val1, ···])

Starts or continues the execution of coroutine co. The first time you resume a coroutine, it starts running its body. The values val1, ··· are passed as the arguments to the body function. If the coroutine has yielded, resume restarts it; the values val1, ··· are passed as the results from the yield.

If the coroutine runs without any errors, resume returns true plus any values passed to yield (if the coroutine yields) or any values returned by the body function (if the coroutine terminates). If there is any error, resume returns false plus the error message.


coroutine.running ()

Returns the running coroutine, or nil when called by the main thread.


coroutine.status (co)

Returns the status of coroutine co, as a string: "running", if the coroutine is running (that is, it called status); "suspended", if the coroutine is suspended in a call to yield, or if it has not started running yet; "normal" if the coroutine is active but not running (that is, it has resumed another coroutine); and "dead" if the coroutine has finished its body function, or if it has stopped with an error.


coroutine.wrap (f)

Creates a new coroutine, with body f. f must be a Lua function. Returns a function that resumes the coroutine each time it is called. Any arguments passed to the function behave as the extra arguments to resume. Returns the same values returned by resume, except the first boolean. In case of error, propagates the error.


coroutine.yield (···)

Suspends the execution of the calling coroutine. The coroutine cannot be running a C function, a metamethod, or an iterator. Any arguments to yield are passed as extra results to resume.

5.3 - Modules

The package library provides basic facilities for loading and building modules in Lua. It exports two of its functions directly in the global environment: require and module. Everything else is exported in a table package.


module (name [, ···])

Creates a module. If there is a table in package.loaded[name], this table is the module. Otherwise, if there is a global table t with the given name, this table is the module. Otherwise creates a new table t and sets it as the value of the global name and the value of package.loaded[name]. This function also initializes t._NAME with the given name, t._M with the module (t itself), and t._PACKAGE with the package name (the full module name minus last component; see below). Finally, module sets t as the new environment of the current function and the new value of package.loaded[name], so that require returns t.

If name is a compound name (that is, one with components separated by dots), module creates (or reuses, if they already exist) tables for each component. For instance, if name is a.b.c, then module stores the module table in field c of field b of global a.

This function can receive optional options after the module name, where each option is a function to be applied over the module.


require (modname)

Loads the given module. The function starts by looking into the package.loaded table to determine whether modname is already loaded. If it is, then require returns the value stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module.

To find a loader, require is guided by the package.loaders array. By changing this array, we can change how require looks for a module. The following explanation is based on the default configuration for package.loaders.

First require queries package.preload[modname]. If it has a value, this value (which should be a function) is the loader. Otherwise require searches for a Lua loader using the path stored in package.path. If that also fails, it searches for a C loader using the path stored in package.cpath. If that also fails, it tries an all-in-one loader (see package.loaders).

Once a loader is found, require calls the loader with a single argument, modname. If the loader returns any value, require assigns the returned value to package.loaded[modname]. If the loader returns no value and has not assigned any value to package.loaded[modname], then require assigns true to this entry. In any case, require returns the final value of package.loaded[modname].

If there is any error loading or running the module, or if it cannot find any loader for the module, then require signals an error.


package.cpath

The path used by require to search for a C loader.

Lua initializes the C path package.cpath in the same way it initializes the Lua path package.path, using the environment variable LUA_CPATH or a default path defined in luaconf.h.


package.loaded

A table used by require to control which modules are already loaded. When you require a module modname and package.loaded[modname] is not false, require simply returns the value stored there.


package.loaders

A table used by require to control how to load modules.

Each entry in this table is a searcher function. When looking for a module, require calls each of these searchers in ascending order, with the module name (the argument given to require) as its sole parameter. The function can return another function (the module loader) or a string explaining why it did not find that module (or nil if it has nothing to say). Lua initializes this table with four functions.

The first searcher simply looks for a loader in the package.preload table.

The second searcher looks for a loader as a Lua library, using the path stored at package.path. A path is a sequence of templates separated by semicolons. For each template, the searcher will change each interrogation mark in the template by filename, which is the module name with each dot replaced by a "directory separator" (such as "/" in Unix); then it will try to open the resulting file name. So, for instance, if the Lua path is the string

     "./?.lua;./?.lc;/usr/local/?/init.lua"

the search for a Lua file for module foo will try to open the files ./foo.lua, ./foo.lc, and /usr/local/foo/init.lua, in that order.

The third searcher looks for a loader as a C library, using the path given by the variable package.cpath. For instance, if the C path is the string

     "./?.so;./?.dll;/usr/local/?/init.so"

the searcher for module foo will try to open the files ./foo.so, ./foo.dll, and /usr/local/foo/init.so, in that order. Once it finds a C library, this searcher first uses a dynamic link facility to link the application with the library. Then it tries to find a C function inside the library to be used as the loader. The name of this C function is the string "luaopen_" concatenated with a copy of the module name where each dot is replaced by an underscore. Moreover, if the module name has a hyphen, its prefix up to (and including) the first hyphen is removed. For instance, if the module name is a.v1-b.c, the function name will be luaopen_b_c.

The fourth searcher tries an all-in-one loader. It searches the C path for a library for the root name of the given module. For instance, when requiring a.b.c, it will search for a C library for a. If found, it looks into it for an open function for the submodule; in our example, that would be luaopen_a_b_c. With this facility, a package can pack several C submodules into one single library, with each submodule keeping its original open function.


package.loadlib (libname, funcname)

Dynamically links the host program with the C library libname. Inside this library, looks for a function funcname and returns this function as a C function. (So, funcname must follow the protocol (see lua_CFunction)).

This is a low-level function. It completely bypasses the package and module system. Unlike require, it does not perform any path searching and does not automatically adds extensions. libname must be the complete file name of the C library, including if necessary a path and extension. funcname must be the exact name exported by the C library (which may depend on the C compiler and linker used).

This function is not supported by ANSI C. As such, it is only available on some platforms (Windows, Linux, Mac OS X, Solaris, BSD, plus other Unix systems that support the dlfcn standard).


package.path

The path used by require to search for a Lua loader.

At start-up, Lua initializes this variable with the value of the environment variable LUA_PATH or with a default path defined in luaconf.h, if the environment variable is not defined. Any ";;" in the value of the environment variable is replaced by the default path.


package.preload

A table to store loaders for specific modules (see require).


package.seeall (module)

Sets a metatable for module with its __index field referring to the global environment, so that this module inherits values from the global environment. To be used as an option to function module.

5.4 - String Manipulation

This library provides generic functions for string manipulation, such as finding and extracting substrings, and pattern matching. When indexing a string in Lua, the first character is at position 1 (not at 0, as in C). Indices are allowed to be negative and are interpreted as indexing backwards, from the end of the string. Thus, the last character is at position -1, and so on.

The string library provides all its functions inside the table string. It also sets a metatable for strings where the __index field points to the string table. Therefore, you can use the string functions in object-oriented style. For instance, string.byte(s, i) can be written as s:byte(i).

The string library assumes one-byte character encodings.


string.byte (s [, i [, j]])

Returns the internal numerical codes of the characters s[i], s[i+1], ···, s[j]. The default value for i is 1; the default value for j is i.

Note that numerical codes are not necessarily portable across platforms.


string.char (···)

Receives zero or more integers. Returns a string with length equal to the number of arguments, in which each character has the internal numerical code equal to its corresponding argument.

Note that numerical codes are not necessarily portable across platforms.


string.dump (function)

Returns a string containing a binary representation of the given function, so that a later loadstring on this string returns a copy of the function. function must be a Lua function without upvalues.


string.find (s, pattern [, init [, plain]])

Looks for the first match of pattern in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain "find substring" operation, with no characters in pattern being considered "magic". Note that if plain is given, then init must be given as well.

If the pattern has captures, then in a successful match the captured values are also returned, after the two indices.


string.format (formatstring, ···)

Returns a formatted version of its variable number of arguments following the description given in its first argument (which must be a string). The format string follows the same rules as the printf family of standard C functions. The only differences are that the options/modifiers *, l, L, n, p, and h are not supported and that there is an extra option, q. The q option formats a string in a form suitable to be safely read back by the Lua interpreter: the string is written between double quotes, and all double quotes, newlines, embedded zeros, and backslashes in the string are correctly escaped when written. For instance, the call
     string.format('%q', 'a string with "quotes" and \n new line')

will produce the string:

     "a string with \"quotes\" and \
      new line"

The options c, d, E, e, f, g, G, i, o, u, X, and x all expect a number as argument, whereas q and s expect a string.

This function does not accept string values containing embedded zeros, except as arguments to the q option.


string.gmatch (s, pattern)

Returns an iterator function that, each time it is called, returns the next captures from pattern over string s. If pattern specifies no captures, then the whole match is produced in each call.

As an example, the following loop

     s = "hello world from Lua"
     for w in string.gmatch(s, "%a+") do
       print(w)
     end

will iterate over all the words from string s, printing one per line. The next example collects all pairs key=value from the given string into a table:

     t = {}
     s = "from=world, to=Lua"
     for k, v in string.gmatch(s, "(%w+)=(%w+)") do
       t[k] = v
     end

For this function, a '^' at the start of a pattern does not work as an anchor, as this would prevent the iteration.


string.gsub (s, pattern, repl [, n])

Returns a copy of s in which all (or the first n, if given) occurrences of the pattern have been replaced by a replacement string specified by repl, which can be a string, a table, or a function. gsub also returns, as its second value, the total number of matches that occurred.

If repl is a string, then its value is used for replacement. The character % works as an escape character: any sequence in repl of the form %n, with n between 1 and 9, stands for the value of the n-th captured substring (see below). The sequence %0 stands for the whole match. The sequence %% stands for a single %.

If repl is a table, then the table is queried for every match, using the first capture as the key; if the pattern specifies no captures, then the whole match is used as the key.

If repl is a function, then this function is called every time a match occurs, with all captured substrings passed as arguments, in order; if the pattern specifies no captures, then the whole match is passed as a sole argument.

If the value returned by the table query or by the function call is a string or a number, then it is used as the replacement string; otherwise, if it is false or nil, then there is no replacement (that is, the original match is kept in the string).

Here are some examples:

     x = string.gsub("hello world", "(%w+)", "%1 %1")
     --> x="hello hello world world"
     
     x = string.gsub("hello world", "%w+", "%0 %0", 1)
     --> x="hello hello world"
     
     x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
     --> x="world hello Lua from"
     
     x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
     --> x="home = /home/roberto, user = roberto"
     
     x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
           return loadstring(s)()
         end)
     --> x="4+5 = 9"
     
     local t = {name="lua", version="5.1"}
     x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
     --> x="lua-5.1.tar.gz"


string.len (s)

Receives a string and returns its length. The empty string "" has length 0. Embedded zeros are counted, so "a\000bc\000" has length 5.


string.lower (s)

Receives a string and returns a copy of this string with all uppercase letters changed to lowercase. All other characters are left unchanged. The definition of what an uppercase letter is depends on the current locale.


string.match (s, pattern [, init])

Looks for the first match of pattern in the string s. If it finds one, then match returns the captures from the pattern; otherwise it returns nil. If pattern specifies no captures, then the whole match is returned. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative.


string.rep (s, n)

Returns a string that is the concatenation of n copies of the string s.


string.reverse (s)

Returns a string that is the string s reversed.


string.sub (s, i [, j])

Returns the substring of s that starts at i and continues until j; i and j can be negative. If j is absent, then it is assumed to be equal to -1 (which is the same as the string length). In particular, the call string.sub(s,1,j) returns a prefix of s with length j, and string.sub(s, -i) returns a suffix of s with length i.


string.upper (s)

Receives a string and returns a copy of this string with all lowercase letters changed to uppercase. All other characters are left unchanged. The definition of what a lowercase letter is depends on the current locale.

5.4.1 - Patterns

Character Class:

A character class is used to represent a set of characters. The following combinations are allowed in describing a character class:

  • x: (where x is not one of the magic characters ^$()%.[]*+-?) represents the character x itself.
  • .: (a dot) represents all characters.
  • %a: represents all letters.
  • %c: represents all control characters.
  • %d: represents all digits.
  • %l: represents all lowercase letters.
  • %p: represents all punctuation characters.
  • %s: represents all space characters.
  • %u: represents all uppercase letters.
  • %w: represents all alphanumeric characters.
  • %x: represents all hexadecimal digits.
  • %z: represents the character with representation 0.
  • %x: (where x is any non-alphanumeric character) represents the character x. This is the standard way to escape the magic characters. Any punctuation character (even the non magic) can be preceded by a '%' when used to represent itself in a pattern.
  • [set]: represents the class which is the union of all characters in set. A range of characters can be specified by separating the end characters of the range with a '-'. All classes %x described above can also be used as components in set. All other characters in set represent themselves. For example, [%w_] (or [_%w]) represents all alphanumeric characters plus the underscore, [0-7] represents the octal digits, and [0-7%l%-] represents the octal digits plus the lowercase letters plus the '-' character.

    The interaction between ranges and classes is not defined. Therefore, patterns like [%a-z] or [a-%%] have no meaning.

  • [^set]: represents the complement of set, where set is interpreted as above.

For all classes represented by single letters (%a, %c, etc.), the corresponding uppercase letter represents the complement of the class. For instance, %S represents all non-space characters.

The definitions of letter, space, and other character groups depend on the current locale. In particular, the class [a-z] may not be equivalent to %l.

Pattern Item:

A pattern item can be

  • a single character class, which matches any single character in the class;
  • a single character class followed by '*', which matches 0 or more repetitions of characters in the class. These repetition items will always match the longest possible sequence;
  • a single character class followed by '+', which matches 1 or more repetitions of characters in the class. These repetition items will always match the longest possible sequence;
  • a single character class followed by '-', which also matches 0 or more repetitions of characters in the class. Unlike '*', these repetition items will always match the shortest possible sequence;
  • a single character class followed by '?', which matches 0 or 1 occurrence of a character in the class;
  • %n, for n between 1 and 9; such item matches a substring equal to the n-th captured string (see below);
  • %bxy, where x and y are two distinct characters; such item matches strings that start with x, end with y, and where the x and y are balanced. This means that, if one reads the string from left to right, counting +1 for an x and -1 for a y, the ending y is the first y where the count reaches 0. For instance, the item %b() matches expressions with balanced parentheses.

Pattern:

A pattern is a sequence of pattern items. A '^' at the beginning of a pattern anchors the match at the beginning of the subject string. A '$' at the end of a pattern anchors the match at the end of the subject string. At other positions, '^' and '$' have no special meaning and represent themselves.

Captures:

A pattern can contain sub-patterns enclosed in parentheses; they describe captures. When a match succeeds, the substrings of the subject string that match captures are stored (captured) for future use. Captures are numbered according to their left parentheses. For instance, in the pattern "(a*(.)%w(%s*))", the part of the string matching "a*(.)%w(%s*)" is stored as the first capture (and therefore has number 1); the character matching "." is captured with number 2, and the part matching "%s*" has number 3.

As a special case, the empty capture () captures the current string position (a number). For instance, if we apply the pattern "()aa()" on the string "flaaap", there will be two captures: 3 and 5.

A pattern cannot contain embedded zeros. Use %z instead.

5.5 - Table Manipulation

This library provides generic functions for table manipulation. It provides all its functions inside the table table.

Most functions in the table library assume that the table represents an array or a list. For these functions, when we talk about the "length" of a table we mean the result of the length operator.


table.concat (table [, sep [, i [, j]]])

Given an array where all elements are strings or numbers, returns table[i]..sep..table[i+1] ··· sep..table[j]. The default value for sep is the empty string, the default for i is 1, and the default for j is the length of the table. If i is greater than j, returns the empty string.


table.insert (table, [pos,] value)

Inserts element value at position pos in table, shifting up other elements to open space, if necessary. The default value for pos is n+1, where n is the length of the table (see §2.5.5), so that a call table.insert(t,x) inserts x at the end of table t.


table.maxn (table)

Returns the largest positive numerical index of the given table, or zero if the table has no positive numerical indices. (To do its job this function does a linear traversal of the whole table.)


table.remove (table [, pos])

Removes from table the element at position pos, shifting down other elements to close the space, if necessary. Returns the value of the removed element. The default value for pos is n, where n is the length of the table, so that a call table.remove(t) removes the last element of table t.


table.sort (table [, comp])

Sorts table elements in a given order, in-place, from table[1] to table[n], where n is the length of the table. If comp is given, then it must be a function that receives two table elements, and returns true when the first is less than the second (so that not comp(a[i+1],a[i]) will be true after the sort). If comp is not given, then the standard Lua operator < is used instead.

The sort algorithm is not stable; that is, elements considered equal by the given order may have their relative positions changed by the sort.

5.6 - Mathematical Functions

This library is an interface to the standard C math library. It provides all its functions inside the table math.


math.abs (x)

Returns the absolute value of x.


math.acos (x)

Returns the arc cosine of x (in radians).


math.asin (x)

Returns the arc sine of x (in radians).


math.atan (x)

Returns the arc tangent of x (in radians).


math.atan2 (y, x)

Returns the arc tangent of y/x (in radians), but uses the signs of both parameters to find the quadrant of the result. (It also handles correctly the case of x being zero.)


math.ceil (x)

Returns the smallest integer larger than or equal to x.


math.cos (x)

Returns the cosine of x (assumed to be in radians).


math.cosh (x)

Returns the hyperbolic cosine of x.


math.deg (x)

Returns the angle x (given in radians) in degrees.


math.exp (x)

Returns the value ex.


math.floor (x)

Returns the largest integer smaller than or equal to x.


math.fmod (x, y)

Returns the remainder of the division of x by y that rounds the quotient towards zero.


math.frexp (x)

Returns m and e such that x = m2e, e is an integer and the absolute value of m is in the range [0.5, 1) (or zero when x is zero).


math.huge

The value HUGE_VAL, a value larger than or equal to any other numerical value.


math.ldexp (m, e)

Returns m2e (e should be an integer).


math.log (x)

Returns the natural logarithm of x.


math.log10 (x)

Returns the base-10 logarithm of x.


math.max (x, ···)

Returns the maximum value among its arguments.


math.min (x, ···)

Returns the minimum value among its arguments.


math.modf (x)

Returns two numbers, the integral part of x and the fractional part of x.


math.pi

The value of pi.


math.pow (x, y)

Returns xy. (You can also use the expression x^y to compute this value.)


math.rad (x)

Returns the angle x (given in degrees) in radians.


math.random ([m [, n]])

This function is an interface to the simple pseudo-random generator function rand provided by ANSI C. (No guarantees can be given for its statistical properties.)

When called without arguments, returns a uniform pseudo-random real number in the range [0,1). When called with an integer number m, math.random returns a uniform pseudo-random integer in the range [1, m]. When called with two integer numbers m and n, math.random returns a uniform pseudo-random integer in the range [m, n].


math.randomseed (x)

Sets x as the "seed" for the pseudo-random generator: equal seeds produce equal sequences of numbers.


math.sin (x)

Returns the sine of x (assumed to be in radians).


math.sinh (x)

Returns the hyperbolic sine of x.


math.sqrt (x)

Returns the square root of x. (You can also use the expression x^0.5 to compute this value.)


math.tan (x)

Returns the tangent of x (assumed to be in radians).


math.tanh (x)

Returns the hyperbolic tangent of x.

5.7 - Input and Output Facilities

The I/O library provides two different styles for file manipulation. The first one uses implicit file descriptors; that is, there are operations to set a default input file and a default output file, and all input/output operations are over these default files. The second style uses explicit file descriptors.

When using implicit file descriptors, all operations are supplied by table io. When using explicit file descriptors, the operation io.open returns a file descriptor and then all operations are supplied as methods of the file descriptor.

The table io also provides three predefined file descriptors with their usual meanings from C: io.stdin, io.stdout, and io.stderr. The I/O library never closes these files.

Unless otherwise stated, all I/O functions return nil on failure (plus an error message as a second result and a system-dependent error code as a third result) and some value different from nil on success.


io.close ([file])

Equivalent to file:close(). Without a file, closes the default output file.


io.flush ()

Equivalent to file:flush over the default output file.


io.input ([file])

When called with a file name, it opens the named file (in text mode), and sets its handle as the default input file. When called with a file handle, it simply sets this file handle as the default input file. When called without parameters, it returns the current default input file.

In case of errors this function raises the error, instead of returning an error code.


io.lines ([filename])

Opens the given file name in read mode and returns an iterator function that, each time it is called, returns a new line from the file. Therefore, the construction

     for line in io.lines(filename) do body end

will iterate over all lines of the file. When the iterator function detects the end of file, it returns nil (to finish the loop) and automatically closes the file.

The call io.lines() (with no file name) is equivalent to io.input():lines(); that is, it iterates over the lines of the default input file. In this case it does not close the file when the loop ends.


io.open (filename [, mode])

This function opens a file, in the mode specified in the string mode. It returns a new file handle, or, in case of errors, nil plus an error message.

The mode string can be any of the following:

  • "r": read mode (the default);
  • "w": write mode;
  • "a": append mode;
  • "r+": update mode, all previous data is preserved;
  • "w+": update mode, all previous data is erased;
  • "a+": append update mode, previous data is preserved, writing is only allowed at the end of file.

The mode string can also have a 'b' at the end, which is needed in some systems to open the file in binary mode. This string is exactly what is used in the standard C function fopen.


io.output ([file])

Similar to io.input, but operates over the default output file.


io.popen (prog [, mode])

Starts program prog in a separated process and returns a file handle that you can use to read data from this program (if mode is "r", the default) or to write data to this program (if mode is "w").

This function is system dependent and is not available on all platforms.


io.read (···)

Equivalent to io.input():read.


io.tmpfile ()

Returns a handle for a temporary file. This file is opened in update mode and it is automatically removed when the program ends.


io.type (obj)

Checks whether obj is a valid file handle. Returns the string "file" if obj is an open file handle, "closed file" if obj is a closed file handle, or nil if obj is not a file handle.


io.write (···)

Equivalent to io.output():write.


file:close ()

Closes file. Note that files are automatically closed when their handles are garbage collected, but that takes an unpredictable amount of time to happen.


file:flush ()

Saves any written data to file.


file:lines ()

Returns an iterator function that, each time it is called, returns a new line from the file. Therefore, the construction

     for line in file:lines() do body end

will iterate over all lines of the file. (Unlike io.lines, this function does not close the file when the loop ends.)


file:read (···)

Reads the file file, according to the given formats, which specify what to read. For each format, the function returns a string (or a number) with the characters read, or nil if it cannot read data with the specified format. When called without formats, it uses a default format that reads the entire next line (see below).

The available formats are

  • "*n": reads a number; this is the only format that returns a number instead of a string.
  • "*a": reads the whole file, starting at the current position. On end of file, it returns the empty string.
  • "*l": reads the next line (skipping the end of line), returning nil on end of file. This is the default format.
  • number: reads a string with up to this number of characters, returning nil on end of file. If number is zero, it reads nothing and returns an empty string, or nil on end of file.


file:seek ([whence] [, offset])

Sets and gets the file position, measured from the beginning of the file, to the position given by offset plus a base specified by the string whence, as follows:

  • "set": base is position 0 (beginning of the file);
  • "cur": base is current position;
  • "end": base is end of file;

In case of success, function seek returns the final file position, measured in bytes from the beginning of the file. If this function fails, it returns nil, plus a string describing the error.

The default value for whence is "cur", and for offset is 0. Therefore, the call file:seek() returns the current file position, without changing it; the call file:seek("set") sets the position to the beginning of the file (and returns 0); and the call file:seek("end") sets the position to the end of the file, and returns its size.


file:setvbuf (mode [, size])

Sets the buffering mode for an output file. There are three available modes:

  • "no": no buffering; the result of any output operation appears immediately.
  • "full": full buffering; output operation is performed only when the buffer is full (or when you explicitly flush the file (see io.flush)).
  • "line": line buffering; output is buffered until a newline is output or there is any input from some special files (such as a terminal device).

For the last two cases, size specifies the size of the buffer, in bytes. The default is an appropriate size.


file:write (···)

Writes the value of each of its arguments to the file. The arguments must be strings or numbers. To write other values, use tostring or string.format before write.

5.8 - Operating System Facilities

This library is implemented through table os.


os.clock ()

Returns an approximation of the amount in seconds of CPU time used by the program.


os.date ([format [, time]])

Returns a string or a table containing date and time, formatted according to the given string format.

If the time argument is present, this is the time to be formatted (see the os.time function for a description of this value). Otherwise, date formats the current time.

If format starts with '!', then the date is formatted in Coordinated Universal Time. After this optional character, if format is the string "*t", then date returns a table with the following fields: year (four digits), month (1--12), day (1--31), hour (0--23), min (0--59), sec (0--61), wday (weekday, Sunday is 1), yday (day of the year), and isdst (daylight saving flag, a boolean).

If format is not "*t", then date returns the date as a string, formatted according to the same rules as the C function strftime.

When called without arguments, date returns a reasonable date and time representation that depends on the host system and on the current locale (that is, os.date() is equivalent to os.date("%c")).


os.difftime (t2, t1)

Returns the number of seconds from time t1 to time t2. In POSIX, Windows, and some other systems, this value is exactly t2-t1.


os.execute ([command])

This function is equivalent to the C function system. It passes command to be executed by an operating system shell. It returns a status code, which is system-dependent. If command is absent, then it returns nonzero if a shell is available and zero otherwise.


os.exit ([code])

Calls the C function exit, with an optional code, to terminate the host program. The default value for code is the success code.


os.getenv (varname)

Returns the value of the process environment variable varname, or nil if the variable is not defined.


os.remove (filename)

Deletes the file or directory with the given name. Directories must be empty to be removed. If this function fails, it returns nil, plus a string describing the error.


os.rename (oldname, newname)

Renames file or directory named oldname to newname. If this function fails, it returns nil, plus a string describing the error.


os.setlocale (locale [, category])

Sets the current locale of the program. locale is a string specifying a locale; category is an optional string describing which category to change: "all", "collate", "ctype", "monetary", "numeric", or "time"; the default category is "all". The function returns the name of the new locale, or nil if the request cannot be honored.

If locale is the empty string, the current locale is set to an implementation-defined native locale. If locale is the string "C", the current locale is set to the standard C locale.

When called with nil as the first argument, this function only returns the name of the current locale for the given category.


os.time ([table])

Returns the current time when called without arguments, or a time representing the date and time specified by the given table. This table must have fields year, month, and day, and may have fields hour, min, sec, and isdst (for a description of these fields, see the os.date function).

The returned value is a number, whose meaning depends on your system. In POSIX, Windows, and some other systems, this number counts the number of seconds since some given start time (the "epoch"). In other systems, the meaning is not specified, and the number returned by time can be used only as an argument to date and difftime.


os.tmpname ()

Returns a string with a file name that can be used for a temporary file. The file must be explicitly opened before its use and explicitly removed when no longer needed.

On some systems (POSIX), this function also creates a file with that name, to avoid security risks. (Someone else might create the file with wrong permissions in the time between getting the name and creating the file.) You still have to open the file to use it and to remove it (even if you do not use it).

When possible, you may prefer to use io.tmpfile, which automatically removes the file when the program ends.

5.9 - The Debug Library

This library provides the functionality of the debug interface to Lua programs. You should exert care when using this library. The functions provided here should be used exclusively for debugging and similar tasks, such as profiling. Please resist the temptation to use them as a usual programming tool: they can be very slow. Moreover, several of these functions violate some assumptions about Lua code (e.g., that variables local to a function cannot be accessed from outside or that userdata metatables cannot be changed by Lua code) and therefore can compromise otherwise secure code.

All functions in this library are provided inside the debug table. All functions that operate over a thread have an optional first argument which is the thread to operate over. The default is always the current thread.


debug.debug ()

Enters an interactive mode with the user, running each string that the user enters. Using simple commands and other debug facilities, the user can inspect global and local variables, change their values, evaluate expressions, and so on. A line containing only the word cont finishes this function, so that the caller continues its execution.

Note that commands for debug.debug are not lexically nested within any function, and so have no direct access to local variables.


debug.getfenv (o)

Returns the environment of object o.


debug.gethook ([thread])

Returns the current hook settings of the thread, as three values: the current hook function, the current hook mask, and the current hook count (as set by the debug.sethook function).


debug.getinfo ([thread,] function [, what])

Returns a table with information about a function. You can give the function directly, or you can give a number as the value of function, which means the function running at level function of the call stack of the given thread: level 0 is the current function (getinfo itself); level 1 is the function that called getinfo; and so on. If function is a number larger than the number of active functions, then getinfo returns nil.

The returned table can contain all the fields returned by lua_getinfo, with the string what describing which fields to fill in. The default for what is to get all information available, except the table of valid lines. If present, the option 'f' adds a field named func with the function itself. If present, the option 'L' adds a field named activelines with the table of valid lines.

For instance, the expression debug.getinfo(1,"n").name returns a table with a name for the current function, if a reasonable name can be found, and the expression debug.getinfo(print) returns a table with all available information about the print function.


debug.getlocal ([thread,] level, local)

This function returns the name and the value of the local variable with index local of the function at level level of the stack. (The first parameter or local variable has index 1, and so on, until the last active local variable.) The function returns nil if there is no local variable with the given index, and raises an error when called with a level out of range. (You can call debug.getinfo to check whether the level is valid.)

Variable names starting with '(' (open parentheses) represent internal variables (loop control variables, temporaries, and C function locals).


debug.getmetatable (object)

Returns the metatable of the given object or nil if it does not have a metatable.


debug.getregistry ()

Returns the registry table (see §3.5).


debug.getupvalue (func, up)

This function returns the name and the value of the upvalue with index up of the function func. The function returns nil if there is no upvalue with the given index.


debug.setfenv (object, table)

Sets the environment of the given object to the given table. Returns object.


debug.sethook ([thread,] hook, mask [, count])

Sets the given function as a hook. The string mask and the number count describe when the hook will be called. The string mask may have the following characters, with the given meaning:

  • "c": the hook is called every time Lua calls a function;
  • "r": the hook is called every time Lua returns from a function;
  • "l": the hook is called every time Lua enters a new line of code.

With a count different from zero, the hook is called after every count instructions.

When called without arguments, debug.sethook turns off the hook.

When the hook is called, its first parameter is a string describing the event that has triggered its call: "call", "return" (or "tail return", when simulating a return from a tail call), "line", and "count". For line events, the hook also gets the new line number as its second parameter. Inside a hook, you can call getinfo with level 2 to get more information about the running function (level 0 is the getinfo function, and level 1 is the hook function), unless the event is "tail return". In this case, Lua is only simulating the return, and a call to getinfo will return invalid data.


debug.setlocal ([thread,] level, local, value)

This function assigns the value value to the local variable with index local of the function at level level of the stack. The function returns nil if there is no local variable with the given index, and raises an error when called with a level out of range. (You can call getinfo to check whether the level is valid.) Otherwise, it returns the name of the local variable.


debug.setmetatable (object, table)

Sets the metatable for the given object to the given table (which can be nil).


debug.setupvalue (func, up, value)

This function assigns the value value to the upvalue with index up of the function func. The function returns nil if there is no upvalue with the given index. Otherwise, it returns the name of the upvalue.


debug.traceback ([thread,] [message [, level]])

Returns a string with a traceback of the call stack. An optional message string is appended at the beginning of the traceback. An optional level number tells at which level to start the traceback (default is 1, the function calling traceback).

6 - Lua Stand-alone

Although Lua has been designed as an extension language, to be embedded in a host C program, it is also frequently used as a stand-alone language. An interpreter for Lua as a stand-alone language, called simply lua, is provided with the standard distribution. The stand-alone interpreter includes all standard libraries, including the debug library. Its usage is:

     lua [options] [script [args]]

The options are:

  • -e stat: executes string stat;
  • -l mod: "requires" mod;
  • -i: enters interactive mode after running script;
  • -v: prints version information;
  • --: stops handling options;
  • -: executes stdin as a file and stops handling options.

After handling its options, lua runs the given script, passing to it the given args as string arguments. When called without arguments, lua behaves as lua -v -i when the standard input (stdin) is a terminal, and as lua - otherwise.

Before running any argument, the interpreter checks for an environment variable LUA_INIT. If its format is @filename, then lua executes the file. Otherwise, lua executes the string itself.

All options are handled in order, except -i. For instance, an invocation like

     $ lua -e'a=1' -e 'print(a)' script.lua

will first set a to 1, then print the value of a (which is '1'), and finally run the file script.lua with no arguments. (Here $ is the shell prompt. Your prompt may be different.)

Before starting to run the script, lua collects all arguments in the command line in a global table called arg. The script name is stored at index 0, the first argument after the script name goes to index 1, and so on. Any arguments before the script name (that is, the interpreter name plus the options) go to negative indices. For instance, in the call

     $ lua -la b.lua t1 t2

the interpreter first runs the file a.lua, then creates a table

     arg = { [-2] = "lua", [-1] = "-la",
             [0] = "b.lua",
             [1] = "t1", [2] = "t2" }

and finally runs the file b.lua. The script is called with arg[1], arg[2], ··· as arguments; it can also access these arguments with the vararg expression '...'.

In interactive mode, if you write an incomplete statement, the interpreter waits for its completion by issuing a different prompt.

If the global variable _PROMPT contains a string, then its value is used as the prompt. Similarly, if the global variable _PROMPT2 contains a string, its value is used as the secondary prompt (issued during incomplete statements). Therefore, both prompts can be changed directly on the command line or in any Lua programs by assigning to _PROMPT. See the next example:

     $ lua -e"_PROMPT='myprompt> '" -i

(The outer pair of quotes is for the shell, the inner pair is for Lua.) Note the use of -i to enter interactive mode; otherwise, the program would just end silently right after the assignment to _PROMPT.

To allow the use of Lua as a script interpreter in Unix systems, the stand-alone interpreter skips the first line of a chunk if it starts with #. Therefore, Lua scripts can be made into executable programs by using chmod +x and the #! form, as in

     #!/usr/local/bin/lua

(Of course, the location of the Lua interpreter may be different in your machine. If lua is in your PATH, then

     #!/usr/bin/env lua

is a more portable solution.)

7 - Incompatibilities with the Previous Version

Here we list the incompatibilities that you may find when moving a program from Lua 5.0 to Lua 5.1. You can avoid most of the incompatibilities compiling Lua with appropriate options (see file luaconf.h). However, all these compatibility options will be removed in the next version of Lua.

7.1 - Changes in the Language

  • The vararg system changed from the pseudo-argument arg with a table with the extra arguments to the vararg expression. (See compile-time option LUA_COMPAT_VARARG in luaconf.h.)
  • There was a subtle change in the scope of the implicit variables of the for statement and for the repeat statement.
  • The long string/long comment syntax ([[string]]) does not allow nesting. You can use the new syntax ([=[string]=]) in these cases. (See compile-time option LUA_COMPAT_LSTR in luaconf.h.)

7.2 - Changes in the Libraries

  • Function string.gfind was renamed string.gmatch. (See compile-time option LUA_COMPAT_GFIND in luaconf.h.)
  • When string.gsub is called with a function as its third argument, whenever this function returns nil or false the replacement string is the whole match, instead of the empty string.
  • Function table.setn was deprecated. Function table.getn corresponds to the new length operator (#); use the operator instead of the function. (See compile-time option LUA_COMPAT_GETN in luaconf.h.)
  • Function loadlib was renamed package.loadlib. (See compile-time option LUA_COMPAT_LOADLIB in luaconf.h.)
  • Function math.mod was renamed math.fmod. (See compile-time option LUA_COMPAT_MOD in luaconf.h.)
  • Functions table.foreach and table.foreachi are deprecated. You can use a for loop with pairs or ipairs instead.
  • There were substantial changes in function require due to the new module system. However, the new behavior is mostly compatible with the old, but require gets the path from package.path instead of from LUA_PATH.
  • Function collectgarbage has different arguments. Function gcinfo is deprecated; use collectgarbage("count") instead.

7.3 - Changes in the API

  • The luaopen_* functions (to open libraries) cannot be called directly, like a regular C function. They must be called through Lua, like a Lua function.
  • Function lua_open was replaced by lua_newstate to allow the user to set a memory-allocation function. You can use luaL_newstate from the standard library to create a state with a standard allocation function (based on realloc).
  • Functions luaL_getn and luaL_setn (from the auxiliary library) are deprecated. Use lua_objlen instead of luaL_getn and nothing instead of luaL_setn.
  • Function luaL_openlib was replaced by luaL_register.
  • Function luaL_checkudata now throws an error when the given value is not a userdata of the expected type. (In Lua 5.0 it returned NULL.)

8 - The Complete Syntax of Lua

Here is the complete syntax of Lua in extended BNF. (It does not describe operator precedences.)


	chunk ::= {stat [`;´]} [laststat [`;´]]

	block ::= chunk

	stat ::=  varlist `=´ explist | 
		 functioncall | 
		 do block end | 
		 while exp do block end | 
		 repeat block until exp | 
		 if exp then block {elseif exp then block} [else block] end | 
		 for Name `=´ exp `,´ exp [`,´ exp] do block end | 
		 for namelist in explist do block end | 
		 function funcname funcbody | 
		 local function Name funcbody | 
		 local namelist [`=´ explist] 

	laststat ::= return [explist] | break

	funcname ::= Name {`.´ Name} [`:´ Name]

	varlist ::= var {`,´ var}

	var ::=  Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name 

	namelist ::= Name {`,´ Name}

	explist ::= {exp `,´} exp

	exp ::=  nil | false | true | Number | String | `...´ | function | 
		 prefixexp | tableconstructor | exp binop exp | unop exp 

	prefixexp ::= var | functioncall | `(´ exp `)´

	functioncall ::=  prefixexp args | prefixexp `:´ Name args 

	args ::=  `(´ [explist] `)´ | tableconstructor | String 

	function ::= function funcbody

	funcbody ::= `(´ [parlist] `)´ block end

	parlist ::= namelist [`,´ `...´] | `...´

	tableconstructor ::= `{´ [fieldlist] `}´

	fieldlist ::= field {fieldsep field} [fieldsep]

	field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp

	fieldsep ::= `,´ | `;´

	binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 
		 `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 
		 and | or

	unop ::= `-´ | not | `#´


Last update: Mon Feb 13 18:54:19 BRST 2012 redis-8.0.2/deps/lua/doc/readme.html000066400000000000000000000015021501533116600172320ustar00rootroot00000000000000 Lua documentation

Lua Documentation

This is the documentation included in the source distribution of Lua 5.1.5. Lua's official web site contains updated documentation, especially the reference manual.


Last update: Fri Feb 3 09:44:42 BRST 2012 redis-8.0.2/deps/lua/etc/000077500000000000000000000000001501533116600151175ustar00rootroot00000000000000redis-8.0.2/deps/lua/etc/Makefile000066400000000000000000000016201501533116600165560ustar00rootroot00000000000000# makefile for Lua etc TOP= .. LIB= $(TOP)/src INC= $(TOP)/src BIN= $(TOP)/src SRC= $(TOP)/src TST= $(TOP)/test CC= gcc CFLAGS= -O2 -Wall -I$(INC) $(MYCFLAGS) MYCFLAGS= MYLDFLAGS= -Wl,-E MYLIBS= -lm #MYLIBS= -lm -Wl,-E -ldl -lreadline -lhistory -lncurses RM= rm -f default: @echo 'Please choose a target: min noparser one strict clean' min: min.c $(CC) $(CFLAGS) $@.c -L$(LIB) -llua $(MYLIBS) echo 'print"Hello there!"' | ./a.out noparser: noparser.o $(CC) noparser.o $(SRC)/lua.o -L$(LIB) -llua $(MYLIBS) $(BIN)/luac $(TST)/hello.lua -./a.out luac.out -./a.out -e'a=1' one: $(CC) $(CFLAGS) all.c $(MYLIBS) ./a.out $(TST)/hello.lua strict: -$(BIN)/lua -e 'print(a);b=2' -$(BIN)/lua -lstrict -e 'print(a)' -$(BIN)/lua -e 'function f() b=2 end f()' -$(BIN)/lua -lstrict -e 'function f() b=2 end f()' clean: $(RM) a.out core core.* *.o luac.out .PHONY: default min noparser one strict clean redis-8.0.2/deps/lua/etc/README000066400000000000000000000017141501533116600160020ustar00rootroot00000000000000This directory contains some useful files and code. Unlike the code in ../src, everything here is in the public domain. If any of the makes fail, you're probably not using the same libraries used to build Lua. Set MYLIBS in Makefile accordingly. all.c Full Lua interpreter in a single file. Do "make one" for a demo. lua.hpp Lua header files for C++ using 'extern "C"'. lua.ico A Lua icon for Windows (and web sites: save as favicon.ico). Drawn by hand by Markus Gritsch . lua.pc pkg-config data for Lua luavs.bat Script to build Lua under "Visual Studio .NET Command Prompt". Run it from the toplevel as etc\luavs.bat. min.c A minimal Lua interpreter. Good for learning and for starting your own. Do "make min" for a demo. noparser.c Linking with noparser.o avoids loading the parsing modules in lualib.a. Do "make noparser" for a demo. strict.lua Traps uses of undeclared global variables. Do "make strict" for a demo. redis-8.0.2/deps/lua/etc/all.c000066400000000000000000000012461501533116600160360ustar00rootroot00000000000000/* * all.c -- Lua core, libraries and interpreter in a single file */ #define luaall_c #include "lapi.c" #include "lcode.c" #include "ldebug.c" #include "ldo.c" #include "ldump.c" #include "lfunc.c" #include "lgc.c" #include "llex.c" #include "lmem.c" #include "lobject.c" #include "lopcodes.c" #include "lparser.c" #include "lstate.c" #include "lstring.c" #include "ltable.c" #include "ltm.c" #include "lundump.c" #include "lvm.c" #include "lzio.c" #include "lauxlib.c" #include "lbaselib.c" #include "ldblib.c" #include "liolib.c" #include "linit.c" #include "lmathlib.c" #include "loadlib.c" #include "loslib.c" #include "lstrlib.c" #include "ltablib.c" #include "lua.c" redis-8.0.2/deps/lua/etc/lua.hpp000066400000000000000000000002771501533116600164170ustar00rootroot00000000000000// lua.hpp // Lua header files for C++ // <> not supplied automatically because Lua also compiles as C++ extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" } redis-8.0.2/deps/lua/etc/lua.ico000066400000000000000000000020661501533116600164000ustar00rootroot00000000000000 è&(( @€€€€€€€€€ÀÀÀ€€€ÿÿÿÿÿÿÿÿÿÿÿÿ„DDH€DDDDDD€DDDDDDDDp„DDDDDDDDHDDDDDDDDDD€tDDDDDDDDDDGDDDDDDDDDDDDDÿÿHÿ„ÿ‡D€DˆˆOx„øˆøD@„DDDO„O„øDøDH„DDDO„O„O÷øDHDDDDO„O„DHøDDDDDDO„O„÷ˆøDDDDDDO„O„ÿtDDDDDDDDDDDDDDDDDDDDDDDˆ„DDDDDDDDDDD‡ÿ÷„DD„DDDDDDDÿÿtDH„DDDDDDHÿÿÿøDHDDDDDDHÿÿÿøD@DDDDDDHÿÿÿøD€DDDDDDDÿÿtDtDDDDDD‡ÿ÷„GDDDDDDDˆ„D€wp„DDDDDDDDHxDHpDDDDDDDDp„DD€DDDDDD€DDDG„DDH€DDDGDDDG„DD€xDHpwpÿ€ÿþÿøÿðÿàÀ?À?€€€€À?À?àGðøþÿ€ÿÿþÿÿÿÿÿÿÿÿÿÇ( €€€€€€€€€€ÀÀÀ€€€ÿÿÿÿÿÿÿÿÿÿÿÿ„DH„DDDHDDDDD€DDDDD@„DDDDDHDDDDDDDDDDDDDDDDDDDDDDDDDøD„DDDÿÿHDDDÿÿ@DDDø€„DDDHtG„DHDDDDtGð?DDÀÿô€€DDDDÿ÷DDDHÿ÷€€DDÀDHð0ÿ÷ÿðÿð°redis-8.0.2/deps/lua/etc/lua.pc000066400000000000000000000012221501533116600162210ustar00rootroot00000000000000# lua.pc -- pkg-config data for Lua # vars from install Makefile # grep '^V=' ../Makefile V= 5.1 # grep '^R=' ../Makefile R= 5.1.5 # grep '^INSTALL_.*=' ../Makefile | sed 's/INSTALL_TOP/prefix/' prefix= /usr/local INSTALL_BIN= ${prefix}/bin INSTALL_INC= ${prefix}/include INSTALL_LIB= ${prefix}/lib INSTALL_MAN= ${prefix}/man/man1 INSTALL_LMOD= ${prefix}/share/lua/${V} INSTALL_CMOD= ${prefix}/lib/lua/${V} # canonical vars exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include Name: Lua Description: An Extensible Extension Language Version: ${R} Requires: Libs: -L${libdir} -llua -lm Cflags: -I${includedir} # (end of lua.pc) redis-8.0.2/deps/lua/etc/luavs.bat000066400000000000000000000020561501533116600167440ustar00rootroot00000000000000@rem Script to build Lua under "Visual Studio .NET Command Prompt". @rem Do not run from this directory; run it from the toplevel: etc\luavs.bat . @rem It creates lua51.dll, lua51.lib, lua.exe, and luac.exe in src. @rem (contributed by David Manura and Mike Pall) @setlocal @set MYCOMPILE=cl /nologo /MD /O2 /W3 /c /D_CRT_SECURE_NO_DEPRECATE @set MYLINK=link /nologo @set MYMT=mt /nologo cd src %MYCOMPILE% /DLUA_BUILD_AS_DLL l*.c del lua.obj luac.obj %MYLINK% /DLL /out:lua51.dll l*.obj if exist lua51.dll.manifest^ %MYMT% -manifest lua51.dll.manifest -outputresource:lua51.dll;2 %MYCOMPILE% /DLUA_BUILD_AS_DLL lua.c %MYLINK% /out:lua.exe lua.obj lua51.lib if exist lua.exe.manifest^ %MYMT% -manifest lua.exe.manifest -outputresource:lua.exe %MYCOMPILE% l*.c print.c del lua.obj linit.obj lbaselib.obj ldblib.obj liolib.obj lmathlib.obj^ loslib.obj ltablib.obj lstrlib.obj loadlib.obj %MYLINK% /out:luac.exe *.obj if exist luac.exe.manifest^ %MYMT% -manifest luac.exe.manifest -outputresource:luac.exe del *.obj *.manifest cd .. redis-8.0.2/deps/lua/etc/min.c000066400000000000000000000014401501533116600160450ustar00rootroot00000000000000/* * min.c -- a minimal Lua interpreter * loads stdin only with minimal error handling. * no interaction, and no standard library, only a "print" function. */ #include #include "lua.h" #include "lauxlib.h" static int print(lua_State *L) { int n=lua_gettop(L); int i; for (i=1; i<=n; i++) { if (i>1) printf("\t"); if (lua_isstring(L,i)) printf("%s",lua_tostring(L,i)); else if (lua_isnil(L,i)) printf("%s","nil"); else if (lua_isboolean(L,i)) printf("%s",lua_toboolean(L,i) ? "true" : "false"); else printf("%s:%p",luaL_typename(L,i),lua_topointer(L,i)); } printf("\n"); return 0; } int main(void) { lua_State *L=lua_open(); lua_register(L,"print",print); if (luaL_dofile(L,NULL)!=0) fprintf(stderr,"%s\n",lua_tostring(L,-1)); lua_close(L); return 0; } redis-8.0.2/deps/lua/etc/noparser.c000066400000000000000000000023451501533116600171200ustar00rootroot00000000000000/* * The code below can be used to make a Lua core that does not contain the * parsing modules (lcode, llex, lparser), which represent 35% of the total core. * You'll only be able to load binary files and strings, precompiled with luac. * (Of course, you'll have to build luac with the original parsing modules!) * * To use this module, simply compile it ("make noparser" does that) and list * its object file before the Lua libraries. The linker should then not load * the parsing modules. To try it, do "make luab". * * If you also want to avoid the dump module (ldump.o), define NODUMP. * #define NODUMP */ #define LUA_CORE #include "llex.h" #include "lparser.h" #include "lzio.h" LUAI_FUNC void luaX_init (lua_State *L) { UNUSED(L); } LUAI_FUNC Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) { UNUSED(z); UNUSED(buff); UNUSED(name); lua_pushliteral(L,"parser not loaded"); lua_error(L); return NULL; } #ifdef NODUMP #include "lundump.h" LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) { UNUSED(f); UNUSED(w); UNUSED(data); UNUSED(strip); #if 1 UNUSED(L); return 0; #else lua_pushliteral(L,"dumper not loaded"); lua_error(L); #endif } #endif redis-8.0.2/deps/lua/etc/strict.lua000066400000000000000000000016401501533116600171330ustar00rootroot00000000000000-- -- strict.lua -- checks uses of undeclared global variables -- All global variables must be 'declared' through a regular assignment -- (even assigning nil will do) in a main chunk before being used -- anywhere or assigned to inside a function. -- local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget local mt = getmetatable(_G) if mt == nil then mt = {} setmetatable(_G, mt) end mt.__declared = {} local function what () local d = getinfo(3, "S") return d and d.what or "C" end mt.__newindex = function (t, n, v) if not mt.__declared[n] then local w = what() if w ~= "main" and w ~= "C" then error("assign to undeclared variable '"..n.."'", 2) end mt.__declared[n] = true end rawset(t, n, v) end mt.__index = function (t, n) if not mt.__declared[n] and what() ~= "C" then error("variable '"..n.."' is not declared", 2) end return rawget(t, n) end redis-8.0.2/deps/lua/src/000077500000000000000000000000001501533116600151335ustar00rootroot00000000000000redis-8.0.2/deps/lua/src/Makefile000066400000000000000000000140751501533116600166020ustar00rootroot00000000000000# makefile for building Lua # see ../INSTALL for installation instructions # see ../Makefile and luaconf.h for further customization # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= # Your platform. See PLATS for possible values. PLAT= none CC?= gcc CFLAGS= -O2 -Wall $(MYCFLAGS) AR= ar rcu RANLIB= ranlib RM= rm -f LIBS= -lm $(MYLIBS) MYCFLAGS= MYLDFLAGS= MYLIBS= # == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE ========= PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris LUA_A= liblua.a CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \ lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \ lundump.o lvm.o lzio.o strbuf.o fpconv.o LIB_O= lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o \ lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o \ lua_bit.o LUA_T= lua LUA_O= lua.o LUAC_T= luac LUAC_O= luac.o print.o ALL_O= $(CORE_O) $(LIB_O) $(LUA_O) $(LUAC_O) ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) ALL_A= $(LUA_A) default: $(PLAT) all: $(ALL_T) o: $(ALL_O) a: $(ALL_A) $(LUA_A): $(CORE_O) $(LIB_O) $(AR) $@ $(CORE_O) $(LIB_O) # DLL needs all object files $(RANLIB) $@ $(LUA_T): $(LUA_O) $(LUA_A) $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) $(LUAC_T): $(LUAC_O) $(LUA_A) $(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) clean: $(RM) $(ALL_T) $(ALL_O) depend: @$(CC) $(CFLAGS) -MM l*.c print.c echo: @echo "PLAT = $(PLAT)" @echo "CC = $(CC)" @echo "CFLAGS = $(CFLAGS)" @echo "AR = $(AR)" @echo "RANLIB = $(RANLIB)" @echo "RM = $(RM)" @echo "MYCFLAGS = $(MYCFLAGS)" @echo "MYLDFLAGS = $(MYLDFLAGS)" @echo "MYLIBS = $(MYLIBS)" # convenience targets for popular platforms none: @echo "Please choose a platform:" @echo " $(PLATS)" aix: $(MAKE) all CC="xlc" CFLAGS="-O2 -DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl" MYLDFLAGS="-brtl -bexpall" ansi: $(MAKE) all MYCFLAGS=-DLUA_ANSI bsd: $(MAKE) all MYCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-Wl,-E" freebsd: $(MAKE) all MYCFLAGS="-DLUA_USE_LINUX" MYLIBS="-Wl,-E -lreadline" generic: $(MAKE) all MYCFLAGS= linux: $(MAKE) all MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E -ldl -lreadline -lhistory -lncurses" macosx: $(MAKE) all MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-lreadline" # use this on Mac OS X 10.3- # $(MAKE) all MYCFLAGS=-DLUA_USE_MACOSX mingw: $(MAKE) "LUA_A=lua51.dll" "LUA_T=lua.exe" \ "AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \ "MYCFLAGS=-DLUA_BUILD_AS_DLL" "MYLIBS=" "MYLDFLAGS=-s" lua.exe $(MAKE) "LUAC_T=luac.exe" luac.exe posix: $(MAKE) all MYCFLAGS=-DLUA_USE_POSIX solaris: $(MAKE) all MYCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl" # list targets that do not create files (but not all makes understand .PHONY) .PHONY: all $(PLATS) default o a clean depend echo none # DO NOT DELETE lapi.o: lapi.c lua.h luaconf.h lapi.h lobject.h llimits.h ldebug.h \ lstate.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h \ lundump.h lvm.h lauxlib.o: lauxlib.c lua.h luaconf.h lauxlib.h lbaselib.o: lbaselib.c lua.h luaconf.h lauxlib.h lualib.h lcode.o: lcode.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lgc.h \ ltable.h ldblib.o: ldblib.c lua.h luaconf.h lauxlib.h lualib.h ldebug.o: ldebug.c lua.h luaconf.h lapi.h lobject.h llimits.h lcode.h \ llex.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h \ lfunc.h lstring.h lgc.h ltable.h lvm.h ldo.o: ldo.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lparser.h lstring.h \ ltable.h lundump.h lvm.h ldump.o: ldump.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h \ lzio.h lmem.h lundump.h lfunc.o: lfunc.c lua.h luaconf.h lfunc.h lobject.h llimits.h lgc.h lmem.h \ lstate.h ltm.h lzio.h lgc.o: lgc.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h linit.o: linit.c lua.h luaconf.h lualib.h lauxlib.h liolib.o: liolib.c lua.h luaconf.h lauxlib.h lualib.h llex.o: llex.c lua.h luaconf.h ldo.h lobject.h llimits.h lstate.h ltm.h \ lzio.h lmem.h llex.h lparser.h lstring.h lgc.h ltable.h lmathlib.o: lmathlib.c lua.h luaconf.h lauxlib.h lualib.h lmem.o: lmem.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ ltm.h lzio.h lmem.h ldo.h loadlib.o: loadlib.c lua.h luaconf.h lauxlib.h lualib.h lobject.o: lobject.c lua.h luaconf.h ldo.h lobject.h llimits.h lstate.h \ ltm.h lzio.h lmem.h lstring.h lgc.h lvm.h lopcodes.o: lopcodes.c lopcodes.h llimits.h lua.h luaconf.h loslib.o: loslib.c lua.h luaconf.h lauxlib.h lualib.h lparser.o: lparser.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h \ lfunc.h lstring.h lgc.h ltable.h lstate.o: lstate.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h llex.h lstring.h ltable.h lstring.o: lstring.c lua.h luaconf.h lmem.h llimits.h lobject.h lstate.h \ ltm.h lzio.h lstring.h lgc.h lstrlib.o: lstrlib.c lua.h luaconf.h lauxlib.h lualib.h ltable.o: ltable.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ ltm.h lzio.h lmem.h ldo.h lgc.h ltable.h ltablib.o: ltablib.c lua.h luaconf.h lauxlib.h lualib.h ltm.o: ltm.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h lzio.h \ lmem.h lstring.h lgc.h ltable.h lua.o: lua.c lua.h luaconf.h lauxlib.h lualib.h luac.o: luac.c lua.h luaconf.h lauxlib.h ldo.h lobject.h llimits.h \ lstate.h ltm.h lzio.h lmem.h lfunc.h lopcodes.h lstring.h lgc.h \ lundump.h lundump.o: lundump.c lua.h luaconf.h ldebug.h lstate.h lobject.h \ llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lstring.h lgc.h lundump.h lvm.o: lvm.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h ltable.h lvm.h lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \ lzio.h print.o: print.c ldebug.h lstate.h lua.h luaconf.h lobject.h llimits.h \ ltm.h lzio.h lmem.h lopcodes.h lundump.h # (end of Makefile) redis-8.0.2/deps/lua/src/fpconv.c000066400000000000000000000136501501533116600165770ustar00rootroot00000000000000/* fpconv - Floating point conversion routines * * Copyright (c) 2011-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries * with locale support will break when the decimal separator is a comma. * * fpconv_* will around these issues with a translation buffer if required. */ #include #include #include #include #include "fpconv.h" /* Lua CJSON assumes the locale is the same for all threads within a * process and doesn't change after initialisation. * * This avoids the need for per thread storage or expensive checks * for call. */ static char locale_decimal_point = '.'; /* In theory multibyte decimal_points are possible, but * Lua CJSON only supports UTF-8 and known locales only have * single byte decimal points ([.,]). * * localconv() may not be thread safe (=>crash), and nl_langinfo() is * not supported on some platforms. Use sprintf() instead - if the * locale does change, at least Lua CJSON won't crash. */ static void fpconv_update_locale() { char buf[8]; snprintf(buf, sizeof(buf), "%g", 0.5); /* Failing this test might imply the platform has a buggy dtoa * implementation or wide characters */ if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) { fprintf(stderr, "Error: wide characters found or printf() bug."); abort(); } locale_decimal_point = buf[1]; } /* Check for a valid number character: [-+0-9a-yA-Y.] * Eg: -0.6e+5, infinity, 0xF0.F0pF0 * * Used to find the probable end of a number. It doesn't matter if * invalid characters are counted - strtod() will find the valid * number if it exists. The risk is that slightly more memory might * be allocated before a parse error occurs. */ static inline int valid_number_character(char ch) { char lower_ch; if ('0' <= ch && ch <= '9') return 1; if (ch == '-' || ch == '+' || ch == '.') return 1; /* Hex digits, exponent (e), base (p), "infinity",.. */ lower_ch = ch | 0x20; if ('a' <= lower_ch && lower_ch <= 'y') return 1; return 0; } /* Calculate the size of the buffer required for a strtod locale * conversion. */ static int strtod_buffer_size(const char *s) { const char *p = s; while (valid_number_character(*p)) p++; return p - s; } /* Similar to strtod(), but must be passed the current locale's decimal point * character. Guaranteed to be called at the start of any valid number in a string */ double fpconv_strtod(const char *nptr, char **endptr) { char localbuf[FPCONV_G_FMT_BUFSIZE]; char *buf, *endbuf, *dp; int buflen; double value; /* System strtod() is fine when decimal point is '.' */ if (locale_decimal_point == '.') return strtod(nptr, endptr); buflen = strtod_buffer_size(nptr); if (!buflen) { /* No valid characters found, standard strtod() return */ *endptr = (char *)nptr; return 0; } /* Duplicate number into buffer */ if (buflen >= FPCONV_G_FMT_BUFSIZE) { /* Handle unusually large numbers */ buf = malloc(buflen + 1); if (!buf) { fprintf(stderr, "Out of memory"); abort(); } } else { /* This is the common case.. */ buf = localbuf; } memcpy(buf, nptr, buflen); buf[buflen] = 0; /* Update decimal point character if found */ dp = strchr(buf, '.'); if (dp) *dp = locale_decimal_point; value = strtod(buf, &endbuf); *endptr = (char *)&nptr[endbuf - buf]; if (buflen >= FPCONV_G_FMT_BUFSIZE) free(buf); return value; } /* "fmt" must point to a buffer of at least 6 characters */ static void set_number_format(char *fmt, int precision) { int d1, d2, i; assert(1 <= precision && precision <= 14); /* Create printf format (%.14g) from precision */ d1 = precision / 10; d2 = precision % 10; fmt[0] = '%'; fmt[1] = '.'; i = 2; if (d1) { fmt[i++] = '0' + d1; } fmt[i++] = '0' + d2; fmt[i++] = 'g'; fmt[i] = 0; } /* Assumes there is always at least 32 characters available in the target buffer */ int fpconv_g_fmt(char *str, double num, int precision) { char buf[FPCONV_G_FMT_BUFSIZE]; char fmt[6]; int len; char *b; set_number_format(fmt, precision); /* Pass through when decimal point character is dot. */ if (locale_decimal_point == '.') return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num); /* snprintf() to a buffer then translate for other decimal point characters */ len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num); /* Copy into target location. Translate decimal point if required */ b = buf; do { *str++ = (*b == locale_decimal_point ? '.' : *b); } while(*b++); return len; } void fpconv_init() { fpconv_update_locale(); } /* vi:ai et sw=4 ts=4: */ redis-8.0.2/deps/lua/src/fpconv.h000066400000000000000000000010061501533116600165740ustar00rootroot00000000000000/* Lua CJSON floating point conversion routines */ /* Buffer required to store the largest string representation of a double. * * Longest double printed with %.14g is 21 characters long: * -1.7976931348623e+308 */ # define FPCONV_G_FMT_BUFSIZE 32 #ifdef USE_INTERNAL_FPCONV static inline void fpconv_init() { /* Do nothing - not required */ } #else extern void fpconv_init(); #endif extern int fpconv_g_fmt(char*, double, int); extern double fpconv_strtod(const char*, char**); /* vi:ai et sw=4 ts=4: */ redis-8.0.2/deps/lua/src/lapi.c000066400000000000000000000556421501533116600162400ustar00rootroot00000000000000/* ** $Id: lapi.c,v 2.55.1.5 2008/07/04 18:41:18 roberto Exp $ ** Lua API ** See Copyright Notice in lua.h */ #include #include #include #include #define lapi_c #define LUA_CORE #include "lua.h" #include "lapi.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lundump.h" #include "lvm.h" const char lua_ident[] = "$Lua: " LUA_RELEASE " " LUA_COPYRIGHT " $\n" "$Authors: " LUA_AUTHORS " $\n" "$URL: www.lua.org $\n"; #define api_checknelems(L, n) api_check(L, (n) <= (L->top - L->base)) #define api_checkvalidindex(L, i) api_check(L, (i) != luaO_nilobject) #define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;} static TValue *index2adr (lua_State *L, int idx) { if (idx > 0) { TValue *o = L->base + (idx - 1); api_check(L, idx <= L->ci->top - L->base); if (o >= L->top) return cast(TValue *, luaO_nilobject); else return o; } else if (idx > LUA_REGISTRYINDEX) { api_check(L, idx != 0 && -idx <= L->top - L->base); return L->top + idx; } else switch (idx) { /* pseudo-indices */ case LUA_REGISTRYINDEX: return registry(L); case LUA_ENVIRONINDEX: { Closure *func = curr_func(L); sethvalue(L, &L->env, func->c.env); return &L->env; } case LUA_GLOBALSINDEX: return gt(L); default: { Closure *func = curr_func(L); idx = LUA_GLOBALSINDEX - idx; return (idx <= func->c.nupvalues) ? &func->c.upvalue[idx-1] : cast(TValue *, luaO_nilobject); } } } static Table *getcurrenv (lua_State *L) { if (L->ci == L->base_ci) /* no enclosing function? */ return hvalue(gt(L)); /* use global table as environment */ else { Closure *func = curr_func(L); return func->c.env; } } void luaA_pushobject (lua_State *L, const TValue *o) { setobj2s(L, L->top, o); api_incr_top(L); } LUA_API int lua_checkstack (lua_State *L, int size) { int res = 1; lua_lock(L); if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK) res = 0; /* stack overflow */ else if (size > 0) { luaD_checkstack(L, size); if (L->ci->top < L->top + size) L->ci->top = L->top + size; } lua_unlock(L); return res; } LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { int i; if (from == to) return; lua_lock(to); api_checknelems(from, n); api_check(from, G(from) == G(to)); api_check(from, to->ci->top - to->top >= n); from->top -= n; for (i = 0; i < n; i++) { setobj2s(to, to->top++, from->top + i); } lua_unlock(to); } LUA_API void lua_setlevel (lua_State *from, lua_State *to) { to->nCcalls = from->nCcalls; } LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { lua_CFunction old; lua_lock(L); old = G(L)->panic; G(L)->panic = panicf; lua_unlock(L); return old; } LUA_API lua_State *lua_newthread (lua_State *L) { lua_State *L1; lua_lock(L); luaC_checkGC(L); L1 = luaE_newthread(L); setthvalue(L, L->top, L1); api_incr_top(L); lua_unlock(L); luai_userstatethread(L, L1); return L1; } /* ** basic stack manipulation */ LUA_API int lua_gettop (lua_State *L) { return cast_int(L->top - L->base); } LUA_API void lua_settop (lua_State *L, int idx) { lua_lock(L); if (idx >= 0) { api_check(L, idx <= L->stack_last - L->base); while (L->top < L->base + idx) setnilvalue(L->top++); L->top = L->base + idx; } else { api_check(L, -(idx+1) <= (L->top - L->base)); L->top += idx+1; /* `subtract' index (index is negative) */ } lua_unlock(L); } LUA_API void lua_remove (lua_State *L, int idx) { StkId p; lua_lock(L); p = index2adr(L, idx); api_checkvalidindex(L, p); while (++p < L->top) setobjs2s(L, p-1, p); L->top--; lua_unlock(L); } LUA_API void lua_insert (lua_State *L, int idx) { StkId p; StkId q; lua_lock(L); p = index2adr(L, idx); api_checkvalidindex(L, p); for (q = L->top; q>p; q--) setobjs2s(L, q, q-1); setobjs2s(L, p, L->top); lua_unlock(L); } LUA_API void lua_replace (lua_State *L, int idx) { StkId o; lua_lock(L); /* explicit test for incompatible code */ if (idx == LUA_ENVIRONINDEX && L->ci == L->base_ci) luaG_runerror(L, "no calling environment"); api_checknelems(L, 1); o = index2adr(L, idx); api_checkvalidindex(L, o); if (idx == LUA_ENVIRONINDEX) { Closure *func = curr_func(L); api_check(L, ttistable(L->top - 1)); func->c.env = hvalue(L->top - 1); luaC_barrier(L, func, L->top - 1); } else { setobj(L, o, L->top - 1); if (idx < LUA_GLOBALSINDEX) /* function upvalue? */ luaC_barrier(L, curr_func(L), L->top - 1); } L->top--; lua_unlock(L); } LUA_API void lua_pushvalue (lua_State *L, int idx) { lua_lock(L); setobj2s(L, L->top, index2adr(L, idx)); api_incr_top(L); lua_unlock(L); } /* ** access functions (stack -> C) */ LUA_API int lua_type (lua_State *L, int idx) { StkId o = index2adr(L, idx); return (o == luaO_nilobject) ? LUA_TNONE : ttype(o); } LUA_API const char *lua_typename (lua_State *L, int t) { UNUSED(L); return (t == LUA_TNONE) ? "no value" : luaT_typenames[t]; } LUA_API int lua_iscfunction (lua_State *L, int idx) { StkId o = index2adr(L, idx); return iscfunction(o); } LUA_API int lua_isnumber (lua_State *L, int idx) { TValue n; const TValue *o = index2adr(L, idx); return tonumber(o, &n); } LUA_API int lua_isstring (lua_State *L, int idx) { int t = lua_type(L, idx); return (t == LUA_TSTRING || t == LUA_TNUMBER); } LUA_API int lua_isuserdata (lua_State *L, int idx) { const TValue *o = index2adr(L, idx); return (ttisuserdata(o) || ttislightuserdata(o)); } LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { StkId o1 = index2adr(L, index1); StkId o2 = index2adr(L, index2); return (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaO_rawequalObj(o1, o2); } LUA_API int lua_equal (lua_State *L, int index1, int index2) { StkId o1, o2; int i; lua_lock(L); /* may call tag method */ o1 = index2adr(L, index1); o2 = index2adr(L, index2); i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2); lua_unlock(L); return i; } LUA_API int lua_lessthan (lua_State *L, int index1, int index2) { StkId o1, o2; int i; lua_lock(L); /* may call tag method */ o1 = index2adr(L, index1); o2 = index2adr(L, index2); i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaV_lessthan(L, o1, o2); lua_unlock(L); return i; } LUA_API lua_Number lua_tonumber (lua_State *L, int idx) { TValue n; const TValue *o = index2adr(L, idx); if (tonumber(o, &n)) return nvalue(o); else return 0; } LUA_API lua_Integer lua_tointeger (lua_State *L, int idx) { TValue n; const TValue *o = index2adr(L, idx); if (tonumber(o, &n)) { lua_Integer res; lua_Number num = nvalue(o); lua_number2integer(res, num); return res; } else return 0; } LUA_API int lua_toboolean (lua_State *L, int idx) { const TValue *o = index2adr(L, idx); return !l_isfalse(o); } LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { StkId o = index2adr(L, idx); if (!ttisstring(o)) { lua_lock(L); /* `luaV_tostring' may create a new string */ if (!luaV_tostring(L, o)) { /* conversion failed? */ if (len != NULL) *len = 0; lua_unlock(L); return NULL; } luaC_checkGC(L); o = index2adr(L, idx); /* previous call may reallocate the stack */ lua_unlock(L); } if (len != NULL) *len = tsvalue(o)->len; return svalue(o); } LUA_API size_t lua_objlen (lua_State *L, int idx) { StkId o = index2adr(L, idx); switch (ttype(o)) { case LUA_TSTRING: return tsvalue(o)->len; case LUA_TUSERDATA: return uvalue(o)->len; case LUA_TTABLE: return luaH_getn(hvalue(o)); case LUA_TNUMBER: { size_t l; lua_lock(L); /* `luaV_tostring' may create a new string */ l = (luaV_tostring(L, o) ? tsvalue(o)->len : 0); lua_unlock(L); return l; } default: return 0; } } LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { StkId o = index2adr(L, idx); return (!iscfunction(o)) ? NULL : clvalue(o)->c.f; } LUA_API void *lua_touserdata (lua_State *L, int idx) { StkId o = index2adr(L, idx); switch (ttype(o)) { case LUA_TUSERDATA: return (rawuvalue(o) + 1); case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; } } LUA_API lua_State *lua_tothread (lua_State *L, int idx) { StkId o = index2adr(L, idx); return (!ttisthread(o)) ? NULL : thvalue(o); } LUA_API const void *lua_topointer (lua_State *L, int idx) { StkId o = index2adr(L, idx); switch (ttype(o)) { case LUA_TTABLE: return hvalue(o); case LUA_TFUNCTION: return clvalue(o); case LUA_TTHREAD: return thvalue(o); case LUA_TUSERDATA: case LUA_TLIGHTUSERDATA: return lua_touserdata(L, idx); default: return NULL; } } /* ** push functions (C -> stack) */ LUA_API void lua_pushnil (lua_State *L) { lua_lock(L); setnilvalue(L->top); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { lua_lock(L); setnvalue(L->top, n); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { lua_lock(L); setnvalue(L->top, cast_num(n)); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushlstring (lua_State *L, const char *s, size_t len) { lua_lock(L); luaC_checkGC(L); setsvalue2s(L, L->top, luaS_newlstr(L, s, len)); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushstring (lua_State *L, const char *s) { if (s == NULL) lua_pushnil(L); else lua_pushlstring(L, s, strlen(s)); } LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { const char *ret; lua_lock(L); luaC_checkGC(L); ret = luaO_pushvfstring(L, fmt, argp); lua_unlock(L); return ret; } LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); luaC_checkGC(L); va_start(argp, fmt); ret = luaO_pushvfstring(L, fmt, argp); va_end(argp); lua_unlock(L); return ret; } LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { Closure *cl; lua_lock(L); luaC_checkGC(L); api_checknelems(L, n); cl = luaF_newCclosure(L, n, getcurrenv(L)); cl->c.f = fn; L->top -= n; while (n--) setobj2n(L, &cl->c.upvalue[n], L->top+n); setclvalue(L, L->top, cl); lua_assert(iswhite(obj2gco(cl))); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushboolean (lua_State *L, int b) { lua_lock(L); setbvalue(L->top, (b != 0)); /* ensure that true is 1 */ api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { lua_lock(L); setpvalue(L->top, p); api_incr_top(L); lua_unlock(L); } LUA_API int lua_pushthread (lua_State *L) { lua_lock(L); setthvalue(L, L->top, L); api_incr_top(L); lua_unlock(L); return (G(L)->mainthread == L); } /* ** get functions (Lua -> stack) */ LUA_API void lua_gettable (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2adr(L, idx); api_checkvalidindex(L, t); luaV_gettable(L, t, L->top - 1, L->top - 1); lua_unlock(L); } LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { StkId t; TValue key; lua_lock(L); t = index2adr(L, idx); api_checkvalidindex(L, t); setsvalue(L, &key, luaS_new(L, k)); luaV_gettable(L, t, &key, L->top); api_incr_top(L); lua_unlock(L); } LUA_API void lua_rawget (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2adr(L, idx); api_check(L, ttistable(t)); setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); lua_unlock(L); } LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { StkId o; lua_lock(L); o = index2adr(L, idx); api_check(L, ttistable(o)); setobj2s(L, L->top, luaH_getnum(hvalue(o), n)); api_incr_top(L); lua_unlock(L); } LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { lua_lock(L); luaC_checkGC(L); sethvalue(L, L->top, luaH_new(L, narray, nrec)); api_incr_top(L); lua_unlock(L); } LUA_API int lua_getmetatable (lua_State *L, int objindex) { const TValue *obj; Table *mt = NULL; int res; lua_lock(L); obj = index2adr(L, objindex); switch (ttype(obj)) { case LUA_TTABLE: mt = hvalue(obj)->metatable; break; case LUA_TUSERDATA: mt = uvalue(obj)->metatable; break; default: mt = G(L)->mt[ttype(obj)]; break; } if (mt == NULL) res = 0; else { sethvalue(L, L->top, mt); api_incr_top(L); res = 1; } lua_unlock(L); return res; } LUA_API void lua_getfenv (lua_State *L, int idx) { StkId o; lua_lock(L); o = index2adr(L, idx); api_checkvalidindex(L, o); switch (ttype(o)) { case LUA_TFUNCTION: sethvalue(L, L->top, clvalue(o)->c.env); break; case LUA_TUSERDATA: sethvalue(L, L->top, uvalue(o)->env); break; case LUA_TTHREAD: setobj2s(L, L->top, gt(thvalue(o))); break; default: setnilvalue(L->top); break; } api_incr_top(L); lua_unlock(L); } /* ** set functions (stack -> Lua) */ LUA_API void lua_settable (lua_State *L, int idx) { StkId t; lua_lock(L); api_checknelems(L, 2); t = index2adr(L, idx); api_checkvalidindex(L, t); luaV_settable(L, t, L->top - 2, L->top - 1); L->top -= 2; /* pop index and value */ lua_unlock(L); } LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { StkId t; TValue key; lua_lock(L); api_checknelems(L, 1); t = index2adr(L, idx); api_checkvalidindex(L, t); setsvalue(L, &key, luaS_new(L, k)); luaV_settable(L, t, &key, L->top - 1); L->top--; /* pop value */ lua_unlock(L); } LUA_API void lua_rawset (lua_State *L, int idx) { StkId t; lua_lock(L); api_checknelems(L, 2); t = index2adr(L, idx); api_check(L, ttistable(t)); if (hvalue(t)->readonly) luaG_runerror(L, "Attempt to modify a readonly table"); setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1); luaC_barriert(L, hvalue(t), L->top-1); L->top -= 2; lua_unlock(L); } LUA_API void lua_rawseti (lua_State *L, int idx, int n) { StkId o; lua_lock(L); api_checknelems(L, 1); o = index2adr(L, idx); api_check(L, ttistable(o)); if (hvalue(o)->readonly) luaG_runerror(L, "Attempt to modify a readonly table"); setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top-1); luaC_barriert(L, hvalue(o), L->top-1); L->top--; lua_unlock(L); } LUA_API int lua_setmetatable (lua_State *L, int objindex) { TValue *obj; Table *mt; lua_lock(L); api_checknelems(L, 1); obj = index2adr(L, objindex); api_checkvalidindex(L, obj); if (ttisnil(L->top - 1)) mt = NULL; else { api_check(L, ttistable(L->top - 1)); mt = hvalue(L->top - 1); } switch (ttype(obj)) { case LUA_TTABLE: { if (hvalue(obj)->readonly) luaG_runerror(L, "Attempt to modify a readonly table"); hvalue(obj)->metatable = mt; if (mt) luaC_objbarriert(L, hvalue(obj), mt); break; } case LUA_TUSERDATA: { uvalue(obj)->metatable = mt; if (mt) luaC_objbarrier(L, rawuvalue(obj), mt); break; } default: { G(L)->mt[ttype(obj)] = mt; break; } } L->top--; lua_unlock(L); return 1; } LUA_API int lua_setfenv (lua_State *L, int idx) { StkId o; int res = 1; lua_lock(L); api_checknelems(L, 1); o = index2adr(L, idx); api_checkvalidindex(L, o); api_check(L, ttistable(L->top - 1)); switch (ttype(o)) { case LUA_TFUNCTION: clvalue(o)->c.env = hvalue(L->top - 1); break; case LUA_TUSERDATA: uvalue(o)->env = hvalue(L->top - 1); break; case LUA_TTHREAD: sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1)); break; default: res = 0; break; } if (res) luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); L->top--; lua_unlock(L); return res; } /* ** `load' and `call' functions (run Lua code) */ #define adjustresults(L,nres) \ { if (nres == LUA_MULTRET && L->top >= L->ci->top) L->ci->top = L->top; } #define checkresults(L,na,nr) \ api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na))) LUA_API void lua_call (lua_State *L, int nargs, int nresults) { StkId func; lua_lock(L); api_checknelems(L, nargs+1); checkresults(L, nargs, nresults); func = L->top - (nargs+1); luaD_call(L, func, nresults); adjustresults(L, nresults); lua_unlock(L); } /* ** Execute a protected call. */ struct CallS { /* data to `f_call' */ StkId func; int nresults; }; static void f_call (lua_State *L, void *ud) { struct CallS *c = cast(struct CallS *, ud); luaD_call(L, c->func, c->nresults); } LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) { struct CallS c; int status; ptrdiff_t func; lua_lock(L); api_checknelems(L, nargs+1); checkresults(L, nargs, nresults); if (errfunc == 0) func = 0; else { StkId o = index2adr(L, errfunc); api_checkvalidindex(L, o); func = savestack(L, o); } c.func = L->top - (nargs+1); /* function to be called */ c.nresults = nresults; status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); adjustresults(L, nresults); lua_unlock(L); return status; } /* ** Execute a protected C call. */ struct CCallS { /* data to `f_Ccall' */ lua_CFunction func; void *ud; }; static void f_Ccall (lua_State *L, void *ud) { struct CCallS *c = cast(struct CCallS *, ud); Closure *cl; cl = luaF_newCclosure(L, 0, getcurrenv(L)); cl->c.f = c->func; setclvalue(L, L->top, cl); /* push function */ api_incr_top(L); setpvalue(L->top, c->ud); /* push only argument */ api_incr_top(L); luaD_call(L, L->top - 2, 0); } LUA_API int lua_cpcall (lua_State *L, lua_CFunction func, void *ud) { struct CCallS c; int status; lua_lock(L); c.func = func; c.ud = ud; status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); lua_unlock(L); return status; } LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname) { ZIO z; int status; lua_lock(L); if (!chunkname) chunkname = "?"; luaZ_init(L, &z, reader, data); status = luaD_protectedparser(L, &z, chunkname); lua_unlock(L); return status; } LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) { int status; TValue *o; lua_lock(L); api_checknelems(L, 1); o = L->top - 1; if (isLfunction(o)) status = luaU_dump(L, clvalue(o)->l.p, writer, data, 0); else status = 1; lua_unlock(L); return status; } LUA_API int lua_status (lua_State *L) { return L->status; } /* ** Garbage-collection function */ LUA_API int lua_gc (lua_State *L, int what, int data) { int res = 0; global_State *g; lua_lock(L); g = G(L); switch (what) { case LUA_GCSTOP: { g->GCthreshold = MAX_LUMEM; break; } case LUA_GCRESTART: { g->GCthreshold = g->totalbytes; break; } case LUA_GCCOLLECT: { luaC_fullgc(L); break; } case LUA_GCCOUNT: { /* GC values are expressed in Kbytes: #bytes/2^10 */ res = cast_int(g->totalbytes >> 10); break; } case LUA_GCCOUNTB: { res = cast_int(g->totalbytes & 0x3ff); break; } case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) { /* end of cycle? */ res = 1; /* signal it */ break; } } break; } case LUA_GCSETPAUSE: { res = g->gcpause; g->gcpause = data; break; } case LUA_GCSETSTEPMUL: { res = g->gcstepmul; g->gcstepmul = data; break; } default: res = -1; /* invalid option */ } lua_unlock(L); return res; } /* ** miscellaneous functions */ LUA_API int lua_error (lua_State *L) { lua_lock(L); api_checknelems(L, 1); luaG_errormsg(L); lua_unlock(L); return 0; /* to avoid warnings */ } LUA_API int lua_next (lua_State *L, int idx) { StkId t; int more; lua_lock(L); t = index2adr(L, idx); api_check(L, ttistable(t)); more = luaH_next(L, hvalue(t), L->top - 1); if (more) { api_incr_top(L); } else /* no more elements */ L->top -= 1; /* remove key */ lua_unlock(L); return more; } LUA_API void lua_concat (lua_State *L, int n) { lua_lock(L); api_checknelems(L, n); if (n >= 2) { luaC_checkGC(L); luaV_concat(L, n, cast_int(L->top - L->base) - 1); L->top -= (n-1); } else if (n == 0) { /* push empty string */ setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); api_incr_top(L); } /* else n == 1; nothing to do */ lua_unlock(L); } LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) { lua_Alloc f; lua_lock(L); if (ud) *ud = G(L)->ud; f = G(L)->frealloc; lua_unlock(L); return f; } LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { lua_lock(L); G(L)->ud = ud; G(L)->frealloc = f; lua_unlock(L); } LUA_API void *lua_newuserdata (lua_State *L, size_t size) { Udata *u; lua_lock(L); luaC_checkGC(L); u = luaS_newudata(L, size, getcurrenv(L)); setuvalue(L, L->top, u); api_incr_top(L); lua_unlock(L); return u + 1; } static const char *aux_upvalue (StkId fi, int n, TValue **val) { Closure *f; if (!ttisfunction(fi)) return NULL; f = clvalue(fi); if (f->c.isC) { if (!(1 <= n && n <= f->c.nupvalues)) return NULL; *val = &f->c.upvalue[n-1]; return ""; } else { Proto *p = f->l.p; if (!(1 <= n && n <= p->sizeupvalues)) return NULL; *val = f->l.upvals[n-1]->v; return getstr(p->upvalues[n-1]); } } LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val; lua_lock(L); name = aux_upvalue(index2adr(L, funcindex), n, &val); if (name) { setobj2s(L, L->top, val); api_incr_top(L); } lua_unlock(L); return name; } LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val; StkId fi; lua_lock(L); fi = index2adr(L, funcindex); api_checknelems(L, 1); name = aux_upvalue(fi, n, &val); if (name) { L->top--; setobj(L, val, L->top); luaC_barrier(L, clvalue(fi), L->top); } lua_unlock(L); return name; } LUA_API void lua_enablereadonlytable (lua_State *L, int objindex, int enabled) { const TValue* o = index2adr(L, objindex); api_check(L, ttistable(o)); Table* t = hvalue(o); api_check(L, t != hvalue(registry(L))); t->readonly = enabled; } LUA_API int lua_isreadonlytable (lua_State *L, int objindex) { const TValue* o = index2adr(L, objindex); api_check(L, ttistable(o)); Table* t = hvalue(o); api_check(L, t != hvalue(registry(L))); return t->readonly; } redis-8.0.2/deps/lua/src/lapi.h000066400000000000000000000004061501533116600162310ustar00rootroot00000000000000/* ** $Id: lapi.h,v 2.2.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions from Lua API ** See Copyright Notice in lua.h */ #ifndef lapi_h #define lapi_h #include "lobject.h" LUAI_FUNC void luaA_pushobject (lua_State *L, const TValue *o); #endif redis-8.0.2/deps/lua/src/lauxlib.c000066400000000000000000000420101501533116600167340ustar00rootroot00000000000000/* ** $Id: lauxlib.c,v 1.159.1.3 2008/01/21 13:20:51 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ #include #include #include #include #include #include /* This file uses only the official API of Lua. ** Any function declared here could be written as an application function. */ #define lauxlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #define FREELIST_REF 0 /* free list of references */ /* convert a stack index to positive */ #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : \ lua_gettop(L) + (i) + 1) /* ** {====================================================== ** Error-report functions ** ======================================================= */ LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) { lua_Debug ar; if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ return luaL_error(L, "bad argument #%d (%s)", narg, extramsg); lua_getinfo(L, "n", &ar); if (strcmp(ar.namewhat, "method") == 0) { narg--; /* do not count `self' */ if (narg == 0) /* error is in the self argument itself? */ return luaL_error(L, "calling " LUA_QS " on bad self (%s)", ar.name, extramsg); } if (ar.name == NULL) ar.name = "?"; return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)", narg, ar.name, extramsg); } LUALIB_API int luaL_typerror (lua_State *L, int narg, const char *tname) { const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); return luaL_argerror(L, narg, msg); } static void tag_error (lua_State *L, int narg, int tag) { luaL_typerror(L, narg, lua_typename(L, tag)); } LUALIB_API void luaL_where (lua_State *L, int level) { lua_Debug ar; if (lua_getstack(L, level, &ar)) { /* check function at level */ lua_getinfo(L, "Sl", &ar); /* get info about it */ if (ar.currentline > 0) { /* is there info? */ lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); return; } } lua_pushliteral(L, ""); /* else, no information available... */ } LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); luaL_where(L, 1); lua_pushvfstring(L, fmt, argp); va_end(argp); lua_concat(L, 2); return lua_error(L); } /* }====================================================== */ LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) { const char *name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg); int i; for (i=0; lst[i]; i++) if (strcmp(lst[i], name) == 0) return i; return luaL_argerror(L, narg, lua_pushfstring(L, "invalid option " LUA_QS, name)); } LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ if (!lua_isnil(L, -1)) /* name already in use? */ return 0; /* leave previous value on top, but return 0 */ lua_pop(L, 1); lua_newtable(L); /* create metatable */ lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ return 1; } LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { void *p = lua_touserdata(L, ud); if (p != NULL) { /* value is a userdata? */ if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ lua_pop(L, 2); /* remove both metatables */ return p; } } } luaL_typerror(L, ud, tname); /* else error */ return NULL; /* to avoid warnings */ } LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *mes) { if (!lua_checkstack(L, space)) luaL_error(L, "stack overflow (%s)", mes); } LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) { if (lua_type(L, narg) != t) tag_error(L, narg, t); } LUALIB_API void luaL_checkany (lua_State *L, int narg) { if (lua_type(L, narg) == LUA_TNONE) luaL_argerror(L, narg, "value expected"); } LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) { const char *s = lua_tolstring(L, narg, len); if (!s) tag_error(L, narg, LUA_TSTRING); return s; } LUALIB_API const char *luaL_optlstring (lua_State *L, int narg, const char *def, size_t *len) { if (lua_isnoneornil(L, narg)) { if (len) *len = (def ? strlen(def) : 0); return def; } else return luaL_checklstring(L, narg, len); } LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) { lua_Number d = lua_tonumber(L, narg); if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */ tag_error(L, narg, LUA_TNUMBER); return d; } LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) { return luaL_opt(L, luaL_checknumber, narg, def); } LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) { lua_Integer d = lua_tointeger(L, narg); if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */ tag_error(L, narg, LUA_TNUMBER); return d; } LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg, lua_Integer def) { return luaL_opt(L, luaL_checkinteger, narg, def); } LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { if (!lua_getmetatable(L, obj)) /* no metatable? */ return 0; lua_pushstring(L, event); lua_rawget(L, -2); if (lua_isnil(L, -1)) { lua_pop(L, 2); /* remove metatable and metafield */ return 0; } else { lua_remove(L, -2); /* remove only metatable */ return 1; } } LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { obj = abs_index(L, obj); if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ return 0; lua_pushvalue(L, obj); lua_call(L, 1, 1); return 1; } LUALIB_API void (luaL_register) (lua_State *L, const char *libname, const luaL_Reg *l) { luaI_openlib(L, libname, l, 0); } static int libsize (const luaL_Reg *l) { int size = 0; for (; l->name; l++) size++; return size; } LUALIB_API void luaI_openlib (lua_State *L, const char *libname, const luaL_Reg *l, int nup) { if (libname) { int size = libsize(l); /* check whether lib already exists */ luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); lua_getfield(L, -1, libname); /* get _LOADED[libname] */ if (!lua_istable(L, -1)) { /* not found? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL) luaL_error(L, "name conflict for module " LUA_QS, libname); lua_pushvalue(L, -1); lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */ } lua_remove(L, -2); /* remove _LOADED table */ lua_insert(L, -(nup+1)); /* move library table to below upvalues */ } for (; l->name; l++) { int i; for (i=0; ifunc, nup); lua_setfield(L, -(nup+2), l->name); } lua_pop(L, nup); /* remove upvalues */ } /* ** {====================================================== ** getn-setn: size for arrays ** ======================================================= */ #if defined(LUA_COMPAT_GETN) static int checkint (lua_State *L, int topop) { int n = (lua_type(L, -1) == LUA_TNUMBER) ? lua_tointeger(L, -1) : -1; lua_pop(L, topop); return n; } static void getsizes (lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); if (lua_isnil(L, -1)) { /* no `size' table? */ lua_pop(L, 1); /* remove nil */ lua_newtable(L); /* create it */ lua_pushvalue(L, -1); /* `size' will be its own metatable */ lua_setmetatable(L, -2); lua_pushliteral(L, "kv"); lua_setfield(L, -2, "__mode"); /* metatable(N).__mode = "kv" */ lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); /* store in register */ } } LUALIB_API void luaL_setn (lua_State *L, int t, int n) { t = abs_index(L, t); lua_pushliteral(L, "n"); lua_rawget(L, t); if (checkint(L, 1) >= 0) { /* is there a numeric field `n'? */ lua_pushliteral(L, "n"); /* use it */ lua_pushinteger(L, n); lua_rawset(L, t); } else { /* use `sizes' */ getsizes(L); lua_pushvalue(L, t); lua_pushinteger(L, n); lua_rawset(L, -3); /* sizes[t] = n */ lua_pop(L, 1); /* remove `sizes' */ } } LUALIB_API int luaL_getn (lua_State *L, int t) { int n; t = abs_index(L, t); lua_pushliteral(L, "n"); /* try t.n */ lua_rawget(L, t); if ((n = checkint(L, 1)) >= 0) return n; getsizes(L); /* else try sizes[t] */ lua_pushvalue(L, t); lua_rawget(L, -2); if ((n = checkint(L, 2)) >= 0) return n; return (int)lua_objlen(L, t); } #endif /* }====================================================== */ LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, const char *r) { const char *wild; size_t l = strlen(p); luaL_Buffer b; luaL_buffinit(L, &b); while ((wild = strstr(s, p)) != NULL) { luaL_addlstring(&b, s, wild - s); /* push prefix */ luaL_addstring(&b, r); /* push replacement in place of pattern */ s = wild + l; /* continue after `p' */ } luaL_addstring(&b, s); /* push last suffix */ luaL_pushresult(&b); return lua_tostring(L, -1); } LUALIB_API const char *luaL_findtable (lua_State *L, int idx, const char *fname, int szhint) { const char *e; lua_pushvalue(L, idx); do { e = strchr(fname, '.'); if (e == NULL) e = fname + strlen(fname); lua_pushlstring(L, fname, e - fname); lua_rawget(L, -2); if (lua_isnil(L, -1)) { /* no such field? */ lua_pop(L, 1); /* remove this nil */ lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ lua_pushlstring(L, fname, e - fname); lua_pushvalue(L, -2); lua_settable(L, -4); /* set new table into field */ } else if (!lua_istable(L, -1)) { /* field has a non-table value? */ lua_pop(L, 2); /* remove table and value */ return fname; /* return problematic part of the name */ } lua_remove(L, -2); /* remove previous table */ fname = e + 1; } while (*e == '.'); return NULL; } /* ** {====================================================== ** Generic Buffer manipulation ** ======================================================= */ #define bufflen(B) ((B)->p - (B)->buffer) #define bufffree(B) ((size_t)(LUAL_BUFFERSIZE - bufflen(B))) #define LIMIT (LUA_MINSTACK/2) static int emptybuffer (luaL_Buffer *B) { size_t l = bufflen(B); if (l == 0) return 0; /* put nothing on stack */ else { lua_pushlstring(B->L, B->buffer, l); B->p = B->buffer; B->lvl++; return 1; } } static void adjuststack (luaL_Buffer *B) { if (B->lvl > 1) { lua_State *L = B->L; int toget = 1; /* number of levels to concat */ size_t toplen = lua_strlen(L, -1); do { size_t l = lua_strlen(L, -(toget+1)); if (B->lvl - toget + 1 >= LIMIT || toplen > l) { toplen += l; toget++; } else break; } while (toget < B->lvl); lua_concat(L, toget); B->lvl = B->lvl - toget + 1; } } LUALIB_API char *luaL_prepbuffer (luaL_Buffer *B) { if (emptybuffer(B)) adjuststack(B); return B->buffer; } LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { while (l--) luaL_addchar(B, *s++); } LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { luaL_addlstring(B, s, strlen(s)); } LUALIB_API void luaL_pushresult (luaL_Buffer *B) { emptybuffer(B); lua_concat(B->L, B->lvl); B->lvl = 1; } LUALIB_API void luaL_addvalue (luaL_Buffer *B) { lua_State *L = B->L; size_t vl; const char *s = lua_tolstring(L, -1, &vl); if (vl <= bufffree(B)) { /* fit into buffer? */ memcpy(B->p, s, vl); /* put it there */ B->p += vl; lua_pop(L, 1); /* remove from stack */ } else { if (emptybuffer(B)) lua_insert(L, -2); /* put buffer before new value */ B->lvl++; /* add new value into B stack */ adjuststack(B); } } LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { B->L = L; B->p = B->buffer; B->lvl = 0; } /* }====================================================== */ LUALIB_API int luaL_ref (lua_State *L, int t) { int ref; t = abs_index(L, t); if (lua_isnil(L, -1)) { lua_pop(L, 1); /* remove from stack */ return LUA_REFNIL; /* `nil' has a unique fixed reference */ } lua_rawgeti(L, t, FREELIST_REF); /* get first free element */ ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */ lua_pop(L, 1); /* remove it from stack */ if (ref != 0) { /* any free element? */ lua_rawgeti(L, t, ref); /* remove it from list */ lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */ } else { /* no free elements */ ref = (int)lua_objlen(L, t); ref++; /* create new reference */ } lua_rawseti(L, t, ref); return ref; } LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { if (ref >= 0) { t = abs_index(L, t); lua_rawgeti(L, t, FREELIST_REF); lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */ lua_pushinteger(L, ref); lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */ } } /* ** {====================================================== ** Load functions ** ======================================================= */ typedef struct LoadF { int extraline; FILE *f; char buff[LUAL_BUFFERSIZE]; } LoadF; static const char *getF (lua_State *L, void *ud, size_t *size) { LoadF *lf = (LoadF *)ud; (void)L; if (lf->extraline) { lf->extraline = 0; *size = 1; return "\n"; } if (feof(lf->f)) return NULL; *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); return (*size > 0) ? lf->buff : NULL; } static int errfile (lua_State *L, const char *what, int fnameindex) { const char *serr = strerror(errno); const char *filename = lua_tostring(L, fnameindex) + 1; lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); lua_remove(L, fnameindex); return LUA_ERRFILE; } LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { LoadF lf; int status, readstatus; int c; int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ lf.extraline = 0; if (filename == NULL) { lua_pushliteral(L, "=stdin"); lf.f = stdin; } else { lua_pushfstring(L, "@%s", filename); lf.f = fopen(filename, "r"); if (lf.f == NULL) return errfile(L, "open", fnameindex); } c = getc(lf.f); if (c == '#') { /* Unix exec. file? */ lf.extraline = 1; while ((c = getc(lf.f)) != EOF && c != '\n') ; /* skip first line */ if (c == '\n') c = getc(lf.f); } if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ if (lf.f == NULL) return errfile(L, "reopen", fnameindex); /* skip eventual `#!...' */ while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ; lf.extraline = 0; } ungetc(c, lf.f); status = lua_load(L, getF, &lf, lua_tostring(L, -1)); readstatus = ferror(lf.f); if (filename) fclose(lf.f); /* close file (even in case of errors) */ if (readstatus) { lua_settop(L, fnameindex); /* ignore results from `lua_load' */ return errfile(L, "read", fnameindex); } lua_remove(L, fnameindex); return status; } typedef struct LoadS { const char *s; size_t size; } LoadS; static const char *getS (lua_State *L, void *ud, size_t *size) { LoadS *ls = (LoadS *)ud; (void)L; if (ls->size == 0) return NULL; *size = ls->size; ls->size = 0; return ls->s; } LUALIB_API int luaL_loadbuffer (lua_State *L, const char *buff, size_t size, const char *name) { LoadS ls; ls.s = buff; ls.size = size; return lua_load(L, getS, &ls, name); } LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s) { return luaL_loadbuffer(L, s, strlen(s), s); } /* }====================================================== */ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); } static int panic (lua_State *L) { (void)L; /* to avoid warnings */ fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1)); return 0; } LUALIB_API lua_State *luaL_newstate (void) { lua_State *L = lua_newstate(l_alloc, NULL); if (L) lua_atpanic(L, &panic); return L; } redis-8.0.2/deps/lua/src/lauxlib.h000066400000000000000000000132211501533116600167430ustar00rootroot00000000000000/* ** $Id: lauxlib.h,v 1.88.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ #ifndef lauxlib_h #define lauxlib_h #include #include #include "lua.h" #if defined(LUA_COMPAT_GETN) LUALIB_API int (luaL_getn) (lua_State *L, int t); LUALIB_API void (luaL_setn) (lua_State *L, int t, int n); #else #define luaL_getn(L,i) ((int)lua_objlen(L, i)) #define luaL_setn(L,i,j) ((void)0) /* no op! */ #endif #if defined(LUA_COMPAT_OPENLIB) #define luaI_openlib luaL_openlib #endif /* extra error code for `luaL_load' */ #define LUA_ERRFILE (LUA_ERRERR+1) typedef struct luaL_Reg { const char *name; lua_CFunction func; } luaL_Reg; LUALIB_API void (luaI_openlib) (lua_State *L, const char *libname, const luaL_Reg *l, int nup); LUALIB_API void (luaL_register) (lua_State *L, const char *libname, const luaL_Reg *l); LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname); LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, size_t *l); LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, const char *def, size_t *l); LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, lua_Integer def); LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); LUALIB_API void (luaL_checkany) (lua_State *L, int narg); LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); LUALIB_API void (luaL_where) (lua_State *L, int lvl); LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, const char *const lst[]); LUALIB_API int (luaL_ref) (lua_State *L, int t); LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, const char *name); LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); LUALIB_API lua_State *(luaL_newstate) (void); LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, const char *r); LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx, const char *fname, int szhint); /* ** =============================================================== ** some useful macros ** =============================================================== */ #define luaL_argcheck(L, cond,numarg,extramsg) \ ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) #define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) #define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) #define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) #define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) #define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) #define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) #define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) #define luaL_dofile(L, fn) \ (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) #define luaL_dostring(L, s) \ (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) #define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) #define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) /* ** {====================================================== ** Generic Buffer manipulation ** ======================================================= */ typedef struct luaL_Buffer { char *p; /* current position in buffer */ int lvl; /* number of strings in the stack (level) */ lua_State *L; char buffer[LUAL_BUFFERSIZE]; } luaL_Buffer; #define luaL_addchar(B,c) \ ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ (*(B)->p++ = (char)(c))) /* compatibility only */ #define luaL_putchar(B,c) luaL_addchar(B,c) #define luaL_addsize(B,n) ((B)->p += (n)) LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B); LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); /* }====================================================== */ /* compatibility with ref system */ /* pre-defined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) #define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \ (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0)) #define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref)) #define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref)) #define luaL_reg luaL_Reg #endif redis-8.0.2/deps/lua/src/lbaselib.c000066400000000000000000000412251501533116600170600ustar00rootroot00000000000000/* ** $Id: lbaselib.c,v 1.191.1.6 2008/02/14 16:46:22 roberto Exp $ ** Basic library ** See Copyright Notice in lua.h */ #include #include #include #include #define lbaselib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" /* ** If your system does not support `stdout', you can just remove this function. ** If you need, you can define your own `print' function, following this ** model but changing `fputs' to put the strings at a proper place ** (a console window or a log file, for instance). */ static int luaB_print (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ int i; lua_getglobal(L, "tostring"); for (i=1; i<=n; i++) { const char *s; lua_pushvalue(L, -1); /* function to be called */ lua_pushvalue(L, i); /* value to print */ lua_call(L, 1, 1); s = lua_tostring(L, -1); /* get result */ if (s == NULL) return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("print")); if (i>1) fputs("\t", stdout); fputs(s, stdout); lua_pop(L, 1); /* pop result */ } fputs("\n", stdout); return 0; } static int luaB_tonumber (lua_State *L) { int base = luaL_optint(L, 2, 10); if (base == 10) { /* standard conversion */ luaL_checkany(L, 1); if (lua_isnumber(L, 1)) { lua_pushnumber(L, lua_tonumber(L, 1)); return 1; } } else { const char *s1 = luaL_checkstring(L, 1); char *s2; unsigned long n; luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); n = strtoul(s1, &s2, base); if (s1 != s2) { /* at least one valid digit? */ while (isspace((unsigned char)(*s2))) s2++; /* skip trailing spaces */ if (*s2 == '\0') { /* no invalid trailing characters? */ lua_pushnumber(L, (lua_Number)n); return 1; } } } lua_pushnil(L); /* else not a number */ return 1; } static int luaB_error (lua_State *L) { int level = luaL_optint(L, 2, 1); lua_settop(L, 1); if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ luaL_where(L, level); lua_pushvalue(L, 1); lua_concat(L, 2); } return lua_error(L); } static int luaB_getmetatable (lua_State *L) { luaL_checkany(L, 1); if (!lua_getmetatable(L, 1)) { lua_pushnil(L); return 1; /* no metatable */ } luaL_getmetafield(L, 1, "__metatable"); return 1; /* returns either __metatable field (if present) or metatable */ } static int luaB_setmetatable (lua_State *L) { int t = lua_type(L, 2); luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table expected"); if (luaL_getmetafield(L, 1, "__metatable")) luaL_error(L, "cannot change a protected metatable"); lua_settop(L, 2); lua_setmetatable(L, 1); return 1; } static void getfunc (lua_State *L, int opt) { if (lua_isfunction(L, 1)) lua_pushvalue(L, 1); else { lua_Debug ar; int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1); luaL_argcheck(L, level >= 0, 1, "level must be non-negative"); if (lua_getstack(L, level, &ar) == 0) luaL_argerror(L, 1, "invalid level"); lua_getinfo(L, "f", &ar); if (lua_isnil(L, -1)) luaL_error(L, "no function environment for tail call at level %d", level); } } static int luaB_getfenv (lua_State *L) { getfunc(L, 1); if (lua_iscfunction(L, -1)) /* is a C function? */ lua_pushvalue(L, LUA_GLOBALSINDEX); /* return the thread's global env. */ else lua_getfenv(L, -1); return 1; } static int luaB_setfenv (lua_State *L) { luaL_checktype(L, 2, LUA_TTABLE); getfunc(L, 0); lua_pushvalue(L, 2); if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0) { /* change environment of current thread */ lua_pushthread(L); lua_insert(L, -2); lua_setfenv(L, -2); return 0; } else if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0) luaL_error(L, LUA_QL("setfenv") " cannot change environment of given object"); return 1; } static int luaB_rawequal (lua_State *L) { luaL_checkany(L, 1); luaL_checkany(L, 2); lua_pushboolean(L, lua_rawequal(L, 1, 2)); return 1; } static int luaB_rawget (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checkany(L, 2); lua_settop(L, 2); lua_rawget(L, 1); return 1; } static int luaB_rawset (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checkany(L, 2); luaL_checkany(L, 3); lua_settop(L, 3); lua_rawset(L, 1); return 1; } static int luaB_gcinfo (lua_State *L) { lua_pushinteger(L, lua_getgccount(L)); return 1; } static int luaB_collectgarbage (lua_State *L) { static const char *const opts[] = {"stop", "restart", "collect", "count", "step", "setpause", "setstepmul", NULL}; static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL}; int o = luaL_checkoption(L, 1, "collect", opts); int ex = luaL_optint(L, 2, 0); int res = lua_gc(L, optsnum[o], ex); switch (optsnum[o]) { case LUA_GCCOUNT: { int b = lua_gc(L, LUA_GCCOUNTB, 0); lua_pushnumber(L, res + ((lua_Number)b/1024)); return 1; } case LUA_GCSTEP: { lua_pushboolean(L, res); return 1; } default: { lua_pushnumber(L, res); return 1; } } } static int luaB_type (lua_State *L) { luaL_checkany(L, 1); lua_pushstring(L, luaL_typename(L, 1)); return 1; } static int luaB_next (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 2); /* create a 2nd argument if there isn't one */ if (lua_next(L, 1)) return 2; else { lua_pushnil(L); return 1; } } static int luaB_pairs (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ lua_pushvalue(L, 1); /* state, */ lua_pushnil(L); /* and initial value */ return 3; } static int ipairsaux (lua_State *L) { int i = luaL_checkint(L, 2); luaL_checktype(L, 1, LUA_TTABLE); i++; /* next value */ lua_pushinteger(L, i); lua_rawgeti(L, 1, i); return (lua_isnil(L, -1)) ? 0 : 2; } static int luaB_ipairs (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ lua_pushvalue(L, 1); /* state, */ lua_pushinteger(L, 0); /* and initial value */ return 3; } static int load_aux (lua_State *L, int status) { if (status == 0) /* OK? */ return 1; else { lua_pushnil(L); lua_insert(L, -2); /* put before error message */ return 2; /* return nil plus error message */ } } static int luaB_loadstring (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); const char *chunkname = luaL_optstring(L, 2, s); return load_aux(L, luaL_loadbuffer(L, s, l, chunkname)); } static int luaB_loadfile (lua_State *L) { const char *fname = luaL_optstring(L, 1, NULL); return load_aux(L, luaL_loadfile(L, fname)); } /* ** Reader for generic `load' function: `lua_load' uses the ** stack for internal stuff, so the reader cannot change the ** stack top. Instead, it keeps its resulting string in a ** reserved slot inside the stack. */ static const char *generic_reader (lua_State *L, void *ud, size_t *size) { (void)ud; /* to avoid warnings */ luaL_checkstack(L, 2, "too many nested functions"); lua_pushvalue(L, 1); /* get function */ lua_call(L, 0, 1); /* call it */ if (lua_isnil(L, -1)) { *size = 0; return NULL; } else if (lua_isstring(L, -1)) { lua_replace(L, 3); /* save string in a reserved stack slot */ return lua_tolstring(L, 3, size); } else luaL_error(L, "reader function must return a string"); return NULL; /* to avoid warnings */ } static int luaB_load (lua_State *L) { int status; const char *cname = luaL_optstring(L, 2, "=(load)"); luaL_checktype(L, 1, LUA_TFUNCTION); lua_settop(L, 3); /* function, eventual name, plus one reserved slot */ status = lua_load(L, generic_reader, NULL, cname); return load_aux(L, status); } static int luaB_dofile (lua_State *L) { const char *fname = luaL_optstring(L, 1, NULL); int n = lua_gettop(L); if (luaL_loadfile(L, fname) != 0) lua_error(L); lua_call(L, 0, LUA_MULTRET); return lua_gettop(L) - n; } static int luaB_assert (lua_State *L) { luaL_checkany(L, 1); if (!lua_toboolean(L, 1)) return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); return lua_gettop(L); } static int luaB_unpack (lua_State *L) { int i, e, 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 */ 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] */ lua_rawgeti(L, 1, i); return n; } static int luaB_select (lua_State *L) { int n = lua_gettop(L); if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') { lua_pushinteger(L, n-1); return 1; } else { int i = luaL_checkint(L, 1); if (i < 0) i = n + i; else if (i > n) i = n; luaL_argcheck(L, 1 <= i, 1, "index out of range"); return n - i; } } static int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); lua_pushboolean(L, (status == 0)); lua_insert(L, 1); return lua_gettop(L); /* return status + all results */ } static int luaB_xpcall (lua_State *L) { int status; luaL_checkany(L, 2); lua_settop(L, 2); lua_insert(L, 1); /* put error function under function to be called */ status = lua_pcall(L, 0, LUA_MULTRET, 1); lua_pushboolean(L, (status == 0)); lua_replace(L, 1); return lua_gettop(L); /* return status + all results */ } static int luaB_tostring (lua_State *L) { luaL_checkany(L, 1); if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */ return 1; /* use its value */ switch (lua_type(L, 1)) { case LUA_TNUMBER: lua_pushstring(L, lua_tostring(L, 1)); break; case LUA_TSTRING: lua_pushvalue(L, 1); break; case LUA_TBOOLEAN: lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false")); break; case LUA_TNIL: lua_pushliteral(L, "nil"); break; default: lua_pushfstring(L, "%s: %p", luaL_typename(L, 1), lua_topointer(L, 1)); break; } return 1; } static int luaB_newproxy (lua_State *L) { lua_settop(L, 1); lua_newuserdata(L, 0); /* create proxy */ if (lua_toboolean(L, 1) == 0) return 1; /* no metatable */ else if (lua_isboolean(L, 1)) { lua_newtable(L); /* create a new metatable `m' ... */ lua_pushvalue(L, -1); /* ... and mark `m' as a valid metatable */ lua_pushboolean(L, 1); lua_rawset(L, lua_upvalueindex(1)); /* weaktable[m] = true */ } else { int validproxy = 0; /* to check if weaktable[metatable(u)] == true */ if (lua_getmetatable(L, 1)) { lua_rawget(L, lua_upvalueindex(1)); validproxy = lua_toboolean(L, -1); lua_pop(L, 1); /* remove value */ } luaL_argcheck(L, validproxy, 1, "boolean or proxy expected"); lua_getmetatable(L, 1); /* metatable is valid; get it */ } lua_setmetatable(L, 2); return 1; } static const luaL_Reg base_funcs[] = { {"assert", luaB_assert}, {"collectgarbage", luaB_collectgarbage}, {"dofile", luaB_dofile}, {"error", luaB_error}, {"gcinfo", luaB_gcinfo}, {"getfenv", luaB_getfenv}, {"getmetatable", luaB_getmetatable}, {"loadfile", luaB_loadfile}, {"load", luaB_load}, {"loadstring", luaB_loadstring}, {"next", luaB_next}, {"pcall", luaB_pcall}, {"print", luaB_print}, {"rawequal", luaB_rawequal}, {"rawget", luaB_rawget}, {"rawset", luaB_rawset}, {"select", luaB_select}, {"setfenv", luaB_setfenv}, {"setmetatable", luaB_setmetatable}, {"tonumber", luaB_tonumber}, {"tostring", luaB_tostring}, {"type", luaB_type}, {"unpack", luaB_unpack}, {"xpcall", luaB_xpcall}, {NULL, NULL} }; /* ** {====================================================== ** Coroutine library ** ======================================================= */ #define CO_RUN 0 /* running */ #define CO_SUS 1 /* suspended */ #define CO_NOR 2 /* 'normal' (it resumed another coroutine) */ #define CO_DEAD 3 static const char *const statnames[] = {"running", "suspended", "normal", "dead"}; static int costatus (lua_State *L, lua_State *co) { if (L == co) return CO_RUN; switch (lua_status(co)) { case LUA_YIELD: return CO_SUS; case 0: { lua_Debug ar; if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */ return CO_NOR; /* it is running */ else if (lua_gettop(co) == 0) return CO_DEAD; else return CO_SUS; /* initial state */ } default: /* some error occured */ return CO_DEAD; } } static int luaB_costatus (lua_State *L) { lua_State *co = lua_tothread(L, 1); luaL_argcheck(L, co, 1, "coroutine expected"); lua_pushstring(L, statnames[costatus(L, co)]); return 1; } static int auxresume (lua_State *L, lua_State *co, int narg) { int status = costatus(L, co); if (!lua_checkstack(co, narg)) luaL_error(L, "too many arguments to resume"); if (status != CO_SUS) { lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]); return -1; /* error flag */ } lua_xmove(L, co, narg); lua_setlevel(L, co); status = lua_resume(co, narg); if (status == 0 || status == LUA_YIELD) { int nres = lua_gettop(co); if (!lua_checkstack(L, nres + 1)) luaL_error(L, "too many results to resume"); lua_xmove(co, L, nres); /* move yielded values */ return nres; } else { lua_xmove(co, L, 1); /* move error message */ return -1; /* error flag */ } } static int luaB_coresume (lua_State *L) { lua_State *co = lua_tothread(L, 1); int r; luaL_argcheck(L, co, 1, "coroutine expected"); r = auxresume(L, co, lua_gettop(L) - 1); if (r < 0) { lua_pushboolean(L, 0); lua_insert(L, -2); return 2; /* return false + error message */ } else { lua_pushboolean(L, 1); lua_insert(L, -(r + 1)); return r + 1; /* return true + `resume' returns */ } } static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); if (r < 0) { if (lua_isstring(L, -1)) { /* error object is a string? */ luaL_where(L, 1); /* add extra info */ lua_insert(L, -2); lua_concat(L, 2); } lua_error(L); /* propagate error */ } return r; } static int luaB_cocreate (lua_State *L) { lua_State *NL = lua_newthread(L); luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, "Lua function expected"); lua_pushvalue(L, 1); /* move function to top */ lua_xmove(L, NL, 1); /* move function from L to NL */ return 1; } static int luaB_cowrap (lua_State *L) { luaB_cocreate(L); lua_pushcclosure(L, luaB_auxwrap, 1); return 1; } static int luaB_yield (lua_State *L) { return lua_yield(L, lua_gettop(L)); } static int luaB_corunning (lua_State *L) { if (lua_pushthread(L)) lua_pushnil(L); /* main thread is not a coroutine */ return 1; } static const luaL_Reg co_funcs[] = { {"create", luaB_cocreate}, {"resume", luaB_coresume}, {"running", luaB_corunning}, {"status", luaB_costatus}, {"wrap", luaB_cowrap}, {"yield", luaB_yield}, {NULL, NULL} }; /* }====================================================== */ static void auxopen (lua_State *L, const char *name, lua_CFunction f, lua_CFunction u) { lua_pushcfunction(L, u); lua_pushcclosure(L, f, 1); lua_setfield(L, -2, name); } static void base_open (lua_State *L) { /* set global _G */ lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setglobal(L, "_G"); /* open lib into global table */ luaL_register(L, "_G", base_funcs); lua_pushliteral(L, LUA_VERSION); lua_setglobal(L, "_VERSION"); /* set global _VERSION */ /* `ipairs' and `pairs' need auxiliary functions as upvalues */ auxopen(L, "ipairs", luaB_ipairs, ipairsaux); auxopen(L, "pairs", luaB_pairs, luaB_next); /* `newproxy' needs a weaktable as upvalue */ lua_createtable(L, 0, 1); /* new table `w' */ lua_pushvalue(L, -1); /* `w' will be its own metatable */ lua_setmetatable(L, -2); lua_pushliteral(L, "kv"); lua_setfield(L, -2, "__mode"); /* metatable(w).__mode = "kv" */ lua_pushcclosure(L, luaB_newproxy, 1); lua_setglobal(L, "newproxy"); /* set global `newproxy' */ } LUALIB_API int luaopen_base (lua_State *L) { base_open(L); luaL_register(L, LUA_COLIBNAME, co_funcs); return 2; } redis-8.0.2/deps/lua/src/lcode.c000066400000000000000000000512621501533116600163730ustar00rootroot00000000000000/* ** $Id: lcode.c,v 2.25.1.5 2011/01/31 14:53:16 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ #include #define lcode_c #define LUA_CORE #include "lua.h" #include "lcode.h" #include "ldebug.h" #include "ldo.h" #include "lgc.h" #include "llex.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" #include "ltable.h" #define hasjumps(e) ((e)->t != (e)->f) static int isnumeral(expdesc *e) { return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); } void luaK_nil (FuncState *fs, int from, int n) { Instruction *previous; if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ if (fs->pc == 0) { /* function start? */ if (from >= fs->nactvar) return; /* positions are already clean */ } else { previous = &fs->f->code[fs->pc-1]; if (GET_OPCODE(*previous) == OP_LOADNIL) { int pfrom = GETARG_A(*previous); int pto = GETARG_B(*previous); if (pfrom <= from && from <= pto+1) { /* can connect both? */ if (from+n-1 > pto) SETARG_B(*previous, from+n-1); return; } } } } luaK_codeABC(fs, OP_LOADNIL, from, from+n-1, 0); /* else no optimization */ } int luaK_jump (FuncState *fs) { int jpc = fs->jpc; /* save list of jumps to here */ int j; fs->jpc = NO_JUMP; j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); luaK_concat(fs, &j, jpc); /* keep them on hold */ return j; } void luaK_ret (FuncState *fs, int first, int nret) { luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); } static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { luaK_codeABC(fs, op, A, B, C); return luaK_jump(fs); } static void fixjump (FuncState *fs, int pc, int dest) { Instruction *jmp = &fs->f->code[pc]; int offset = dest-(pc+1); lua_assert(dest != NO_JUMP); if (abs(offset) > MAXARG_sBx) luaX_syntaxerror(fs->ls, "control structure too long"); SETARG_sBx(*jmp, offset); } /* ** returns current `pc' and marks it as a jump target (to avoid wrong ** optimizations with consecutive instructions not in the same basic block). */ int luaK_getlabel (FuncState *fs) { fs->lasttarget = fs->pc; return fs->pc; } static int getjump (FuncState *fs, int pc) { int offset = GETARG_sBx(fs->f->code[pc]); if (offset == NO_JUMP) /* point to itself represents end of list */ return NO_JUMP; /* end of list */ else return (pc+1)+offset; /* turn offset into absolute position */ } static Instruction *getjumpcontrol (FuncState *fs, int pc) { Instruction *pi = &fs->f->code[pc]; if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) return pi-1; else return pi; } /* ** check whether list has any jump that do not produce a value ** (or produce an inverted value) */ static int need_value (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) { Instruction i = *getjumpcontrol(fs, list); if (GET_OPCODE(i) != OP_TESTSET) return 1; } return 0; /* not found */ } static int patchtestreg (FuncState *fs, int node, int reg) { Instruction *i = getjumpcontrol(fs, node); if (GET_OPCODE(*i) != OP_TESTSET) return 0; /* cannot patch other instructions */ if (reg != NO_REG && reg != GETARG_B(*i)) SETARG_A(*i, reg); else /* no register to put value or register already has the value */ *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); return 1; } static void removevalues (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) patchtestreg(fs, list, NO_REG); } static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, int dtarget) { while (list != NO_JUMP) { int next = getjump(fs, list); if (patchtestreg(fs, list, reg)) fixjump(fs, list, vtarget); else fixjump(fs, list, dtarget); /* jump to default target */ list = next; } } static void dischargejpc (FuncState *fs) { patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); fs->jpc = NO_JUMP; } void luaK_patchlist (FuncState *fs, int list, int target) { if (target == fs->pc) luaK_patchtohere(fs, list); else { lua_assert(target < fs->pc); patchlistaux(fs, list, target, NO_REG, target); } } void luaK_patchtohere (FuncState *fs, int list) { luaK_getlabel(fs); luaK_concat(fs, &fs->jpc, list); } void luaK_concat (FuncState *fs, int *l1, int l2) { if (l2 == NO_JUMP) return; else if (*l1 == NO_JUMP) *l1 = l2; else { int list = *l1; int next; while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ list = next; fixjump(fs, list, l2); } } void luaK_checkstack (FuncState *fs, int n) { int newstack = fs->freereg + n; if (newstack > fs->f->maxstacksize) { if (newstack >= MAXSTACK) luaX_syntaxerror(fs->ls, "function or expression too complex"); fs->f->maxstacksize = cast_byte(newstack); } } void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } static void freereg (FuncState *fs, int reg) { if (!ISK(reg) && reg >= fs->nactvar) { fs->freereg--; lua_assert(reg == fs->freereg); } } static void freeexp (FuncState *fs, expdesc *e) { if (e->k == VNONRELOC) freereg(fs, e->u.s.info); } static int addk (FuncState *fs, TValue *k, TValue *v) { lua_State *L = fs->L; TValue *idx = luaH_set(L, fs->h, k); Proto *f = fs->f; int oldsize = f->sizek; if (ttisnumber(idx)) { lua_assert(luaO_rawequalObj(&fs->f->k[cast_int(nvalue(idx))], v)); return cast_int(nvalue(idx)); } else { /* constant not found; create a new entry */ setnvalue(idx, cast_num(fs->nk)); luaM_growvector(L, f->k, fs->nk, f->sizek, TValue, MAXARG_Bx, "constant table overflow"); while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); setobj(L, &f->k[fs->nk], v); luaC_barrier(L, f, v); return fs->nk++; } } int luaK_stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->L, &o, s); return addk(fs, &o, &o); } int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; setnvalue(&o, r); return addk(fs, &o, &o); } static int boolK (FuncState *fs, int b) { TValue o; setbvalue(&o, b); return addk(fs, &o, &o); } static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); /* cannot use nil as key; instead use table itself to represent nil */ sethvalue(fs->L, &k, fs->h); return addk(fs, &k, &v); } void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { if (e->k == VCALL) { /* expression is an open function call? */ SETARG_C(getcode(fs, e), nresults+1); } else if (e->k == VVARARG) { SETARG_B(getcode(fs, e), nresults+1); SETARG_A(getcode(fs, e), fs->freereg); luaK_reserveregs(fs, 1); } } void luaK_setoneret (FuncState *fs, expdesc *e) { if (e->k == VCALL) { /* expression is an open function call? */ e->k = VNONRELOC; e->u.s.info = GETARG_A(getcode(fs, e)); } else if (e->k == VVARARG) { SETARG_B(getcode(fs, e), 2); e->k = VRELOCABLE; /* can relocate its simple result */ } } void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { case VLOCAL: { e->k = VNONRELOC; break; } case VUPVAL: { e->u.s.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.s.info, 0); e->k = VRELOCABLE; break; } case VGLOBAL: { e->u.s.info = luaK_codeABx(fs, OP_GETGLOBAL, 0, e->u.s.info); e->k = VRELOCABLE; break; } case VINDEXED: { freereg(fs, e->u.s.aux); freereg(fs, e->u.s.info); e->u.s.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.s.info, e->u.s.aux); e->k = VRELOCABLE; break; } case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; } default: break; /* there is one value available (somewhere) */ } } static int code_label (FuncState *fs, int A, int b, int jump) { luaK_getlabel(fs); /* those instructions may be jump targets */ return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); } static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: { luaK_nil(fs, reg, 1); break; } case VFALSE: case VTRUE: { luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); break; } case VK: { luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info); break; } case VKNUM: { luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval)); break; } case VRELOCABLE: { Instruction *pc = &getcode(fs, e); SETARG_A(*pc, reg); break; } case VNONRELOC: { if (reg != e->u.s.info) luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0); break; } default: { lua_assert(e->k == VVOID || e->k == VJMP); return; /* nothing to do... */ } } e->u.s.info = reg; e->k = VNONRELOC; } static void discharge2anyreg (FuncState *fs, expdesc *e) { if (e->k != VNONRELOC) { luaK_reserveregs(fs, 1); discharge2reg(fs, e, fs->freereg-1); } } static void exp2reg (FuncState *fs, expdesc *e, int reg) { discharge2reg(fs, e, reg); if (e->k == VJMP) luaK_concat(fs, &e->t, e->u.s.info); /* put this jump in `t' list */ if (hasjumps(e)) { int final; /* position after whole expression */ int p_f = NO_JUMP; /* position of an eventual LOAD false */ int p_t = NO_JUMP; /* position of an eventual LOAD true */ if (need_value(fs, e->t) || need_value(fs, e->f)) { int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); p_f = code_label(fs, reg, 0, 1); p_t = code_label(fs, reg, 1, 0); luaK_patchtohere(fs, fj); } final = luaK_getlabel(fs); patchlistaux(fs, e->f, final, reg, p_f); patchlistaux(fs, e->t, final, reg, p_t); } e->f = e->t = NO_JUMP; e->u.s.info = reg; e->k = VNONRELOC; } void luaK_exp2nextreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); freeexp(fs, e); luaK_reserveregs(fs, 1); exp2reg(fs, e, fs->freereg - 1); } int luaK_exp2anyreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); if (e->k == VNONRELOC) { if (!hasjumps(e)) return e->u.s.info; /* exp is already in a register */ if (e->u.s.info >= fs->nactvar) { /* reg. is not a local? */ exp2reg(fs, e, e->u.s.info); /* put value on it */ return e->u.s.info; } } luaK_exp2nextreg(fs, e); /* default */ return e->u.s.info; } void luaK_exp2val (FuncState *fs, expdesc *e) { if (hasjumps(e)) luaK_exp2anyreg(fs, e); else luaK_dischargevars(fs, e); } int luaK_exp2RK (FuncState *fs, expdesc *e) { luaK_exp2val(fs, e); switch (e->k) { case VKNUM: case VTRUE: case VFALSE: case VNIL: { if (fs->nk <= MAXINDEXRK) { /* constant fit in RK operand? */ e->u.s.info = (e->k == VNIL) ? nilK(fs) : (e->k == VKNUM) ? luaK_numberK(fs, e->u.nval) : boolK(fs, (e->k == VTRUE)); e->k = VK; return RKASK(e->u.s.info); } else break; } case VK: { if (e->u.s.info <= MAXINDEXRK) /* constant fit in argC? */ return RKASK(e->u.s.info); else break; } default: break; } /* not a constant in the right range: put it in a register */ return luaK_exp2anyreg(fs, e); } void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); exp2reg(fs, ex, var->u.s.info); return; } case VUPVAL: { int e = luaK_exp2anyreg(fs, ex); luaK_codeABC(fs, OP_SETUPVAL, e, var->u.s.info, 0); break; } case VGLOBAL: { int e = luaK_exp2anyreg(fs, ex); luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info); break; } case VINDEXED: { int e = luaK_exp2RK(fs, ex); luaK_codeABC(fs, OP_SETTABLE, var->u.s.info, var->u.s.aux, e); break; } default: { lua_assert(0); /* invalid var kind to store */ break; } } freeexp(fs, ex); } void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int func; luaK_exp2anyreg(fs, e); freeexp(fs, e); func = fs->freereg; luaK_reserveregs(fs, 2); luaK_codeABC(fs, OP_SELF, func, e->u.s.info, luaK_exp2RK(fs, key)); freeexp(fs, key); e->u.s.info = func; e->k = VNONRELOC; } static void invertjump (FuncState *fs, expdesc *e) { Instruction *pc = getjumpcontrol(fs, e->u.s.info); lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && GET_OPCODE(*pc) != OP_TEST); SETARG_A(*pc, !(GETARG_A(*pc))); } static int jumponcond (FuncState *fs, expdesc *e, int cond) { if (e->k == VRELOCABLE) { Instruction ie = getcode(fs, e); if (GET_OPCODE(ie) == OP_NOT) { fs->pc--; /* remove previous OP_NOT */ return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); } /* else go through */ } discharge2anyreg(fs, e); freeexp(fs, e); return condjump(fs, OP_TESTSET, NO_REG, e->u.s.info, cond); } void luaK_goiftrue (FuncState *fs, expdesc *e) { int pc; /* pc of last jump */ luaK_dischargevars(fs, e); switch (e->k) { case VK: case VKNUM: case VTRUE: { pc = NO_JUMP; /* always true; do nothing */ break; } case VJMP: { invertjump(fs, e); pc = e->u.s.info; break; } default: { pc = jumponcond(fs, e, 0); break; } } luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */ luaK_patchtohere(fs, e->t); e->t = NO_JUMP; } static void luaK_goiffalse (FuncState *fs, expdesc *e) { int pc; /* pc of last jump */ luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { pc = NO_JUMP; /* always false; do nothing */ break; } case VJMP: { pc = e->u.s.info; break; } default: { pc = jumponcond(fs, e, 1); break; } } luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */ luaK_patchtohere(fs, e->f); e->f = NO_JUMP; } static void codenot (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { e->k = VTRUE; break; } case VK: case VKNUM: case VTRUE: { e->k = VFALSE; break; } case VJMP: { invertjump(fs, e); break; } case VRELOCABLE: case VNONRELOC: { discharge2anyreg(fs, e); freeexp(fs, e); e->u.s.info = luaK_codeABC(fs, OP_NOT, 0, e->u.s.info, 0); e->k = VRELOCABLE; break; } default: { lua_assert(0); /* cannot happen */ break; } } /* interchange true and false lists */ { int temp = e->f; e->f = e->t; e->t = temp; } removevalues(fs, e->f); removevalues(fs, e->t); } void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { t->u.s.aux = luaK_exp2RK(fs, k); t->k = VINDEXED; } static int constfolding (OpCode op, expdesc *e1, expdesc *e2) { lua_Number v1, v2, r; if (!isnumeral(e1) || !isnumeral(e2)) return 0; v1 = e1->u.nval; v2 = e2->u.nval; switch (op) { case OP_ADD: r = luai_numadd(v1, v2); break; case OP_SUB: r = luai_numsub(v1, v2); break; case OP_MUL: r = luai_nummul(v1, v2); break; case OP_DIV: if (v2 == 0) return 0; /* do not attempt to divide by 0 */ r = luai_numdiv(v1, v2); break; case OP_MOD: if (v2 == 0) return 0; /* do not attempt to divide by 0 */ r = luai_nummod(v1, v2); break; case OP_POW: r = luai_numpow(v1, v2); break; case OP_UNM: r = luai_numunm(v1); break; case OP_LEN: return 0; /* no constant folding for 'len' */ default: lua_assert(0); r = 0; break; } if (luai_numisnan(r)) return 0; /* do not attempt to produce NaN */ e1->u.nval = r; return 1; } static void codearith (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) { if (constfolding(op, e1, e2)) return; else { int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; int o1 = luaK_exp2RK(fs, e1); if (o1 > o2) { freeexp(fs, e1); freeexp(fs, e2); } else { freeexp(fs, e2); freeexp(fs, e1); } e1->u.s.info = luaK_codeABC(fs, op, 0, o1, o2); e1->k = VRELOCABLE; } } static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, expdesc *e2) { int o1 = luaK_exp2RK(fs, e1); int o2 = luaK_exp2RK(fs, e2); freeexp(fs, e2); freeexp(fs, e1); if (cond == 0 && op != OP_EQ) { int temp; /* exchange args to replace by `<' or `<=' */ temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ cond = 1; } e1->u.s.info = condjump(fs, op, cond, o1, o2); e1->k = VJMP; } void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e) { expdesc e2; e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; switch (op) { case OPR_MINUS: { if (!isnumeral(e)) luaK_exp2anyreg(fs, e); /* cannot operate on non-numeric constants */ codearith(fs, OP_UNM, e, &e2); break; } case OPR_NOT: codenot(fs, e); break; case OPR_LEN: { luaK_exp2anyreg(fs, e); /* cannot operate on constants */ codearith(fs, OP_LEN, e, &e2); break; } default: lua_assert(0); } } void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { switch (op) { case OPR_AND: { luaK_goiftrue(fs, v); break; } case OPR_OR: { luaK_goiffalse(fs, v); break; } case OPR_CONCAT: { luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ break; } case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: case OPR_MOD: case OPR_POW: { if (!isnumeral(v)) luaK_exp2RK(fs, v); break; } default: { luaK_exp2RK(fs, v); break; } } } void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2) { switch (op) { case OPR_AND: { lua_assert(e1->t == NO_JUMP); /* list must be closed */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->f, e1->f); *e1 = *e2; break; } case OPR_OR: { lua_assert(e1->f == NO_JUMP); /* list must be closed */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->t, e1->t); *e1 = *e2; break; } case OPR_CONCAT: { luaK_exp2val(fs, e2); if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { lua_assert(e1->u.s.info == GETARG_B(getcode(fs, e2))-1); freeexp(fs, e1); SETARG_B(getcode(fs, e2), e1->u.s.info); e1->k = VRELOCABLE; e1->u.s.info = e2->u.s.info; } else { luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ codearith(fs, OP_CONCAT, e1, e2); } break; } case OPR_ADD: codearith(fs, OP_ADD, e1, e2); break; case OPR_SUB: codearith(fs, OP_SUB, e1, e2); break; case OPR_MUL: codearith(fs, OP_MUL, e1, e2); break; case OPR_DIV: codearith(fs, OP_DIV, e1, e2); break; case OPR_MOD: codearith(fs, OP_MOD, e1, e2); break; case OPR_POW: codearith(fs, OP_POW, e1, e2); break; case OPR_EQ: codecomp(fs, OP_EQ, 1, e1, e2); break; case OPR_NE: codecomp(fs, OP_EQ, 0, e1, e2); break; case OPR_LT: codecomp(fs, OP_LT, 1, e1, e2); break; case OPR_LE: codecomp(fs, OP_LE, 1, e1, e2); break; case OPR_GT: codecomp(fs, OP_LT, 0, e1, e2); break; case OPR_GE: codecomp(fs, OP_LE, 0, e1, e2); break; default: lua_assert(0); } } void luaK_fixline (FuncState *fs, int line) { fs->f->lineinfo[fs->pc - 1] = line; } static int luaK_code (FuncState *fs, Instruction i, int line) { Proto *f = fs->f; dischargejpc(fs); /* `pc' will change */ /* put new instruction in code array */ luaM_growvector(fs->L, f->code, fs->pc, f->sizecode, Instruction, MAX_INT, "code size overflow"); f->code[fs->pc] = i; /* save corresponding line information */ luaM_growvector(fs->L, f->lineinfo, fs->pc, f->sizelineinfo, int, MAX_INT, "code size overflow"); f->lineinfo[fs->pc] = line; return fs->pc++; } int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { lua_assert(getOpMode(o) == iABC); lua_assert(getBMode(o) != OpArgN || b == 0); lua_assert(getCMode(o) != OpArgN || c == 0); return luaK_code(fs, CREATE_ABC(o, a, b, c), fs->ls->lastline); } int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); lua_assert(getCMode(o) == OpArgN); return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline); } void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; int b = (tostore == LUA_MULTRET) ? 0 : tostore; lua_assert(tostore != 0); if (c <= MAXARG_C) luaK_codeABC(fs, OP_SETLIST, base, b, c); else { luaK_codeABC(fs, OP_SETLIST, base, b, 0); luaK_code(fs, cast(Instruction, c), fs->ls->lastline); } fs->freereg = base + 1; /* free registers with list values */ } redis-8.0.2/deps/lua/src/lcode.h000066400000000000000000000052761501533116600164040ustar00rootroot00000000000000/* ** $Id: lcode.h,v 1.48.1.1 2007/12/27 13:02:25 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ #ifndef lcode_h #define lcode_h #include "llex.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" /* ** Marks the end of a patch list. It is an invalid value both as an absolute ** address, and as a list link (would link an element to itself). */ #define NO_JUMP (-1) /* ** grep "ORDER OPR" if you change these enums */ typedef enum BinOpr { OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, OPR_CONCAT, OPR_NE, OPR_EQ, OPR_LT, OPR_LE, OPR_GT, OPR_GE, OPR_AND, OPR_OR, OPR_NOBINOPR } BinOpr; typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; #define getcode(fs,e) ((fs)->f->code[(e)->u.s.info]) #define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) #define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); LUAI_FUNC int luaK_codeABC (FuncState *fs, OpCode o, int A, int B, int C); LUAI_FUNC void luaK_fixline (FuncState *fs, int line); LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r); LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_jump (FuncState *fs); LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); LUAI_FUNC int luaK_getlabel (FuncState *fs); LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v); LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, expdesc *v2); LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); #endif redis-8.0.2/deps/lua/src/ldblib.c000066400000000000000000000235541501533116600165400ustar00rootroot00000000000000/* ** $Id: ldblib.c,v 1.104.1.4 2009/08/04 18:50:18 roberto Exp $ ** Interface from Lua to its debug API ** See Copyright Notice in lua.h */ #include #include #include #define ldblib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" static int db_getregistry (lua_State *L) { lua_pushvalue(L, LUA_REGISTRYINDEX); return 1; } static int db_getmetatable (lua_State *L) { luaL_checkany(L, 1); if (!lua_getmetatable(L, 1)) { lua_pushnil(L); /* no metatable */ } return 1; } static int db_setmetatable (lua_State *L) { int t = lua_type(L, 2); luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table expected"); lua_settop(L, 2); lua_pushboolean(L, lua_setmetatable(L, 1)); return 1; } static int db_getfenv (lua_State *L) { luaL_checkany(L, 1); lua_getfenv(L, 1); return 1; } static int db_setfenv (lua_State *L) { luaL_checktype(L, 2, LUA_TTABLE); lua_settop(L, 2); if (lua_setfenv(L, 1) == 0) luaL_error(L, LUA_QL("setfenv") " cannot change environment of given object"); return 1; } static void settabss (lua_State *L, const char *i, const char *v) { lua_pushstring(L, v); lua_setfield(L, -2, i); } static void settabsi (lua_State *L, const char *i, int v) { lua_pushinteger(L, v); lua_setfield(L, -2, i); } static lua_State *getthread (lua_State *L, int *arg) { if (lua_isthread(L, 1)) { *arg = 1; return lua_tothread(L, 1); } else { *arg = 0; return L; } } static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) { if (L == L1) { lua_pushvalue(L, -2); lua_remove(L, -3); } else lua_xmove(L1, L, 1); lua_setfield(L, -2, fname); } static int db_getinfo (lua_State *L) { lua_Debug ar; int arg; lua_State *L1 = getthread(L, &arg); const char *options = luaL_optstring(L, arg+2, "flnSu"); if (lua_isnumber(L, arg+1)) { if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) { lua_pushnil(L); /* level out of range */ return 1; } } else if (lua_isfunction(L, arg+1)) { lua_pushfstring(L, ">%s", options); options = lua_tostring(L, -1); lua_pushvalue(L, arg+1); lua_xmove(L, L1, 1); } else return luaL_argerror(L, arg+1, "function or level expected"); if (!lua_getinfo(L1, options, &ar)) return luaL_argerror(L, arg+2, "invalid option"); lua_createtable(L, 0, 2); if (strchr(options, 'S')) { settabss(L, "source", ar.source); settabss(L, "short_src", ar.short_src); settabsi(L, "linedefined", ar.linedefined); settabsi(L, "lastlinedefined", ar.lastlinedefined); settabss(L, "what", ar.what); } if (strchr(options, 'l')) settabsi(L, "currentline", ar.currentline); if (strchr(options, 'u')) settabsi(L, "nups", ar.nups); if (strchr(options, 'n')) { settabss(L, "name", ar.name); settabss(L, "namewhat", ar.namewhat); } if (strchr(options, 'L')) treatstackoption(L, L1, "activelines"); if (strchr(options, 'f')) treatstackoption(L, L1, "func"); return 1; /* return table */ } static int db_getlocal (lua_State *L) { int arg; lua_State *L1 = getthread(L, &arg); lua_Debug ar; const char *name; if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); name = lua_getlocal(L1, &ar, luaL_checkint(L, arg+2)); if (name) { lua_xmove(L1, L, 1); lua_pushstring(L, name); lua_pushvalue(L, -2); return 2; } else { lua_pushnil(L); return 1; } } static int db_setlocal (lua_State *L) { int arg; lua_State *L1 = getthread(L, &arg); lua_Debug ar; if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */ return luaL_argerror(L, arg+1, "level out of range"); luaL_checkany(L, arg+3); lua_settop(L, arg+3); lua_xmove(L, L1, 1); lua_pushstring(L, lua_setlocal(L1, &ar, luaL_checkint(L, arg+2))); return 1; } static int auxupvalue (lua_State *L, int get) { const char *name; int n = luaL_checkint(L, 2); luaL_checktype(L, 1, LUA_TFUNCTION); if (lua_iscfunction(L, 1)) return 0; /* cannot touch C upvalues from Lua */ name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); if (name == NULL) return 0; lua_pushstring(L, name); lua_insert(L, -(get+1)); return get + 1; } static int db_getupvalue (lua_State *L) { return auxupvalue(L, 1); } static int db_setupvalue (lua_State *L) { luaL_checkany(L, 3); return auxupvalue(L, 0); } static const char KEY_HOOK = 'h'; static void hookf (lua_State *L, lua_Debug *ar) { static const char *const hooknames[] = {"call", "return", "line", "count", "tail return"}; lua_pushlightuserdata(L, (void *)&KEY_HOOK); lua_rawget(L, LUA_REGISTRYINDEX); lua_pushlightuserdata(L, L); lua_rawget(L, -2); if (lua_isfunction(L, -1)) { lua_pushstring(L, hooknames[(int)ar->event]); if (ar->currentline >= 0) lua_pushinteger(L, ar->currentline); else lua_pushnil(L); lua_assert(lua_getinfo(L, "lS", ar)); lua_call(L, 2, 0); } } static int makemask (const char *smask, int count) { int mask = 0; if (strchr(smask, 'c')) mask |= LUA_MASKCALL; if (strchr(smask, 'r')) mask |= LUA_MASKRET; if (strchr(smask, 'l')) mask |= LUA_MASKLINE; if (count > 0) mask |= LUA_MASKCOUNT; return mask; } static char *unmakemask (int mask, char *smask) { int i = 0; if (mask & LUA_MASKCALL) smask[i++] = 'c'; if (mask & LUA_MASKRET) smask[i++] = 'r'; if (mask & LUA_MASKLINE) smask[i++] = 'l'; smask[i] = '\0'; return smask; } static void gethooktable (lua_State *L) { lua_pushlightuserdata(L, (void *)&KEY_HOOK); lua_rawget(L, LUA_REGISTRYINDEX); if (!lua_istable(L, -1)) { lua_pop(L, 1); lua_createtable(L, 0, 1); lua_pushlightuserdata(L, (void *)&KEY_HOOK); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); } } static int db_sethook (lua_State *L) { int arg, mask, count; lua_Hook func; lua_State *L1 = getthread(L, &arg); if (lua_isnoneornil(L, arg+1)) { lua_settop(L, arg+1); func = NULL; mask = 0; count = 0; /* turn off hooks */ } else { const char *smask = luaL_checkstring(L, arg+2); luaL_checktype(L, arg+1, LUA_TFUNCTION); count = luaL_optint(L, arg+3, 0); func = hookf; mask = makemask(smask, count); } gethooktable(L); lua_pushlightuserdata(L, L1); lua_pushvalue(L, arg+1); lua_rawset(L, -3); /* set new hook */ lua_pop(L, 1); /* remove hook table */ lua_sethook(L1, func, mask, count); /* set hooks */ return 0; } static int db_gethook (lua_State *L) { int arg; lua_State *L1 = getthread(L, &arg); char buff[5]; int mask = lua_gethookmask(L1); lua_Hook hook = lua_gethook(L1); if (hook != NULL && hook != hookf) /* external hook? */ lua_pushliteral(L, "external hook"); else { gethooktable(L); lua_pushlightuserdata(L, L1); lua_rawget(L, -2); /* get hook */ lua_remove(L, -2); /* remove hook table */ } lua_pushstring(L, unmakemask(mask, buff)); lua_pushinteger(L, lua_gethookcount(L1)); return 3; } static int db_debug (lua_State *L) { for (;;) { char buffer[250]; fputs("lua_debug> ", stderr); if (fgets(buffer, sizeof(buffer), stdin) == 0 || strcmp(buffer, "cont\n") == 0) return 0; if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") || lua_pcall(L, 0, 0, 0)) { fputs(lua_tostring(L, -1), stderr); fputs("\n", stderr); } lua_settop(L, 0); /* remove eventual returns */ } } #define LEVELS1 12 /* size of the first part of the stack */ #define LEVELS2 10 /* size of the second part of the stack */ static int db_errorfb (lua_State *L) { int level; int firstpart = 1; /* still before eventual `...' */ int arg; lua_State *L1 = getthread(L, &arg); lua_Debug ar; if (lua_isnumber(L, arg+2)) { level = (int)lua_tointeger(L, arg+2); lua_pop(L, 1); } else level = (L == L1) ? 1 : 0; /* level 0 may be this own function */ if (lua_gettop(L) == arg) lua_pushliteral(L, ""); else if (!lua_isstring(L, arg+1)) return 1; /* message is not a string */ else lua_pushliteral(L, "\n"); lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { if (level > LEVELS1 && firstpart) { /* no more than `LEVELS2' more levels? */ if (!lua_getstack(L1, level+LEVELS2, &ar)) level--; /* keep going */ else { lua_pushliteral(L, "\n\t..."); /* too many levels */ while (lua_getstack(L1, level+LEVELS2, &ar)) /* find last levels */ level++; } firstpart = 0; continue; } lua_pushliteral(L, "\n\t"); lua_getinfo(L1, "Snl", &ar); lua_pushfstring(L, "%s:", ar.short_src); if (ar.currentline > 0) lua_pushfstring(L, "%d:", ar.currentline); if (*ar.namewhat != '\0') /* is there a name? */ lua_pushfstring(L, " in function " LUA_QS, ar.name); else { if (*ar.what == 'm') /* main? */ lua_pushfstring(L, " in main chunk"); else if (*ar.what == 'C' || *ar.what == 't') lua_pushliteral(L, " ?"); /* C function or tail call */ else lua_pushfstring(L, " in function <%s:%d>", ar.short_src, ar.linedefined); } lua_concat(L, lua_gettop(L) - arg); } lua_concat(L, lua_gettop(L) - arg); return 1; } static const luaL_Reg dblib[] = { {"debug", db_debug}, {"getfenv", db_getfenv}, {"gethook", db_gethook}, {"getinfo", db_getinfo}, {"getlocal", db_getlocal}, {"getregistry", db_getregistry}, {"getmetatable", db_getmetatable}, {"getupvalue", db_getupvalue}, {"setfenv", db_setfenv}, {"sethook", db_sethook}, {"setlocal", db_setlocal}, {"setmetatable", db_setmetatable}, {"setupvalue", db_setupvalue}, {"traceback", db_errorfb}, {NULL, NULL} }; LUALIB_API int luaopen_debug (lua_State *L) { luaL_register(L, LUA_DBLIBNAME, dblib); return 1; } redis-8.0.2/deps/lua/src/ldebug.c000066400000000000000000000407071501533116600165510ustar00rootroot00000000000000/* ** $Id: ldebug.c,v 2.29.1.6 2008/05/08 16:56:26 roberto Exp $ ** Debug Interface ** See Copyright Notice in lua.h */ #include #include #include #define ldebug_c #define LUA_CORE #include "lua.h" #include "lapi.h" #include "lcode.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lobject.h" #include "lopcodes.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lvm.h" static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); static int currentpc (lua_State *L, CallInfo *ci) { if (!isLua(ci)) return -1; /* function is not a Lua function? */ if (ci == L->ci) ci->savedpc = L->savedpc; return pcRel(ci->savedpc, ci_func(ci)->l.p); } static int currentline (lua_State *L, CallInfo *ci) { int pc = currentpc(L, ci); if (pc < 0) return -1; /* only active lua functions have current-line information */ else return getline(ci_func(ci)->l.p, pc); } /* ** this function can be called asynchronous (e.g. during a signal) */ LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { if (func == NULL || mask == 0) { /* turn off hooks? */ mask = 0; func = NULL; } L->hook = func; L->basehookcount = count; resethookcount(L); L->hookmask = cast_byte(mask); return 1; } LUA_API lua_Hook lua_gethook (lua_State *L) { return L->hook; } LUA_API int lua_gethookmask (lua_State *L) { return L->hookmask; } LUA_API int lua_gethookcount (lua_State *L) { return L->basehookcount; } LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { int status; CallInfo *ci; lua_lock(L); for (ci = L->ci; level > 0 && ci > L->base_ci; ci--) { level--; if (f_isLua(ci)) /* Lua function? */ level -= ci->tailcalls; /* skip lost tail calls */ } if (level == 0 && ci > L->base_ci) { /* level found? */ status = 1; ar->i_ci = cast_int(ci - L->base_ci); } else if (level < 0) { /* level is of a lost tail call? */ status = 1; ar->i_ci = 0; } else status = 0; /* no such level */ lua_unlock(L); return status; } static Proto *getluaproto (CallInfo *ci) { return (isLua(ci) ? ci_func(ci)->l.p : NULL); } static const char *findlocal (lua_State *L, CallInfo *ci, int n) { const char *name; Proto *fp = getluaproto(ci); if (fp && (name = luaF_getlocalname(fp, n, currentpc(L, ci))) != NULL) return name; /* is a local variable in a Lua function */ else { StkId limit = (ci == L->ci) ? L->top : (ci+1)->func; if (limit - ci->base >= n && n > 0) /* is 'n' inside 'ci' stack? */ return "(*temporary)"; else return NULL; } } LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { CallInfo *ci = L->base_ci + ar->i_ci; const char *name = findlocal(L, ci, n); lua_lock(L); if (name) luaA_pushobject(L, ci->base + (n - 1)); lua_unlock(L); return name; } LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { CallInfo *ci = L->base_ci + ar->i_ci; const char *name = findlocal(L, ci, n); lua_lock(L); if (name) setobjs2s(L, ci->base + (n - 1), L->top - 1); L->top--; /* pop value */ lua_unlock(L); return name; } static void funcinfo (lua_Debug *ar, Closure *cl) { if (cl->c.isC) { ar->source = "=[C]"; ar->linedefined = -1; ar->lastlinedefined = -1; ar->what = "C"; } else { ar->source = getstr(cl->l.p->source); ar->linedefined = cl->l.p->linedefined; ar->lastlinedefined = cl->l.p->lastlinedefined; ar->what = (ar->linedefined == 0) ? "main" : "Lua"; } luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); } static void info_tailcall (lua_Debug *ar) { ar->name = ar->namewhat = ""; ar->what = "tail"; ar->lastlinedefined = ar->linedefined = ar->currentline = -1; ar->source = "=(tail call)"; luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); ar->nups = 0; } static void collectvalidlines (lua_State *L, Closure *f) { if (f == NULL || f->c.isC) { setnilvalue(L->top); } else { Table *t = luaH_new(L, 0, 0); int *lineinfo = f->l.p->lineinfo; int i; for (i=0; il.p->sizelineinfo; i++) setbvalue(luaH_setnum(L, t, lineinfo[i]), 1); sethvalue(L, L->top, t); } incr_top(L); } static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, Closure *f, CallInfo *ci) { int status = 1; if (f == NULL) { info_tailcall(ar); return status; } for (; *what; what++) { switch (*what) { case 'S': { funcinfo(ar, f); break; } case 'l': { ar->currentline = (ci) ? currentline(L, ci) : -1; break; } case 'u': { ar->nups = f->c.nupvalues; break; } case 'n': { ar->namewhat = (ci) ? getfuncname(L, ci, &ar->name) : NULL; if (ar->namewhat == NULL) { ar->namewhat = ""; /* not found */ ar->name = NULL; } break; } case 'L': case 'f': /* handled by lua_getinfo */ break; default: status = 0; /* invalid option */ } } return status; } LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { int status; Closure *f = NULL; CallInfo *ci = NULL; lua_lock(L); if (*what == '>') { StkId func = L->top - 1; luai_apicheck(L, ttisfunction(func)); what++; /* skip the '>' */ f = clvalue(func); L->top--; /* pop function */ } else if (ar->i_ci != 0) { /* no tail call? */ ci = L->base_ci + ar->i_ci; lua_assert(ttisfunction(ci->func)); f = clvalue(ci->func); } status = auxgetinfo(L, what, ar, f, ci); if (strchr(what, 'f')) { if (f == NULL) setnilvalue(L->top); else setclvalue(L, L->top, f); incr_top(L); } if (strchr(what, 'L')) collectvalidlines(L, f); lua_unlock(L); return status; } /* ** {====================================================== ** Symbolic Execution and code checker ** ======================================================= */ #define check(x) if (!(x)) return 0; #define checkjump(pt,pc) check(0 <= pc && pc < pt->sizecode) #define checkreg(pt,reg) check((reg) < (pt)->maxstacksize) static int precheck (const Proto *pt) { check(pt->maxstacksize <= MAXSTACK); check(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize); check(!(pt->is_vararg & VARARG_NEEDSARG) || (pt->is_vararg & VARARG_HASARG)); check(pt->sizeupvalues <= pt->nups); check(pt->sizelineinfo == pt->sizecode || pt->sizelineinfo == 0); check(pt->sizecode > 0 && GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN); return 1; } #define checkopenop(pt,pc) luaG_checkopenop((pt)->code[(pc)+1]) int luaG_checkopenop (Instruction i) { switch (GET_OPCODE(i)) { case OP_CALL: case OP_TAILCALL: case OP_RETURN: case OP_SETLIST: { check(GETARG_B(i) == 0); return 1; } default: return 0; /* invalid instruction after an open call */ } } static int checkArgMode (const Proto *pt, int r, enum OpArgMask mode) { switch (mode) { case OpArgN: check(r == 0); break; case OpArgU: break; case OpArgR: checkreg(pt, r); break; case OpArgK: check(ISK(r) ? INDEXK(r) < pt->sizek : r < pt->maxstacksize); break; } return 1; } static Instruction symbexec (const Proto *pt, int lastpc, int reg) { int pc; int last; /* stores position of last instruction that changed `reg' */ last = pt->sizecode-1; /* points to final return (a `neutral' instruction) */ check(precheck(pt)); for (pc = 0; pc < lastpc; pc++) { Instruction i = pt->code[pc]; OpCode op = GET_OPCODE(i); int a = GETARG_A(i); int b = 0; int c = 0; check(op < NUM_OPCODES); checkreg(pt, a); switch (getOpMode(op)) { case iABC: { b = GETARG_B(i); c = GETARG_C(i); check(checkArgMode(pt, b, getBMode(op))); check(checkArgMode(pt, c, getCMode(op))); break; } case iABx: { b = GETARG_Bx(i); if (getBMode(op) == OpArgK) check(b < pt->sizek); break; } case iAsBx: { b = GETARG_sBx(i); if (getBMode(op) == OpArgR) { int dest = pc+1+b; check(0 <= dest && dest < pt->sizecode); if (dest > 0) { int j; /* check that it does not jump to a setlist count; this is tricky, because the count from a previous setlist may have the same value of an invalid setlist; so, we must go all the way back to the first of them (if any) */ for (j = 0; j < dest; j++) { Instruction d = pt->code[dest-1-j]; if (!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)) break; } /* if 'j' is even, previous value is not a setlist (even if it looks like one) */ check((j&1) == 0); } } break; } } if (testAMode(op)) { if (a == reg) last = pc; /* change register `a' */ } if (testTMode(op)) { check(pc+2 < pt->sizecode); /* check skip */ check(GET_OPCODE(pt->code[pc+1]) == OP_JMP); } switch (op) { case OP_LOADBOOL: { if (c == 1) { /* does it jump? */ check(pc+2 < pt->sizecode); /* check its jump */ check(GET_OPCODE(pt->code[pc+1]) != OP_SETLIST || GETARG_C(pt->code[pc+1]) != 0); } break; } case OP_LOADNIL: { if (a <= reg && reg <= b) last = pc; /* set registers from `a' to `b' */ break; } case OP_GETUPVAL: case OP_SETUPVAL: { check(b < pt->nups); break; } case OP_GETGLOBAL: case OP_SETGLOBAL: { check(ttisstring(&pt->k[b])); break; } case OP_SELF: { checkreg(pt, a+1); if (reg == a+1) last = pc; break; } case OP_CONCAT: { check(b < c); /* at least two operands */ break; } case OP_TFORLOOP: { check(c >= 1); /* at least one result (control variable) */ checkreg(pt, a+2+c); /* space for results */ if (reg >= a+2) last = pc; /* affect all regs above its base */ break; } case OP_FORLOOP: case OP_FORPREP: checkreg(pt, a+3); /* go through */ case OP_JMP: { int dest = pc+1+b; /* not full check and jump is forward and do not skip `lastpc'? */ if (reg != NO_REG && pc < dest && dest <= lastpc) pc += b; /* do the jump */ break; } case OP_CALL: case OP_TAILCALL: { if (b != 0) { checkreg(pt, a+b-1); } c--; /* c = num. returns */ if (c == LUA_MULTRET) { check(checkopenop(pt, pc)); } else if (c != 0) checkreg(pt, a+c-1); if (reg >= a) last = pc; /* affect all registers above base */ break; } case OP_RETURN: { b--; /* b = num. returns */ if (b > 0) checkreg(pt, a+b-1); break; } case OP_SETLIST: { if (b > 0) checkreg(pt, a + b); if (c == 0) { pc++; check(pc < pt->sizecode - 1); } break; } case OP_CLOSURE: { int nup, j; check(b < pt->sizep); nup = pt->p[b]->nups; check(pc + nup < pt->sizecode); for (j = 1; j <= nup; j++) { OpCode op1 = GET_OPCODE(pt->code[pc + j]); check(op1 == OP_GETUPVAL || op1 == OP_MOVE); } if (reg != NO_REG) /* tracing? */ pc += nup; /* do not 'execute' these pseudo-instructions */ break; } case OP_VARARG: { check((pt->is_vararg & VARARG_ISVARARG) && !(pt->is_vararg & VARARG_NEEDSARG)); b--; if (b == LUA_MULTRET) check(checkopenop(pt, pc)); checkreg(pt, a+b-1); break; } default: break; } } return pt->code[last]; } #undef check #undef checkjump #undef checkreg /* }====================================================== */ int luaG_checkcode (const Proto *pt) { return (symbexec(pt, pt->sizecode, NO_REG) != 0); } static const char *kname (Proto *p, int c) { if (ISK(c) && ttisstring(&p->k[INDEXK(c)])) return svalue(&p->k[INDEXK(c)]); else return "?"; } static const char *getobjname (lua_State *L, CallInfo *ci, int stackpos, const char **name) { if (isLua(ci)) { /* a Lua function? */ Proto *p = ci_func(ci)->l.p; int pc = currentpc(L, ci); Instruction i; *name = luaF_getlocalname(p, stackpos+1, pc); if (*name) /* is a local? */ return "local"; i = symbexec(p, pc, stackpos); /* try symbolic execution */ lua_assert(pc != -1); switch (GET_OPCODE(i)) { case OP_GETGLOBAL: { int g = GETARG_Bx(i); /* global index */ lua_assert(ttisstring(&p->k[g])); *name = svalue(&p->k[g]); return "global"; } case OP_MOVE: { int a = GETARG_A(i); int b = GETARG_B(i); /* move from `b' to `a' */ if (b < a) return getobjname(L, ci, b, name); /* get name for `b' */ break; } case OP_GETTABLE: { int k = GETARG_C(i); /* key index */ *name = kname(p, k); return "field"; } case OP_GETUPVAL: { int u = GETARG_B(i); /* upvalue index */ *name = p->upvalues ? getstr(p->upvalues[u]) : "?"; return "upvalue"; } case OP_SELF: { int k = GETARG_C(i); /* key index */ *name = kname(p, k); return "method"; } default: break; } } return NULL; /* no useful name found */ } static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { Instruction i; if ((isLua(ci) && ci->tailcalls > 0) || !isLua(ci - 1)) return NULL; /* calling function is not Lua (or is unknown) */ ci--; /* calling function */ i = ci_func(ci)->l.p->code[currentpc(L, ci)]; if (GET_OPCODE(i) == OP_CALL || GET_OPCODE(i) == OP_TAILCALL || GET_OPCODE(i) == OP_TFORLOOP) return getobjname(L, ci, GETARG_A(i), name); else return NULL; /* no useful name can be found */ } /* only ANSI way to check whether a pointer points to an array */ static int isinstack (CallInfo *ci, const TValue *o) { StkId p; for (p = ci->base; p < ci->top; p++) if (o == p) return 1; return 0; } void luaG_typeerror (lua_State *L, const TValue *o, const char *op) { const char *name = NULL; const char *t = luaT_typenames[ttype(o)]; const char *kind = (isinstack(L->ci, o)) ? getobjname(L, L->ci, cast_int(o - L->base), &name) : NULL; if (kind) luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)", op, kind, name, t); else luaG_runerror(L, "attempt to %s a %s value", op, t); } void luaG_concaterror (lua_State *L, StkId p1, StkId p2) { if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; lua_assert(!ttisstring(p1) && !ttisnumber(p1)); luaG_typeerror(L, p1, "concatenate"); } void luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) { TValue temp; if (luaV_tonumber(p1, &temp) == NULL) p2 = p1; /* first operand is wrong */ luaG_typeerror(L, p2, "perform arithmetic on"); } int luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { const char *t1 = luaT_typenames[ttype(p1)]; const char *t2 = luaT_typenames[ttype(p2)]; if (t1[2] == t2[2]) luaG_runerror(L, "attempt to compare two %s values", t1); else luaG_runerror(L, "attempt to compare %s with %s", t1, t2); return 0; } static void addinfo (lua_State *L, const char *msg) { CallInfo *ci = L->ci; if (isLua(ci)) { /* is Lua code? */ char buff[LUA_IDSIZE]; /* add file:line information */ int line = currentline(L, ci); luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE); luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } } void luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { /* is there an error handling function? */ StkId errfunc = restorestack(L, L->errfunc); if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR); setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ incr_top(L); luaD_call(L, L->top - 2, 1); /* call it */ } luaD_throw(L, LUA_ERRRUN); } void luaG_runerror (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); addinfo(L, luaO_pushvfstring(L, fmt, argp)); va_end(argp); luaG_errormsg(L); } redis-8.0.2/deps/lua/src/ldebug.h000066400000000000000000000020451501533116600165470ustar00rootroot00000000000000/* ** $Id: ldebug.h,v 2.3.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions from Debug Interface module ** See Copyright Notice in lua.h */ #ifndef ldebug_h #define ldebug_h #include "lstate.h" #define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) #define getline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0) #define resethookcount(L) (L->hookcount = L->basehookcount) LUAI_FUNC void luaG_typeerror (lua_State *L, const TValue *o, const char *opname); LUAI_FUNC void luaG_concaterror (lua_State *L, StkId p1, StkId p2); LUAI_FUNC void luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC int luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC void luaG_runerror (lua_State *L, const char *fmt, ...); LUAI_FUNC void luaG_errormsg (lua_State *L); LUAI_FUNC int luaG_checkcode (const Proto *pt); LUAI_FUNC int luaG_checkopenop (Instruction i); #endif redis-8.0.2/deps/lua/src/ldo.c000066400000000000000000000350131501533116600160570ustar00rootroot00000000000000/* ** $Id: ldo.c,v 2.38.1.4 2012/01/18 02:27:10 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ #include #include #include #define ldo_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lundump.h" #include "lvm.h" #include "lzio.h" /* ** {====================================================== ** Error-recovery functions ** ======================================================= */ /* chain list of long jump buffers */ struct lua_longjmp { struct lua_longjmp *previous; luai_jmpbuf b; volatile int status; /* error code */ }; void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { switch (errcode) { case LUA_ERRMEM: { setsvalue2s(L, oldtop, luaS_newliteral(L, MEMERRMSG)); break; } case LUA_ERRERR: { setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); break; } case LUA_ERRSYNTAX: case LUA_ERRRUN: { setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ break; } } L->top = oldtop + 1; } static void restore_stack_limit (lua_State *L) { lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); if (L->size_ci > LUAI_MAXCALLS) { /* there was an overflow? */ int inuse = cast_int(L->ci - L->base_ci); if (inuse + 1 < LUAI_MAXCALLS) /* can `undo' overflow? */ luaD_reallocCI(L, LUAI_MAXCALLS); } } static void resetstack (lua_State *L, int status) { L->ci = L->base_ci; L->base = L->ci->base; luaF_close(L, L->base); /* close eventual pending closures */ luaD_seterrorobj(L, status, L->base); L->nCcalls = L->baseCcalls; L->allowhook = 1; restore_stack_limit(L); L->errfunc = 0; L->errorJmp = NULL; } void luaD_throw (lua_State *L, int errcode) { if (L->errorJmp) { L->errorJmp->status = errcode; LUAI_THROW(L, L->errorJmp); } else { L->status = cast_byte(errcode); if (G(L)->panic) { resetstack(L, errcode); lua_unlock(L); G(L)->panic(L); } exit(EXIT_FAILURE); } } int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { struct lua_longjmp lj; lj.status = 0; lj.previous = L->errorJmp; /* chain new error handler */ L->errorJmp = &lj; LUAI_TRY(L, &lj, (*f)(L, ud); ); L->errorJmp = lj.previous; /* restore old error handler */ return lj.status; } /* }====================================================== */ static void correctstack (lua_State *L, TValue *oldstack) { CallInfo *ci; GCObject *up; L->top = (L->top - oldstack) + L->stack; for (up = L->openupval; up != NULL; up = up->gch.next) gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; for (ci = L->base_ci; ci <= L->ci; ci++) { ci->top = (ci->top - oldstack) + L->stack; ci->base = (ci->base - oldstack) + L->stack; ci->func = (ci->func - oldstack) + L->stack; } L->base = (L->base - oldstack) + L->stack; } void luaD_reallocstack (lua_State *L, int newsize) { TValue *oldstack = L->stack; int realsize = newsize + 1 + EXTRA_STACK; lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue); L->stacksize = realsize; L->stack_last = L->stack+newsize; correctstack(L, oldstack); } void luaD_reallocCI (lua_State *L, int newsize) { CallInfo *oldci = L->base_ci; luaM_reallocvector(L, L->base_ci, L->size_ci, newsize, CallInfo); L->size_ci = newsize; L->ci = (L->ci - oldci) + L->base_ci; L->end_ci = L->base_ci + L->size_ci - 1; } void luaD_growstack (lua_State *L, int n) { if (n <= L->stacksize) /* double size is enough? */ luaD_reallocstack(L, 2*L->stacksize); else luaD_reallocstack(L, L->stacksize + n); } static CallInfo *growCI (lua_State *L) { if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */ luaD_throw(L, LUA_ERRERR); else { luaD_reallocCI(L, 2*L->size_ci); if (L->size_ci > LUAI_MAXCALLS) luaG_runerror(L, "stack overflow"); } return ++L->ci; } void luaD_callhook (lua_State *L, int event, int line) { lua_Hook hook = L->hook; if (hook && L->allowhook) { ptrdiff_t top = savestack(L, L->top); ptrdiff_t ci_top = savestack(L, L->ci->top); lua_Debug ar; ar.event = event; ar.currentline = line; if (event == LUA_HOOKTAILRET) ar.i_ci = 0; /* tail call; no debug information about it */ else ar.i_ci = cast_int(L->ci - L->base_ci); luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ L->ci->top = L->top + LUA_MINSTACK; lua_assert(L->ci->top <= L->stack_last); L->allowhook = 0; /* cannot call hooks inside a hook */ lua_unlock(L); (*hook)(L, &ar); lua_lock(L); lua_assert(!L->allowhook); L->allowhook = 1; L->ci->top = restorestack(L, ci_top); L->top = restorestack(L, top); } } static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { int i; int nfixargs = p->numparams; Table *htab = NULL; StkId base, fixed; for (; actual < nfixargs; ++actual) setnilvalue(L->top++); #if defined(LUA_COMPAT_VARARG) if (p->is_vararg & VARARG_NEEDSARG) { /* compat. with old-style vararg? */ int nvar = actual - nfixargs; /* number of extra arguments */ lua_assert(p->is_vararg & VARARG_HASARG); luaC_checkGC(L); luaD_checkstack(L, p->maxstacksize); htab = luaH_new(L, nvar, 1); /* create `arg' table */ for (i=0; itop - nvar + i); /* store counter in field `n' */ setnvalue(luaH_setstr(L, htab, luaS_newliteral(L, "n")), cast_num(nvar)); } #endif /* move fixed parameters to final position */ fixed = L->top - actual; /* first fixed argument */ base = L->top; /* final position of first argument */ for (i=0; itop++, fixed+i); setnilvalue(fixed+i); } /* add `arg' parameter */ if (htab) { sethvalue(L, L->top++, htab); lua_assert(iswhite(obj2gco(htab))); } return base; } static StkId tryfuncTM (lua_State *L, StkId func) { const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); StkId p; ptrdiff_t funcr = savestack(L, func); if (!ttisfunction(tm)) luaG_typeerror(L, func, "call"); /* Open a hole inside the stack at `func' */ for (p = L->top; p > func; p--) setobjs2s(L, p, p-1); incr_top(L); func = restorestack(L, funcr); /* previous call may change stack */ setobj2s(L, func, tm); /* tag method is the new function to be called */ return func; } #define inc_ci(L) \ ((L->ci == L->end_ci) ? growCI(L) : \ (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci)) int luaD_precall (lua_State *L, StkId func, int nresults) { LClosure *cl; ptrdiff_t funcr; if (!ttisfunction(func)) /* `func' is not a function? */ func = tryfuncTM(L, func); /* check the `function' tag method */ funcr = savestack(L, func); cl = &clvalue(func)->l; L->ci->savedpc = L->savedpc; if (!cl->isC) { /* Lua function? prepare its call */ CallInfo *ci; StkId st, base; Proto *p = cl->p; luaD_checkstack(L, p->maxstacksize + p->numparams); func = restorestack(L, funcr); if (!p->is_vararg) { /* no varargs? */ base = func + 1; if (L->top > base + p->numparams) L->top = base + p->numparams; } else { /* vararg function */ int nargs = cast_int(L->top - func) - 1; base = adjust_varargs(L, p, nargs); func = restorestack(L, funcr); /* previous call may change the stack */ } ci = inc_ci(L); /* now `enter' new function */ ci->func = func; L->base = ci->base = base; ci->top = L->base + p->maxstacksize; lua_assert(ci->top <= L->stack_last); L->savedpc = p->code; /* starting point */ ci->tailcalls = 0; ci->nresults = nresults; for (st = L->top; st < ci->top; st++) setnilvalue(st); L->top = ci->top; if (L->hookmask & LUA_MASKCALL) { L->savedpc++; /* hooks assume 'pc' is already incremented */ luaD_callhook(L, LUA_HOOKCALL, -1); L->savedpc--; /* correct 'pc' */ } return PCRLUA; } else { /* if is a C function, call it */ CallInfo *ci; int n; luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ ci = inc_ci(L); /* now `enter' new function */ ci->func = restorestack(L, funcr); L->base = ci->base = ci->func + 1; ci->top = L->top + LUA_MINSTACK; lua_assert(ci->top <= L->stack_last); ci->nresults = nresults; if (L->hookmask & LUA_MASKCALL) luaD_callhook(L, LUA_HOOKCALL, -1); lua_unlock(L); n = (*curr_func(L)->c.f)(L); /* do the actual call */ lua_lock(L); if (n < 0) /* yielding? */ return PCRYIELD; else { luaD_poscall(L, L->top - n); return PCRC; } } } static StkId callrethooks (lua_State *L, StkId firstResult) { ptrdiff_t fr = savestack(L, firstResult); /* next call may change stack */ luaD_callhook(L, LUA_HOOKRET, -1); if (f_isLua(L->ci)) { /* Lua function? */ while ((L->hookmask & LUA_MASKRET) && L->ci->tailcalls--) /* tail calls */ luaD_callhook(L, LUA_HOOKTAILRET, -1); } return restorestack(L, fr); } int luaD_poscall (lua_State *L, StkId firstResult) { StkId res; int wanted, i; CallInfo *ci; if (L->hookmask & LUA_MASKRET) firstResult = callrethooks(L, firstResult); ci = L->ci--; res = ci->func; /* res == final position of 1st result */ wanted = ci->nresults; L->base = (ci - 1)->base; /* restore base */ L->savedpc = (ci - 1)->savedpc; /* restore savedpc */ /* move results to correct place */ for (i = wanted; i != 0 && firstResult < L->top; i--) setobjs2s(L, res++, firstResult++); while (i-- > 0) setnilvalue(res++); L->top = res; return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ } /* ** Call a function (C or Lua). The function to be called is at *func. ** The arguments are on the stack, right after the function. ** When returns, all the results are on the stack, starting at the original ** function position. */ void luaD_call (lua_State *L, StkId func, int nResults) { if (++L->nCcalls >= LUAI_MAXCCALLS) { if (L->nCcalls == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */ luaV_execute(L, 1); /* call it */ L->nCcalls--; luaC_checkGC(L); } static void resume (lua_State *L, void *ud) { StkId firstArg = cast(StkId, ud); CallInfo *ci = L->ci; if (L->status == 0) { /* start coroutine? */ lua_assert(ci == L->base_ci && firstArg > L->base); if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA) return; } else { /* resuming from previous yield */ lua_assert(L->status == LUA_YIELD); L->status = 0; if (!f_isLua(ci)) { /* `common' yield? */ /* finish interrupted execution of `OP_CALL' */ lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL || GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL); if (luaD_poscall(L, firstArg)) /* complete it... */ L->top = L->ci->top; /* and correct top if not multiple results */ } else /* yielded inside a hook: just continue its execution */ L->base = L->ci->base; } luaV_execute(L, cast_int(L->ci - L->base_ci)); } static int resume_error (lua_State *L, const char *msg) { L->top = L->ci->base; setsvalue2s(L, L->top, luaS_new(L, msg)); incr_top(L); lua_unlock(L); return LUA_ERRRUN; } LUA_API int lua_resume (lua_State *L, int nargs) { int status; lua_lock(L); if (L->status != LUA_YIELD && (L->status != 0 || L->ci != L->base_ci)) return resume_error(L, "cannot resume non-suspended coroutine"); if (L->nCcalls >= LUAI_MAXCCALLS) return resume_error(L, "C stack overflow"); luai_userstateresume(L, nargs); lua_assert(L->errfunc == 0); L->baseCcalls = ++L->nCcalls; status = luaD_rawrunprotected(L, resume, L->top - nargs); if (status != 0) { /* error? */ L->status = cast_byte(status); /* mark thread as `dead' */ luaD_seterrorobj(L, status, L->top); L->ci->top = L->top; } else { lua_assert(L->nCcalls == L->baseCcalls); status = L->status; } --L->nCcalls; lua_unlock(L); return status; } LUA_API int lua_yield (lua_State *L, int nresults) { luai_userstateyield(L, nresults); lua_lock(L); if (L->nCcalls > L->baseCcalls) luaG_runerror(L, "attempt to yield across metamethod/C-call boundary"); L->base = L->top - nresults; /* protect stack slots below */ L->status = LUA_YIELD; lua_unlock(L); return -1; } int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) { int status; unsigned short oldnCcalls = L->nCcalls; ptrdiff_t old_ci = saveci(L, L->ci); lu_byte old_allowhooks = L->allowhook; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef; status = luaD_rawrunprotected(L, func, u); if (status != 0) { /* an error occurred? */ StkId oldtop = restorestack(L, old_top); luaF_close(L, oldtop); /* close eventual pending closures */ luaD_seterrorobj(L, status, oldtop); L->nCcalls = oldnCcalls; L->ci = restoreci(L, old_ci); L->base = L->ci->base; L->savedpc = L->ci->savedpc; L->allowhook = old_allowhooks; restore_stack_limit(L); } L->errfunc = old_errfunc; return status; } /* ** Execute a protected parser. */ struct SParser { /* data to `f_parser' */ ZIO *z; Mbuffer buff; /* buffer to be used by the scanner */ const char *name; }; static void f_parser (lua_State *L, void *ud) { int i; Proto *tf; Closure *cl; struct SParser *p = cast(struct SParser *, ud); luaZ_lookahead(p->z); luaC_checkGC(L); tf = (luaY_parser)(L, p->z, &p->buff, p->name); cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L))); cl->l.p = tf; for (i = 0; i < tf->nups; i++) /* initialize eventual upvalues */ cl->l.upvals[i] = luaF_newupval(L); setclvalue(L, L->top, cl); incr_top(L); } int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) { struct SParser p; int status; p.z = z; p.name = name; luaZ_initbuffer(L, &p.buff); status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc); luaZ_freebuffer(L, &p.buff); return status; } redis-8.0.2/deps/lua/src/ldo.h000066400000000000000000000035511501533116600160660ustar00rootroot00000000000000/* ** $Id: ldo.h,v 2.7.1.1 2007/12/27 13:02:25 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ #ifndef ldo_h #define ldo_h #include "lobject.h" #include "lstate.h" #include "lzio.h" #define luaD_checkstack(L,n) \ if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \ luaD_growstack(L, n); \ else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); #define incr_top(L) {luaD_checkstack(L,1); L->top++;} #define savestack(L,p) ((char *)(p) - (char *)L->stack) #define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) #define saveci(L,p) ((char *)(p) - (char *)L->base_ci) #define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n))) /* results from luaD_precall */ #define PCRLUA 0 /* initiated a call to a Lua function */ #define PCRC 1 /* did a call to a C function */ #define PCRYIELD 2 /* C funtion yielded */ /* type of protected functions, to be ran by `runprotected' */ typedef void (*Pfunc) (lua_State *L, void *ud); LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name); LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line); LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize); LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); LUAI_FUNC void luaD_growstack (lua_State *L, int n); LUAI_FUNC void luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); #endif redis-8.0.2/deps/lua/src/ldump.c000066400000000000000000000060271501533116600164250ustar00rootroot00000000000000/* ** $Id: ldump.c,v 2.8.1.1 2007/12/27 13:02:25 roberto Exp $ ** save precompiled Lua chunks ** See Copyright Notice in lua.h */ #include #define ldump_c #define LUA_CORE #include "lua.h" #include "lobject.h" #include "lstate.h" #include "lundump.h" typedef struct { lua_State* L; lua_Writer writer; void* data; int strip; int status; } DumpState; #define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D) #define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D) static void DumpBlock(const void* b, size_t size, DumpState* D) { if (D->status==0) { lua_unlock(D->L); D->status=(*D->writer)(D->L,b,size,D->data); lua_lock(D->L); } } static void DumpChar(int y, DumpState* D) { char x=(char)y; DumpVar(x,D); } static void DumpInt(int x, DumpState* D) { DumpVar(x,D); } static void DumpNumber(lua_Number x, DumpState* D) { DumpVar(x,D); } static void DumpVector(const void* b, int n, size_t size, DumpState* D) { DumpInt(n,D); DumpMem(b,n,size,D); } static void DumpString(const TString* s, DumpState* D) { if (s==NULL) { size_t size=0; DumpVar(size,D); } else { size_t size=s->tsv.len+1; /* include trailing '\0' */ DumpVar(size,D); DumpBlock(getstr(s),size,D); } } #define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D) static void DumpFunction(const Proto* f, const TString* p, DumpState* D); static void DumpConstants(const Proto* f, DumpState* D) { int i,n=f->sizek; DumpInt(n,D); for (i=0; ik[i]; DumpChar(ttype(o),D); switch (ttype(o)) { case LUA_TNIL: break; case LUA_TBOOLEAN: DumpChar(bvalue(o),D); break; case LUA_TNUMBER: DumpNumber(nvalue(o),D); break; case LUA_TSTRING: DumpString(rawtsvalue(o),D); break; default: lua_assert(0); /* cannot happen */ break; } } n=f->sizep; DumpInt(n,D); for (i=0; ip[i],f->source,D); } static void DumpDebug(const Proto* f, DumpState* D) { int i,n; n= (D->strip) ? 0 : f->sizelineinfo; DumpVector(f->lineinfo,n,sizeof(int),D); n= (D->strip) ? 0 : f->sizelocvars; DumpInt(n,D); for (i=0; ilocvars[i].varname,D); DumpInt(f->locvars[i].startpc,D); DumpInt(f->locvars[i].endpc,D); } n= (D->strip) ? 0 : f->sizeupvalues; DumpInt(n,D); for (i=0; iupvalues[i],D); } static void DumpFunction(const Proto* f, const TString* p, DumpState* D) { DumpString((f->source==p || D->strip) ? NULL : f->source,D); DumpInt(f->linedefined,D); DumpInt(f->lastlinedefined,D); DumpChar(f->nups,D); DumpChar(f->numparams,D); DumpChar(f->is_vararg,D); DumpChar(f->maxstacksize,D); DumpCode(f,D); DumpConstants(f,D); DumpDebug(f,D); } static void DumpHeader(DumpState* D) { char h[LUAC_HEADERSIZE]; luaU_header(h); DumpBlock(h,LUAC_HEADERSIZE,D); } /* ** dump Lua function as precompiled chunk */ int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) { DumpState D; D.L=L; D.writer=w; D.data=data; D.strip=strip; D.status=0; DumpHeader(&D); DumpFunction(f,NULL,&D); return D.status; } redis-8.0.2/deps/lua/src/lfunc.c000066400000000000000000000110121501533116600164010ustar00rootroot00000000000000/* ** $Id: lfunc.c,v 2.12.1.2 2007/12/28 14:58:43 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ #include #define lfunc_c #define LUA_CORE #include "lua.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e) { Closure *c = cast(Closure *, luaM_malloc(L, sizeCclosure(nelems))); luaC_link(L, obj2gco(c), LUA_TFUNCTION); c->c.isC = 1; c->c.env = e; c->c.nupvalues = cast_byte(nelems); return c; } Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) { Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems))); luaC_link(L, obj2gco(c), LUA_TFUNCTION); c->l.isC = 0; c->l.env = e; c->l.nupvalues = cast_byte(nelems); while (nelems--) c->l.upvals[nelems] = NULL; return c; } UpVal *luaF_newupval (lua_State *L) { UpVal *uv = luaM_new(L, UpVal); luaC_link(L, obj2gco(uv), LUA_TUPVAL); uv->v = &uv->u.value; setnilvalue(uv->v); return uv; } UpVal *luaF_findupval (lua_State *L, StkId level) { global_State *g = G(L); GCObject **pp = &L->openupval; UpVal *p; UpVal *uv; while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) { lua_assert(p->v != &p->u.value); if (p->v == level) { /* found a corresponding upvalue? */ if (isdead(g, obj2gco(p))) /* is it dead? */ changewhite(obj2gco(p)); /* ressurect it */ return p; } pp = &p->next; } uv = luaM_new(L, UpVal); /* not found: create a new one */ uv->tt = LUA_TUPVAL; uv->marked = luaC_white(g); uv->v = level; /* current value lives in the stack */ uv->next = *pp; /* chain it in the proper position */ *pp = obj2gco(uv); uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ uv->u.l.next = g->uvhead.u.l.next; uv->u.l.next->u.l.prev = uv; g->uvhead.u.l.next = uv; lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); return uv; } static void unlinkupval (UpVal *uv) { lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */ uv->u.l.prev->u.l.next = uv->u.l.next; } void luaF_freeupval (lua_State *L, UpVal *uv) { if (uv->v != &uv->u.value) /* is it open? */ unlinkupval(uv); /* remove from open list */ luaM_free(L, uv); /* free upvalue */ } void luaF_close (lua_State *L, StkId level) { UpVal *uv; global_State *g = G(L); while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) { GCObject *o = obj2gco(uv); lua_assert(!isblack(o) && uv->v != &uv->u.value); L->openupval = uv->next; /* remove from `open' list */ if (isdead(g, o)) luaF_freeupval(L, uv); /* free upvalue */ else { unlinkupval(uv); setobj(L, &uv->u.value, uv->v); uv->v = &uv->u.value; /* now current value lives here */ luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */ } } } Proto *luaF_newproto (lua_State *L) { Proto *f = luaM_new(L, Proto); luaC_link(L, obj2gco(f), LUA_TPROTO); f->k = NULL; f->sizek = 0; f->p = NULL; f->sizep = 0; f->code = NULL; f->sizecode = 0; f->sizelineinfo = 0; f->sizeupvalues = 0; f->nups = 0; f->upvalues = NULL; f->numparams = 0; f->is_vararg = 0; f->maxstacksize = 0; f->lineinfo = NULL; f->sizelocvars = 0; f->locvars = NULL; f->linedefined = 0; f->lastlinedefined = 0; f->source = NULL; return f; } void luaF_freeproto (lua_State *L, Proto *f) { luaM_freearray(L, f->code, f->sizecode, Instruction); luaM_freearray(L, f->p, f->sizep, Proto *); luaM_freearray(L, f->k, f->sizek, TValue); luaM_freearray(L, f->lineinfo, f->sizelineinfo, int); luaM_freearray(L, f->locvars, f->sizelocvars, struct LocVar); luaM_freearray(L, f->upvalues, f->sizeupvalues, TString *); luaM_free(L, f); } void luaF_freeclosure (lua_State *L, Closure *c) { int size = (c->c.isC) ? sizeCclosure(c->c.nupvalues) : sizeLclosure(c->l.nupvalues); luaM_freemem(L, c, size); } /* ** Look for n-th local variable at line `line' in function `func'. ** Returns NULL if not found. */ const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { int i; for (i = 0; isizelocvars && f->locvars[i].startpc <= pc; i++) { if (pc < f->locvars[i].endpc) { /* is variable active? */ local_number--; if (local_number == 0) return getstr(f->locvars[i].varname); } } return NULL; /* not found */ } redis-8.0.2/deps/lua/src/lfunc.h000066400000000000000000000021451501533116600164150ustar00rootroot00000000000000/* ** $Id: lfunc.h,v 2.4.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ #ifndef lfunc_h #define lfunc_h #include "lobject.h" #define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \ cast(int, sizeof(TValue)*((n)-1))) #define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \ cast(int, sizeof(TValue *)*((n)-1))) LUAI_FUNC Proto *luaF_newproto (lua_State *L); LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e); LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e); LUAI_FUNC UpVal *luaF_newupval (lua_State *L); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); LUAI_FUNC void luaF_close (lua_State *L, StkId level); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); LUAI_FUNC void luaF_freeclosure (lua_State *L, Closure *c); LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, int pc); #endif redis-8.0.2/deps/lua/src/lgc.c000066400000000000000000000471251501533116600160550ustar00rootroot00000000000000/* ** $Id: lgc.c,v 2.38.1.2 2011/03/18 18:05:38 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ #include #define lgc_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #define GCSTEPSIZE 1024u #define GCSWEEPMAX 40 #define GCSWEEPCOST 10 #define GCFINALIZECOST 100 #define maskmarks cast_byte(~(bitmask(BLACKBIT)|WHITEBITS)) #define makewhite(g,x) \ ((x)->gch.marked = cast_byte(((x)->gch.marked & maskmarks) | luaC_white(g))) #define white2gray(x) reset2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) #define black2gray(x) resetbit((x)->gch.marked, BLACKBIT) #define stringmark(s) reset2bits((s)->tsv.marked, WHITE0BIT, WHITE1BIT) #define isfinalized(u) testbit((u)->marked, FINALIZEDBIT) #define markfinalized(u) l_setbit((u)->marked, FINALIZEDBIT) #define KEYWEAK bitmask(KEYWEAKBIT) #define VALUEWEAK bitmask(VALUEWEAKBIT) #define markvalue(g,o) { checkconsistency(o); \ if (iscollectable(o) && iswhite(gcvalue(o))) reallymarkobject(g,gcvalue(o)); } #define markobject(g,t) { if (iswhite(obj2gco(t))) \ reallymarkobject(g, obj2gco(t)); } #define setthreshold(g) (g->GCthreshold = (g->estimate/100) * g->gcpause) static void removeentry (Node *n) { lua_assert(ttisnil(gval(n))); if (iscollectable(gkey(n))) setttype(gkey(n), LUA_TDEADKEY); /* dead key; remove it */ } static void reallymarkobject (global_State *g, GCObject *o) { lua_assert(iswhite(o) && !isdead(g, o)); white2gray(o); switch (o->gch.tt) { case LUA_TSTRING: { return; } case LUA_TUSERDATA: { Table *mt = gco2u(o)->metatable; gray2black(o); /* udata are never gray */ if (mt) markobject(g, mt); markobject(g, gco2u(o)->env); return; } case LUA_TUPVAL: { UpVal *uv = gco2uv(o); markvalue(g, uv->v); if (uv->v == &uv->u.value) /* closed? */ gray2black(o); /* open upvalues are never black */ return; } case LUA_TFUNCTION: { gco2cl(o)->c.gclist = g->gray; g->gray = o; break; } case LUA_TTABLE: { gco2h(o)->gclist = g->gray; g->gray = o; break; } case LUA_TTHREAD: { gco2th(o)->gclist = g->gray; g->gray = o; break; } case LUA_TPROTO: { gco2p(o)->gclist = g->gray; g->gray = o; break; } default: lua_assert(0); } } static void marktmu (global_State *g) { GCObject *u = g->tmudata; if (u) { do { u = u->gch.next; makewhite(g, u); /* may be marked, if left from previous GC */ reallymarkobject(g, u); } while (u != g->tmudata); } } /* move `dead' udata that need finalization to list `tmudata' */ size_t luaC_separateudata (lua_State *L, int all) { global_State *g = G(L); size_t deadmem = 0; GCObject **p = &g->mainthread->next; GCObject *curr; while ((curr = *p) != NULL) { if (!(iswhite(curr) || all) || isfinalized(gco2u(curr))) p = &curr->gch.next; /* don't bother with them */ else if (fasttm(L, gco2u(curr)->metatable, TM_GC) == NULL) { markfinalized(gco2u(curr)); /* don't need finalization */ p = &curr->gch.next; } else { /* must call its gc method */ deadmem += sizeudata(gco2u(curr)); markfinalized(gco2u(curr)); *p = curr->gch.next; /* link `curr' at the end of `tmudata' list */ if (g->tmudata == NULL) /* list is empty? */ g->tmudata = curr->gch.next = curr; /* creates a circular list */ else { curr->gch.next = g->tmudata->gch.next; g->tmudata->gch.next = curr; g->tmudata = curr; } } } return deadmem; } static int traversetable (global_State *g, Table *h) { int i; int weakkey = 0; int weakvalue = 0; const TValue *mode; if (h->metatable) markobject(g, h->metatable); mode = gfasttm(g, h->metatable, TM_MODE); if (mode && ttisstring(mode)) { /* is there a weak mode? */ weakkey = (strchr(svalue(mode), 'k') != NULL); weakvalue = (strchr(svalue(mode), 'v') != NULL); if (weakkey || weakvalue) { /* is really weak? */ h->marked &= ~(KEYWEAK | VALUEWEAK); /* clear bits */ h->marked |= cast_byte((weakkey << KEYWEAKBIT) | (weakvalue << VALUEWEAKBIT)); h->gclist = g->weak; /* must be cleared after GC, ... */ g->weak = obj2gco(h); /* ... so put in the appropriate list */ } } if (weakkey && weakvalue) return 1; if (!weakvalue) { i = h->sizearray; while (i--) markvalue(g, &h->array[i]); } i = sizenode(h); while (i--) { Node *n = gnode(h, i); lua_assert(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); if (ttisnil(gval(n))) removeentry(n); /* remove empty entries */ else { lua_assert(!ttisnil(gkey(n))); if (!weakkey) markvalue(g, gkey(n)); if (!weakvalue) markvalue(g, gval(n)); } } return weakkey || weakvalue; } /* ** All marks are conditional because a GC may happen while the ** prototype is still being created */ static void traverseproto (global_State *g, Proto *f) { int i; if (f->source) stringmark(f->source); for (i=0; isizek; i++) /* mark literals */ markvalue(g, &f->k[i]); for (i=0; isizeupvalues; i++) { /* mark upvalue names */ if (f->upvalues[i]) stringmark(f->upvalues[i]); } for (i=0; isizep; i++) { /* mark nested protos */ if (f->p[i]) markobject(g, f->p[i]); } for (i=0; isizelocvars; i++) { /* mark local-variable names */ if (f->locvars[i].varname) stringmark(f->locvars[i].varname); } } static void traverseclosure (global_State *g, Closure *cl) { markobject(g, cl->c.env); if (cl->c.isC) { int i; for (i=0; ic.nupvalues; i++) /* mark its upvalues */ markvalue(g, &cl->c.upvalue[i]); } else { int i; lua_assert(cl->l.nupvalues == cl->l.p->nups); markobject(g, cl->l.p); for (i=0; il.nupvalues; i++) /* mark its upvalues */ markobject(g, cl->l.upvals[i]); } } static void checkstacksizes (lua_State *L, StkId max) { int ci_used = cast_int(L->ci - L->base_ci); /* number of `ci' in use */ int s_used = cast_int(max - L->stack); /* part of stack in use */ if (L->size_ci > LUAI_MAXCALLS) /* handling overflow? */ return; /* do not touch the stacks */ if (4*ci_used < L->size_ci && 2*BASIC_CI_SIZE < L->size_ci) luaD_reallocCI(L, L->size_ci/2); /* still big enough... */ condhardstacktests(luaD_reallocCI(L, ci_used + 1)); if (4*s_used < L->stacksize && 2*(BASIC_STACK_SIZE+EXTRA_STACK) < L->stacksize) luaD_reallocstack(L, L->stacksize/2); /* still big enough... */ condhardstacktests(luaD_reallocstack(L, s_used)); } static void traversestack (global_State *g, lua_State *l) { StkId o, lim; CallInfo *ci; markvalue(g, gt(l)); lim = l->top; for (ci = l->base_ci; ci <= l->ci; ci++) { lua_assert(ci->top <= l->stack_last); if (lim < ci->top) lim = ci->top; } for (o = l->stack; o < l->top; o++) markvalue(g, o); for (; o <= lim; o++) setnilvalue(o); checkstacksizes(l, lim); } /* ** traverse one gray object, turning it to black. ** Returns `quantity' traversed. */ static l_mem propagatemark (global_State *g) { GCObject *o = g->gray; lua_assert(isgray(o)); gray2black(o); switch (o->gch.tt) { case LUA_TTABLE: { Table *h = gco2h(o); g->gray = h->gclist; if (traversetable(g, h)) /* table is weak? */ black2gray(o); /* keep it gray */ return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(Node) * sizenode(h); } case LUA_TFUNCTION: { Closure *cl = gco2cl(o); g->gray = cl->c.gclist; traverseclosure(g, cl); return (cl->c.isC) ? sizeCclosure(cl->c.nupvalues) : sizeLclosure(cl->l.nupvalues); } case LUA_TTHREAD: { lua_State *th = gco2th(o); g->gray = th->gclist; th->gclist = g->grayagain; g->grayagain = o; black2gray(o); traversestack(g, th); return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; } case LUA_TPROTO: { Proto *p = gco2p(o); g->gray = p->gclist; traverseproto(g, p); return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto *) * p->sizep + sizeof(TValue) * p->sizek + sizeof(int) * p->sizelineinfo + sizeof(LocVar) * p->sizelocvars + sizeof(TString *) * p->sizeupvalues; } default: lua_assert(0); return 0; } } static size_t propagateall (global_State *g) { size_t m = 0; while (g->gray) m += propagatemark(g); return m; } /* ** The next function tells whether a key or value can be cleared from ** a weak table. Non-collectable objects are never removed from weak ** tables. Strings behave as `values', so are never removed too. for ** other objects: if really collected, cannot keep them; for userdata ** being finalized, keep them in keys, but not in values */ static int iscleared (const TValue *o, int iskey) { if (!iscollectable(o)) return 0; if (ttisstring(o)) { stringmark(rawtsvalue(o)); /* strings are `values', so are never weak */ return 0; } return iswhite(gcvalue(o)) || (ttisuserdata(o) && (!iskey && isfinalized(uvalue(o)))); } /* ** clear collected entries from weaktables */ static void cleartable (GCObject *l) { while (l) { Table *h = gco2h(l); int i = h->sizearray; lua_assert(testbit(h->marked, VALUEWEAKBIT) || testbit(h->marked, KEYWEAKBIT)); if (testbit(h->marked, VALUEWEAKBIT)) { while (i--) { TValue *o = &h->array[i]; if (iscleared(o, 0)) /* value was collected? */ setnilvalue(o); /* remove value */ } } i = sizenode(h); while (i--) { Node *n = gnode(h, i); if (!ttisnil(gval(n)) && /* non-empty entry? */ (iscleared(key2tval(n), 1) || iscleared(gval(n), 0))) { setnilvalue(gval(n)); /* remove value ... */ removeentry(n); /* remove entry from table */ } } l = h->gclist; } } static void freeobj (lua_State *L, GCObject *o) { switch (o->gch.tt) { case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; case LUA_TFUNCTION: luaF_freeclosure(L, gco2cl(o)); break; case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break; case LUA_TTABLE: luaH_free(L, gco2h(o)); break; case LUA_TTHREAD: { lua_assert(gco2th(o) != L && gco2th(o) != G(L)->mainthread); luaE_freethread(L, gco2th(o)); break; } case LUA_TSTRING: { G(L)->strt.nuse--; luaM_freemem(L, o, sizestring(gco2ts(o))); break; } case LUA_TUSERDATA: { luaM_freemem(L, o, sizeudata(gco2u(o))); break; } default: lua_assert(0); } } #define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM) static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { GCObject *curr; global_State *g = G(L); int deadmask = otherwhite(g); while ((curr = *p) != NULL && count-- > 0) { if (curr->gch.tt == LUA_TTHREAD) /* sweep open upvalues of each thread */ sweepwholelist(L, &gco2th(curr)->openupval); if ((curr->gch.marked ^ WHITEBITS) & deadmask) { /* not dead? */ lua_assert(!isdead(g, curr) || testbit(curr->gch.marked, FIXEDBIT)); makewhite(g, curr); /* make it white (for next cycle) */ p = &curr->gch.next; } else { /* must erase `curr' */ lua_assert(isdead(g, curr) || deadmask == bitmask(SFIXEDBIT)); *p = curr->gch.next; if (curr == g->rootgc) /* is the first element of the list? */ g->rootgc = curr->gch.next; /* adjust first */ freeobj(L, curr); } } return p; } static void checkSizes (lua_State *L) { global_State *g = G(L); /* check size of string hash */ if (g->strt.nuse < cast(lu_int32, g->strt.size/4) && g->strt.size > MINSTRTABSIZE*2) luaS_resize(L, g->strt.size/2); /* table is too big */ /* check size of buffer */ if (luaZ_sizebuffer(&g->buff) > LUA_MINBUFFER*2) { /* buffer too big? */ size_t newsize = luaZ_sizebuffer(&g->buff) / 2; luaZ_resizebuffer(L, &g->buff, newsize); } } static void GCTM (lua_State *L) { global_State *g = G(L); GCObject *o = g->tmudata->gch.next; /* get first element */ Udata *udata = rawgco2u(o); const TValue *tm; /* remove udata from `tmudata' */ if (o == g->tmudata) /* last element? */ g->tmudata = NULL; else g->tmudata->gch.next = udata->uv.next; udata->uv.next = g->mainthread->next; /* return it to `root' list */ g->mainthread->next = o; makewhite(g, o); tm = fasttm(L, udata->uv.metatable, TM_GC); if (tm != NULL) { lu_byte oldah = L->allowhook; lu_mem oldt = g->GCthreshold; L->allowhook = 0; /* stop debug hooks during GC tag method */ g->GCthreshold = 2*g->totalbytes; /* avoid GC steps */ setobj2s(L, L->top, tm); setuvalue(L, L->top+1, udata); L->top += 2; luaD_call(L, L->top - 2, 0); L->allowhook = oldah; /* restore hooks */ g->GCthreshold = oldt; /* restore threshold */ } } /* ** Call all GC tag methods */ void luaC_callGCTM (lua_State *L) { while (G(L)->tmudata) GCTM(L); } void luaC_freeall (lua_State *L) { global_State *g = G(L); int i; g->currentwhite = WHITEBITS | bitmask(SFIXEDBIT); /* mask to collect all elements */ sweepwholelist(L, &g->rootgc); for (i = 0; i < g->strt.size; i++) /* free all string lists */ sweepwholelist(L, &g->strt.hash[i]); } static void markmt (global_State *g) { int i; for (i=0; imt[i]) markobject(g, g->mt[i]); } /* mark root set */ static void markroot (lua_State *L) { global_State *g = G(L); g->gray = NULL; g->grayagain = NULL; g->weak = NULL; markobject(g, g->mainthread); /* make global table be traversed before main stack */ markvalue(g, gt(g->mainthread)); markvalue(g, registry(L)); markmt(g); g->gcstate = GCSpropagate; } static void remarkupvals (global_State *g) { UpVal *uv; for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); if (isgray(obj2gco(uv))) markvalue(g, uv->v); } } static void atomic (lua_State *L) { global_State *g = G(L); size_t udsize; /* total size of userdata to be finalized */ /* remark occasional upvalues of (maybe) dead threads */ remarkupvals(g); /* traverse objects cautch by write barrier and by 'remarkupvals' */ propagateall(g); /* remark weak tables */ g->gray = g->weak; g->weak = NULL; lua_assert(!iswhite(obj2gco(g->mainthread))); markobject(g, L); /* mark running thread */ markmt(g); /* mark basic metatables (again) */ propagateall(g); /* remark gray again */ g->gray = g->grayagain; g->grayagain = NULL; propagateall(g); udsize = luaC_separateudata(L, 0); /* separate userdata to be finalized */ marktmu(g); /* mark `preserved' userdata */ udsize += propagateall(g); /* remark, to propagate `preserveness' */ cleartable(g->weak); /* remove collected objects from weak tables */ /* flip current white */ g->currentwhite = cast_byte(otherwhite(g)); g->sweepstrgc = 0; g->sweepgc = &g->rootgc; g->gcstate = GCSsweepstring; g->estimate = g->totalbytes - udsize; /* first estimate */ } static l_mem singlestep (lua_State *L) { global_State *g = G(L); /*lua_checkmemory(L);*/ switch (g->gcstate) { case GCSpause: { markroot(L); /* start a new collection */ return 0; } case GCSpropagate: { if (g->gray) return propagatemark(g); else { /* no more `gray' objects */ atomic(L); /* finish mark phase */ return 0; } } case GCSsweepstring: { lu_mem old = g->totalbytes; sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]); if (g->sweepstrgc >= g->strt.size) /* nothing more to sweep? */ g->gcstate = GCSsweep; /* end sweep-string phase */ lua_assert(old >= g->totalbytes); g->estimate -= old - g->totalbytes; return GCSWEEPCOST; } case GCSsweep: { lu_mem old = g->totalbytes; g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); if (*g->sweepgc == NULL) { /* nothing more to sweep? */ checkSizes(L); g->gcstate = GCSfinalize; /* end sweep phase */ } lua_assert(old >= g->totalbytes); g->estimate -= old - g->totalbytes; return GCSWEEPMAX*GCSWEEPCOST; } case GCSfinalize: { if (g->tmudata) { GCTM(L); if (g->estimate > GCFINALIZECOST) g->estimate -= GCFINALIZECOST; return GCFINALIZECOST; } else { g->gcstate = GCSpause; /* end collection */ g->gcdept = 0; return 0; } } default: lua_assert(0); return 0; } } void luaC_step (lua_State *L) { global_State *g = G(L); l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul; if (lim == 0) lim = (MAX_LUMEM-1)/2; /* no limit */ g->gcdept += g->totalbytes - g->GCthreshold; do { lim -= singlestep(L); if (g->gcstate == GCSpause) break; } while (lim > 0); if (g->gcstate != GCSpause) { if (g->gcdept < GCSTEPSIZE) g->GCthreshold = g->totalbytes + GCSTEPSIZE; /* - lim/g->gcstepmul;*/ else { g->gcdept -= GCSTEPSIZE; g->GCthreshold = g->totalbytes; } } else { setthreshold(g); } } void luaC_fullgc (lua_State *L) { global_State *g = G(L); if (g->gcstate <= GCSpropagate) { /* reset sweep marks to sweep all elements (returning them to white) */ g->sweepstrgc = 0; g->sweepgc = &g->rootgc; /* reset other collector lists */ g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->gcstate = GCSsweepstring; } lua_assert(g->gcstate != GCSpause && g->gcstate != GCSpropagate); /* finish any pending sweep phase */ while (g->gcstate != GCSfinalize) { lua_assert(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); singlestep(L); } markroot(L); while (g->gcstate != GCSpause) { singlestep(L); } setthreshold(g); } void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v) { global_State *g = G(L); lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); lua_assert(ttype(&o->gch) != LUA_TTABLE); /* must keep invariant? */ if (g->gcstate == GCSpropagate) reallymarkobject(g, v); /* restore invariant */ else /* don't mind */ makewhite(g, o); /* mark as white just to avoid other barriers */ } void luaC_barrierback (lua_State *L, Table *t) { global_State *g = G(L); GCObject *o = obj2gco(t); lua_assert(isblack(o) && !isdead(g, o)); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); black2gray(o); /* make table gray (again) */ t->gclist = g->grayagain; g->grayagain = o; } void luaC_link (lua_State *L, GCObject *o, lu_byte tt) { global_State *g = G(L); o->gch.next = g->rootgc; g->rootgc = o; o->gch.marked = luaC_white(g); o->gch.tt = tt; } void luaC_linkupval (lua_State *L, UpVal *uv) { global_State *g = G(L); GCObject *o = obj2gco(uv); o->gch.next = g->rootgc; /* link upvalue into `rootgc' list */ g->rootgc = o; if (isgray(o)) { if (g->gcstate == GCSpropagate) { gray2black(o); /* closed upvalues need barrier */ luaC_barrier(L, uv, uv->v); } else { /* sweep phase: sweep it (turning it into white) */ makewhite(g, o); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); } } } redis-8.0.2/deps/lua/src/lgc.h000066400000000000000000000061271501533116600160570ustar00rootroot00000000000000/* ** $Id: lgc.h,v 2.15.1.1 2007/12/27 13:02:25 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ #ifndef lgc_h #define lgc_h #include "lobject.h" /* ** Possible states of the Garbage Collector */ #define GCSpause 0 #define GCSpropagate 1 #define GCSsweepstring 2 #define GCSsweep 3 #define GCSfinalize 4 /* ** some userful bit tricks */ #define resetbits(x,m) ((x) &= cast(lu_byte, ~(m))) #define setbits(x,m) ((x) |= (m)) #define testbits(x,m) ((x) & (m)) #define bitmask(b) (1<<(b)) #define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) #define l_setbit(x,b) setbits(x, bitmask(b)) #define resetbit(x,b) resetbits(x, bitmask(b)) #define testbit(x,b) testbits(x, bitmask(b)) #define set2bits(x,b1,b2) setbits(x, (bit2mask(b1, b2))) #define reset2bits(x,b1,b2) resetbits(x, (bit2mask(b1, b2))) #define test2bits(x,b1,b2) testbits(x, (bit2mask(b1, b2))) /* ** Layout for bit use in `marked' field: ** bit 0 - object is white (type 0) ** bit 1 - object is white (type 1) ** bit 2 - object is black ** bit 3 - for userdata: has been finalized ** bit 3 - for tables: has weak keys ** bit 4 - for tables: has weak values ** bit 5 - object is fixed (should not be collected) ** bit 6 - object is "super" fixed (only the main thread) */ #define WHITE0BIT 0 #define WHITE1BIT 1 #define BLACKBIT 2 #define FINALIZEDBIT 3 #define KEYWEAKBIT 3 #define VALUEWEAKBIT 4 #define FIXEDBIT 5 #define SFIXEDBIT 6 #define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) #define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) #define isblack(x) testbit((x)->gch.marked, BLACKBIT) #define isgray(x) (!isblack(x) && !iswhite(x)) #define otherwhite(g) (g->currentwhite ^ WHITEBITS) #define isdead(g,v) ((v)->gch.marked & otherwhite(g) & WHITEBITS) #define changewhite(x) ((x)->gch.marked ^= WHITEBITS) #define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) #define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) #define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) #define luaC_checkGC(L) { \ condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ if (G(L)->totalbytes >= G(L)->GCthreshold) \ luaC_step(L); } #define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ luaC_barrierf(L,obj2gco(p),gcvalue(v)); } #define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \ luaC_barrierback(L,t); } #define luaC_objbarrier(L,p,o) \ { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ luaC_barrierf(L,obj2gco(p),obj2gco(o)); } #define luaC_objbarriert(L,t,o) \ { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); } LUAI_FUNC size_t luaC_separateudata (lua_State *L, int all); LUAI_FUNC void luaC_callGCTM (lua_State *L); LUAI_FUNC void luaC_freeall (lua_State *L); LUAI_FUNC void luaC_step (lua_State *L); LUAI_FUNC void luaC_fullgc (lua_State *L); LUAI_FUNC void luaC_link (lua_State *L, GCObject *o, lu_byte tt); LUAI_FUNC void luaC_linkupval (lua_State *L, UpVal *uv); LUAI_FUNC void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v); LUAI_FUNC void luaC_barrierback (lua_State *L, Table *t); #endif redis-8.0.2/deps/lua/src/linit.c000066400000000000000000000013751501533116600164240ustar00rootroot00000000000000/* ** $Id: linit.c,v 1.14.1.1 2007/12/27 13:02:25 roberto Exp $ ** Initialization of libraries for lua.c ** See Copyright Notice in lua.h */ #define linit_c #define LUA_LIB #include "lua.h" #include "lualib.h" #include "lauxlib.h" static const luaL_Reg lualibs[] = { {"", luaopen_base}, {LUA_LOADLIBNAME, luaopen_package}, {LUA_TABLIBNAME, luaopen_table}, {LUA_IOLIBNAME, luaopen_io}, {LUA_OSLIBNAME, luaopen_os}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, {LUA_DBLIBNAME, luaopen_debug}, {NULL, NULL} }; LUALIB_API void luaL_openlibs (lua_State *L) { const luaL_Reg *lib = lualibs; for (; lib->func; lib++) { lua_pushcfunction(L, lib->func); lua_pushstring(L, lib->name); lua_call(L, 1, 0); } } redis-8.0.2/deps/lua/src/liolib.c000066400000000000000000000322321501533116600165530ustar00rootroot00000000000000/* ** $Id: liolib.c,v 2.73.1.4 2010/05/14 15:33:51 roberto Exp $ ** Standard I/O (and system) library ** See Copyright Notice in lua.h */ #include #include #include #include #define liolib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #define IO_INPUT 1 #define IO_OUTPUT 2 static const char *const fnames[] = {"input", "output"}; static int pushresult (lua_State *L, int i, const char *filename) { int en = errno; /* calls to Lua API may change this value */ if (i) { lua_pushboolean(L, 1); return 1; } else { lua_pushnil(L); if (filename) lua_pushfstring(L, "%s: %s", filename, strerror(en)); else lua_pushfstring(L, "%s", strerror(en)); lua_pushinteger(L, en); return 3; } } static void fileerror (lua_State *L, int arg, const char *filename) { lua_pushfstring(L, "%s: %s", filename, strerror(errno)); luaL_argerror(L, arg, lua_tostring(L, -1)); } #define tofilep(L) ((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE)) static int io_type (lua_State *L) { void *ud; luaL_checkany(L, 1); ud = lua_touserdata(L, 1); lua_getfield(L, LUA_REGISTRYINDEX, LUA_FILEHANDLE); if (ud == NULL || !lua_getmetatable(L, 1) || !lua_rawequal(L, -2, -1)) lua_pushnil(L); /* not a file */ else if (*((FILE **)ud) == NULL) lua_pushliteral(L, "closed file"); else lua_pushliteral(L, "file"); return 1; } static FILE *tofile (lua_State *L) { FILE **f = tofilep(L); if (*f == NULL) luaL_error(L, "attempt to use a closed file"); return *f; } /* ** When creating file handles, always creates a `closed' file handle ** before opening the actual file; so, if there is a memory error, the ** file is not left opened. */ static FILE **newfile (lua_State *L) { FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *)); *pf = NULL; /* file handle is currently `closed' */ luaL_getmetatable(L, LUA_FILEHANDLE); lua_setmetatable(L, -2); return pf; } /* ** function to (not) close the standard files stdin, stdout, and stderr */ static int io_noclose (lua_State *L) { lua_pushnil(L); lua_pushliteral(L, "cannot close standard file"); return 2; } /* ** function to close 'popen' files */ static int io_pclose (lua_State *L) { FILE **p = tofilep(L); int ok = lua_pclose(L, *p); *p = NULL; return pushresult(L, ok, NULL); } /* ** function to close regular files */ static int io_fclose (lua_State *L) { FILE **p = tofilep(L); int ok = (fclose(*p) == 0); *p = NULL; return pushresult(L, ok, NULL); } static int aux_close (lua_State *L) { lua_getfenv(L, 1); lua_getfield(L, -1, "__close"); return (lua_tocfunction(L, -1))(L); } static int io_close (lua_State *L) { if (lua_isnone(L, 1)) lua_rawgeti(L, LUA_ENVIRONINDEX, IO_OUTPUT); tofile(L); /* make sure argument is a file */ return aux_close(L); } static int io_gc (lua_State *L) { FILE *f = *tofilep(L); /* ignore closed files */ if (f != NULL) aux_close(L); return 0; } static int io_tostring (lua_State *L) { FILE *f = *tofilep(L); if (f == NULL) lua_pushliteral(L, "file (closed)"); else lua_pushfstring(L, "file (%p)", f); return 1; } static int io_open (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); FILE **pf = newfile(L); *pf = fopen(filename, mode); return (*pf == NULL) ? pushresult(L, 0, filename) : 1; } /* ** this function has a separated environment, which defines the ** correct __close for 'popen' files */ static int io_popen (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); FILE **pf = newfile(L); *pf = lua_popen(L, filename, mode); return (*pf == NULL) ? pushresult(L, 0, filename) : 1; } static int io_tmpfile (lua_State *L) { FILE **pf = newfile(L); *pf = tmpfile(); return (*pf == NULL) ? pushresult(L, 0, NULL) : 1; } static FILE *getiofile (lua_State *L, int findex) { FILE *f; lua_rawgeti(L, LUA_ENVIRONINDEX, findex); f = *(FILE **)lua_touserdata(L, -1); if (f == NULL) luaL_error(L, "standard %s file is closed", fnames[findex - 1]); return f; } static int g_iofile (lua_State *L, int f, const char *mode) { if (!lua_isnoneornil(L, 1)) { const char *filename = lua_tostring(L, 1); if (filename) { FILE **pf = newfile(L); *pf = fopen(filename, mode); if (*pf == NULL) fileerror(L, 1, filename); } else { tofile(L); /* check that it's a valid file handle */ lua_pushvalue(L, 1); } lua_rawseti(L, LUA_ENVIRONINDEX, f); } /* return current value */ lua_rawgeti(L, LUA_ENVIRONINDEX, f); return 1; } static int io_input (lua_State *L) { return g_iofile(L, IO_INPUT, "r"); } static int io_output (lua_State *L) { return g_iofile(L, IO_OUTPUT, "w"); } static int io_readline (lua_State *L); static void aux_lines (lua_State *L, int idx, int toclose) { lua_pushvalue(L, idx); lua_pushboolean(L, toclose); /* close/not close file when finished */ lua_pushcclosure(L, io_readline, 2); } static int f_lines (lua_State *L) { tofile(L); /* check that it's a valid file handle */ aux_lines(L, 1, 0); return 1; } static int io_lines (lua_State *L) { if (lua_isnoneornil(L, 1)) { /* no arguments? */ /* will iterate over default input */ lua_rawgeti(L, LUA_ENVIRONINDEX, IO_INPUT); return f_lines(L); } else { const char *filename = luaL_checkstring(L, 1); FILE **pf = newfile(L); *pf = fopen(filename, "r"); if (*pf == NULL) fileerror(L, 1, filename); aux_lines(L, lua_gettop(L), 1); return 1; } } /* ** {====================================================== ** READ ** ======================================================= */ static int read_number (lua_State *L, FILE *f) { lua_Number d; if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) { lua_pushnumber(L, d); return 1; } else { lua_pushnil(L); /* "result" to be removed */ return 0; /* read fails */ } } static int test_eof (lua_State *L, FILE *f) { int c = getc(f); ungetc(c, f); lua_pushlstring(L, NULL, 0); return (c != EOF); } static int read_line (lua_State *L, FILE *f) { luaL_Buffer b; luaL_buffinit(L, &b); for (;;) { size_t l; char *p = luaL_prepbuffer(&b); if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */ luaL_pushresult(&b); /* close buffer */ return (lua_objlen(L, -1) > 0); /* check whether read something */ } l = strlen(p); if (l == 0 || p[l-1] != '\n') luaL_addsize(&b, l); else { luaL_addsize(&b, l - 1); /* do not include `eol' */ luaL_pushresult(&b); /* close buffer */ return 1; /* read at least an `eol' */ } } } static int read_chars (lua_State *L, FILE *f, size_t n) { size_t rlen; /* how much to read */ size_t nr; /* number of chars actually read */ luaL_Buffer b; luaL_buffinit(L, &b); rlen = LUAL_BUFFERSIZE; /* try to read that much each time */ do { char *p = luaL_prepbuffer(&b); if (rlen > n) rlen = n; /* cannot read more than asked */ nr = fread(p, sizeof(char), rlen, f); luaL_addsize(&b, nr); n -= nr; /* still have to read `n' chars */ } while (n > 0 && nr == rlen); /* until end of count or eof */ luaL_pushresult(&b); /* close buffer */ return (n == 0 || lua_objlen(L, -1) > 0); } static int g_read (lua_State *L, FILE *f, int first) { int nargs = lua_gettop(L) - 1; int success; int n; clearerr(f); if (nargs == 0) { /* no arguments? */ success = read_line(L, f); n = first+1; /* to return 1 result */ } else { /* ensure stack space for all results and for auxlib's buffer */ luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); success = 1; for (n = first; nargs-- && success; n++) { if (lua_type(L, n) == LUA_TNUMBER) { size_t l = (size_t)lua_tointeger(L, n); success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); } else { const char *p = lua_tostring(L, n); luaL_argcheck(L, p && p[0] == '*', n, "invalid option"); switch (p[1]) { case 'n': /* number */ success = read_number(L, f); break; case 'l': /* line */ success = read_line(L, f); break; case 'a': /* file */ read_chars(L, f, ~((size_t)0)); /* read MAX_SIZE_T chars */ success = 1; /* always success */ break; default: return luaL_argerror(L, n, "invalid format"); } } } } if (ferror(f)) return pushresult(L, 0, NULL); if (!success) { lua_pop(L, 1); /* remove last result */ lua_pushnil(L); /* push nil instead */ } return n - first; } static int io_read (lua_State *L) { return g_read(L, getiofile(L, IO_INPUT), 1); } static int f_read (lua_State *L) { return g_read(L, tofile(L), 2); } static int io_readline (lua_State *L) { FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1)); int sucess; if (f == NULL) /* file is already closed? */ luaL_error(L, "file is already closed"); sucess = read_line(L, f); if (ferror(f)) return luaL_error(L, "%s", strerror(errno)); if (sucess) return 1; else { /* EOF */ if (lua_toboolean(L, lua_upvalueindex(2))) { /* generator created file? */ lua_settop(L, 0); lua_pushvalue(L, lua_upvalueindex(1)); aux_close(L); /* close it */ } return 0; } } /* }====================================================== */ static int g_write (lua_State *L, FILE *f, int arg) { int nargs = lua_gettop(L) - 1; int status = 1; for (; nargs--; arg++) { if (lua_type(L, arg) == LUA_TNUMBER) { /* optimization: could be done exactly as for strings */ status = status && fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0; } else { size_t l; const char *s = luaL_checklstring(L, arg, &l); status = status && (fwrite(s, sizeof(char), l, f) == l); } } return pushresult(L, status, NULL); } static int io_write (lua_State *L) { return g_write(L, getiofile(L, IO_OUTPUT), 1); } static int f_write (lua_State *L) { return g_write(L, tofile(L), 2); } static int f_seek (lua_State *L) { static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; static const char *const modenames[] = {"set", "cur", "end", NULL}; FILE *f = tofile(L); int op = luaL_checkoption(L, 2, "cur", modenames); long offset = luaL_optlong(L, 3, 0); op = fseek(f, offset, mode[op]); if (op) return pushresult(L, 0, NULL); /* error */ else { lua_pushinteger(L, ftell(f)); return 1; } } static int f_setvbuf (lua_State *L) { static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; static const char *const modenames[] = {"no", "full", "line", NULL}; FILE *f = tofile(L); int op = luaL_checkoption(L, 2, NULL, modenames); lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); int res = setvbuf(f, NULL, mode[op], sz); return pushresult(L, res == 0, NULL); } static int io_flush (lua_State *L) { return pushresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL); } static int f_flush (lua_State *L) { return pushresult(L, fflush(tofile(L)) == 0, NULL); } static const luaL_Reg iolib[] = { {"close", io_close}, {"flush", io_flush}, {"input", io_input}, {"lines", io_lines}, {"open", io_open}, {"output", io_output}, {"popen", io_popen}, {"read", io_read}, {"tmpfile", io_tmpfile}, {"type", io_type}, {"write", io_write}, {NULL, NULL} }; static const luaL_Reg flib[] = { {"close", io_close}, {"flush", f_flush}, {"lines", f_lines}, {"read", f_read}, {"seek", f_seek}, {"setvbuf", f_setvbuf}, {"write", f_write}, {"__gc", io_gc}, {"__tostring", io_tostring}, {NULL, NULL} }; static void createmeta (lua_State *L) { luaL_newmetatable(L, LUA_FILEHANDLE); /* create metatable for file handles */ lua_pushvalue(L, -1); /* push metatable */ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ luaL_register(L, NULL, flib); /* file methods */ } static void createstdfile (lua_State *L, FILE *f, int k, const char *fname) { *newfile(L) = f; if (k > 0) { lua_pushvalue(L, -1); lua_rawseti(L, LUA_ENVIRONINDEX, k); } lua_pushvalue(L, -2); /* copy environment */ lua_setfenv(L, -2); /* set it */ lua_setfield(L, -3, fname); } static void newfenv (lua_State *L, lua_CFunction cls) { lua_createtable(L, 0, 1); lua_pushcfunction(L, cls); lua_setfield(L, -2, "__close"); } LUALIB_API int luaopen_io (lua_State *L) { createmeta(L); /* create (private) environment (with fields IO_INPUT, IO_OUTPUT, __close) */ newfenv(L, io_fclose); lua_replace(L, LUA_ENVIRONINDEX); /* open library */ luaL_register(L, LUA_IOLIBNAME, iolib); /* create (and set) default files */ newfenv(L, io_noclose); /* close function for default files */ createstdfile(L, stdin, IO_INPUT, "stdin"); createstdfile(L, stdout, IO_OUTPUT, "stdout"); createstdfile(L, stderr, 0, "stderr"); lua_pop(L, 1); /* pop environment for default files */ lua_getfield(L, -1, "popen"); newfenv(L, io_pclose); /* create environment for 'popen' */ lua_setfenv(L, -2); /* set fenv for 'popen' */ lua_pop(L, 1); /* pop 'popen' */ return 1; } redis-8.0.2/deps/lua/src/llex.c000066400000000000000000000303251501533116600162460ustar00rootroot00000000000000/* ** $Id: llex.c,v 2.20.1.2 2009/11/23 14:58:22 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ #include #include #include #define llex_c #define LUA_CORE #include "lua.h" #include "ldo.h" #include "llex.h" #include "lobject.h" #include "lparser.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "lzio.h" #define next(ls) (ls->current = zgetc(ls->z)) #define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') /* ORDER RESERVED */ const char *const luaX_tokens [] = { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "..", "...", "==", ">=", "<=", "~=", "", "", "", "", NULL }; #define save_and_next(ls) (save(ls, ls->current), next(ls)) static void save (LexState *ls, int c) { Mbuffer *b = ls->buff; if (b->n + 1 > b->buffsize) { size_t newsize; if (b->buffsize >= MAX_SIZET/2) luaX_lexerror(ls, "lexical element too long", 0); newsize = b->buffsize * 2; luaZ_resizebuffer(ls->L, b, newsize); } b->buffer[b->n++] = cast(char, c); } void luaX_init (lua_State *L) { int i; for (i=0; itsv.reserved = cast_byte(i+1); /* reserved word */ } } #define MAXSRC 80 const char *luaX_token2str (LexState *ls, int token) { if (token < FIRST_RESERVED) { lua_assert(token == cast(unsigned char, token)); return (iscntrl(token)) ? luaO_pushfstring(ls->L, "char(%d)", token) : luaO_pushfstring(ls->L, "%c", token); } else return luaX_tokens[token-FIRST_RESERVED]; } static const char *txtToken (LexState *ls, int token) { switch (token) { case TK_NAME: case TK_STRING: case TK_NUMBER: save(ls, '\0'); return luaZ_buffer(ls->buff); default: return luaX_token2str(ls, token); } } void luaX_lexerror (LexState *ls, const char *msg, int token) { char buff[MAXSRC]; luaO_chunkid(buff, getstr(ls->source), MAXSRC); msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg); if (token) luaO_pushfstring(ls->L, "%s near " LUA_QS, msg, txtToken(ls, token)); luaD_throw(ls->L, LUA_ERRSYNTAX); } void luaX_syntaxerror (LexState *ls, const char *msg) { luaX_lexerror(ls, msg, ls->t.token); } TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->L; TString *ts = luaS_newlstr(L, str, l); TValue *o = luaH_setstr(L, ls->fs->h, ts); /* entry for `str' */ if (ttisnil(o)) { setbvalue(o, 1); /* make sure `str' will not be collected */ luaC_checkGC(L); } return ts; } static void inclinenumber (LexState *ls) { int old = ls->current; lua_assert(currIsNewline(ls)); next(ls); /* skip `\n' or `\r' */ if (currIsNewline(ls) && ls->current != old) next(ls); /* skip `\n\r' or `\r\n' */ if (++ls->linenumber >= MAX_INT) luaX_syntaxerror(ls, "chunk has too many lines"); } void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source) { ls->decpoint = '.'; ls->L = L; ls->lookahead.token = TK_EOS; /* no look-ahead token */ ls->z = z; ls->fs = NULL; ls->linenumber = 1; ls->lastline = 1; ls->source = source; luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ next(ls); /* read first char */ } /* ** ======================================================= ** LEXICAL ANALYZER ** ======================================================= */ static int check_next (LexState *ls, const char *set) { if (!strchr(set, ls->current)) return 0; save_and_next(ls); return 1; } static void buffreplace (LexState *ls, char from, char to) { size_t n = luaZ_bufflen(ls->buff); char *p = luaZ_buffer(ls->buff); while (n--) if (p[n] == from) p[n] = to; } static void trydecpoint (LexState *ls, SemInfo *seminfo) { /* format error: try to update decimal point separator */ struct lconv *cv = localeconv(); char old = ls->decpoint; ls->decpoint = (cv ? cv->decimal_point[0] : '.'); buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) { /* format error with correct decimal point: no more options */ buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ luaX_lexerror(ls, "malformed number", TK_NUMBER); } } /* LUA_NUMBER */ static void read_numeral (LexState *ls, SemInfo *seminfo) { lua_assert(isdigit(ls->current)); do { save_and_next(ls); } while (isdigit(ls->current) || ls->current == '.'); if (check_next(ls, "Ee")) /* `E'? */ check_next(ls, "+-"); /* optional exponent sign */ while (isalnum(ls->current) || ls->current == '_') save_and_next(ls); save(ls, '\0'); buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) /* format error? */ trydecpoint(ls, seminfo); /* try to update decimal point separator */ } static int skip_sep (LexState *ls) { int count = 0; int s = ls->current; lua_assert(s == '[' || s == ']'); save_and_next(ls); while (ls->current == '=') { save_and_next(ls); count++; } return (ls->current == s) ? count : (-count) - 1; } static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { int cont = 0; (void)(cont); /* avoid warnings when `cont' is not used */ save_and_next(ls); /* skip 2nd `[' */ if (currIsNewline(ls)) /* string starts with a newline? */ inclinenumber(ls); /* skip it */ for (;;) { switch (ls->current) { case EOZ: luaX_lexerror(ls, (seminfo) ? "unfinished long string" : "unfinished long comment", TK_EOS); break; /* to avoid warnings */ #if defined(LUA_COMPAT_LSTR) case '[': { if (skip_sep(ls) == sep) { save_and_next(ls); /* skip 2nd `[' */ cont++; #if LUA_COMPAT_LSTR == 1 if (sep == 0) luaX_lexerror(ls, "nesting of [[...]] is deprecated", '['); #endif } break; } #endif case ']': { if (skip_sep(ls) == sep) { save_and_next(ls); /* skip 2nd `]' */ #if defined(LUA_COMPAT_LSTR) && LUA_COMPAT_LSTR == 2 cont--; if (sep == 0 && cont >= 0) break; #endif goto endloop; } break; } case '\n': case '\r': { save(ls, '\n'); inclinenumber(ls); if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */ break; } default: { if (seminfo) save_and_next(ls); else next(ls); } } } endloop: if (seminfo) seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), luaZ_bufflen(ls->buff) - 2*(2 + sep)); } static void read_string (LexState *ls, int del, SemInfo *seminfo) { save_and_next(ls); while (ls->current != del) { switch (ls->current) { case EOZ: luaX_lexerror(ls, "unfinished string", TK_EOS); continue; /* to avoid warnings */ case '\n': case '\r': luaX_lexerror(ls, "unfinished string", TK_STRING); continue; /* to avoid warnings */ case '\\': { int c; next(ls); /* do not save the `\' */ switch (ls->current) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '\n': /* go through */ case '\r': save(ls, '\n'); inclinenumber(ls); continue; case EOZ: continue; /* will raise an error next loop */ default: { if (!isdigit(ls->current)) save_and_next(ls); /* handles \\, \", \', and \? */ else { /* \xxx */ int i = 0; c = 0; do { c = 10*c + (ls->current-'0'); next(ls); } while (++i<3 && isdigit(ls->current)); if (c > UCHAR_MAX) luaX_lexerror(ls, "escape sequence too large", TK_STRING); save(ls, c); } continue; } } save(ls, c); next(ls); continue; } default: save_and_next(ls); } } save_and_next(ls); /* skip delimiter */ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1, luaZ_bufflen(ls->buff) - 2); } static int llex (LexState *ls, SemInfo *seminfo) { luaZ_resetbuffer(ls->buff); for (;;) { switch (ls->current) { case '\n': case '\r': { inclinenumber(ls); continue; } case '-': { next(ls); if (ls->current != '-') return '-'; /* else is a comment */ next(ls); if (ls->current == '[') { int sep = skip_sep(ls); luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ if (sep >= 0) { read_long_string(ls, NULL, sep); /* long comment */ luaZ_resetbuffer(ls->buff); continue; } } /* else short comment */ while (!currIsNewline(ls) && ls->current != EOZ) next(ls); continue; } case '[': { int sep = skip_sep(ls); if (sep >= 0) { read_long_string(ls, seminfo, sep); return TK_STRING; } else if (sep == -1) return '['; else luaX_lexerror(ls, "invalid long string delimiter", TK_STRING); } case '=': { next(ls); if (ls->current != '=') return '='; else { next(ls); return TK_EQ; } } case '<': { next(ls); if (ls->current != '=') return '<'; else { next(ls); return TK_LE; } } case '>': { next(ls); if (ls->current != '=') return '>'; else { next(ls); return TK_GE; } } case '~': { next(ls); if (ls->current != '=') return '~'; else { next(ls); return TK_NE; } } case '"': case '\'': { read_string(ls, ls->current, seminfo); return TK_STRING; } case '.': { save_and_next(ls); if (check_next(ls, ".")) { if (check_next(ls, ".")) return TK_DOTS; /* ... */ else return TK_CONCAT; /* .. */ } else if (!isdigit(ls->current)) return '.'; else { read_numeral(ls, seminfo); return TK_NUMBER; } } case EOZ: { return TK_EOS; } default: { if (isspace(ls->current)) { lua_assert(!currIsNewline(ls)); next(ls); continue; } else if (isdigit(ls->current)) { read_numeral(ls, seminfo); return TK_NUMBER; } else if (isalpha(ls->current) || ls->current == '_') { /* identifier or reserved word */ TString *ts; do { save_and_next(ls); } while (isalnum(ls->current) || ls->current == '_'); ts = luaX_newstring(ls, luaZ_buffer(ls->buff), luaZ_bufflen(ls->buff)); if (ts->tsv.reserved > 0) /* reserved word? */ return ts->tsv.reserved - 1 + FIRST_RESERVED; else { seminfo->ts = ts; return TK_NAME; } } else { int c = ls->current; next(ls); return c; /* single-char tokens (+ - / ...) */ } } } } } void luaX_next (LexState *ls) { ls->lastline = ls->linenumber; if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ ls->t = ls->lookahead; /* use this one */ ls->lookahead.token = TK_EOS; /* and discharge it */ } else ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ } void luaX_lookahead (LexState *ls) { lua_assert(ls->lookahead.token == TK_EOS); ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); } redis-8.0.2/deps/lua/src/llex.h000066400000000000000000000042011501533116600162450ustar00rootroot00000000000000/* ** $Id: llex.h,v 1.58.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ #ifndef llex_h #define llex_h #include "lobject.h" #include "lzio.h" #define FIRST_RESERVED 257 /* maximum length of a reserved word */ #define TOKEN_LEN (sizeof("function")/sizeof(char)) /* * WARNING: if you change the order of this enumeration, * grep "ORDER RESERVED" */ enum RESERVED { /* terminal symbols denoted by reserved words */ TK_AND = FIRST_RESERVED, TK_BREAK, TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* other terminal symbols */ TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER, TK_NAME, TK_STRING, TK_EOS }; /* number of reserved words */ #define NUM_RESERVED (cast(int, TK_WHILE-FIRST_RESERVED+1)) /* array with token `names' */ LUAI_DATA const char *const luaX_tokens []; typedef union { lua_Number r; TString *ts; } SemInfo; /* semantics information */ typedef struct Token { int token; SemInfo seminfo; } Token; typedef struct LexState { int current; /* current character (charint) */ int linenumber; /* input line counter */ int lastline; /* line of last token `consumed' */ Token t; /* current token */ Token lookahead; /* look ahead token */ struct FuncState *fs; /* `FuncState' is private to the parser */ struct lua_State *L; ZIO *z; /* input stream */ Mbuffer *buff; /* buffer for tokens */ TString *source; /* current source name */ char decpoint; /* locale decimal point */ } LexState; LUAI_FUNC void luaX_init (lua_State *L); LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source); LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l); LUAI_FUNC void luaX_next (LexState *ls); LUAI_FUNC void luaX_lookahead (LexState *ls); LUAI_FUNC void luaX_lexerror (LexState *ls, const char *msg, int token); LUAI_FUNC void luaX_syntaxerror (LexState *ls, const char *s); LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); #endif redis-8.0.2/deps/lua/src/llimits.h000066400000000000000000000044551501533116600167710ustar00rootroot00000000000000/* ** $Id: llimits.h,v 1.69.1.1 2007/12/27 13:02:25 roberto Exp $ ** Limits, basic types, and some other `installation-dependent' definitions ** See Copyright Notice in lua.h */ #ifndef llimits_h #define llimits_h #include #include #include "lua.h" typedef LUAI_UINT32 lu_int32; typedef LUAI_UMEM lu_mem; typedef LUAI_MEM l_mem; /* chars used as small naturals (so that `char' is reserved for characters) */ typedef unsigned char lu_byte; #define MAX_SIZET ((size_t)(~(size_t)0)-2) #define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) #define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ /* ** conversion of pointer to integer ** this is for hashing only; there is no problem if the integer ** cannot hold the whole pointer value */ #define IntPoint(p) ((unsigned int)(lu_mem)(p)) /* type to ensure maximum alignment */ typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; /* result of a `usual argument conversion' over lua_Number */ typedef LUAI_UACNUMBER l_uacNumber; /* internal assertions for in-house debugging */ #ifdef lua_assert #define check_exp(c,e) (lua_assert(c), (e)) #define api_check(l,e) lua_assert(e) #else #define lua_assert(c) ((void)0) #define check_exp(c,e) (e) #define api_check luai_apicheck #endif #ifndef UNUSED #define UNUSED(x) ((void)(x)) /* to avoid warnings */ #endif #ifndef cast #define cast(t, exp) ((t)(exp)) #endif #define cast_byte(i) cast(lu_byte, (i)) #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) /* ** type for virtual-machine instructions ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) */ typedef lu_int32 Instruction; /* maximum stack for a Lua function */ #define MAXSTACK 250 /* minimum size for the string table (must be power of 2) */ #ifndef MINSTRTABSIZE #define MINSTRTABSIZE 32 #endif /* minimum size for string buffer */ #ifndef LUA_MINBUFFER #define LUA_MINBUFFER 32 #endif #ifndef lua_lock #define lua_lock(L) ((void) 0) #define lua_unlock(L) ((void) 0) #endif #ifndef luai_threadyield #define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} #endif /* ** macro to control inclusion of some hard tests on stack reallocation */ #ifndef HARDSTACKTESTS #define condhardstacktests(x) ((void)0) #else #define condhardstacktests(x) x #endif #endif redis-8.0.2/deps/lua/src/lmathlib.c000066400000000000000000000133071501533116600170770ustar00rootroot00000000000000/* ** $Id: lmathlib.c,v 1.67.1.1 2007/12/27 13:02:25 roberto Exp $ ** Standard mathematical library ** See Copyright Notice in lua.h */ #include #include #define lmathlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #undef PI #define PI (3.14159265358979323846) #define RADIANS_PER_DEGREE (PI/180.0) static int math_abs (lua_State *L) { lua_pushnumber(L, fabs(luaL_checknumber(L, 1))); return 1; } static int math_sin (lua_State *L) { lua_pushnumber(L, sin(luaL_checknumber(L, 1))); return 1; } static int math_sinh (lua_State *L) { lua_pushnumber(L, sinh(luaL_checknumber(L, 1))); return 1; } static int math_cos (lua_State *L) { lua_pushnumber(L, cos(luaL_checknumber(L, 1))); return 1; } static int math_cosh (lua_State *L) { lua_pushnumber(L, cosh(luaL_checknumber(L, 1))); return 1; } static int math_tan (lua_State *L) { lua_pushnumber(L, tan(luaL_checknumber(L, 1))); return 1; } static int math_tanh (lua_State *L) { lua_pushnumber(L, tanh(luaL_checknumber(L, 1))); return 1; } static int math_asin (lua_State *L) { lua_pushnumber(L, asin(luaL_checknumber(L, 1))); return 1; } static int math_acos (lua_State *L) { lua_pushnumber(L, acos(luaL_checknumber(L, 1))); return 1; } static int math_atan (lua_State *L) { lua_pushnumber(L, atan(luaL_checknumber(L, 1))); return 1; } static int math_atan2 (lua_State *L) { lua_pushnumber(L, atan2(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } static int math_ceil (lua_State *L) { lua_pushnumber(L, ceil(luaL_checknumber(L, 1))); return 1; } static int math_floor (lua_State *L) { lua_pushnumber(L, floor(luaL_checknumber(L, 1))); return 1; } static int math_fmod (lua_State *L) { lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } static int math_modf (lua_State *L) { double ip; double fp = modf(luaL_checknumber(L, 1), &ip); lua_pushnumber(L, ip); lua_pushnumber(L, fp); return 2; } static int math_sqrt (lua_State *L) { lua_pushnumber(L, sqrt(luaL_checknumber(L, 1))); return 1; } static int math_pow (lua_State *L) { lua_pushnumber(L, pow(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } static int math_log (lua_State *L) { lua_pushnumber(L, log(luaL_checknumber(L, 1))); return 1; } static int math_log10 (lua_State *L) { lua_pushnumber(L, log10(luaL_checknumber(L, 1))); return 1; } static int math_exp (lua_State *L) { lua_pushnumber(L, exp(luaL_checknumber(L, 1))); return 1; } static int math_deg (lua_State *L) { lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE); return 1; } static int math_rad (lua_State *L) { lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE); return 1; } static int math_frexp (lua_State *L) { int e; lua_pushnumber(L, frexp(luaL_checknumber(L, 1), &e)); lua_pushinteger(L, e); return 2; } static int math_ldexp (lua_State *L) { lua_pushnumber(L, ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))); return 1; } static int math_min (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ lua_Number dmin = luaL_checknumber(L, 1); int i; for (i=2; i<=n; i++) { lua_Number d = luaL_checknumber(L, i); if (d < dmin) dmin = d; } lua_pushnumber(L, dmin); return 1; } static int math_max (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ lua_Number dmax = luaL_checknumber(L, 1); int i; for (i=2; i<=n; i++) { lua_Number d = luaL_checknumber(L, i); if (d > dmax) dmax = d; } lua_pushnumber(L, dmax); return 1; } static int math_random (lua_State *L) { /* the `%' avoids the (rare) case of r==1, and is needed also because on some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; switch (lua_gettop(L)) { /* check number of arguments */ case 0: { /* no arguments */ lua_pushnumber(L, r); /* Number between 0 and 1 */ break; } case 1: { /* only upper limit */ int u = luaL_checkint(L, 1); luaL_argcheck(L, 1<=u, 1, "interval is empty"); lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */ break; } case 2: { /* lower and upper limits */ int l = luaL_checkint(L, 1); int u = luaL_checkint(L, 2); luaL_argcheck(L, l<=u, 2, "interval is empty"); lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */ break; } default: return luaL_error(L, "wrong number of arguments"); } return 1; } static int math_randomseed (lua_State *L) { srand(luaL_checkint(L, 1)); return 0; } static const luaL_Reg mathlib[] = { {"abs", math_abs}, {"acos", math_acos}, {"asin", math_asin}, {"atan2", math_atan2}, {"atan", math_atan}, {"ceil", math_ceil}, {"cosh", math_cosh}, {"cos", math_cos}, {"deg", math_deg}, {"exp", math_exp}, {"floor", math_floor}, {"fmod", math_fmod}, {"frexp", math_frexp}, {"ldexp", math_ldexp}, {"log10", math_log10}, {"log", math_log}, {"max", math_max}, {"min", math_min}, {"modf", math_modf}, {"pow", math_pow}, {"rad", math_rad}, {"random", math_random}, {"randomseed", math_randomseed}, {"sinh", math_sinh}, {"sin", math_sin}, {"sqrt", math_sqrt}, {"tanh", math_tanh}, {"tan", math_tan}, {NULL, NULL} }; /* ** Open math library */ LUALIB_API int luaopen_math (lua_State *L) { luaL_register(L, LUA_MATHLIBNAME, mathlib); lua_pushnumber(L, PI); lua_setfield(L, -2, "pi"); lua_pushnumber(L, HUGE_VAL); lua_setfield(L, -2, "huge"); #if defined(LUA_COMPAT_MOD) lua_getfield(L, -1, "fmod"); lua_setfield(L, -2, "mod"); #endif return 1; } redis-8.0.2/deps/lua/src/lmem.c000066400000000000000000000041741501533116600162370ustar00rootroot00000000000000/* ** $Id: lmem.c,v 1.70.1.1 2007/12/27 13:02:25 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ #include #define lmem_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" /* ** About the realloc function: ** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); ** (`osize' is the old size, `nsize' is the new size) ** ** Lua ensures that (ptr == NULL) iff (osize == 0). ** ** * frealloc(ud, NULL, 0, x) creates a new block of size `x' ** ** * frealloc(ud, p, x, 0) frees the block `p' ** (in this specific case, frealloc must return NULL). ** particularly, frealloc(ud, NULL, 0, 0) does nothing ** (which is equivalent to free(NULL) in ANSI C) ** ** frealloc returns NULL if it cannot create or reallocate the area ** (any reallocation to an equal or smaller size cannot fail!) */ #define MINSIZEARRAY 4 void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, int limit, const char *errormsg) { void *newblock; int newsize; if (*size >= limit/2) { /* cannot double it? */ if (*size >= limit) /* cannot grow even a little? */ luaG_runerror(L, errormsg); newsize = limit; /* still have at least one free place */ } else { newsize = (*size)*2; if (newsize < MINSIZEARRAY) newsize = MINSIZEARRAY; /* minimum size */ } newblock = luaM_reallocv(L, block, *size, newsize, size_elems); *size = newsize; /* update only when everything else is OK */ return newblock; } void *luaM_toobig (lua_State *L) { luaG_runerror(L, "memory allocation error: block too big"); return NULL; /* to avoid warnings */ } /* ** generic allocation routine. */ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { global_State *g = G(L); lua_assert((osize == 0) == (block == NULL)); block = (*g->frealloc)(g->ud, block, osize, nsize); if (block == NULL && nsize > 0) luaD_throw(L, LUA_ERRMEM); lua_assert((nsize == 0) == (block == NULL)); g->totalbytes = (g->totalbytes - osize) + nsize; return block; } redis-8.0.2/deps/lua/src/lmem.h000066400000000000000000000027261501533116600162450ustar00rootroot00000000000000/* ** $Id: lmem.h,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ #ifndef lmem_h #define lmem_h #include #include "llimits.h" #include "lua.h" #define MEMERRMSG "not enough memory" #define luaM_reallocv(L,b,on,n,e) \ ((cast(size_t, (n)+1) <= MAX_SIZET/(e)) ? /* +1 to avoid warnings */ \ luaM_realloc_(L, (b), (on)*(e), (n)*(e)) : \ luaM_toobig(L)) #define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) #define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) #define luaM_freearray(L, b, n, t) luaM_reallocv(L, (b), n, 0, sizeof(t)) #define luaM_malloc(L,t) luaM_realloc_(L, NULL, 0, (t)) #define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) #define luaM_newvector(L,n,t) \ cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t))) #define luaM_growvector(L,v,nelems,size,t,limit,e) \ if ((nelems)+1 > (size)) \ ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e))) #define luaM_reallocvector(L, v,oldn,n,t) \ ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t)))) LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, size_t size); LUAI_FUNC void *luaM_toobig (lua_State *L); LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elem, int limit, const char *errormsg); #endif redis-8.0.2/deps/lua/src/loadlib.c000066400000000000000000000454201501533116600167120ustar00rootroot00000000000000/* ** $Id: loadlib.c,v 1.52.1.4 2009/09/09 13:17:16 roberto Exp $ ** Dynamic library loader for Lua ** See Copyright Notice in lua.h ** ** This module contains an implementation of loadlib for Unix systems ** that have dlfcn, an implementation for Darwin (Mac OS X), an ** implementation for Windows, and a stub for other systems. */ #include #include #define loadlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" /* prefix for open functions in C libraries */ #define LUA_POF "luaopen_" /* separator for open functions in C libraries */ #define LUA_OFSEP "_" #define LIBPREFIX "LOADLIB: " #define POF LUA_POF #define LIB_FAIL "open" /* error codes for ll_loadfunc */ #define ERRLIB 1 #define ERRFUNC 2 #define setprogdir(L) ((void)0) static void ll_unloadlib (void *lib); static void *ll_load (lua_State *L, const char *path); static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym); #if defined(LUA_DL_DLOPEN) /* ** {======================================================================== ** This is an implementation of loadlib based on the dlfcn interface. ** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD, ** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least ** as an emulation layer on top of native functions. ** ========================================================================= */ #include static void ll_unloadlib (void *lib) { dlclose(lib); } static void *ll_load (lua_State *L, const char *path) { void *lib = dlopen(path, RTLD_NOW); if (lib == NULL) lua_pushstring(L, dlerror()); return lib; } static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { lua_CFunction f = (lua_CFunction)dlsym(lib, sym); if (f == NULL) lua_pushstring(L, dlerror()); return f; } /* }====================================================== */ #elif defined(LUA_DL_DLL) /* ** {====================================================================== ** This is an implementation of loadlib for Windows using native functions. ** ======================================================================= */ #include #undef setprogdir static void setprogdir (lua_State *L) { char buff[MAX_PATH + 1]; char *lb; DWORD nsize = sizeof(buff)/sizeof(char); DWORD n = GetModuleFileNameA(NULL, buff, nsize); if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL) luaL_error(L, "unable to get ModuleFileName"); else { *lb = '\0'; luaL_gsub(L, lua_tostring(L, -1), LUA_EXECDIR, buff); lua_remove(L, -2); /* remove original string */ } } static void pusherror (lua_State *L) { int error = GetLastError(); char buffer[128]; if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, 0, buffer, sizeof(buffer), NULL)) lua_pushstring(L, buffer); else lua_pushfstring(L, "system error %d\n", error); } static void ll_unloadlib (void *lib) { FreeLibrary((HINSTANCE)lib); } static void *ll_load (lua_State *L, const char *path) { HINSTANCE lib = LoadLibraryA(path); if (lib == NULL) pusherror(L); return lib; } static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { lua_CFunction f = (lua_CFunction)GetProcAddress((HINSTANCE)lib, sym); if (f == NULL) pusherror(L); return f; } /* }====================================================== */ #elif defined(LUA_DL_DYLD) /* ** {====================================================================== ** Native Mac OS X / Darwin Implementation ** ======================================================================= */ #include /* Mac appends a `_' before C function names */ #undef POF #define POF "_" LUA_POF static void pusherror (lua_State *L) { const char *err_str; const char *err_file; NSLinkEditErrors err; int err_num; NSLinkEditError(&err, &err_num, &err_file, &err_str); lua_pushstring(L, err_str); } static const char *errorfromcode (NSObjectFileImageReturnCode ret) { switch (ret) { case NSObjectFileImageInappropriateFile: return "file is not a bundle"; case NSObjectFileImageArch: return "library is for wrong CPU type"; case NSObjectFileImageFormat: return "bad format"; case NSObjectFileImageAccess: return "cannot access file"; case NSObjectFileImageFailure: default: return "unable to load library"; } } static void ll_unloadlib (void *lib) { NSUnLinkModule((NSModule)lib, NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES); } static void *ll_load (lua_State *L, const char *path) { NSObjectFileImage img; NSObjectFileImageReturnCode ret; /* this would be a rare case, but prevents crashing if it happens */ if(!_dyld_present()) { lua_pushliteral(L, "dyld not present"); return NULL; } ret = NSCreateObjectFileImageFromFile(path, &img); if (ret == NSObjectFileImageSuccess) { NSModule mod = NSLinkModule(img, path, NSLINKMODULE_OPTION_PRIVATE | NSLINKMODULE_OPTION_RETURN_ON_ERROR); NSDestroyObjectFileImage(img); if (mod == NULL) pusherror(L); return mod; } lua_pushstring(L, errorfromcode(ret)); return NULL; } static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { NSSymbol nss = NSLookupSymbolInModule((NSModule)lib, sym); if (nss == NULL) { lua_pushfstring(L, "symbol " LUA_QS " not found", sym); return NULL; } return (lua_CFunction)NSAddressOfSymbol(nss); } /* }====================================================== */ #else /* ** {====================================================== ** Fallback for other systems ** ======================================================= */ #undef LIB_FAIL #define LIB_FAIL "absent" #define DLMSG "dynamic libraries not enabled; check your Lua installation" static void ll_unloadlib (void *lib) { (void)lib; /* to avoid warnings */ } static void *ll_load (lua_State *L, const char *path) { (void)path; /* to avoid warnings */ lua_pushliteral(L, DLMSG); return NULL; } static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) { (void)lib; (void)sym; /* to avoid warnings */ lua_pushliteral(L, DLMSG); return NULL; } /* }====================================================== */ #endif static void **ll_register (lua_State *L, const char *path) { void **plib; lua_pushfstring(L, "%s%s", LIBPREFIX, path); lua_gettable(L, LUA_REGISTRYINDEX); /* check library in registry? */ if (!lua_isnil(L, -1)) /* is there an entry? */ plib = (void **)lua_touserdata(L, -1); else { /* no entry yet; create one */ lua_pop(L, 1); plib = (void **)lua_newuserdata(L, sizeof(const void *)); *plib = NULL; luaL_getmetatable(L, "_LOADLIB"); lua_setmetatable(L, -2); lua_pushfstring(L, "%s%s", LIBPREFIX, path); lua_pushvalue(L, -2); lua_settable(L, LUA_REGISTRYINDEX); } return plib; } /* ** __gc tag method: calls library's `ll_unloadlib' function with the lib ** handle */ static int gctm (lua_State *L) { void **lib = (void **)luaL_checkudata(L, 1, "_LOADLIB"); if (*lib) ll_unloadlib(*lib); *lib = NULL; /* mark library as closed */ return 0; } static int ll_loadfunc (lua_State *L, const char *path, const char *sym) { void **reg = ll_register(L, path); if (*reg == NULL) *reg = ll_load(L, path); if (*reg == NULL) return ERRLIB; /* unable to load library */ else { lua_CFunction f = ll_sym(L, *reg, sym); if (f == NULL) return ERRFUNC; /* unable to find function */ lua_pushcfunction(L, f); return 0; /* return function */ } } static int ll_loadlib (lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *init = luaL_checkstring(L, 2); int stat = ll_loadfunc(L, path, init); if (stat == 0) /* no errors? */ return 1; /* return the loaded function */ else { /* error; error message is on stack top */ lua_pushnil(L); lua_insert(L, -2); lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init"); return 3; /* return nil, error message, and where */ } } /* ** {====================================================== ** 'require' function ** ======================================================= */ static int readable (const char *filename) { FILE *f = fopen(filename, "r"); /* try to open file */ if (f == NULL) return 0; /* open failed */ fclose(f); return 1; } static const char *pushnexttemplate (lua_State *L, const char *path) { const char *l; while (*path == *LUA_PATHSEP) path++; /* skip separators */ if (*path == '\0') return NULL; /* no more templates */ l = strchr(path, *LUA_PATHSEP); /* find next separator */ if (l == NULL) l = path + strlen(path); lua_pushlstring(L, path, l - path); /* template */ return l; } static const char *findfile (lua_State *L, const char *name, const char *pname) { const char *path; name = luaL_gsub(L, name, ".", LUA_DIRSEP); lua_getfield(L, LUA_ENVIRONINDEX, pname); path = lua_tostring(L, -1); if (path == NULL) luaL_error(L, LUA_QL("package.%s") " must be a string", pname); lua_pushliteral(L, ""); /* error accumulator */ while ((path = pushnexttemplate(L, path)) != NULL) { const char *filename; filename = luaL_gsub(L, lua_tostring(L, -1), LUA_PATH_MARK, name); lua_remove(L, -2); /* remove path template */ if (readable(filename)) /* does file exist and is readable? */ return filename; /* return that file name */ lua_pushfstring(L, "\n\tno file " LUA_QS, filename); lua_remove(L, -2); /* remove file name */ lua_concat(L, 2); /* add entry to possible error message */ } return NULL; /* not found */ } static void loaderror (lua_State *L, const char *filename) { luaL_error(L, "error loading module " LUA_QS " from file " LUA_QS ":\n\t%s", lua_tostring(L, 1), filename, lua_tostring(L, -1)); } static int loader_Lua (lua_State *L) { const char *filename; const char *name = luaL_checkstring(L, 1); filename = findfile(L, name, "path"); if (filename == NULL) return 1; /* library not found in this path */ if (luaL_loadfile(L, filename) != 0) loaderror(L, filename); return 1; /* library loaded successfully */ } static const char *mkfuncname (lua_State *L, const char *modname) { const char *funcname; const char *mark = strchr(modname, *LUA_IGMARK); if (mark) modname = mark + 1; funcname = luaL_gsub(L, modname, ".", LUA_OFSEP); funcname = lua_pushfstring(L, POF"%s", funcname); lua_remove(L, -2); /* remove 'gsub' result */ return funcname; } static int loader_C (lua_State *L) { const char *funcname; const char *name = luaL_checkstring(L, 1); const char *filename = findfile(L, name, "cpath"); if (filename == NULL) return 1; /* library not found in this path */ funcname = mkfuncname(L, name); if (ll_loadfunc(L, filename, funcname) != 0) loaderror(L, filename); return 1; /* library loaded successfully */ } static int loader_Croot (lua_State *L) { const char *funcname; const char *filename; const char *name = luaL_checkstring(L, 1); const char *p = strchr(name, '.'); int stat; if (p == NULL) return 0; /* is root */ lua_pushlstring(L, name, p - name); filename = findfile(L, lua_tostring(L, -1), "cpath"); if (filename == NULL) return 1; /* root not found */ funcname = mkfuncname(L, name); if ((stat = ll_loadfunc(L, filename, funcname)) != 0) { if (stat != ERRFUNC) loaderror(L, filename); /* real error */ lua_pushfstring(L, "\n\tno module " LUA_QS " in file " LUA_QS, name, filename); return 1; /* function not found */ } return 1; } static int loader_preload (lua_State *L) { const char *name = luaL_checkstring(L, 1); lua_getfield(L, LUA_ENVIRONINDEX, "preload"); if (!lua_istable(L, -1)) luaL_error(L, LUA_QL("package.preload") " must be a table"); lua_getfield(L, -1, name); if (lua_isnil(L, -1)) /* not found? */ lua_pushfstring(L, "\n\tno field package.preload['%s']", name); return 1; } static const int sentinel_ = 0; #define sentinel ((void *)&sentinel_) static int ll_require (lua_State *L) { const char *name = luaL_checkstring(L, 1); int i; lua_settop(L, 1); /* _LOADED table will be at index 2 */ lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); lua_getfield(L, 2, name); if (lua_toboolean(L, -1)) { /* is it there? */ if (lua_touserdata(L, -1) == sentinel) /* check loops */ luaL_error(L, "loop or previous error loading module " LUA_QS, name); return 1; /* package is already loaded */ } /* else must load it; iterate over available loaders */ lua_getfield(L, LUA_ENVIRONINDEX, "loaders"); if (!lua_istable(L, -1)) luaL_error(L, LUA_QL("package.loaders") " must be a table"); lua_pushliteral(L, ""); /* error message accumulator */ for (i=1; ; i++) { lua_rawgeti(L, -2, i); /* get a loader */ if (lua_isnil(L, -1)) luaL_error(L, "module " LUA_QS " not found:%s", name, lua_tostring(L, -2)); lua_pushstring(L, name); lua_call(L, 1, 1); /* call it */ if (lua_isfunction(L, -1)) /* did it find module? */ break; /* module loaded successfully */ else if (lua_isstring(L, -1)) /* loader returned error message? */ lua_concat(L, 2); /* accumulate it */ else lua_pop(L, 1); } lua_pushlightuserdata(L, sentinel); lua_setfield(L, 2, name); /* _LOADED[name] = sentinel */ lua_pushstring(L, name); /* pass name as argument to module */ lua_call(L, 1, 1); /* run loaded module */ if (!lua_isnil(L, -1)) /* non-nil return? */ lua_setfield(L, 2, name); /* _LOADED[name] = returned value */ lua_getfield(L, 2, name); if (lua_touserdata(L, -1) == sentinel) { /* module did not set a value? */ lua_pushboolean(L, 1); /* use true as result */ lua_pushvalue(L, -1); /* extra copy to be returned */ lua_setfield(L, 2, name); /* _LOADED[name] = true */ } return 1; } /* }====================================================== */ /* ** {====================================================== ** 'module' function ** ======================================================= */ static void setfenv (lua_State *L) { lua_Debug ar; if (lua_getstack(L, 1, &ar) == 0 || lua_getinfo(L, "f", &ar) == 0 || /* get calling function */ lua_iscfunction(L, -1)) luaL_error(L, LUA_QL("module") " not called from a Lua function"); lua_pushvalue(L, -2); lua_setfenv(L, -2); lua_pop(L, 1); } static void dooptions (lua_State *L, int n) { int i; for (i = 2; i <= n; i++) { lua_pushvalue(L, i); /* get option (a function) */ lua_pushvalue(L, -2); /* module */ lua_call(L, 1, 0); } } static void modinit (lua_State *L, const char *modname) { const char *dot; lua_pushvalue(L, -1); lua_setfield(L, -2, "_M"); /* module._M = module */ lua_pushstring(L, modname); lua_setfield(L, -2, "_NAME"); dot = strrchr(modname, '.'); /* look for last dot in module name */ if (dot == NULL) dot = modname; else dot++; /* set _PACKAGE as package name (full module name minus last part) */ lua_pushlstring(L, modname, dot - modname); lua_setfield(L, -2, "_PACKAGE"); } static int ll_module (lua_State *L) { const char *modname = luaL_checkstring(L, 1); int loaded = lua_gettop(L) + 1; /* index of _LOADED table */ lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); lua_getfield(L, loaded, modname); /* get _LOADED[modname] */ if (!lua_istable(L, -1)) { /* not found? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ if (luaL_findtable(L, LUA_GLOBALSINDEX, modname, 1) != NULL) return luaL_error(L, "name conflict for module " LUA_QS, modname); lua_pushvalue(L, -1); lua_setfield(L, loaded, modname); /* _LOADED[modname] = new table */ } /* check whether table already has a _NAME field */ lua_getfield(L, -1, "_NAME"); if (!lua_isnil(L, -1)) /* is table an initialized module? */ lua_pop(L, 1); else { /* no; initialize it */ lua_pop(L, 1); modinit(L, modname); } lua_pushvalue(L, -1); setfenv(L); dooptions(L, loaded - 1); return 0; } static int ll_seeall (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); if (!lua_getmetatable(L, 1)) { lua_createtable(L, 0, 1); /* create new metatable */ lua_pushvalue(L, -1); lua_setmetatable(L, 1); } lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setfield(L, -2, "__index"); /* mt.__index = _G */ return 0; } /* }====================================================== */ /* auxiliary mark (for internal use) */ #define AUXMARK "\1" static void setpath (lua_State *L, const char *fieldname, const char *envname, const char *def) { const char *path = getenv(envname); if (path == NULL) /* no environment variable? */ lua_pushstring(L, def); /* use default */ else { /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ path = luaL_gsub(L, path, LUA_PATHSEP LUA_PATHSEP, LUA_PATHSEP AUXMARK LUA_PATHSEP); luaL_gsub(L, path, AUXMARK, def); lua_remove(L, -2); } setprogdir(L); lua_setfield(L, -2, fieldname); } static const luaL_Reg pk_funcs[] = { {"loadlib", ll_loadlib}, {"seeall", ll_seeall}, {NULL, NULL} }; static const luaL_Reg ll_funcs[] = { {"module", ll_module}, {"require", ll_require}, {NULL, NULL} }; static const lua_CFunction loaders[] = {loader_preload, loader_Lua, loader_C, loader_Croot, NULL}; LUALIB_API int luaopen_package (lua_State *L) { int i; /* create new type _LOADLIB */ luaL_newmetatable(L, "_LOADLIB"); lua_pushcfunction(L, gctm); lua_setfield(L, -2, "__gc"); /* create `package' table */ luaL_register(L, LUA_LOADLIBNAME, pk_funcs); #if defined(LUA_COMPAT_LOADLIB) lua_getfield(L, -1, "loadlib"); lua_setfield(L, LUA_GLOBALSINDEX, "loadlib"); #endif lua_pushvalue(L, -1); lua_replace(L, LUA_ENVIRONINDEX); /* create `loaders' table */ lua_createtable(L, sizeof(loaders)/sizeof(loaders[0]) - 1, 0); /* fill it with pre-defined loaders */ for (i=0; loaders[i] != NULL; i++) { lua_pushcfunction(L, loaders[i]); lua_rawseti(L, -2, i+1); } lua_setfield(L, -2, "loaders"); /* put it in field `loaders' */ setpath(L, "path", LUA_PATH, LUA_PATH_DEFAULT); /* set field `path' */ setpath(L, "cpath", LUA_CPATH, LUA_CPATH_DEFAULT); /* set field `cpath' */ /* store config information */ lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n" LUA_EXECDIR "\n" LUA_IGMARK); lua_setfield(L, -2, "config"); /* set field `loaded' */ luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2); lua_setfield(L, -2, "loaded"); /* set field `preload' */ lua_newtable(L); lua_setfield(L, -2, "preload"); lua_pushvalue(L, LUA_GLOBALSINDEX); luaL_register(L, NULL, ll_funcs); /* open lib into global table */ lua_pop(L, 1); return 1; /* return 'package' table */ } redis-8.0.2/deps/lua/src/lobject.c000066400000000000000000000125721501533116600167300ustar00rootroot00000000000000/* ** $Id: lobject.c,v 2.22.1.1 2007/12/27 13:02:25 roberto Exp $ ** Some generic functions over Lua objects ** See Copyright Notice in lua.h */ #include #include #include #include #include #define lobject_c #define LUA_CORE #include "lua.h" #include "ldo.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "lvm.h" const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL}; /* ** converts an integer to a "floating point byte", represented as ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if ** eeeee != 0 and (xxx) otherwise. */ int luaO_int2fb (unsigned int x) { int e = 0; /* expoent */ while (x >= 16) { x = (x+1) >> 1; e++; } if (x < 8) return x; else return ((e+1) << 3) | (cast_int(x) - 8); } /* converts back */ int luaO_fb2int (int x) { int e = (x >> 3) & 31; if (e == 0) return x; else return ((x & 7)+8) << (e - 1); } int luaO_log2 (unsigned int x) { static const lu_byte log_2[256] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 }; int l = -1; while (x >= 256) { l += 8; x >>= 8; } return l + log_2[x]; } int luaO_rawequalObj (const TValue *t1, const TValue *t2) { if (ttype(t1) != ttype(t2)) return 0; else switch (ttype(t1)) { case LUA_TNIL: return 1; case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* boolean true must be 1 !! */ case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); default: lua_assert(iscollectable(t1)); return gcvalue(t1) == gcvalue(t2); } } int luaO_str2d (const char *s, lua_Number *result) { char *endptr; *result = lua_str2number(s, &endptr); if (endptr == s) return 0; /* conversion failed */ if (*endptr == 'x' || *endptr == 'X') /* maybe an hexadecimal constant? */ *result = cast_num(strtoul(s, &endptr, 16)); if (*endptr == '\0') return 1; /* most common case */ while (isspace(cast(unsigned char, *endptr))) endptr++; if (*endptr != '\0') return 0; /* invalid trailing characters? */ return 1; } static void pushstr (lua_State *L, const char *str) { setsvalue2s(L, L->top, luaS_new(L, str)); incr_top(L); } /* this function handles only `%d', `%c', %f, %p, and `%s' formats */ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { int n = 1; pushstr(L, ""); for (;;) { const char *e = strchr(fmt, '%'); if (e == NULL) break; setsvalue2s(L, L->top, luaS_newlstr(L, fmt, e-fmt)); incr_top(L); switch (*(e+1)) { case 's': { const char *s = va_arg(argp, char *); if (s == NULL) s = "(null)"; pushstr(L, s); break; } case 'c': { char buff[2]; buff[0] = cast(char, va_arg(argp, int)); buff[1] = '\0'; pushstr(L, buff); break; } case 'd': { setnvalue(L->top, cast_num(va_arg(argp, int))); incr_top(L); break; } case 'f': { setnvalue(L->top, cast_num(va_arg(argp, l_uacNumber))); incr_top(L); break; } case 'p': { char buff[4*sizeof(void *) + 8]; /* should be enough space for a `%p' */ sprintf(buff, "%p", va_arg(argp, void *)); pushstr(L, buff); break; } case '%': { pushstr(L, "%"); break; } default: { char buff[3]; buff[0] = '%'; buff[1] = *(e+1); buff[2] = '\0'; pushstr(L, buff); break; } } n += 2; fmt = e+2; } pushstr(L, fmt); luaV_concat(L, n+1, cast_int(L->top - L->base) - 1); L->top -= n; return svalue(L->top - 1); } const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { const char *msg; va_list argp; va_start(argp, fmt); msg = luaO_pushvfstring(L, fmt, argp); va_end(argp); return msg; } void luaO_chunkid (char *out, const char *source, size_t bufflen) { if (*source == '=') { strncpy(out, source+1, bufflen); /* remove first char */ out[bufflen-1] = '\0'; /* ensures null termination */ } else { /* out = "source", or "...source" */ if (*source == '@') { size_t l; source++; /* skip the `@' */ bufflen -= sizeof(" '...' "); l = strlen(source); strcpy(out, ""); if (l > bufflen) { source += (l-bufflen); /* get last part of file name */ strcat(out, "..."); } strcat(out, source); } else { /* out = [string "string"] */ size_t len = strcspn(source, "\n\r"); /* stop at first newline */ bufflen -= sizeof(" [string \"...\"] "); if (len > bufflen) len = bufflen; strcpy(out, "[string \""); if (source[len] != '\0') { /* must truncate? */ strncat(out, source, len); strcat(out, "..."); } else strcat(out, source); strcat(out, "\"]"); } } } redis-8.0.2/deps/lua/src/lobject.h000066400000000000000000000205051501533116600167300ustar00rootroot00000000000000/* ** $Id: lobject.h,v 2.20.1.2 2008/08/06 13:29:48 roberto Exp $ ** Type definitions for Lua objects ** See Copyright Notice in lua.h */ #ifndef lobject_h #define lobject_h #include #include "llimits.h" #include "lua.h" /* tags for values visible from Lua */ #define LAST_TAG LUA_TTHREAD #define NUM_TAGS (LAST_TAG+1) /* ** Extra tags for non-values */ #define LUA_TPROTO (LAST_TAG+1) #define LUA_TUPVAL (LAST_TAG+2) #define LUA_TDEADKEY (LAST_TAG+3) /* ** Union of all collectable objects */ typedef union GCObject GCObject; /* ** Common Header for all collectable objects (in macro form, to be ** included in other objects) */ #define CommonHeader GCObject *next; lu_byte tt; lu_byte marked /* ** Common header in struct form */ typedef struct GCheader { CommonHeader; } GCheader; /* ** Union of all Lua values */ typedef union { GCObject *gc; void *p; lua_Number n; int b; } Value; /* ** Tagged Values */ #define TValuefields Value value; int tt typedef struct lua_TValue { TValuefields; } TValue; /* Macros to test type */ #define ttisnil(o) (ttype(o) == LUA_TNIL) #define ttisnumber(o) (ttype(o) == LUA_TNUMBER) #define ttisstring(o) (ttype(o) == LUA_TSTRING) #define ttistable(o) (ttype(o) == LUA_TTABLE) #define ttisfunction(o) (ttype(o) == LUA_TFUNCTION) #define ttisboolean(o) (ttype(o) == LUA_TBOOLEAN) #define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA) #define ttisthread(o) (ttype(o) == LUA_TTHREAD) #define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA) /* Macros to access values */ #define ttype(o) ((o)->tt) #define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc) #define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p) #define nvalue(o) check_exp(ttisnumber(o), (o)->value.n) #define rawtsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts) #define tsvalue(o) (&rawtsvalue(o)->tsv) #define rawuvalue(o) check_exp(ttisuserdata(o), &(o)->value.gc->u) #define uvalue(o) (&rawuvalue(o)->uv) #define clvalue(o) check_exp(ttisfunction(o), &(o)->value.gc->cl) #define hvalue(o) check_exp(ttistable(o), &(o)->value.gc->h) #define bvalue(o) check_exp(ttisboolean(o), (o)->value.b) #define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th) #define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0)) /* ** for internal debug only */ #define checkconsistency(obj) \ lua_assert(!iscollectable(obj) || (ttype(obj) == (obj)->value.gc->gch.tt)) #define checkliveness(g,obj) \ lua_assert(!iscollectable(obj) || \ ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc))) /* Macros to set values */ #define setnilvalue(obj) ((obj)->tt=LUA_TNIL) #define setnvalue(obj,x) \ { TValue *i_o=(obj); i_o->value.n=(x); i_o->tt=LUA_TNUMBER; } #define setpvalue(obj,x) \ { TValue *i_o=(obj); i_o->value.p=(x); i_o->tt=LUA_TLIGHTUSERDATA; } #define setbvalue(obj,x) \ { TValue *i_o=(obj); i_o->value.b=(x); i_o->tt=LUA_TBOOLEAN; } #define setsvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TSTRING; \ checkliveness(G(L),i_o); } #define setuvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUSERDATA; \ checkliveness(G(L),i_o); } #define setthvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTHREAD; \ checkliveness(G(L),i_o); } #define setclvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TFUNCTION; \ checkliveness(G(L),i_o); } #define sethvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTABLE; \ checkliveness(G(L),i_o); } #define setptvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TPROTO; \ checkliveness(G(L),i_o); } #define setobj(L,obj1,obj2) \ { const TValue *o2=(obj2); TValue *o1=(obj1); \ o1->value = o2->value; o1->tt=o2->tt; \ checkliveness(G(L),o1); } /* ** different types of sets, according to destination */ /* from stack to (same) stack */ #define setobjs2s setobj /* to stack (not from same stack) */ #define setobj2s setobj #define setsvalue2s setsvalue #define sethvalue2s sethvalue #define setptvalue2s setptvalue /* from table to same table */ #define setobjt2t setobj /* to table */ #define setobj2t setobj /* to new object */ #define setobj2n setobj #define setsvalue2n setsvalue #define setttype(obj, tt) (ttype(obj) = (tt)) #define iscollectable(o) (ttype(o) >= LUA_TSTRING) typedef TValue *StkId; /* index to stack elements */ /* ** String headers for string table */ typedef union TString { L_Umaxalign dummy; /* ensures maximum alignment for strings */ struct { CommonHeader; lu_byte reserved; unsigned int hash; size_t len; } tsv; } TString; #define getstr(ts) cast(const char *, (ts) + 1) #define svalue(o) getstr(rawtsvalue(o)) typedef union Udata { L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ struct { CommonHeader; struct Table *metatable; struct Table *env; size_t len; } uv; } Udata; /* ** Function Prototypes */ typedef struct Proto { CommonHeader; TValue *k; /* constants used by the function */ Instruction *code; struct Proto **p; /* functions defined inside the function */ int *lineinfo; /* map from opcodes to source lines */ struct LocVar *locvars; /* information about local variables */ TString **upvalues; /* upvalue names */ TString *source; int sizeupvalues; int sizek; /* size of `k' */ int sizecode; int sizelineinfo; int sizep; /* size of `p' */ int sizelocvars; int linedefined; int lastlinedefined; GCObject *gclist; lu_byte nups; /* number of upvalues */ lu_byte numparams; lu_byte is_vararg; lu_byte maxstacksize; } Proto; /* masks for new-style vararg */ #define VARARG_HASARG 1 #define VARARG_ISVARARG 2 #define VARARG_NEEDSARG 4 typedef struct LocVar { TString *varname; int startpc; /* first point where variable is active */ int endpc; /* first point where variable is dead */ } LocVar; /* ** Upvalues */ typedef struct UpVal { CommonHeader; TValue *v; /* points to stack or to its own value */ union { TValue value; /* the value (when closed) */ struct { /* double linked list (when open) */ struct UpVal *prev; struct UpVal *next; } l; } u; } UpVal; /* ** Closures */ #define ClosureHeader \ CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \ struct Table *env typedef struct CClosure { ClosureHeader; lua_CFunction f; TValue upvalue[1]; } CClosure; typedef struct LClosure { ClosureHeader; struct Proto *p; UpVal *upvals[1]; } LClosure; typedef union Closure { CClosure c; LClosure l; } Closure; #define iscfunction(o) (ttype(o) == LUA_TFUNCTION && clvalue(o)->c.isC) #define isLfunction(o) (ttype(o) == LUA_TFUNCTION && !clvalue(o)->c.isC) /* ** Tables */ typedef union TKey { struct { TValuefields; struct Node *next; /* for chaining */ } nk; TValue tvk; } TKey; typedef struct Node { TValue i_val; TKey i_key; } Node; typedef struct Table { CommonHeader; lu_byte flags; /* 1<

lsizenode)) #define luaO_nilobject (&luaO_nilobject_) LUAI_DATA const TValue luaO_nilobject_; #define ceillog2(x) (luaO_log2((x)-1) + 1) LUAI_FUNC int luaO_log2 (unsigned int x); LUAI_FUNC int luaO_int2fb (unsigned int x); LUAI_FUNC int luaO_fb2int (int x); LUAI_FUNC int luaO_rawequalObj (const TValue *t1, const TValue *t2); LUAI_FUNC int luaO_str2d (const char *s, lua_Number *result); LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp); LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len); #endif redis-8.0.2/deps/lua/src/lopcodes.c000066400000000000000000000055041501533116600171130ustar00rootroot00000000000000/* ** $Id: lopcodes.c,v 1.37.1.1 2007/12/27 13:02:25 roberto Exp $ ** See Copyright Notice in lua.h */ #define lopcodes_c #define LUA_CORE #include "lopcodes.h" /* ORDER OP */ const char *const luaP_opnames[NUM_OPCODES+1] = { "MOVE", "LOADK", "LOADBOOL", "LOADNIL", "GETUPVAL", "GETGLOBAL", "GETTABLE", "SETGLOBAL", "SETUPVAL", "SETTABLE", "NEWTABLE", "SELF", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", "UNM", "NOT", "LEN", "CONCAT", "JMP", "EQ", "LT", "LE", "TEST", "TESTSET", "CALL", "TAILCALL", "RETURN", "FORLOOP", "FORPREP", "TFORLOOP", "SETLIST", "CLOSE", "CLOSURE", "VARARG", NULL }; #define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m)) const lu_byte luaP_opmodes[NUM_OPCODES] = { /* T A B C mode opcode */ opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */ ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LOADNIL */ ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */ ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_GETGLOBAL */ ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */ ,opmode(0, 0, OpArgK, OpArgN, iABx) /* OP_SETGLOBAL */ ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */ ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */ ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */ ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */ ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */ ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */ ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TEST */ ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */ ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */ ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */ ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */ ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TFORLOOP */ ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ ,opmode(0, 0, OpArgN, OpArgN, iABC) /* OP_CLOSE */ ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ }; redis-8.0.2/deps/lua/src/lopcodes.h000066400000000000000000000176261501533116600171300ustar00rootroot00000000000000/* ** $Id: lopcodes.h,v 1.125.1.1 2007/12/27 13:02:25 roberto Exp $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ #ifndef lopcodes_h #define lopcodes_h #include "llimits.h" /*=========================================================================== We assume that instructions are unsigned numbers. All instructions have an opcode in the first 6 bits. Instructions can have the following fields: `A' : 8 bits `B' : 9 bits `C' : 9 bits `Bx' : 18 bits (`B' and `C' together) `sBx' : signed Bx A signed argument is represented in excess K; that is, the number value is the unsigned value minus K. K is exactly the maximum value for that argument (so that -max is represented by 0, and +max is represented by 2*max), which is half the maximum for the corresponding unsigned argument. ===========================================================================*/ enum OpMode {iABC, iABx, iAsBx}; /* basic instruction format */ /* ** size and position of opcode arguments. */ #define SIZE_C 9 #define SIZE_B 9 #define SIZE_Bx (SIZE_C + SIZE_B) #define SIZE_A 8 #define SIZE_OP 6 #define POS_OP 0 #define POS_A (POS_OP + SIZE_OP) #define POS_C (POS_A + SIZE_A) #define POS_B (POS_C + SIZE_C) #define POS_Bx POS_C /* ** limits for opcode arguments. ** we use (signed) int to manipulate most arguments, ** so they must fit in LUAI_BITSINT-1 bits (-1 for sign) */ #if SIZE_Bx < LUAI_BITSINT-1 #define MAXARG_Bx ((1<>1) /* `sBx' is signed */ #else #define MAXARG_Bx MAX_INT #define MAXARG_sBx MAX_INT #endif #define MAXARG_A ((1<>POS_OP) & MASK1(SIZE_OP,0))) #define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ ((cast(Instruction, o)<>POS_A) & MASK1(SIZE_A,0))) #define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) | \ ((cast(Instruction, u)<>POS_B) & MASK1(SIZE_B,0))) #define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) | \ ((cast(Instruction, b)<>POS_C) & MASK1(SIZE_C,0))) #define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) | \ ((cast(Instruction, b)<>POS_Bx) & MASK1(SIZE_Bx,0))) #define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) | \ ((cast(Instruction, b)< C) then pc++ */ OP_TESTSET,/* A B C if (R(B) <=> C) then R(A) := R(B) else pc++ */ OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ OP_RETURN,/* A B return R(A), ... ,R(A+B-2) (see note) */ OP_FORLOOP,/* A sBx R(A)+=R(A+2); if R(A) =) R(A)*/ OP_CLOSURE,/* A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n)) */ OP_VARARG/* A B R(A), R(A+1), ..., R(A+B-1) = vararg */ } OpCode; #define NUM_OPCODES (cast(int, OP_VARARG) + 1) /*=========================================================================== Notes: (*) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1, and can be 0: OP_CALL then sets `top' to last_result+1, so next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use `top'. (*) In OP_VARARG, if (B == 0) then use actual number of varargs and set top (like in OP_CALL with C == 0). (*) In OP_RETURN, if (B == 0) then return up to `top' (*) In OP_SETLIST, if (B == 0) then B = `top'; if (C == 0) then next `instruction' is real C (*) For comparisons, A specifies what condition the test should accept (true or false). (*) All `skips' (pc++) assume that next instruction is a jump ===========================================================================*/ /* ** masks for instruction properties. The format is: ** bits 0-1: op mode ** bits 2-3: C arg mode ** bits 4-5: B arg mode ** bit 6: instruction set register A ** bit 7: operator is a test */ enum OpArgMask { OpArgN, /* argument is not used */ OpArgU, /* argument is used */ OpArgR, /* argument is a register or a jump offset */ OpArgK /* argument is a constant or register/constant */ }; LUAI_DATA const lu_byte luaP_opmodes[NUM_OPCODES]; #define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3)) #define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3)) #define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3)) #define testAMode(m) (luaP_opmodes[m] & (1 << 6)) #define testTMode(m) (luaP_opmodes[m] & (1 << 7)) LUAI_DATA const char *const luaP_opnames[NUM_OPCODES+1]; /* opcode names */ /* number of list items to accumulate before a SETLIST instruction */ #define LFIELDS_PER_FLUSH 50 #endif redis-8.0.2/deps/lua/src/loslib.c000066400000000000000000000140711501533116600165660ustar00rootroot00000000000000/* ** $Id: loslib.c,v 1.19.1.3 2008/01/18 16:38:18 roberto Exp $ ** Standard Operating System library ** See Copyright Notice in lua.h */ #include #include #include #include #include #define loslib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" static int os_pushresult (lua_State *L, int i, const char *filename) { int en = errno; /* calls to Lua API may change this value */ if (i) { lua_pushboolean(L, 1); return 1; } else { lua_pushnil(L); lua_pushfstring(L, "%s: %s", filename, strerror(en)); lua_pushinteger(L, en); return 3; } } static int os_execute (lua_State *L) { lua_pushinteger(L, system(luaL_optstring(L, 1, NULL))); return 1; } static int os_remove (lua_State *L) { const char *filename = luaL_checkstring(L, 1); return os_pushresult(L, remove(filename) == 0, filename); } static int os_rename (lua_State *L) { const char *fromname = luaL_checkstring(L, 1); const char *toname = luaL_checkstring(L, 2); return os_pushresult(L, rename(fromname, toname) == 0, fromname); } static int os_tmpname (lua_State *L) { char buff[LUA_TMPNAMBUFSIZE]; int err; lua_tmpnam(buff, err); if (err) return luaL_error(L, "unable to generate a unique filename"); lua_pushstring(L, buff); return 1; } static int os_getenv (lua_State *L) { lua_pushstring(L, getenv(luaL_checkstring(L, 1))); /* if NULL push nil */ return 1; } static int os_clock (lua_State *L) { lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); return 1; } /* ** {====================================================== ** Time/Date operations ** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, ** wday=%w+1, yday=%j, isdst=? } ** ======================================================= */ static void setfield (lua_State *L, const char *key, int value) { lua_pushinteger(L, value); lua_setfield(L, -2, key); } static void setboolfield (lua_State *L, const char *key, int value) { if (value < 0) /* undefined? */ return; /* does not set field */ lua_pushboolean(L, value); lua_setfield(L, -2, key); } static int getboolfield (lua_State *L, const char *key) { int res; lua_getfield(L, -1, key); res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1); lua_pop(L, 1); return res; } static int getfield (lua_State *L, const char *key, int d) { int res; lua_getfield(L, -1, key); if (lua_isnumber(L, -1)) res = (int)lua_tointeger(L, -1); else { if (d < 0) return luaL_error(L, "field " LUA_QS " missing in date table", key); res = d; } lua_pop(L, 1); return res; } static int os_date (lua_State *L) { const char *s = luaL_optstring(L, 1, "%c"); time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); struct tm *stm; if (*s == '!') { /* UTC? */ stm = gmtime(&t); s++; /* skip `!' */ } else stm = localtime(&t); if (stm == NULL) /* invalid date? */ lua_pushnil(L); else if (strcmp(s, "*t") == 0) { lua_createtable(L, 0, 9); /* 9 = number of fields */ setfield(L, "sec", stm->tm_sec); setfield(L, "min", stm->tm_min); setfield(L, "hour", stm->tm_hour); setfield(L, "day", stm->tm_mday); setfield(L, "month", stm->tm_mon+1); setfield(L, "year", stm->tm_year+1900); setfield(L, "wday", stm->tm_wday+1); setfield(L, "yday", stm->tm_yday+1); setboolfield(L, "isdst", stm->tm_isdst); } else { char cc[3]; luaL_Buffer b; cc[0] = '%'; cc[2] = '\0'; luaL_buffinit(L, &b); for (; *s; s++) { if (*s != '%' || *(s + 1) == '\0') /* no conversion specifier? */ luaL_addchar(&b, *s); else { size_t reslen; char buff[200]; /* should be big enough for any conversion result */ cc[1] = *(++s); reslen = strftime(buff, sizeof(buff), cc, stm); luaL_addlstring(&b, buff, reslen); } } luaL_pushresult(&b); } return 1; } static int os_time (lua_State *L) { time_t t; if (lua_isnoneornil(L, 1)) /* called without args? */ t = time(NULL); /* get current time */ else { struct tm ts; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); /* make sure table is at the top */ ts.tm_sec = getfield(L, "sec", 0); ts.tm_min = getfield(L, "min", 0); ts.tm_hour = getfield(L, "hour", 12); ts.tm_mday = getfield(L, "day", -1); ts.tm_mon = getfield(L, "month", -1) - 1; ts.tm_year = getfield(L, "year", -1) - 1900; ts.tm_isdst = getboolfield(L, "isdst"); t = mktime(&ts); } if (t == (time_t)(-1)) lua_pushnil(L); else lua_pushnumber(L, (lua_Number)t); return 1; } static int os_difftime (lua_State *L) { lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), (time_t)(luaL_optnumber(L, 2, 0)))); return 1; } /* }====================================================== */ static int os_setlocale (lua_State *L) { static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME}; static const char *const catnames[] = {"all", "collate", "ctype", "monetary", "numeric", "time", NULL}; const char *l = luaL_optstring(L, 1, NULL); int op = luaL_checkoption(L, 2, "all", catnames); lua_pushstring(L, setlocale(cat[op], l)); return 1; } static int os_exit (lua_State *L) { exit(luaL_optint(L, 1, EXIT_SUCCESS)); } static const luaL_Reg syslib[] = { {"clock", os_clock}, {"date", os_date}, {"difftime", os_difftime}, {"execute", os_execute}, {"exit", os_exit}, {"getenv", os_getenv}, {"remove", os_remove}, {"rename", os_rename}, {"setlocale", os_setlocale}, {"time", os_time}, {"tmpname", os_tmpname}, {NULL, NULL} }; /* }====================================================== */ #define UNUSED(V) ((void) V) /* Only a subset is loaded currently, for sandboxing concerns. */ static const luaL_Reg sandbox_syslib[] = { {"clock", os_clock}, {NULL, NULL} }; LUALIB_API int luaopen_os (lua_State *L) { UNUSED(syslib); luaL_register(L, LUA_OSLIBNAME, sandbox_syslib); return 1; } redis-8.0.2/deps/lua/src/lparser.c000066400000000000000000001075301501533116600167550ustar00rootroot00000000000000/* ** $Id: lparser.c,v 2.42.1.4 2011/10/21 19:31:42 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ #include #define lparser_c #define LUA_CORE #include "lua.h" #include "lcode.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "llex.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #define hasmultret(k) ((k) == VCALL || (k) == VVARARG) #define getlocvar(fs, i) ((fs)->f->locvars[(fs)->actvar[i]]) #define luaY_checklimit(fs,v,l,m) if ((v)>(l)) errorlimit(fs,l,m) /* ** nodes for block list (list of active blocks) */ typedef struct BlockCnt { struct BlockCnt *previous; /* chain */ int breaklist; /* list of jumps out of this loop */ lu_byte nactvar; /* # active locals outside the breakable structure */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isbreakable; /* true if `block' is a loop */ } BlockCnt; /* ** prototypes for recursive non-terminal functions */ static void chunk (LexState *ls); static void expr (LexState *ls, expdesc *v); static void anchor_token (LexState *ls) { if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { TString *ts = ls->t.seminfo.ts; luaX_newstring(ls, getstr(ts), ts->tsv.len); } } static void error_expected (LexState *ls, int token) { luaX_syntaxerror(ls, luaO_pushfstring(ls->L, LUA_QS " expected", luaX_token2str(ls, token))); } static void errorlimit (FuncState *fs, int limit, const char *what) { const char *msg = (fs->f->linedefined == 0) ? luaO_pushfstring(fs->L, "main function has more than %d %s", limit, what) : luaO_pushfstring(fs->L, "function at line %d has more than %d %s", fs->f->linedefined, limit, what); luaX_lexerror(fs->ls, msg, 0); } static int testnext (LexState *ls, int c) { if (ls->t.token == c) { luaX_next(ls); return 1; } else return 0; } static void check (LexState *ls, int c) { if (ls->t.token != c) error_expected(ls, c); } static void checknext (LexState *ls, int c) { check(ls, c); luaX_next(ls); } #define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } static void check_match (LexState *ls, int what, int who, int where) { if (!testnext(ls, what)) { if (where == ls->linenumber) error_expected(ls, what); else { luaX_syntaxerror(ls, luaO_pushfstring(ls->L, LUA_QS " expected (to close " LUA_QS " at line %d)", luaX_token2str(ls, what), luaX_token2str(ls, who), where)); } } } static TString *str_checkname (LexState *ls) { TString *ts; check(ls, TK_NAME); ts = ls->t.seminfo.ts; luaX_next(ls); return ts; } static void init_exp (expdesc *e, expkind k, int i) { e->f = e->t = NO_JUMP; e->k = k; e->u.s.info = i; } static void codestring (LexState *ls, expdesc *e, TString *s) { init_exp(e, VK, luaK_stringK(ls->fs, s)); } static void checkname(LexState *ls, expdesc *e) { codestring(ls, e, str_checkname(ls)); } static int registerlocalvar (LexState *ls, TString *varname) { FuncState *fs = ls->fs; Proto *f = fs->f; int oldsize = f->sizelocvars; luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, LocVar, SHRT_MAX, "too many local variables"); while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; f->locvars[fs->nlocvars].varname = varname; luaC_objbarrier(ls->L, f, varname); return fs->nlocvars++; } #define new_localvarliteral(ls,v,n) \ new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char))-1), n) static void new_localvar (LexState *ls, TString *name, int n) { FuncState *fs = ls->fs; luaY_checklimit(fs, fs->nactvar+n+1, LUAI_MAXVARS, "local variables"); fs->actvar[fs->nactvar+n] = cast(unsigned short, registerlocalvar(ls, name)); } static void adjustlocalvars (LexState *ls, int nvars) { FuncState *fs = ls->fs; fs->nactvar = cast_byte(fs->nactvar + nvars); for (; nvars; nvars--) { getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc; } } static void removevars (LexState *ls, int tolevel) { FuncState *fs = ls->fs; while (fs->nactvar > tolevel) getlocvar(fs, --fs->nactvar).endpc = fs->pc; } static int indexupvalue (FuncState *fs, TString *name, expdesc *v) { int i; Proto *f = fs->f; int oldsize = f->sizeupvalues; for (i=0; inups; i++) { if (fs->upvalues[i].k == v->k && fs->upvalues[i].info == v->u.s.info) { lua_assert(f->upvalues[i] == name); return i; } } /* new one */ luaY_checklimit(fs, f->nups + 1, LUAI_MAXUPVALUES, "upvalues"); luaM_growvector(fs->L, f->upvalues, f->nups, f->sizeupvalues, TString *, MAX_INT, ""); while (oldsize < f->sizeupvalues) f->upvalues[oldsize++] = NULL; f->upvalues[f->nups] = name; luaC_objbarrier(fs->L, f, name); lua_assert(v->k == VLOCAL || v->k == VUPVAL); fs->upvalues[f->nups].k = cast_byte(v->k); fs->upvalues[f->nups].info = cast_byte(v->u.s.info); return f->nups++; } static int searchvar (FuncState *fs, TString *n) { int i; for (i=fs->nactvar-1; i >= 0; i--) { if (n == getlocvar(fs, i).varname) return i; } return -1; /* not found */ } static void markupval (FuncState *fs, int level) { BlockCnt *bl = fs->bl; while (bl && bl->nactvar > level) bl = bl->previous; if (bl) bl->upval = 1; } static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) { /* no more levels? */ init_exp(var, VGLOBAL, NO_REG); /* default is global variable */ return VGLOBAL; } else { int v = searchvar(fs, n); /* look up at current level */ if (v >= 0) { init_exp(var, VLOCAL, v); if (!base) markupval(fs, v); /* local will be used as an upval */ return VLOCAL; } else { /* not found at current level; try upper one */ if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL) return VGLOBAL; var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */ var->k = VUPVAL; /* upvalue in this level */ return VUPVAL; } } } static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; if (singlevaraux(fs, varname, var, 1) == VGLOBAL) var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */ } static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { FuncState *fs = ls->fs; int extra = nvars - nexps; if (hasmultret(e->k)) { extra++; /* includes call itself */ if (extra < 0) extra = 0; luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ if (extra > 1) luaK_reserveregs(fs, extra-1); } else { if (e->k != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */ if (extra > 0) { int reg = fs->freereg; luaK_reserveregs(fs, extra); luaK_nil(fs, reg, extra); } } } static void enterlevel (LexState *ls) { if (++ls->L->nCcalls > LUAI_MAXCCALLS) luaX_lexerror(ls, "chunk has too many syntax levels", 0); } #define leavelevel(ls) ((ls)->L->nCcalls--) static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isbreakable) { bl->breaklist = NO_JUMP; bl->isbreakable = isbreakable; bl->nactvar = fs->nactvar; bl->upval = 0; bl->previous = fs->bl; fs->bl = bl; lua_assert(fs->freereg == fs->nactvar); } static void leaveblock (FuncState *fs) { BlockCnt *bl = fs->bl; fs->bl = bl->previous; removevars(fs->ls, bl->nactvar); if (bl->upval) luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); /* a block either controls scope or breaks (never both) */ lua_assert(!bl->isbreakable || !bl->upval); lua_assert(bl->nactvar == fs->nactvar); fs->freereg = fs->nactvar; /* free registers */ luaK_patchtohere(fs, bl->breaklist); } static void pushclosure (LexState *ls, FuncState *func, expdesc *v) { FuncState *fs = ls->fs; Proto *f = fs->f; int oldsize = f->sizep; int i; luaM_growvector(ls->L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "constant table overflow"); while (oldsize < f->sizep) f->p[oldsize++] = NULL; f->p[fs->np++] = func->f; luaC_objbarrier(ls->L, f, func->f); init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np-1)); for (i=0; if->nups; i++) { OpCode o = (func->upvalues[i].k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; luaK_codeABC(fs, o, 0, func->upvalues[i].info, 0); } } static void open_func (LexState *ls, FuncState *fs) { lua_State *L = ls->L; Proto *f = luaF_newproto(L); fs->f = f; fs->prev = ls->fs; /* linked list of funcstates */ fs->ls = ls; fs->L = L; ls->fs = fs; fs->pc = 0; fs->lasttarget = -1; fs->jpc = NO_JUMP; fs->freereg = 0; fs->nk = 0; fs->np = 0; fs->nlocvars = 0; fs->nactvar = 0; fs->bl = NULL; f->source = ls->source; f->maxstacksize = 2; /* registers 0/1 are always valid */ fs->h = luaH_new(L, 0, 0); /* anchor table of constants and prototype (to avoid being collected) */ sethvalue2s(L, L->top, fs->h); incr_top(L); setptvalue2s(L, L->top, f); incr_top(L); } static void close_func (LexState *ls) { lua_State *L = ls->L; FuncState *fs = ls->fs; Proto *f = fs->f; removevars(ls, 0); luaK_ret(fs, 0, 0); /* final return */ luaM_reallocvector(L, f->code, f->sizecode, fs->pc, Instruction); f->sizecode = fs->pc; luaM_reallocvector(L, f->lineinfo, f->sizelineinfo, fs->pc, int); f->sizelineinfo = fs->pc; luaM_reallocvector(L, f->k, f->sizek, fs->nk, TValue); f->sizek = fs->nk; luaM_reallocvector(L, f->p, f->sizep, fs->np, Proto *); f->sizep = fs->np; luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar); f->sizelocvars = fs->nlocvars; luaM_reallocvector(L, f->upvalues, f->sizeupvalues, f->nups, TString *); f->sizeupvalues = f->nups; lua_assert(luaG_checkcode(f)); lua_assert(fs->bl == NULL); ls->fs = fs->prev; /* last token read was anchored in defunct function; must reanchor it */ if (fs) anchor_token(ls); L->top -= 2; /* remove table and prototype from the stack */ } 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)); 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); lua_assert(funcstate.prev == NULL); lua_assert(funcstate.f->nups == 0); lua_assert(lexstate.fs == NULL); return funcstate.f; } /*============================================================*/ /* GRAMMAR RULES */ /*============================================================*/ static void field (LexState *ls, expdesc *v) { /* field -> ['.' | ':'] NAME */ FuncState *fs = ls->fs; expdesc key; luaK_exp2anyreg(fs, v); luaX_next(ls); /* skip the dot or colon */ checkname(ls, &key); luaK_indexed(fs, v, &key); } static void yindex (LexState *ls, expdesc *v) { /* index -> '[' expr ']' */ luaX_next(ls); /* skip the '[' */ expr(ls, v); luaK_exp2val(ls->fs, v); checknext(ls, ']'); } /* ** {====================================================================== ** Rules for Constructors ** ======================================================================= */ struct ConsControl { expdesc v; /* last list item read */ expdesc *t; /* table descriptor */ int nh; /* total number of `record' elements */ int na; /* total number of array elements */ int tostore; /* number of array elements pending to be stored */ }; static void recfield (LexState *ls, struct ConsControl *cc) { /* recfield -> (NAME | `['exp1`]') = exp1 */ FuncState *fs = ls->fs; int reg = ls->fs->freereg; expdesc key, val; int rkkey; if (ls->t.token == TK_NAME) { luaY_checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); checkname(ls, &key); } else /* ls->t.token == '[' */ yindex(ls, &key); cc->nh++; checknext(ls, '='); rkkey = luaK_exp2RK(fs, &key); expr(ls, &val); luaK_codeABC(fs, OP_SETTABLE, cc->t->u.s.info, rkkey, luaK_exp2RK(fs, &val)); fs->freereg = reg; /* free registers */ } static void closelistfield (FuncState *fs, struct ConsControl *cc) { if (cc->v.k == VVOID) return; /* there is no list item */ luaK_exp2nextreg(fs, &cc->v); cc->v.k = VVOID; if (cc->tostore == LFIELDS_PER_FLUSH) { luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); /* flush */ cc->tostore = 0; /* no more items pending */ } } static void lastlistfield (FuncState *fs, struct ConsControl *cc) { if (cc->tostore == 0) return; if (hasmultret(cc->v.k)) { luaK_setmultret(fs, &cc->v); luaK_setlist(fs, cc->t->u.s.info, cc->na, LUA_MULTRET); cc->na--; /* do not count last expression (unknown number of elements) */ } else { if (cc->v.k != VVOID) luaK_exp2nextreg(fs, &cc->v); luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); } } static void listfield (LexState *ls, struct ConsControl *cc) { expr(ls, &cc->v); luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); cc->na++; cc->tostore++; } static void constructor (LexState *ls, expdesc *t) { /* constructor -> ?? */ FuncState *fs = ls->fs; int line = ls->linenumber; int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); struct ConsControl cc; cc.na = cc.nh = cc.tostore = 0; cc.t = t; init_exp(t, VRELOCABLE, pc); init_exp(&cc.v, VVOID, 0); /* no value (yet) */ luaK_exp2nextreg(ls->fs, t); /* fix it at stack top (for gc) */ checknext(ls, '{'); do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); if (ls->t.token == '}') break; closelistfield(fs, &cc); switch(ls->t.token) { case TK_NAME: { /* may be listfields or recfields */ luaX_lookahead(ls); if (ls->lookahead.token != '=') /* expression? */ listfield(ls, &cc); else recfield(ls, &cc); break; } case '[': { /* constructor_item -> recfield */ recfield(ls, &cc); break; } default: { /* constructor_part -> listfield */ listfield(ls, &cc); break; } } } while (testnext(ls, ',') || testnext(ls, ';')); check_match(ls, '}', '{', line); lastlistfield(fs, &cc); SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */ SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ } /* }====================================================================== */ static void parlist (LexState *ls) { /* parlist -> [ param { `,' param } ] */ FuncState *fs = ls->fs; Proto *f = fs->f; int nparams = 0; f->is_vararg = 0; if (ls->t.token != ')') { /* is `parlist' not empty? */ do { switch (ls->t.token) { case TK_NAME: { /* param -> NAME */ new_localvar(ls, str_checkname(ls), nparams++); break; } case TK_DOTS: { /* param -> `...' */ luaX_next(ls); #if defined(LUA_COMPAT_VARARG) /* use `arg' as default name */ new_localvarliteral(ls, "arg", nparams++); f->is_vararg = VARARG_HASARG | VARARG_NEEDSARG; #endif f->is_vararg |= VARARG_ISVARARG; break; } default: luaX_syntaxerror(ls, " or " LUA_QL("...") " expected"); } } while (!f->is_vararg && testnext(ls, ',')); } adjustlocalvars(ls, nparams); f->numparams = cast_byte(fs->nactvar - (f->is_vararg & VARARG_HASARG)); luaK_reserveregs(fs, fs->nactvar); /* reserve register for parameters */ } static void body (LexState *ls, expdesc *e, int needself, int line) { /* body -> `(' parlist `)' chunk END */ FuncState new_fs; open_func(ls, &new_fs); new_fs.f->linedefined = line; checknext(ls, '('); if (needself) { new_localvarliteral(ls, "self", 0); adjustlocalvars(ls, 1); } parlist(ls); checknext(ls, ')'); chunk(ls); new_fs.f->lastlinedefined = ls->linenumber; check_match(ls, TK_END, TK_FUNCTION, line); close_func(ls); pushclosure(ls, &new_fs, e); } static int explist1 (LexState *ls, expdesc *v) { /* explist1 -> expr { `,' expr } */ int n = 1; /* at least one expression */ expr(ls, v); while (testnext(ls, ',')) { luaK_exp2nextreg(ls->fs, v); expr(ls, v); n++; } return n; } static void funcargs (LexState *ls, expdesc *f) { FuncState *fs = ls->fs; expdesc args; int base, nparams; int line = ls->linenumber; switch (ls->t.token) { case '(': { /* funcargs -> `(' [ explist1 ] `)' */ if (line != ls->lastline) luaX_syntaxerror(ls,"ambiguous syntax (function call x new statement)"); luaX_next(ls); if (ls->t.token == ')') /* arg list is empty? */ args.k = VVOID; else { explist1(ls, &args); luaK_setmultret(fs, &args); } check_match(ls, ')', '(', line); break; } case '{': { /* funcargs -> constructor */ constructor(ls, &args); break; } case TK_STRING: { /* funcargs -> STRING */ codestring(ls, &args, ls->t.seminfo.ts); luaX_next(ls); /* must use `seminfo' before `next' */ break; } default: { luaX_syntaxerror(ls, "function arguments expected"); return; } } lua_assert(f->k == VNONRELOC); base = f->u.s.info; /* base register for call */ if (hasmultret(args.k)) nparams = LUA_MULTRET; /* open call */ else { if (args.k != VVOID) luaK_exp2nextreg(fs, &args); /* close last argument */ nparams = fs->freereg - (base+1); } init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); luaK_fixline(fs, line); fs->freereg = base+1; /* call remove function and arguments and leaves (unless changed) one result */ } /* ** {====================================================================== ** Expression parsing ** ======================================================================= */ static void prefixexp (LexState *ls, expdesc *v) { /* prefixexp -> NAME | '(' expr ')' */ switch (ls->t.token) { case '(': { int line = ls->linenumber; luaX_next(ls); expr(ls, v); check_match(ls, ')', '(', line); luaK_dischargevars(ls->fs, v); return; } case TK_NAME: { singlevar(ls, v); return; } default: { luaX_syntaxerror(ls, "unexpected symbol"); return; } } } static void primaryexp (LexState *ls, expdesc *v) { /* primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs } */ FuncState *fs = ls->fs; prefixexp(ls, v); for (;;) { switch (ls->t.token) { case '.': { /* field */ field(ls, v); break; } case '[': { /* `[' exp1 `]' */ expdesc key; luaK_exp2anyreg(fs, v); yindex(ls, &key); luaK_indexed(fs, v, &key); break; } case ':': { /* `:' NAME funcargs */ expdesc key; luaX_next(ls); checkname(ls, &key); luaK_self(fs, v, &key); funcargs(ls, v); break; } case '(': case TK_STRING: case '{': { /* funcargs */ luaK_exp2nextreg(fs, v); funcargs(ls, v); break; } default: return; } } } static void simpleexp (LexState *ls, expdesc *v) { /* simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp */ switch (ls->t.token) { case TK_NUMBER: { init_exp(v, VKNUM, 0); v->u.nval = ls->t.seminfo.r; break; } case TK_STRING: { codestring(ls, v, ls->t.seminfo.ts); break; } case TK_NIL: { init_exp(v, VNIL, 0); break; } case TK_TRUE: { init_exp(v, VTRUE, 0); break; } case TK_FALSE: { init_exp(v, VFALSE, 0); break; } case TK_DOTS: { /* vararg */ FuncState *fs = ls->fs; check_condition(ls, fs->f->is_vararg, "cannot use " LUA_QL("...") " outside a vararg function"); fs->f->is_vararg &= ~VARARG_NEEDSARG; /* don't need 'arg' */ init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); break; } case '{': { /* constructor */ constructor(ls, v); return; } case TK_FUNCTION: { luaX_next(ls); body(ls, v, 0, ls->linenumber); return; } default: { primaryexp(ls, v); return; } } luaX_next(ls); } static UnOpr getunopr (int op) { switch (op) { case TK_NOT: return OPR_NOT; case '-': return OPR_MINUS; case '#': return OPR_LEN; default: return OPR_NOUNOPR; } } static BinOpr getbinopr (int op) { switch (op) { case '+': return OPR_ADD; case '-': return OPR_SUB; case '*': return OPR_MUL; case '/': return OPR_DIV; case '%': return OPR_MOD; case '^': return OPR_POW; case TK_CONCAT: return OPR_CONCAT; case TK_NE: return OPR_NE; case TK_EQ: return OPR_EQ; case '<': return OPR_LT; case TK_LE: return OPR_LE; case '>': return OPR_GT; case TK_GE: return OPR_GE; case TK_AND: return OPR_AND; case TK_OR: return OPR_OR; default: return OPR_NOBINOPR; } } static const struct { lu_byte left; /* left priority for each binary operator */ lu_byte right; /* right priority */ } priority[] = { /* ORDER OPR */ {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `/' `%' */ {10, 9}, {5, 4}, /* power and concat (right associative) */ {3, 3}, {3, 3}, /* equality and inequality */ {3, 3}, {3, 3}, {3, 3}, {3, 3}, /* order */ {2, 2}, {1, 1} /* logical (and/or) */ }; #define UNARY_PRIORITY 8 /* priority for unary operators */ /* ** subexpr -> (simpleexp | unop subexpr) { binop subexpr } ** where `binop' is any binary operator with a priority higher than `limit' */ static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit) { BinOpr op; UnOpr uop; enterlevel(ls); uop = getunopr(ls->t.token); if (uop != OPR_NOUNOPR) { luaX_next(ls); subexpr(ls, v, UNARY_PRIORITY); luaK_prefix(ls->fs, uop, v); } else simpleexp(ls, v); /* expand while operators have priorities higher than `limit' */ op = getbinopr(ls->t.token); while (op != OPR_NOBINOPR && priority[op].left > limit) { expdesc v2; BinOpr nextop; luaX_next(ls); luaK_infix(ls->fs, op, v); /* read sub-expression with higher priority */ nextop = subexpr(ls, &v2, priority[op].right); luaK_posfix(ls->fs, op, v, &v2); op = nextop; } leavelevel(ls); return op; /* return first untreated operator */ } static void expr (LexState *ls, expdesc *v) { subexpr(ls, v, 0); } /* }==================================================================== */ /* ** {====================================================================== ** Rules for Statements ** ======================================================================= */ static int block_follow (int token) { switch (token) { case TK_ELSE: case TK_ELSEIF: case TK_END: case TK_UNTIL: case TK_EOS: return 1; default: return 0; } } static void block (LexState *ls) { /* block -> chunk */ FuncState *fs = ls->fs; BlockCnt bl; enterblock(fs, &bl, 0); chunk(ls); lua_assert(bl.breaklist == NO_JUMP); leaveblock(fs); } /* ** structure to chain all variables in the left-hand side of an ** assignment */ struct LHS_assign { struct LHS_assign *prev; expdesc v; /* variable (global, local, upvalue, or indexed) */ }; /* ** check whether, in an assignment to a local variable, the local variable ** is needed in a previous assignment (to a table). If so, save original ** local value in a safe place and use this safe copy in the previous ** assignment. */ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { FuncState *fs = ls->fs; int extra = fs->freereg; /* eventual position to save local variable */ int conflict = 0; for (; lh; lh = lh->prev) { if (lh->v.k == VINDEXED) { if (lh->v.u.s.info == v->u.s.info) { /* conflict? */ conflict = 1; lh->v.u.s.info = extra; /* previous assignment will use safe copy */ } if (lh->v.u.s.aux == v->u.s.info) { /* conflict? */ conflict = 1; lh->v.u.s.aux = extra; /* previous assignment will use safe copy */ } } } if (conflict) { luaK_codeABC(fs, OP_MOVE, fs->freereg, v->u.s.info, 0); /* make copy */ luaK_reserveregs(fs, 1); } } static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { expdesc e; check_condition(ls, VLOCAL <= lh->v.k && lh->v.k <= VINDEXED, "syntax error"); if (testnext(ls, ',')) { /* assignment -> `,' primaryexp assignment */ struct LHS_assign nv; nv.prev = lh; primaryexp(ls, &nv.v); if (nv.v.k == VLOCAL) check_conflict(ls, lh, &nv.v); luaY_checklimit(ls->fs, nvars, LUAI_MAXCCALLS - ls->L->nCcalls, "variables in assignment"); assignment(ls, &nv, nvars+1); } else { /* assignment -> `=' explist1 */ int nexps; checknext(ls, '='); nexps = explist1(ls, &e); if (nexps != nvars) { adjust_assign(ls, nvars, nexps, &e); if (nexps > nvars) ls->fs->freereg -= nexps - nvars; /* remove extra values */ } else { luaK_setoneret(ls->fs, &e); /* close last expression */ luaK_storevar(ls->fs, &lh->v, &e); return; /* avoid default */ } } init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ luaK_storevar(ls->fs, &lh->v, &e); } static int cond (LexState *ls) { /* cond -> exp */ expdesc v; expr(ls, &v); /* read condition */ if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */ luaK_goiftrue(ls->fs, &v); return v.f; } static void breakstat (LexState *ls) { FuncState *fs = ls->fs; BlockCnt *bl = fs->bl; int upval = 0; while (bl && !bl->isbreakable) { upval |= bl->upval; bl = bl->previous; } if (!bl) luaX_syntaxerror(ls, "no loop to break"); if (upval) luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); luaK_concat(fs, &bl->breaklist, luaK_jump(fs)); } static void whilestat (LexState *ls, int line) { /* whilestat -> WHILE cond DO block END */ FuncState *fs = ls->fs; int whileinit; int condexit; BlockCnt bl; luaX_next(ls); /* skip WHILE */ whileinit = luaK_getlabel(fs); condexit = cond(ls); enterblock(fs, &bl, 1); checknext(ls, TK_DO); block(ls); luaK_patchlist(fs, luaK_jump(fs), whileinit); check_match(ls, TK_END, TK_WHILE, line); leaveblock(fs); luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ } static void repeatstat (LexState *ls, int line) { /* repeatstat -> REPEAT block UNTIL cond */ int condexit; FuncState *fs = ls->fs; int repeat_init = luaK_getlabel(fs); BlockCnt bl1, bl2; enterblock(fs, &bl1, 1); /* loop block */ enterblock(fs, &bl2, 0); /* scope block */ luaX_next(ls); /* skip REPEAT */ chunk(ls); check_match(ls, TK_UNTIL, TK_REPEAT, line); condexit = cond(ls); /* read condition (inside scope block) */ if (!bl2.upval) { /* no upvalues? */ leaveblock(fs); /* finish scope */ luaK_patchlist(ls->fs, condexit, repeat_init); /* close the loop */ } else { /* complete semantics when there are upvalues */ breakstat(ls); /* if condition then break */ luaK_patchtohere(ls->fs, condexit); /* else... */ leaveblock(fs); /* finish scope... */ luaK_patchlist(ls->fs, luaK_jump(fs), repeat_init); /* and repeat */ } leaveblock(fs); /* finish loop */ } static int exp1 (LexState *ls) { expdesc e; int k; expr(ls, &e); k = e.k; luaK_exp2nextreg(ls->fs, &e); return k; } static void forbody (LexState *ls, int base, int line, int nvars, int isnum) { /* forbody -> DO block */ BlockCnt bl; FuncState *fs = ls->fs; int prep, endfor; adjustlocalvars(ls, 3); /* control variables */ checknext(ls, TK_DO); prep = isnum ? luaK_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : luaK_jump(fs); enterblock(fs, &bl, 0); /* scope for declared variables */ adjustlocalvars(ls, nvars); luaK_reserveregs(fs, nvars); block(ls); leaveblock(fs); /* end of scope for declared variables */ luaK_patchtohere(fs, prep); endfor = (isnum) ? luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP) : luaK_codeABC(fs, OP_TFORLOOP, base, 0, nvars); luaK_fixline(fs, line); /* pretend that `OP_FOR' starts the loop */ luaK_patchlist(fs, (isnum ? endfor : luaK_jump(fs)), prep + 1); } static void fornum (LexState *ls, TString *varname, int line) { /* fornum -> NAME = exp1,exp1[,exp1] forbody */ FuncState *fs = ls->fs; int base = fs->freereg; new_localvarliteral(ls, "(for index)", 0); new_localvarliteral(ls, "(for limit)", 1); new_localvarliteral(ls, "(for step)", 2); new_localvar(ls, varname, 3); checknext(ls, '='); exp1(ls); /* initial value */ checknext(ls, ','); exp1(ls); /* limit */ if (testnext(ls, ',')) exp1(ls); /* optional step */ else { /* default step = 1 */ luaK_codeABx(fs, OP_LOADK, fs->freereg, luaK_numberK(fs, 1)); luaK_reserveregs(fs, 1); } forbody(ls, base, line, 1, 1); } static void forlist (LexState *ls, TString *indexname) { /* forlist -> NAME {,NAME} IN explist1 forbody */ FuncState *fs = ls->fs; expdesc e; int nvars = 0; int line; int base = fs->freereg; /* create control variables */ new_localvarliteral(ls, "(for generator)", nvars++); new_localvarliteral(ls, "(for state)", nvars++); new_localvarliteral(ls, "(for control)", nvars++); /* create declared variables */ new_localvar(ls, indexname, nvars++); while (testnext(ls, ',')) new_localvar(ls, str_checkname(ls), nvars++); checknext(ls, TK_IN); line = ls->linenumber; adjust_assign(ls, 3, explist1(ls, &e), &e); luaK_checkstack(fs, 3); /* extra space to call generator */ forbody(ls, base, line, nvars - 3, 0); } static void forstat (LexState *ls, int line) { /* forstat -> FOR (fornum | forlist) END */ FuncState *fs = ls->fs; TString *varname; BlockCnt bl; enterblock(fs, &bl, 1); /* scope for loop and control variables */ luaX_next(ls); /* skip `for' */ varname = str_checkname(ls); /* first variable name */ switch (ls->t.token) { case '=': fornum(ls, varname, line); break; case ',': case TK_IN: forlist(ls, varname); break; default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected"); } check_match(ls, TK_END, TK_FOR, line); leaveblock(fs); /* loop scope (`break' jumps to this point) */ } static int test_then_block (LexState *ls) { /* test_then_block -> [IF | ELSEIF] cond THEN block */ int condexit; luaX_next(ls); /* skip IF or ELSEIF */ condexit = cond(ls); checknext(ls, TK_THEN); block(ls); /* `then' part */ return condexit; } static void ifstat (LexState *ls, int line) { /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ FuncState *fs = ls->fs; int flist; int escapelist = NO_JUMP; flist = test_then_block(ls); /* IF cond THEN block */ while (ls->t.token == TK_ELSEIF) { luaK_concat(fs, &escapelist, luaK_jump(fs)); luaK_patchtohere(fs, flist); flist = test_then_block(ls); /* ELSEIF cond THEN block */ } if (ls->t.token == TK_ELSE) { luaK_concat(fs, &escapelist, luaK_jump(fs)); luaK_patchtohere(fs, flist); luaX_next(ls); /* skip ELSE (after patch, for correct line info) */ block(ls); /* `else' part */ } else luaK_concat(fs, &escapelist, flist); luaK_patchtohere(fs, escapelist); check_match(ls, TK_END, TK_IF, line); } static void localfunc (LexState *ls) { expdesc v, b; FuncState *fs = ls->fs; new_localvar(ls, str_checkname(ls), 0); init_exp(&v, VLOCAL, fs->freereg); luaK_reserveregs(fs, 1); adjustlocalvars(ls, 1); body(ls, &b, 0, ls->linenumber); luaK_storevar(fs, &v, &b); /* debug information will only see the variable after this point! */ getlocvar(fs, fs->nactvar - 1).startpc = fs->pc; } static void localstat (LexState *ls) { /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */ int nvars = 0; int nexps; expdesc e; do { new_localvar(ls, str_checkname(ls), nvars++); } while (testnext(ls, ',')); if (testnext(ls, '=')) nexps = explist1(ls, &e); else { e.k = VVOID; nexps = 0; } adjust_assign(ls, nvars, nexps, &e); adjustlocalvars(ls, nvars); } static int funcname (LexState *ls, expdesc *v) { /* funcname -> NAME {field} [`:' NAME] */ int needself = 0; singlevar(ls, v); while (ls->t.token == '.') field(ls, v); if (ls->t.token == ':') { needself = 1; field(ls, v); } return needself; } static void funcstat (LexState *ls, int line) { /* funcstat -> FUNCTION funcname body */ int needself; expdesc v, b; luaX_next(ls); /* skip FUNCTION */ needself = funcname(ls, &v); body(ls, &b, needself, line); luaK_storevar(ls->fs, &v, &b); luaK_fixline(ls->fs, line); /* definition `happens' in the first line */ } static void exprstat (LexState *ls) { /* stat -> func | assignment */ FuncState *fs = ls->fs; struct LHS_assign v; primaryexp(ls, &v.v); if (v.v.k == VCALL) /* stat -> func */ SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ else { /* stat -> assignment */ v.prev = NULL; assignment(ls, &v, 1); } } static void retstat (LexState *ls) { /* stat -> RETURN explist */ FuncState *fs = ls->fs; expdesc e; int first, nret; /* registers with returned values */ luaX_next(ls); /* skip RETURN */ if (block_follow(ls->t.token) || ls->t.token == ';') first = nret = 0; /* return no values */ else { nret = explist1(ls, &e); /* optional return values */ if (hasmultret(e.k)) { luaK_setmultret(fs, &e); if (e.k == VCALL && nret == 1) { /* tail call? */ SET_OPCODE(getcode(fs,&e), OP_TAILCALL); lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); } first = fs->nactvar; nret = LUA_MULTRET; /* return all values */ } else { if (nret == 1) /* only one single value? */ first = luaK_exp2anyreg(fs, &e); else { luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */ first = fs->nactvar; /* return all `active' values */ lua_assert(nret == fs->freereg - first); } } } luaK_ret(fs, first, nret); } static int statement (LexState *ls) { int line = ls->linenumber; /* may be needed for error messages */ switch (ls->t.token) { case TK_IF: { /* stat -> ifstat */ ifstat(ls, line); return 0; } case TK_WHILE: { /* stat -> whilestat */ whilestat(ls, line); return 0; } case TK_DO: { /* stat -> DO block END */ luaX_next(ls); /* skip DO */ block(ls); check_match(ls, TK_END, TK_DO, line); return 0; } case TK_FOR: { /* stat -> forstat */ forstat(ls, line); return 0; } case TK_REPEAT: { /* stat -> repeatstat */ repeatstat(ls, line); return 0; } case TK_FUNCTION: { funcstat(ls, line); /* stat -> funcstat */ return 0; } case TK_LOCAL: { /* stat -> localstat */ luaX_next(ls); /* skip LOCAL */ if (testnext(ls, TK_FUNCTION)) /* local function? */ localfunc(ls); else localstat(ls); return 0; } case TK_RETURN: { /* stat -> retstat */ retstat(ls); return 1; /* must be last statement */ } case TK_BREAK: { /* stat -> breakstat */ luaX_next(ls); /* skip BREAK */ breakstat(ls); return 1; /* must be last statement */ } default: { exprstat(ls); return 0; /* to avoid warnings */ } } } static void chunk (LexState *ls) { /* chunk -> { stat [`;'] } */ int islast = 0; enterlevel(ls); while (!islast && !block_follow(ls->t.token)) { islast = statement(ls); testnext(ls, ';'); lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && ls->fs->freereg >= ls->fs->nactvar); ls->fs->freereg = ls->fs->nactvar; /* free registers */ } leavelevel(ls); } /* }====================================================================== */ redis-8.0.2/deps/lua/src/lparser.h000066400000000000000000000043251501533116600167600ustar00rootroot00000000000000/* ** $Id: lparser.h,v 1.57.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ #ifndef lparser_h #define lparser_h #include "llimits.h" #include "lobject.h" #include "lzio.h" /* ** Expression descriptor */ typedef enum { VVOID, /* no value */ VNIL, VTRUE, VFALSE, VK, /* info = index of constant in `k' */ VKNUM, /* nval = numerical value */ VLOCAL, /* info = local register */ VUPVAL, /* info = index of upvalue in `upvalues' */ VGLOBAL, /* info = index of table; aux = index of global name in `k' */ VINDEXED, /* info = table register; aux = index register (or `k') */ VJMP, /* info = instruction pc */ VRELOCABLE, /* info = instruction pc */ VNONRELOC, /* info = result register */ VCALL, /* info = instruction pc */ VVARARG /* info = instruction pc */ } expkind; typedef struct expdesc { expkind k; union { struct { int info, aux; } s; lua_Number nval; } u; int t; /* patch list of `exit when true' */ int f; /* patch list of `exit when false' */ } expdesc; typedef struct upvaldesc { lu_byte k; lu_byte info; } upvaldesc; struct BlockCnt; /* defined in lparser.c */ /* state needed to generate code for a given function */ typedef struct FuncState { Proto *f; /* current function header */ Table *h; /* table to find (and reuse) elements in `k' */ struct FuncState *prev; /* enclosing function */ struct LexState *ls; /* lexical state */ struct lua_State *L; /* copy of the Lua state */ struct BlockCnt *bl; /* chain of current blocks */ int pc; /* next position to code (equivalent to `ncode') */ int lasttarget; /* `pc' of last `jump target' */ int jpc; /* list of pending jumps to `pc' */ int freereg; /* first free register */ int nk; /* number of elements in `k' */ int np; /* number of elements in `p' */ short nlocvars; /* number of elements in `locvars' */ lu_byte nactvar; /* number of active local variables */ upvaldesc upvalues[LUAI_MAXUPVALUES]; /* upvalues */ unsigned short actvar[LUAI_MAXVARS]; /* declared-variable stack */ } FuncState; LUAI_FUNC Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name); #endif redis-8.0.2/deps/lua/src/lstate.c000066400000000000000000000130521501533116600165740ustar00rootroot00000000000000/* ** $Id: lstate.c,v 2.36.1.2 2008/01/03 15:20:39 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ #include #define lstate_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "llex.h" #include "lmem.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #define state_size(x) (sizeof(x) + LUAI_EXTRASPACE) #define fromstate(l) (cast(lu_byte *, (l)) - LUAI_EXTRASPACE) #define tostate(l) (cast(lua_State *, cast(lu_byte *, l) + LUAI_EXTRASPACE)) /* ** Main thread combines a thread state and the global state */ typedef struct LG { lua_State l; global_State g; } LG; static void stack_init (lua_State *L1, lua_State *L) { /* initialize CallInfo array */ L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo); L1->ci = L1->base_ci; L1->size_ci = BASIC_CI_SIZE; L1->end_ci = L1->base_ci + L1->size_ci - 1; /* initialize stack array */ L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue); L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK; L1->top = L1->stack; L1->stack_last = L1->stack+(L1->stacksize - EXTRA_STACK)-1; /* initialize first ci */ L1->ci->func = L1->top; setnilvalue(L1->top++); /* `function' entry for this `ci' */ L1->base = L1->ci->base = L1->top; L1->ci->top = L1->top + LUA_MINSTACK; } static void freestack (lua_State *L, lua_State *L1) { luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo); luaM_freearray(L, L1->stack, L1->stacksize, TValue); } /* ** open parts that may cause memory-allocation errors */ static void f_luaopen (lua_State *L, void *ud) { global_State *g = G(L); UNUSED(ud); stack_init(L, L); /* init stack */ sethvalue(L, gt(L), luaH_new(L, 0, 2)); /* table of globals */ sethvalue(L, registry(L), luaH_new(L, 0, 2)); /* registry */ luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ luaT_init(L); luaX_init(L); luaS_fix(luaS_newliteral(L, MEMERRMSG)); g->GCthreshold = 4*g->totalbytes; } static void preinit_state (lua_State *L, global_State *g) { G(L) = g; L->stack = NULL; L->stacksize = 0; L->errorJmp = NULL; L->hook = NULL; L->hookmask = 0; L->basehookcount = 0; L->allowhook = 1; resethookcount(L); L->openupval = NULL; L->size_ci = 0; L->nCcalls = L->baseCcalls = 0; L->status = 0; L->base_ci = L->ci = NULL; L->savedpc = NULL; L->errfunc = 0; setnilvalue(gt(L)); } static void close_state (lua_State *L) { global_State *g = G(L); luaF_close(L, L->stack); /* close all upvalues for this thread */ luaC_freeall(L); /* collect all objects */ lua_assert(g->rootgc == obj2gco(L)); lua_assert(g->strt.nuse == 0); luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size, TString *); luaZ_freebuffer(L, &g->buff); freestack(L, L); lua_assert(g->totalbytes == sizeof(LG)); (*g->frealloc)(g->ud, fromstate(L), state_size(LG), 0); } lua_State *luaE_newthread (lua_State *L) { lua_State *L1 = tostate(luaM_malloc(L, state_size(lua_State))); luaC_link(L, obj2gco(L1), LUA_TTHREAD); preinit_state(L1, G(L)); stack_init(L1, L); /* init stack */ setobj2n(L, gt(L1), gt(L)); /* share table of globals */ L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; resethookcount(L1); lua_assert(iswhite(obj2gco(L1))); return L1; } void luaE_freethread (lua_State *L, lua_State *L1) { luaF_close(L1, L1->stack); /* close all upvalues for this thread */ lua_assert(L1->openupval == NULL); luai_userstatefree(L1); freestack(L, L1); luaM_freemem(L, fromstate(L1), state_size(lua_State)); } LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; void *l = (*f)(ud, NULL, 0, state_size(LG)); if (l == NULL) return NULL; L = tostate(l); g = &((LG *)L)->g; L->next = NULL; L->tt = LUA_TTHREAD; g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); L->marked = luaC_white(g); set2bits(L->marked, FIXEDBIT, SFIXEDBIT); preinit_state(L, g); g->frealloc = f; g->ud = ud; g->mainthread = L; g->uvhead.u.l.prev = &g->uvhead; g->uvhead.u.l.next = &g->uvhead; g->GCthreshold = 0; /* mark it as unfinished state */ g->strt.size = 0; g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(registry(L)); luaZ_initbuffer(L, &g->buff); g->panic = NULL; g->gcstate = GCSpause; g->rootgc = obj2gco(L); g->sweepstrgc = 0; g->sweepgc = &g->rootgc; g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->tmudata = NULL; g->totalbytes = sizeof(LG); g->gcpause = LUAI_GCPAUSE; g->gcstepmul = LUAI_GCMUL; g->gcdept = 0; for (i=0; imt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) { /* memory allocation error: free partial state */ close_state(L); L = NULL; } else luai_userstateopen(L); return L; } static void callallgcTM (lua_State *L, void *ud) { UNUSED(ud); luaC_callGCTM(L); /* call GC metamethods for all udata */ } LUA_API void lua_close (lua_State *L) { L = G(L)->mainthread; /* only the main thread can be closed */ lua_lock(L); luaF_close(L, L->stack); /* close all upvalues for this thread */ luaC_separateudata(L, 1); /* separate udata that have GC metamethods */ L->errfunc = 0; /* no error function during GC metamethods */ do { /* repeat until no more errors */ L->ci = L->base_ci; L->base = L->top = L->ci->base; L->nCcalls = L->baseCcalls = 0; } while (luaD_rawrunprotected(L, callallgcTM, NULL) != 0); lua_assert(G(L)->tmudata == NULL); luai_userstateclose(L); close_state(L); } redis-8.0.2/deps/lua/src/lstate.h000066400000000000000000000116231501533116600166030ustar00rootroot00000000000000/* ** $Id: lstate.h,v 2.24.1.2 2008/01/03 15:20:39 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ #ifndef lstate_h #define lstate_h #include "lua.h" #include "lobject.h" #include "ltm.h" #include "lzio.h" struct lua_longjmp; /* defined in ldo.c */ /* table of globals */ #define gt(L) (&L->l_gt) /* registry */ #define registry(L) (&G(L)->l_registry) /* extra stack space to handle TM calls and some other extras */ #define EXTRA_STACK 5 #define BASIC_CI_SIZE 8 #define BASIC_STACK_SIZE (2*LUA_MINSTACK) typedef struct stringtable { GCObject **hash; lu_int32 nuse; /* number of elements */ int size; } stringtable; /* ** informations about a call */ typedef struct CallInfo { StkId base; /* base for this function */ StkId func; /* function index in the stack */ StkId top; /* top for this function */ const Instruction *savedpc; int nresults; /* expected number of results from this function */ int tailcalls; /* number of tail calls lost under this entry */ } CallInfo; #define curr_func(L) (clvalue(L->ci->func)) #define ci_func(ci) (clvalue((ci)->func)) #define f_isLua(ci) (!ci_func(ci)->c.isC) #define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci)) /* ** `global state', shared by all threads of this state */ typedef struct global_State { stringtable strt; /* hash table for strings */ lua_Alloc frealloc; /* function to reallocate memory */ void *ud; /* auxiliary data to `frealloc' */ lu_byte currentwhite; lu_byte gcstate; /* state of garbage collector */ int sweepstrgc; /* position of sweep in `strt' */ GCObject *rootgc; /* list of all collectable objects */ GCObject **sweepgc; /* position of sweep in `rootgc' */ GCObject *gray; /* list of gray objects */ GCObject *grayagain; /* list of objects to be traversed atomically */ GCObject *weak; /* list of weak tables (to be cleared) */ GCObject *tmudata; /* last element of list of userdata to be GC */ Mbuffer buff; /* temporary buffer for string concatentation */ lu_mem GCthreshold; lu_mem totalbytes; /* number of bytes currently allocated */ lu_mem estimate; /* an estimate of number of bytes actually in use */ lu_mem gcdept; /* how much GC is `behind schedule' */ int gcpause; /* size of pause between successive GCs */ int gcstepmul; /* GC `granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ TValue l_registry; struct lua_State *mainthread; UpVal uvhead; /* head of double-linked list of all open upvalues */ struct Table *mt[NUM_TAGS]; /* metatables for basic types */ TString *tmname[TM_N]; /* array with tag-method names */ } global_State; /* ** `per thread' state */ struct lua_State { CommonHeader; lu_byte status; StkId top; /* first free slot in the stack */ StkId base; /* base of current function */ global_State *l_G; CallInfo *ci; /* call info for current function */ const Instruction *savedpc; /* `savedpc' of current function */ StkId stack_last; /* last free slot in the stack */ StkId stack; /* stack base */ CallInfo *end_ci; /* points after end of ci array*/ CallInfo *base_ci; /* array of CallInfo's */ int stacksize; int size_ci; /* size of array `base_ci' */ unsigned short nCcalls; /* number of nested C calls */ unsigned short baseCcalls; /* nested C calls when resuming coroutine */ lu_byte hookmask; lu_byte allowhook; int basehookcount; int hookcount; lua_Hook hook; TValue l_gt; /* table of globals */ TValue env; /* temporary place for environments */ GCObject *openupval; /* list of open upvalues in this stack */ GCObject *gclist; struct lua_longjmp *errorJmp; /* current error recover point */ ptrdiff_t errfunc; /* current error handling function (stack index) */ }; #define G(L) (L->l_G) /* ** Union of all collectable objects */ union GCObject { GCheader gch; union TString ts; union Udata u; union Closure cl; struct Table h; struct Proto p; struct UpVal uv; struct lua_State th; /* thread */ }; /* macros to convert a GCObject into a specific value */ #define rawgco2ts(o) check_exp((o)->gch.tt == LUA_TSTRING, &((o)->ts)) #define gco2ts(o) (&rawgco2ts(o)->tsv) #define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) #define gco2u(o) (&rawgco2u(o)->uv) #define gco2cl(o) check_exp((o)->gch.tt == LUA_TFUNCTION, &((o)->cl)) #define gco2h(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) #define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) #define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) #define ngcotouv(o) \ check_exp((o) == NULL || (o)->gch.tt == LUA_TUPVAL, &((o)->uv)) #define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) /* macro to convert any Lua object into a GCObject */ #define obj2gco(v) (cast(GCObject *, (v))) LUAI_FUNC lua_State *luaE_newthread (lua_State *L); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); #endif redis-8.0.2/deps/lua/src/lstring.c000066400000000000000000000057501501533116600167700ustar00rootroot00000000000000/* ** $Id: lstring.c,v 2.8.1.1 2007/12/27 13:02:25 roberto Exp $ ** String table (keeps all strings handled by Lua) ** See Copyright Notice in lua.h */ #include #define lstring_c #define LUA_CORE #include "lua.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" void luaS_resize (lua_State *L, int newsize) { GCObject **newhash; stringtable *tb; int i; if (G(L)->gcstate == GCSsweepstring) return; /* cannot resize during GC traverse */ newhash = luaM_newvector(L, newsize, GCObject *); tb = &G(L)->strt; for (i=0; isize; i++) { GCObject *p = tb->hash[i]; while (p) { /* for each node in the list */ GCObject *next = p->gch.next; /* save next */ unsigned int h = gco2ts(p)->hash; int h1 = lmod(h, newsize); /* new position */ lua_assert(cast_int(h%newsize) == lmod(h, newsize)); p->gch.next = newhash[h1]; /* chain it */ newhash[h1] = p; p = next; } } luaM_freearray(L, tb->hash, tb->size, TString *); tb->size = newsize; tb->hash = newhash; } static TString *newlstr (lua_State *L, const char *str, size_t l, unsigned int h) { TString *ts; stringtable *tb; if (l+1 > (MAX_SIZET - sizeof(TString))/sizeof(char)) luaM_toobig(L); ts = cast(TString *, luaM_malloc(L, (l+1)*sizeof(char)+sizeof(TString))); ts->tsv.len = l; ts->tsv.hash = h; ts->tsv.marked = luaC_white(G(L)); ts->tsv.tt = LUA_TSTRING; ts->tsv.reserved = 0; memcpy(ts+1, str, l*sizeof(char)); ((char *)(ts+1))[l] = '\0'; /* ending 0 */ tb = &G(L)->strt; h = lmod(h, tb->size); ts->tsv.next = tb->hash[h]; /* chain new entry */ tb->hash[h] = obj2gco(ts); tb->nuse++; if (tb->nuse > cast(lu_int32, tb->size) && tb->size <= MAX_INT/2) luaS_resize(L, tb->size*2); /* too crowded */ return ts; } TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { GCObject *o; unsigned int h = cast(unsigned int, l); /* seed */ size_t step = 1; size_t l1; for (l1=l; l1>=step; l1-=step) /* compute hash */ h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1])); for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)]; o != NULL; o = o->gch.next) { TString *ts = rawgco2ts(o); if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) { /* string may be dead */ if (isdead(G(L), o)) changewhite(o); return ts; } } return newlstr(L, str, l, h); /* not found */ } Udata *luaS_newudata (lua_State *L, size_t s, Table *e) { Udata *u; if (s > MAX_SIZET - sizeof(Udata)) luaM_toobig(L); u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata))); u->uv.marked = luaC_white(G(L)); /* is not finalized */ u->uv.tt = LUA_TUSERDATA; u->uv.len = s; u->uv.metatable = NULL; u->uv.env = e; /* chain it on udata list (after main thread) */ u->uv.next = G(L)->mainthread->next; G(L)->mainthread->next = obj2gco(u); return u; } redis-8.0.2/deps/lua/src/lstring.h000066400000000000000000000014561501533116600167740ustar00rootroot00000000000000/* ** $Id: lstring.h,v 1.43.1.1 2007/12/27 13:02:25 roberto Exp $ ** String table (keep all strings handled by Lua) ** See Copyright Notice in lua.h */ #ifndef lstring_h #define lstring_h #include "lgc.h" #include "lobject.h" #include "lstate.h" #define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) #define sizeudata(u) (sizeof(union Udata)+(u)->len) #define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s))) #define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ (sizeof(s)/sizeof(char))-1)) #define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) LUAI_FUNC void luaS_resize (lua_State *L, int newsize); LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); #endif redis-8.0.2/deps/lua/src/lstrlib.c000066400000000000000000000560111501533116600167550ustar00rootroot00000000000000/* ** $Id: lstrlib.c,v 1.132.1.5 2010/05/14 15:34:19 roberto Exp $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ #include #include #include #include #include #define lstrlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" /* macro to `unsign' a character */ #define uchar(c) ((unsigned char)(c)) static int str_len (lua_State *L) { size_t l; luaL_checklstring(L, 1, &l); lua_pushinteger(L, l); return 1; } static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) { /* relative string position: negative means back from end */ if (pos < 0) pos += (ptrdiff_t)len + 1; return (pos >= 0) ? pos : 0; } static int str_sub (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l); ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l); if (start < 1) start = 1; if (end > (ptrdiff_t)l) end = (ptrdiff_t)l; if (start <= end) lua_pushlstring(L, s+start-1, end-start+1); else lua_pushliteral(L, ""); return 1; } static int str_reverse (lua_State *L) { size_t l; luaL_Buffer b; const char *s = luaL_checklstring(L, 1, &l); luaL_buffinit(L, &b); while (l--) luaL_addchar(&b, s[l]); luaL_pushresult(&b); return 1; } static int str_lower (lua_State *L) { size_t l; size_t i; luaL_Buffer b; const char *s = luaL_checklstring(L, 1, &l); luaL_buffinit(L, &b); for (i=0; i 0) luaL_addlstring(&b, s, l); luaL_pushresult(&b); return 1; } static int str_byte (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); ptrdiff_t posi = posrelat(luaL_optinteger(L, 2, 1), l); ptrdiff_t pose = posrelat(luaL_optinteger(L, 3, posi), l); int n, i; if (posi <= 0) posi = 1; if ((size_t)pose > l) pose = l; if (posi > pose) return 0; /* empty interval; return no values */ n = (int)(pose - posi + 1); if (posi + n <= pose) /* overflow? */ luaL_error(L, "string slice too long"); luaL_checkstack(L, n, "string slice too long"); for (i=0; i= ms->level || ms->capture[l].len == CAP_UNFINISHED) return luaL_error(ms->L, "invalid capture index"); return l; } static int capture_to_close (MatchState *ms) { int level = ms->level; for (level--; level>=0; level--) if (ms->capture[level].len == CAP_UNFINISHED) return level; return luaL_error(ms->L, "invalid pattern capture"); } static const char *classend (MatchState *ms, const char *p) { switch (*p++) { case L_ESC: { if (*p == '\0') luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); return p+1; } case '[': { if (*p == '^') p++; do { /* look for a `]' */ if (*p == '\0') luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); if (*(p++) == L_ESC && *p != '\0') p++; /* skip escapes (e.g. `%]') */ } while (*p != ']'); return p+1; } default: { return p; } } } static int match_class (int c, int cl) { int res; switch (tolower(cl)) { case 'a' : res = isalpha(c); break; case 'c' : res = iscntrl(c); break; case 'd' : res = isdigit(c); break; case 'l' : res = islower(c); break; case 'p' : res = ispunct(c); break; case 's' : res = isspace(c); break; case 'u' : res = isupper(c); break; case 'w' : res = isalnum(c); break; case 'x' : res = isxdigit(c); break; case 'z' : res = (c == 0); break; default: return (cl == c); } return (islower(cl) ? res : !res); } static int matchbracketclass (int c, const char *p, const char *ec) { int sig = 1; if (*(p+1) == '^') { sig = 0; p++; /* skip the `^' */ } while (++p < ec) { if (*p == L_ESC) { p++; if (match_class(c, uchar(*p))) return sig; } else if ((*(p+1) == '-') && (p+2 < ec)) { p+=2; if (uchar(*(p-2)) <= c && c <= uchar(*p)) return sig; } else if (uchar(*p) == c) return sig; } return !sig; } static int singlematch (int c, const char *p, const char *ep) { switch (*p) { case '.': return 1; /* matches any char */ case L_ESC: return match_class(c, uchar(*(p+1))); case '[': return matchbracketclass(c, p, ep-1); default: return (uchar(*p) == c); } } static const char *match (MatchState *ms, const char *s, const char *p); static const char *matchbalance (MatchState *ms, const char *s, const char *p) { if (*p == 0 || *(p+1) == 0) luaL_error(ms->L, "unbalanced pattern"); if (*s != *p) return NULL; else { int b = *p; int e = *(p+1); int cont = 1; while (++s < ms->src_end) { if (*s == e) { if (--cont == 0) return s+1; } else if (*s == b) cont++; } } return NULL; /* string ends out of balance */ } static const char *max_expand (MatchState *ms, const char *s, const char *p, const char *ep) { ptrdiff_t i = 0; /* counts maximum expand for item */ while ((s+i)src_end && singlematch(uchar(*(s+i)), p, ep)) i++; /* keeps trying to match with the maximum repetitions */ while (i>=0) { const char *res = match(ms, (s+i), ep+1); if (res) return res; i--; /* else didn't match; reduce 1 repetition to try again */ } return NULL; } static const char *min_expand (MatchState *ms, const char *s, const char *p, const char *ep) { for (;;) { const char *res = match(ms, s, ep+1); if (res != NULL) return res; else if (ssrc_end && singlematch(uchar(*s), p, ep)) s++; /* try with one more repetition */ else return NULL; } } static const char *start_capture (MatchState *ms, const char *s, const char *p, int what) { const char *res; int level = ms->level; if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); ms->capture[level].init = s; ms->capture[level].len = what; ms->level = level+1; if ((res=match(ms, s, p)) == NULL) /* match failed? */ ms->level--; /* undo capture */ return res; } static const char *end_capture (MatchState *ms, const char *s, const char *p) { int l = capture_to_close(ms); const char *res; ms->capture[l].len = s - ms->capture[l].init; /* close capture */ if ((res = match(ms, s, p)) == NULL) /* match failed? */ ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ return res; } static const char *match_capture (MatchState *ms, const char *s, int l) { size_t len; l = check_capture(ms, l); len = ms->capture[l].len; if ((size_t)(ms->src_end-s) >= len && memcmp(ms->capture[l].init, s, len) == 0) return s+len; else return NULL; } static const char *match (MatchState *ms, const char *s, const char *p) { init: /* using goto's to optimize tail recursion */ switch (*p) { case '(': { /* start capture */ if (*(p+1) == ')') /* position capture? */ return start_capture(ms, s, p+2, CAP_POSITION); else return start_capture(ms, s, p+1, CAP_UNFINISHED); } case ')': { /* end capture */ return end_capture(ms, s, p+1); } case L_ESC: { switch (*(p+1)) { case 'b': { /* balanced string? */ s = matchbalance(ms, s, p+2); if (s == NULL) return NULL; p+=4; goto init; /* else return match(ms, s, p+4); */ } case 'f': { /* frontier? */ const char *ep; char previous; p += 2; if (*p != '[') luaL_error(ms->L, "missing " LUA_QL("[") " after " LUA_QL("%%f") " in pattern"); ep = classend(ms, p); /* points to what is next */ previous = (s == ms->src_init) ? '\0' : *(s-1); if (matchbracketclass(uchar(previous), p, ep-1) || !matchbracketclass(uchar(*s), p, ep-1)) return NULL; p=ep; goto init; /* else return match(ms, s, ep); */ } default: { if (isdigit(uchar(*(p+1)))) { /* capture results (%0-%9)? */ s = match_capture(ms, s, uchar(*(p+1))); if (s == NULL) return NULL; p+=2; goto init; /* else return match(ms, s, p+2) */ } goto dflt; /* case default */ } } } case '\0': { /* end of pattern */ return s; /* match succeeded */ } case '$': { if (*(p+1) == '\0') /* is the `$' the last char in pattern? */ return (s == ms->src_end) ? s : NULL; /* check end of string */ else goto dflt; } default: dflt: { /* it is a pattern item */ const char *ep = classend(ms, p); /* points to what is next */ int m = ssrc_end && singlematch(uchar(*s), p, ep); switch (*ep) { case '?': { /* optional */ const char *res; if (m && ((res=match(ms, s+1, ep+1)) != NULL)) return res; p=ep+1; goto init; /* else return match(ms, s, ep+1); */ } case '*': { /* 0 or more repetitions */ return max_expand(ms, s, p, ep); } case '+': { /* 1 or more repetitions */ return (m ? max_expand(ms, s+1, p, ep) : NULL); } case '-': { /* 0 or more repetitions (minimum) */ return min_expand(ms, s, p, ep); } default: { if (!m) return NULL; s++; p=ep; goto init; /* else return match(ms, s+1, ep); */ } } } } } static const char *lmemfind (const char *s1, size_t l1, const char *s2, size_t l2) { if (l2 == 0) return s1; /* empty strings are everywhere */ else if (l2 > l1) return NULL; /* avoids a negative `l1' */ else { const char *init; /* to search for a `*s2' inside `s1' */ l2--; /* 1st char will be checked by `memchr' */ l1 = l1-l2; /* `s2' cannot be found after that */ while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { init++; /* 1st char is already checked */ if (memcmp(init, s2+1, l2) == 0) return init-1; else { /* correct `l1' and `s1' to try again */ l1 -= init-s1; s1 = init; } } return NULL; /* not found */ } } static void push_onecapture (MatchState *ms, int i, const char *s, const char *e) { if (i >= ms->level) { if (i == 0) /* ms->level == 0, too */ lua_pushlstring(ms->L, s, e - s); /* add whole match */ else luaL_error(ms->L, "invalid capture index"); } else { ptrdiff_t l = ms->capture[i].len; if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); if (l == CAP_POSITION) lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1); else lua_pushlstring(ms->L, ms->capture[i].init, l); } } static int push_captures (MatchState *ms, const char *s, const char *e) { int i; int nlevels = (ms->level == 0 && s) ? 1 : ms->level; luaL_checkstack(ms->L, nlevels, "too many captures"); for (i = 0; i < nlevels; i++) push_onecapture(ms, i, s, e); return nlevels; /* number of strings pushed */ } static int str_find_aux (lua_State *L, int find) { size_t l1, l2; const char *s = luaL_checklstring(L, 1, &l1); const char *p = luaL_checklstring(L, 2, &l2); ptrdiff_t init = posrelat(luaL_optinteger(L, 3, 1), l1) - 1; if (init < 0) init = 0; else if ((size_t)(init) > l1) init = (ptrdiff_t)l1; if (find && (lua_toboolean(L, 4) || /* explicit request? */ strpbrk(p, SPECIALS) == NULL)) { /* or no special characters? */ /* do a plain search */ const char *s2 = lmemfind(s+init, l1-init, p, l2); if (s2) { lua_pushinteger(L, s2-s+1); lua_pushinteger(L, s2-s+l2); return 2; } } else { MatchState ms; int anchor = (*p == '^') ? (p++, 1) : 0; const char *s1=s+init; ms.L = L; ms.src_init = s; ms.src_end = s+l1; do { const char *res; ms.level = 0; if ((res=match(&ms, s1, p)) != NULL) { if (find) { lua_pushinteger(L, s1-s+1); /* start */ lua_pushinteger(L, res-s); /* end */ return push_captures(&ms, NULL, 0) + 2; } else return push_captures(&ms, s1, res); } } while (s1++ < ms.src_end && !anchor); } lua_pushnil(L); /* not found */ return 1; } static int str_find (lua_State *L) { return str_find_aux(L, 1); } static int str_match (lua_State *L) { return str_find_aux(L, 0); } static int gmatch_aux (lua_State *L) { MatchState ms; size_t ls; const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); const char *p = lua_tostring(L, lua_upvalueindex(2)); const char *src; ms.L = L; ms.src_init = s; ms.src_end = s+ls; for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); src <= ms.src_end; src++) { const char *e; ms.level = 0; if ((e = match(&ms, src, p)) != NULL) { lua_Integer newstart = e-s; if (e == src) newstart++; /* empty match? go at least one position */ lua_pushinteger(L, newstart); lua_replace(L, lua_upvalueindex(3)); return push_captures(&ms, src, e); } } return 0; /* not found */ } static int gmatch (lua_State *L) { luaL_checkstring(L, 1); luaL_checkstring(L, 2); lua_settop(L, 2); lua_pushinteger(L, 0); lua_pushcclosure(L, gmatch_aux, 3); return 1; } static int gfind_nodef (lua_State *L) { return luaL_error(L, LUA_QL("string.gfind") " was renamed to " LUA_QL("string.gmatch")); } static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { size_t l, i; const char *news = lua_tolstring(ms->L, 3, &l); for (i = 0; i < l; i++) { if (news[i] != L_ESC) luaL_addchar(b, news[i]); else { i++; /* skip ESC */ if (!isdigit(uchar(news[i]))) luaL_addchar(b, news[i]); else if (news[i] == '0') luaL_addlstring(b, s, e - s); else { push_onecapture(ms, news[i] - '1', s, e); luaL_addvalue(b); /* add capture to accumulated result */ } } } } static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { lua_State *L = ms->L; switch (lua_type(L, 3)) { case LUA_TNUMBER: case LUA_TSTRING: { add_s(ms, b, s, e); return; } case LUA_TFUNCTION: { int n; lua_pushvalue(L, 3); n = push_captures(ms, s, e); lua_call(L, n, 1); break; } case LUA_TTABLE: { push_onecapture(ms, 0, s, e); lua_gettable(L, 3); break; } } if (!lua_toboolean(L, -1)) { /* nil or false? */ lua_pop(L, 1); lua_pushlstring(L, s, e - s); /* keep original text */ } else if (!lua_isstring(L, -1)) luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); luaL_addvalue(b); /* add result to accumulator */ } static int str_gsub (lua_State *L) { size_t srcl; const char *src = luaL_checklstring(L, 1, &srcl); const char *p = luaL_checkstring(L, 2); int tr = lua_type(L, 3); int max_s = luaL_optint(L, 4, srcl+1); int anchor = (*p == '^') ? (p++, 1) : 0; int n = 0; MatchState ms; luaL_Buffer b; luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, "string/function/table expected"); luaL_buffinit(L, &b); ms.L = L; ms.src_init = src; ms.src_end = src+srcl; while (n < max_s) { const char *e; ms.level = 0; e = match(&ms, src, p); if (e) { n++; add_value(&ms, &b, src, e); } if (e && e>src) /* non empty match? */ src = e; /* skip it */ else if (src < ms.src_end) luaL_addchar(&b, *src++); else break; if (anchor) break; } luaL_addlstring(&b, src, ms.src_end-src); luaL_pushresult(&b); lua_pushinteger(L, n); /* number of substitutions */ return 2; } /* }====================================================== */ /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ #define MAX_ITEM 512 /* valid flags in a format specification */ #define FLAGS "-+ #0" /* ** maximum size of each format specification (such as '%-099.99d') ** (+10 accounts for %99.99x plus margin of error) */ #define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { size_t l; const char *s = luaL_checklstring(L, arg, &l); luaL_addchar(b, '"'); while (l--) { switch (*s) { case '"': case '\\': case '\n': { luaL_addchar(b, '\\'); luaL_addchar(b, *s); break; } case '\r': { luaL_addlstring(b, "\\r", 2); break; } case '\0': { luaL_addlstring(b, "\\000", 4); break; } default: { luaL_addchar(b, *s); break; } } s++; } luaL_addchar(b, '"'); } static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ if ((size_t)(p - strfrmt) >= sizeof(FLAGS)) luaL_error(L, "invalid format (repeated flags)"); if (isdigit(uchar(*p))) p++; /* skip width */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ if (*p == '.') { p++; if (isdigit(uchar(*p))) p++; /* skip precision */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ } if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); *(form++) = '%'; strncpy(form, strfrmt, p - strfrmt + 1); form += p - strfrmt + 1; *form = '\0'; return p; } static void addintlen (char *form) { size_t l = strlen(form); char spec = form[l - 1]; strcpy(form + l - 1, LUA_INTFRMLEN); form[l + sizeof(LUA_INTFRMLEN) - 2] = spec; form[l + sizeof(LUA_INTFRMLEN) - 1] = '\0'; } static int str_format (lua_State *L) { int top = lua_gettop(L); int arg = 1; size_t sfl; const char *strfrmt = luaL_checklstring(L, arg, &sfl); const char *strfrmt_end = strfrmt+sfl; luaL_Buffer b; luaL_buffinit(L, &b); while (strfrmt < strfrmt_end) { if (*strfrmt != L_ESC) luaL_addchar(&b, *strfrmt++); else if (*++strfrmt == L_ESC) luaL_addchar(&b, *strfrmt++); /* %% */ else { /* format item */ char form[MAX_FORMAT]; /* to store the format (`%...') */ char buff[MAX_ITEM]; /* to store the formatted item */ if (++arg > top) luaL_argerror(L, arg, "no value"); strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { sprintf(buff, form, (int)luaL_checknumber(L, arg)); break; } case 'd': case 'i': { addintlen(form); sprintf(buff, form, (LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'o': case 'u': case 'x': case 'X': { addintlen(form); sprintf(buff, form, (unsigned LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'e': case 'E': case 'f': case 'g': case 'G': { sprintf(buff, form, (double)luaL_checknumber(L, arg)); break; } case 'q': { addquoted(L, &b, arg); continue; /* skip the 'addsize' at the end */ } case 's': { size_t l; const char *s = luaL_checklstring(L, arg, &l); if (!strchr(form, '.') && l >= 100) { /* no precision and string is too long to be formatted; keep original string */ lua_pushvalue(L, arg); luaL_addvalue(&b); continue; /* skip the `addsize' at the end */ } else { sprintf(buff, form, s); break; } } default: { /* also treat cases `pnLlh' */ return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " LUA_QL("format"), *(strfrmt - 1)); } } luaL_addlstring(&b, buff, strlen(buff)); } } luaL_pushresult(&b); return 1; } static const luaL_Reg strlib[] = { {"byte", str_byte}, {"char", str_char}, {"dump", str_dump}, {"find", str_find}, {"format", str_format}, {"gfind", gfind_nodef}, {"gmatch", gmatch}, {"gsub", str_gsub}, {"len", str_len}, {"lower", str_lower}, {"match", str_match}, {"rep", str_rep}, {"reverse", str_reverse}, {"sub", str_sub}, {"upper", str_upper}, {NULL, NULL} }; static void createmetatable (lua_State *L) { lua_createtable(L, 0, 1); /* create metatable for strings */ lua_pushliteral(L, ""); /* dummy string */ lua_pushvalue(L, -2); lua_setmetatable(L, -2); /* set string metatable */ lua_pop(L, 1); /* pop dummy string */ lua_pushvalue(L, -2); /* string library... */ lua_setfield(L, -2, "__index"); /* ...is the __index metamethod */ lua_pop(L, 1); /* pop metatable */ } /* ** Open string library */ LUALIB_API int luaopen_string (lua_State *L) { luaL_register(L, LUA_STRLIBNAME, strlib); #if defined(LUA_COMPAT_GFIND) lua_getfield(L, -1, "gmatch"); lua_setfield(L, -2, "gfind"); #endif createmetatable(L); return 1; } redis-8.0.2/deps/lua/src/ltable.c000066400000000000000000000376321501533116600165550ustar00rootroot00000000000000/* ** $Id: ltable.c,v 2.32.1.2 2007/12/28 15:32:23 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ /* ** Implementation of tables (aka arrays, objects, or hash tables). ** Tables keep its elements in two parts: an array part and a hash part. ** Non-negative integer keys are all candidates to be kept in the array ** part. The actual size of the array is the largest `n' such that at ** least half the slots between 0 and n are in use. ** Hash uses a mix of chained scatter table with Brent's variation. ** A main invariant of these tables is that, if an element is not ** in its main position (i.e. the `original' position that its hash gives ** to it), then the colliding element is in its own main position. ** Hence even when the load factor reaches 100%, performance remains good. */ #include #include #define ltable_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "ltable.h" /* ** max size of array part is 2^MAXBITS */ #if LUAI_BITSINT > 26 #define MAXBITS 26 #else #define MAXBITS (LUAI_BITSINT-2) #endif #define MAXASIZE (1 << MAXBITS) #define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) #define hashstr(t,str) hashpow2(t, (str)->tsv.hash) #define hashboolean(t,p) hashpow2(t, p) /* ** for some types, it is better to avoid modulus by power of 2, as ** they tend to have many 2 factors. */ #define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) #define hashpointer(t,p) hashmod(t, IntPoint(p)) /* ** number of ints inside a lua_Number */ #define numints cast_int(sizeof(lua_Number)/sizeof(int)) #define dummynode (&dummynode_) static const Node dummynode_ = { {{NULL}, LUA_TNIL}, /* value */ {{{NULL}, LUA_TNIL, NULL}} /* key */ }; /* ** hash for lua_Numbers */ static Node *hashnum (const Table *t, lua_Number n) { unsigned int a[numints]; int i; if (luai_numeq(n, 0)) /* avoid problems with -0 */ return gnode(t, 0); memcpy(a, &n, sizeof(a)); for (i = 1; i < numints; i++) a[0] += a[i]; return hashmod(t, a[0]); } /* ** returns the `main' position of an element in a table (that is, the index ** of its hash value) */ static Node *mainposition (const Table *t, const TValue *key) { switch (ttype(key)) { case LUA_TNUMBER: return hashnum(t, nvalue(key)); case LUA_TSTRING: return hashstr(t, rawtsvalue(key)); case LUA_TBOOLEAN: return hashboolean(t, bvalue(key)); case LUA_TLIGHTUSERDATA: return hashpointer(t, pvalue(key)); default: return hashpointer(t, gcvalue(key)); } } /* ** returns the index for `key' if `key' is an appropriate key to live in ** the array part of the table, -1 otherwise. */ static int arrayindex (const TValue *key) { if (ttisnumber(key)) { lua_Number n = nvalue(key); int k; lua_number2int(k, n); if (luai_numeq(cast_num(k), n)) return k; } return -1; /* `key' did not match some condition */ } /* ** returns the index of a `key' for table traversals. First goes all ** elements in the array part, then elements in the hash part. The ** beginning of a traversal is signalled by -1. */ static int findindex (lua_State *L, Table *t, StkId key) { int i; if (ttisnil(key)) return -1; /* first iteration */ i = arrayindex(key); if (0 < i && i <= t->sizearray) /* is `key' inside array part? */ return i-1; /* yes; that's the index (corrected to C) */ else { Node *n = mainposition(t, key); do { /* check whether `key' is somewhere in the chain */ /* key may be dead already, but it is ok to use it in `next' */ if (luaO_rawequalObj(key2tval(n), key) || (ttype(gkey(n)) == LUA_TDEADKEY && iscollectable(key) && gcvalue(gkey(n)) == gcvalue(key))) { i = cast_int(n - gnode(t, 0)); /* key index in hash table */ /* hash elements are numbered after array ones */ return i + t->sizearray; } else n = gnext(n); } while (n); luaG_runerror(L, "invalid key to " LUA_QL("next")); /* key not found */ return 0; /* to avoid warnings */ } } int luaH_next (lua_State *L, Table *t, StkId key) { int i = findindex(L, t, key); /* find original element */ for (i++; i < t->sizearray; i++) { /* try first array part */ if (!ttisnil(&t->array[i])) { /* a non-nil value? */ setnvalue(key, cast_num(i+1)); setobj2s(L, key+1, &t->array[i]); return 1; } } for (i -= t->sizearray; i < sizenode(t); i++) { /* then hash part */ if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ setobj2s(L, key, key2tval(gnode(t, i))); setobj2s(L, key+1, gval(gnode(t, i))); return 1; } } return 0; /* no more elements */ } /* ** {============================================================= ** Rehash ** ============================================================== */ static int computesizes (int nums[], int *narray) { int i; int twotoi; /* 2^i */ int a = 0; /* number of elements smaller than 2^i */ int na = 0; /* number of elements to go to array part */ int n = 0; /* optimal size for array part */ for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { if (nums[i] > 0) { a += nums[i]; if (a > twotoi/2) { /* more than half elements present? */ n = twotoi; /* optimal size (till now) */ na = a; /* all elements smaller than n will go to array part */ } } if (a == *narray) break; /* all elements already counted */ } *narray = n; lua_assert(*narray/2 <= na && na <= *narray); return na; } static int countint (const TValue *key, int *nums) { int k = arrayindex(key); if (0 < k && k <= MAXASIZE) { /* is `key' an appropriate array index? */ nums[ceillog2(k)]++; /* count as such */ return 1; } else return 0; } static int numusearray (const Table *t, int *nums) { int lg; int ttlg; /* 2^lg */ int ause = 0; /* summation of `nums' */ int i = 1; /* count to traverse all array keys */ for (lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { /* for each slice */ int lc = 0; /* counter */ int lim = ttlg; if (lim > t->sizearray) { lim = t->sizearray; /* adjust upper limit */ if (i > lim) break; /* no more elements to count */ } /* count elements in range (2^(lg-1), 2^lg] */ for (; i <= lim; i++) { if (!ttisnil(&t->array[i-1])) lc++; } nums[lg] += lc; ause += lc; } return ause; } static int numusehash (const Table *t, int *nums, int *pnasize) { int totaluse = 0; /* total number of elements */ int ause = 0; /* summation of `nums' */ int i = sizenode(t); while (i--) { Node *n = &t->node[i]; if (!ttisnil(gval(n))) { ause += countint(key2tval(n), nums); totaluse++; } } *pnasize += ause; return totaluse; } static void setarrayvector (lua_State *L, Table *t, int size) { int i; luaM_reallocvector(L, t->array, t->sizearray, size, TValue); for (i=t->sizearray; iarray[i]); t->sizearray = size; } static void setnodevector (lua_State *L, Table *t, int size) { int lsize; if (size == 0) { /* no elements to hash part? */ t->node = cast(Node *, dummynode); /* use common `dummynode' */ lsize = 0; } else { int i; lsize = ceillog2(size); if (lsize > MAXBITS) luaG_runerror(L, "table overflow"); size = twoto(lsize); t->node = luaM_newvector(L, size, Node); for (i=0; ilsizenode = cast_byte(lsize); t->lastfree = gnode(t, size); /* all positions are free */ } static void resize (lua_State *L, Table *t, int nasize, int nhsize) { int i; int oldasize = t->sizearray; int oldhsize = t->lsizenode; Node *nold = t->node; /* save old hash ... */ if (nasize > oldasize) /* array part must grow? */ setarrayvector(L, t, nasize); /* create new hash part with appropriate size */ setnodevector(L, t, nhsize); if (nasize < oldasize) { /* array part must shrink? */ t->sizearray = nasize; /* re-insert elements from vanishing slice */ for (i=nasize; iarray[i])) setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]); } /* shrink array */ luaM_reallocvector(L, t->array, oldasize, nasize, TValue); } /* re-insert elements from hash part */ for (i = twoto(oldhsize) - 1; i >= 0; i--) { Node *old = nold+i; if (!ttisnil(gval(old))) setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old)); } if (nold != dummynode) luaM_freearray(L, nold, twoto(oldhsize), Node); /* free old array */ } void luaH_resizearray (lua_State *L, Table *t, int nasize) { int nsize = (t->node == dummynode) ? 0 : sizenode(t); resize(L, t, nasize, nsize); } static void rehash (lua_State *L, Table *t, const TValue *ek) { int nasize, na; int nums[MAXBITS+1]; /* nums[i] = number of keys between 2^(i-1) and 2^i */ int i; int totaluse; for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */ nasize = numusearray(t, nums); /* count keys in array part */ totaluse = nasize; /* all those keys are integer keys */ totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ /* count extra key */ nasize += countint(ek, nums); totaluse++; /* compute new size for array part */ na = computesizes(nums, &nasize); /* resize the table to new computed sizes */ resize(L, t, nasize, totaluse - na); } /* ** }============================================================= */ Table *luaH_new (lua_State *L, int narray, int nhash) { Table *t = luaM_new(L, Table); luaC_link(L, obj2gco(t), LUA_TTABLE); t->metatable = NULL; t->flags = cast_byte(~0); /* temporary values (kept only if some malloc fails) */ t->array = NULL; t->sizearray = 0; t->lsizenode = 0; t->readonly = 0; t->node = cast(Node *, dummynode); setarrayvector(L, t, narray); setnodevector(L, t, nhash); return t; } void luaH_free (lua_State *L, Table *t) { if (t->node != dummynode) luaM_freearray(L, t->node, sizenode(t), Node); luaM_freearray(L, t->array, t->sizearray, TValue); luaM_free(L, t); } static Node *getfreepos (Table *t) { while (t->lastfree-- > t->node) { if (ttisnil(gkey(t->lastfree))) return t->lastfree; } return NULL; /* could not find a free place */ } /* ** inserts a new key into a hash table; first, check whether key's main ** position is free. If not, check whether colliding node is in its main ** position or not: if it is not, move colliding node to an empty place and ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ static TValue *newkey (lua_State *L, Table *t, const TValue *key) { Node *mp = mainposition(t, key); if (!ttisnil(gval(mp)) || mp == dummynode) { Node *othern; Node *n = getfreepos(t); /* get a free place */ if (n == NULL) { /* cannot find a free place? */ rehash(L, t, key); /* grow table */ return luaH_set(L, t, key); /* re-insert key into grown table */ } lua_assert(n != dummynode); othern = mainposition(t, key2tval(mp)); if (othern != mp) { /* is colliding node out of its main position? */ /* yes; move colliding node into free position */ while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ gnext(mp) = NULL; /* now `mp' is free */ setnilvalue(gval(mp)); } else { /* colliding node is in its own main position */ /* new node will go into free position */ gnext(n) = gnext(mp); /* chain new position */ gnext(mp) = n; mp = n; } } gkey(mp)->value = key->value; gkey(mp)->tt = key->tt; luaC_barriert(L, t, key); lua_assert(ttisnil(gval(mp))); return gval(mp); } /* ** 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)) return &t->array[key-1]; else { lua_Number nk = cast_num(key); Node *n = hashnum(t, nk); do { /* check whether `key' is somewhere in the chain */ if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; } } /* ** search function for strings */ const TValue *luaH_getstr (Table *t, TString *key) { Node *n = hashstr(t, key); do { /* check whether `key' is somewhere in the chain */ if (ttisstring(gkey(n)) && rawtsvalue(gkey(n)) == key) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; } /* ** main search function */ const TValue *luaH_get (Table *t, const TValue *key) { switch (ttype(key)) { case LUA_TNIL: return luaO_nilobject; case LUA_TSTRING: return luaH_getstr(t, rawtsvalue(key)); case LUA_TNUMBER: { int k; lua_Number n = nvalue(key); lua_number2int(k, n); if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */ return luaH_getnum(t, k); /* use specialized version */ /* else go through */ } default: { Node *n = mainposition(t, key); do { /* check whether `key' is somewhere in the chain */ if (luaO_rawequalObj(key2tval(n), key)) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; } } } TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { const TValue *p = luaH_get(t, key); t->flags = 0; if (p != luaO_nilobject) return cast(TValue *, p); else { if (ttisnil(key)) luaG_runerror(L, "table index is nil"); else if (ttisnumber(key) && luai_numisnan(nvalue(key))) luaG_runerror(L, "table index is NaN"); return newkey(L, t, key); } } TValue *luaH_setnum (lua_State *L, Table *t, int key) { const TValue *p = luaH_getnum(t, key); if (p != luaO_nilobject) return cast(TValue *, p); else { TValue k; setnvalue(&k, cast_num(key)); return newkey(L, t, &k); } } TValue *luaH_setstr (lua_State *L, Table *t, TString *key) { const TValue *p = luaH_getstr(t, key); if (p != luaO_nilobject) return cast(TValue *, p); else { TValue k; setsvalue(L, &k, key); return newkey(L, t, &k); } } static int unbound_search (Table *t, unsigned int j) { unsigned int i = j; /* i is zero or a present index */ j++; /* find `i' and `j' such that i is present and j is not */ while (!ttisnil(luaH_getnum(t, j))) { i = j; j *= 2; if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ /* table was built with bad purposes: resort to linear search */ i = 1; while (!ttisnil(luaH_getnum(t, i))) i++; return i - 1; } } /* now do a binary search between them */ while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(luaH_getnum(t, m))) j = m; else i = m; } return i; } /* ** Try to find a boundary in table `t'. A `boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ int luaH_getn (Table *t) { unsigned int j = t->sizearray; if (j > 0 && ttisnil(&t->array[j - 1])) { /* there is a boundary in the array part: (binary) search for it */ unsigned int i = 0; while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(&t->array[m - 1])) j = m; else i = m; } return i; } /* else must find a boundary in hash part */ else if (t->node == dummynode) /* hash part is empty? */ return j; /* that is easy... */ else return unbound_search(t, j); } #if defined(LUA_DEBUG) Node *luaH_mainposition (const Table *t, const TValue *key) { return mainposition(t, key); } int luaH_isdummy (Node *n) { return n == dummynode; } #endif redis-8.0.2/deps/lua/src/ltable.h000066400000000000000000000022401501533116600165450ustar00rootroot00000000000000/* ** $Id: ltable.h,v 2.10.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ #ifndef ltable_h #define ltable_h #include "lobject.h" #define gnode(t,i) (&(t)->node[i]) #define gkey(n) (&(n)->i_key.nk) #define gval(n) (&(n)->i_val) #define gnext(n) ((n)->i_key.nk.next) #define key2tval(n) (&(n)->i_key.tvk) LUAI_FUNC const TValue *luaH_getnum (Table *t, int key); LUAI_FUNC TValue *luaH_setnum (lua_State *L, Table *t, int key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC TValue *luaH_setstr (lua_State *L, Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); LUAI_FUNC Table *luaH_new (lua_State *L, int narray, int lnhash); LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); LUAI_FUNC int luaH_getn (Table *t); #if defined(LUA_DEBUG) LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); LUAI_FUNC int luaH_isdummy (Node *n); #endif #endif redis-8.0.2/deps/lua/src/ltablib.c000066400000000000000000000162551501533116600167210ustar00rootroot00000000000000/* ** $Id: ltablib.c,v 1.38.1.3 2008/02/14 16:46:58 roberto Exp $ ** Library for Table Manipulation ** See Copyright Notice in lua.h */ #include #define ltablib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #define aux_getn(L,n) (luaL_checktype(L, n, LUA_TTABLE), luaL_getn(L, n)) static int foreachi (lua_State *L) { int i; int n = aux_getn(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); for (i=1; i <= n; i++) { lua_pushvalue(L, 2); /* function */ lua_pushinteger(L, i); /* 1st argument */ lua_rawgeti(L, 1, i); /* 2nd argument */ lua_call(L, 2, 1); if (!lua_isnil(L, -1)) return 1; lua_pop(L, 1); /* remove nil result */ } return 0; } static int foreach (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checktype(L, 2, LUA_TFUNCTION); lua_pushnil(L); /* first key */ while (lua_next(L, 1)) { lua_pushvalue(L, 2); /* function */ lua_pushvalue(L, -3); /* key */ lua_pushvalue(L, -3); /* value */ lua_call(L, 2, 1); if (!lua_isnil(L, -1)) return 1; lua_pop(L, 2); /* remove value and result */ } return 0; } static int maxn (lua_State *L) { lua_Number max = 0; luaL_checktype(L, 1, LUA_TTABLE); lua_pushnil(L); /* first key */ while (lua_next(L, 1)) { lua_pop(L, 1); /* remove value */ if (lua_type(L, -1) == LUA_TNUMBER) { lua_Number v = lua_tonumber(L, -1); if (v > max) max = v; } } lua_pushnumber(L, max); return 1; } static int getn (lua_State *L) { lua_pushinteger(L, aux_getn(L, 1)); return 1; } static int setn (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); #ifndef luaL_setn luaL_setn(L, 1, luaL_checkint(L, 2)); #else luaL_error(L, LUA_QL("setn") " is obsolete"); #endif lua_pushvalue(L, 1); return 1; } static int tinsert (lua_State *L) { int e = aux_getn(L, 1) + 1; /* first empty element */ int pos; /* where to insert new element */ switch (lua_gettop(L)) { case 2: { /* called with only 2 arguments */ pos = e; /* insert new element at the end */ break; } case 3: { int i; pos = luaL_checkint(L, 2); /* 2nd argument is the position */ if (pos > e) e = pos; /* `grow' array if necessary */ for (i = e; i > pos; i--) { /* move up elements */ lua_rawgeti(L, 1, i-1); lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ } break; } default: { return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); } } luaL_setn(L, 1, e); /* new size */ lua_rawseti(L, 1, pos); /* t[pos] = v */ return 0; } static int tremove (lua_State *L) { int e = aux_getn(L, 1); int pos = luaL_optint(L, 2, e); if (!(1 <= pos && pos <= e)) /* position is outside bounds? */ return 0; /* nothing to remove */ luaL_setn(L, 1, e - 1); /* t.n = n-1 */ lua_rawgeti(L, 1, pos); /* result = t[pos] */ for ( ;pos= P */ while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { if (i>u) luaL_error(L, "invalid order function for sorting"); lua_pop(L, 1); /* remove a[i] */ } /* repeat --j until a[j] <= P */ while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { if (j #define ltm_c #define LUA_CORE #include "lua.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" const char *const luaT_typenames[] = { "nil", "boolean", "userdata", "number", "string", "table", "function", "userdata", "thread", "proto", "upval" }; void luaT_init (lua_State *L) { static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__gc", "__mode", "__eq", "__add", "__sub", "__mul", "__div", "__mod", "__pow", "__unm", "__len", "__lt", "__le", "__concat", "__call" }; int i; for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); luaS_fix(G(L)->tmname[i]); /* never collect these names */ } } /* ** function to be used with macro "fasttm": optimized for absence of ** tag methods */ const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { const TValue *tm = luaH_getstr(events, ename); lua_assert(event <= TM_EQ); if (ttisnil(tm)) { /* no tag method? */ events->flags |= cast_byte(1u<metatable; break; case LUA_TUSERDATA: mt = uvalue(o)->metatable; break; default: mt = G(L)->mt[ttype(o)]; } return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); } redis-8.0.2/deps/lua/src/ltm.h000066400000000000000000000017721501533116600161070ustar00rootroot00000000000000/* ** $Id: ltm.h,v 2.6.1.1 2007/12/27 13:02:25 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ #ifndef ltm_h #define ltm_h #include "lobject.h" /* * WARNING: if you change the order of this enumeration, * grep "ORDER TM" */ typedef enum { TM_INDEX, TM_NEWINDEX, TM_GC, TM_MODE, TM_EQ, /* last tag method with `fast' access */ TM_ADD, TM_SUB, TM_MUL, TM_DIV, TM_MOD, TM_POW, TM_UNM, TM_LEN, TM_LT, TM_LE, TM_CONCAT, TM_CALL, TM_N /* number of elements in the enum */ } TMS; #define gfasttm(g,et,e) ((et) == NULL ? NULL : \ ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) #define fasttm(l,et,e) gfasttm(G(l), et, e) LUAI_DATA const char *const luaT_typenames[]; LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event); LUAI_FUNC void luaT_init (lua_State *L); #endif redis-8.0.2/deps/lua/src/lua.c000066400000000000000000000236631501533116600160720ustar00rootroot00000000000000/* ** $Id: lua.c,v 1.160.1.2 2007/12/28 15:32:23 roberto Exp $ ** Lua stand-alone interpreter ** See Copyright Notice in lua.h */ #include #include #include #include #define lua_c #include "lua.h" #include "lauxlib.h" #include "lualib.h" static lua_State *globalL = NULL; static const char *progname = LUA_PROGNAME; static void lstop (lua_State *L, lua_Debug *ar) { (void)ar; /* unused arg. */ lua_sethook(L, NULL, 0, 0); luaL_error(L, "interrupted!"); } static void laction (int i) { signal(i, SIG_DFL); /* if another SIGINT happens before lstop, terminate process (default action) */ lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } static void print_usage (void) { fprintf(stderr, "usage: %s [options] [script [args]].\n" "Available options are:\n" " -e stat execute string " LUA_QL("stat") "\n" " -l name require library " LUA_QL("name") "\n" " -i enter interactive mode after executing " LUA_QL("script") "\n" " -v show version information\n" " -- stop handling options\n" " - execute stdin and stop handling options\n" , progname); fflush(stderr); } static void l_message (const char *pname, const char *msg) { if (pname) fprintf(stderr, "%s: ", pname); fprintf(stderr, "%s\n", msg); fflush(stderr); } static int report (lua_State *L, int status) { if (status && !lua_isnil(L, -1)) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error object is not a string)"; l_message(progname, msg); lua_pop(L, 1); } return status; } static int traceback (lua_State *L) { if (!lua_isstring(L, 1)) /* 'message' not a string? */ return 1; /* keep it intact */ lua_getfield(L, LUA_GLOBALSINDEX, "debug"); if (!lua_istable(L, -1)) { lua_pop(L, 1); return 1; } lua_getfield(L, -1, "traceback"); if (!lua_isfunction(L, -1)) { lua_pop(L, 2); return 1; } lua_pushvalue(L, 1); /* pass error message */ lua_pushinteger(L, 2); /* skip this function and traceback */ lua_call(L, 2, 1); /* call debug.traceback */ return 1; } static int docall (lua_State *L, int narg, int clear) { int status; int base = lua_gettop(L) - narg; /* function index */ lua_pushcfunction(L, traceback); /* push traceback function */ lua_insert(L, base); /* put it under chunk and args */ signal(SIGINT, laction); status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base); signal(SIGINT, SIG_DFL); lua_remove(L, base); /* remove traceback function */ /* force a complete garbage collection in case of errors */ if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); return status; } static void print_version (void) { l_message(NULL, LUA_RELEASE " " LUA_COPYRIGHT); } static int getargs (lua_State *L, char **argv, int n) { int narg; int i; int argc = 0; while (argv[argc]) argc++; /* count total number of arguments */ narg = argc - (n + 1); /* number of arguments to the script */ luaL_checkstack(L, narg + 3, "too many arguments to script"); for (i=n+1; i < argc; i++) lua_pushstring(L, argv[i]); lua_createtable(L, narg, n + 1); for (i=0; i < argc; i++) { lua_pushstring(L, argv[i]); lua_rawseti(L, -2, i - n); } return narg; } static int dofile (lua_State *L, const char *name) { int status = luaL_loadfile(L, name) || docall(L, 0, 1); return report(L, status); } static int dostring (lua_State *L, const char *s, const char *name) { int status = luaL_loadbuffer(L, s, strlen(s), name) || docall(L, 0, 1); return report(L, status); } static int dolibrary (lua_State *L, const char *name) { lua_getglobal(L, "require"); lua_pushstring(L, name); return report(L, docall(L, 1, 1)); } static const char *get_prompt (lua_State *L, int firstline) { const char *p; lua_getfield(L, LUA_GLOBALSINDEX, firstline ? "_PROMPT" : "_PROMPT2"); p = lua_tostring(L, -1); if (p == NULL) p = (firstline ? LUA_PROMPT : LUA_PROMPT2); lua_pop(L, 1); /* remove global */ return p; } static int incomplete (lua_State *L, int status) { if (status == LUA_ERRSYNTAX) { size_t lmsg; const char *msg = lua_tolstring(L, -1, &lmsg); const char *tp = msg + lmsg - (sizeof(LUA_QL("")) - 1); if (strstr(msg, LUA_QL("")) == tp) { lua_pop(L, 1); return 1; } } return 0; /* else... */ } static int pushline (lua_State *L, int firstline) { char buffer[LUA_MAXINPUT]; char *b = buffer; size_t l; const char *prmt = get_prompt(L, firstline); if (lua_readline(L, b, prmt) == 0) return 0; /* no input */ l = strlen(b); if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ b[l-1] = '\0'; /* remove it */ if (firstline && b[0] == '=') /* first line starts with `=' ? */ lua_pushfstring(L, "return %s", b+1); /* change it to `return' */ else lua_pushstring(L, b); lua_freeline(L, b); return 1; } static int loadline (lua_State *L) { int status; lua_settop(L, 0); if (!pushline(L, 1)) return -1; /* no input */ for (;;) { /* repeat until gets a complete line */ status = luaL_loadbuffer(L, lua_tostring(L, 1), lua_strlen(L, 1), "=stdin"); if (!incomplete(L, status)) break; /* cannot try to add lines? */ if (!pushline(L, 0)) /* no more input? */ return -1; lua_pushliteral(L, "\n"); /* add a new line... */ lua_insert(L, -2); /* ...between the two lines */ lua_concat(L, 3); /* join them */ } lua_saveline(L, 1); lua_remove(L, 1); /* remove line */ return status; } static void dotty (lua_State *L) { int status; const char *oldprogname = progname; progname = NULL; while ((status = loadline(L)) != -1) { if (status == 0) status = docall(L, 0, 0); report(L, status); if (status == 0 && lua_gettop(L) > 0) { /* any result to print? */ lua_getglobal(L, "print"); lua_insert(L, 1); if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != 0) l_message(progname, lua_pushfstring(L, "error calling " LUA_QL("print") " (%s)", lua_tostring(L, -1))); } } lua_settop(L, 0); /* clear stack */ fputs("\n", stdout); fflush(stdout); progname = oldprogname; } static int handle_script (lua_State *L, char **argv, int n) { int status; const char *fname; int narg = getargs(L, argv, n); /* collect arguments */ lua_setglobal(L, "arg"); fname = argv[n]; if (strcmp(fname, "-") == 0 && strcmp(argv[n-1], "--") != 0) fname = NULL; /* stdin */ status = luaL_loadfile(L, fname); lua_insert(L, -(narg+1)); if (status == 0) status = docall(L, narg, 0); else lua_pop(L, narg); return report(L, status); } /* check that argument has no extra characters at the end */ #define notail(x) {if ((x)[2] != '\0') return -1;} static int collectargs (char **argv, int *pi, int *pv, int *pe) { int i; for (i = 1; argv[i] != NULL; i++) { if (argv[i][0] != '-') /* not an option? */ return i; switch (argv[i][1]) { /* option */ case '-': notail(argv[i]); return (argv[i+1] != NULL ? i+1 : 0); case '\0': return i; case 'i': notail(argv[i]); *pi = 1; /* go through */ case 'v': notail(argv[i]); *pv = 1; break; case 'e': *pe = 1; /* go through */ case 'l': if (argv[i][2] == '\0') { i++; if (argv[i] == NULL) return -1; } break; default: return -1; /* invalid option */ } } return 0; } static int runargs (lua_State *L, char **argv, int n) { int i; for (i = 1; i < n; i++) { if (argv[i] == NULL) continue; lua_assert(argv[i][0] == '-'); switch (argv[i][1]) { /* option */ case 'e': { const char *chunk = argv[i] + 2; if (*chunk == '\0') chunk = argv[++i]; lua_assert(chunk != NULL); if (dostring(L, chunk, "=(command line)") != 0) return 1; break; } case 'l': { const char *filename = argv[i] + 2; if (*filename == '\0') filename = argv[++i]; lua_assert(filename != NULL); if (dolibrary(L, filename)) return 1; /* stop if file fails */ break; } default: break; } } return 0; } static int handle_luainit (lua_State *L) { const char *init = getenv(LUA_INIT); if (init == NULL) return 0; /* status OK */ else if (init[0] == '@') return dofile(L, init+1); else return dostring(L, init, "=" LUA_INIT); } struct Smain { int argc; char **argv; int status; }; static int pmain (lua_State *L) { struct Smain *s = (struct Smain *)lua_touserdata(L, 1); char **argv = s->argv; int script; int has_i = 0, has_v = 0, has_e = 0; globalL = L; if (argv[0] && argv[0][0]) progname = argv[0]; lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ luaL_openlibs(L); /* open libraries */ lua_gc(L, LUA_GCRESTART, 0); s->status = handle_luainit(L); if (s->status != 0) return 0; script = collectargs(argv, &has_i, &has_v, &has_e); if (script < 0) { /* invalid args? */ print_usage(); s->status = 1; return 0; } if (has_v) print_version(); s->status = runargs(L, argv, (script > 0) ? script : s->argc); if (s->status != 0) return 0; if (script) s->status = handle_script(L, argv, script); if (s->status != 0) return 0; if (has_i) dotty(L); else if (script == 0 && !has_e && !has_v) { if (lua_stdin_is_tty()) { print_version(); dotty(L); } else dofile(L, NULL); /* executes stdin as a file */ } return 0; } int main (int argc, char **argv) { int status; struct Smain s; lua_State *L = lua_open(); /* create state */ if (L == NULL) { l_message(argv[0], "cannot create state: not enough memory"); return EXIT_FAILURE; } s.argc = argc; s.argv = argv; status = lua_cpcall(L, &pmain, &s); report(L, status); lua_close(L); return (status || s.status) ? EXIT_FAILURE : EXIT_SUCCESS; } redis-8.0.2/deps/lua/src/lua.h000066400000000000000000000270611501533116600160730ustar00rootroot00000000000000/* ** $Id: lua.h,v 1.218.1.7 2012/01/13 20:36:20 roberto Exp $ ** Lua - An Extensible Extension Language ** Lua.org, PUC-Rio, Brazil (http://www.lua.org) ** See Copyright Notice at the end of this file */ #ifndef lua_h #define lua_h #include #include #include "luaconf.h" #define LUA_VERSION "Lua 5.1" #define LUA_RELEASE "Lua 5.1.5" #define LUA_VERSION_NUM 501 #define LUA_COPYRIGHT "Copyright (C) 1994-2012 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes" /* mark for precompiled code (`Lua') */ #define LUA_SIGNATURE "\033Lua" /* option for multiple returns in `lua_pcall' and `lua_call' */ #define LUA_MULTRET (-1) /* ** pseudo-indices */ #define LUA_REGISTRYINDEX (-10000) #define LUA_ENVIRONINDEX (-10001) #define LUA_GLOBALSINDEX (-10002) #define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i)) /* thread status; 0 is OK */ #define LUA_YIELD 1 #define LUA_ERRRUN 2 #define LUA_ERRSYNTAX 3 #define LUA_ERRMEM 4 #define LUA_ERRERR 5 typedef struct lua_State lua_State; typedef int (*lua_CFunction) (lua_State *L); /* ** functions that read/write blocks when loading/dumping Lua chunks */ typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); /* ** prototype for memory-allocation functions */ typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); /* ** basic types */ #define LUA_TNONE (-1) #define LUA_TNIL 0 #define LUA_TBOOLEAN 1 #define LUA_TLIGHTUSERDATA 2 #define LUA_TNUMBER 3 #define LUA_TSTRING 4 #define LUA_TTABLE 5 #define LUA_TFUNCTION 6 #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8 /* minimum Lua stack available to a C function */ #define LUA_MINSTACK 20 /* ** generic extra include file */ #if defined(LUA_USER_H) #include LUA_USER_H #endif /* type of numbers in Lua */ typedef LUA_NUMBER lua_Number; /* type for integer functions */ typedef LUA_INTEGER lua_Integer; /* ** state manipulation */ LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); LUA_API void (lua_close) (lua_State *L); LUA_API lua_State *(lua_newthread) (lua_State *L); LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); /* ** basic stack manipulation */ LUA_API int (lua_gettop) (lua_State *L); LUA_API void (lua_settop) (lua_State *L, int idx); LUA_API void (lua_pushvalue) (lua_State *L, int idx); LUA_API void (lua_remove) (lua_State *L, int idx); LUA_API void (lua_insert) (lua_State *L, int idx); LUA_API void (lua_replace) (lua_State *L, int idx); LUA_API int (lua_checkstack) (lua_State *L, int sz); LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); /* ** access functions (stack -> C) */ LUA_API int (lua_isnumber) (lua_State *L, int idx); LUA_API int (lua_isstring) (lua_State *L, int idx); LUA_API int (lua_iscfunction) (lua_State *L, int idx); LUA_API int (lua_isuserdata) (lua_State *L, int idx); LUA_API int (lua_type) (lua_State *L, int idx); LUA_API const char *(lua_typename) (lua_State *L, int tp); LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2); LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2); LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx); LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx); LUA_API int (lua_toboolean) (lua_State *L, int idx); LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); LUA_API size_t (lua_objlen) (lua_State *L, int idx); LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); LUA_API void *(lua_touserdata) (lua_State *L, int idx); LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); LUA_API const void *(lua_topointer) (lua_State *L, int idx); /* ** push functions (C -> stack) */ LUA_API void (lua_pushnil) (lua_State *L); LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l); LUA_API void (lua_pushstring) (lua_State *L, const char *s); LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp); LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); LUA_API void (lua_pushboolean) (lua_State *L, int b); LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); LUA_API int (lua_pushthread) (lua_State *L); /* ** get functions (Lua -> stack) */ LUA_API void (lua_gettable) (lua_State *L, int idx); LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); LUA_API void (lua_rawget) (lua_State *L, int idx); LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); LUA_API void (lua_getfenv) (lua_State *L, int idx); /* ** set functions (stack -> Lua) */ LUA_API void (lua_settable) (lua_State *L, int idx); LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); LUA_API void (lua_rawset) (lua_State *L, int idx); LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); LUA_API int (lua_setmetatable) (lua_State *L, int objindex); LUA_API int (lua_setfenv) (lua_State *L, int idx); /* ** `load' and `call' functions (load and run Lua code) */ LUA_API void (lua_call) (lua_State *L, int nargs, int nresults); LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud); LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname); LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); /* ** coroutine functions */ LUA_API int (lua_yield) (lua_State *L, int nresults); LUA_API int (lua_resume) (lua_State *L, int narg); LUA_API int (lua_status) (lua_State *L); /* ** garbage-collection function and options */ #define LUA_GCSTOP 0 #define LUA_GCRESTART 1 #define LUA_GCCOLLECT 2 #define LUA_GCCOUNT 3 #define LUA_GCCOUNTB 4 #define LUA_GCSTEP 5 #define LUA_GCSETPAUSE 6 #define LUA_GCSETSTEPMUL 7 LUA_API int (lua_gc) (lua_State *L, int what, int data); /* ** miscellaneous functions */ LUA_API int (lua_error) (lua_State *L); LUA_API int (lua_next) (lua_State *L, int idx); LUA_API void (lua_concat) (lua_State *L, int n); LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); /* ** =============================================================== ** some useful macros ** =============================================================== */ #define lua_pop(L,n) lua_settop(L, -(n)-1) #define lua_newtable(L) lua_createtable(L, 0, 0) #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) #define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) #define lua_strlen(L,i) lua_objlen(L, (i)) #define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) #define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) #define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) #define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) #define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) #define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) #define lua_pushliteral(L, s) \ lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) #define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL) /* ** compatibility macros and functions */ #define lua_open() luaL_newstate() #define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX) #define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0) #define lua_Chunkreader lua_Reader #define lua_Chunkwriter lua_Writer /* hack */ LUA_API void lua_setlevel (lua_State *from, lua_State *to); /* ** {====================================================================== ** Debug API ** ======================================================================= */ /* ** Event codes */ #define LUA_HOOKCALL 0 #define LUA_HOOKRET 1 #define LUA_HOOKLINE 2 #define LUA_HOOKCOUNT 3 #define LUA_HOOKTAILRET 4 /* ** Event masks */ #define LUA_MASKCALL (1 << LUA_HOOKCALL) #define LUA_MASKRET (1 << LUA_HOOKRET) #define LUA_MASKLINE (1 << LUA_HOOKLINE) #define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) typedef struct lua_Debug lua_Debug; /* activation record */ /* Functions to be called by the debuger in specific events */ typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar); LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n); LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n); LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n); LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n); LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count); LUA_API lua_Hook lua_gethook (lua_State *L); LUA_API int lua_gethookmask (lua_State *L); LUA_API int lua_gethookcount (lua_State *L); struct lua_Debug { int event; const char *name; /* (n) */ const char *namewhat; /* (n) `global', `local', `field', `method' */ const char *what; /* (S) `Lua', `C', `main', `tail' */ const char *source; /* (S) */ int currentline; /* (l) */ int nups; /* (u) number of upvalues */ int linedefined; /* (S) */ int lastlinedefined; /* (S) */ char short_src[LUA_IDSIZE]; /* (S) */ /* private part */ int i_ci; /* active function */ }; LUA_API void lua_enablereadonlytable (lua_State *L, int index, int enabled); LUA_API int lua_isreadonlytable (lua_State *L, int index); /* }====================================================================== */ /****************************************************************************** * Copyright (C) 1994-2012 Lua.org, PUC-Rio. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************/ #endif redis-8.0.2/deps/lua/src/lua_bit.c000066400000000000000000000125771501533116600167320ustar00rootroot00000000000000/* ** Lua BitOp -- a bit operations library for Lua 5.1/5.2. ** http://bitop.luajit.org/ ** ** Copyright (C) 2008-2012 Mike Pall. All rights reserved. ** ** Permission is hereby granted, free of charge, to any person obtaining ** a copy of this software and associated documentation files (the ** "Software"), to deal in the Software without restriction, including ** without limitation the rights to use, copy, modify, merge, publish, ** distribute, sublicense, and/or sell copies of the Software, and to ** permit persons to whom the Software is furnished to do so, subject to ** the following conditions: ** ** The above copyright notice and this permission notice shall be ** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ** ** [ MIT license: http://www.opensource.org/licenses/mit-license.php ] */ #define LUA_BITOP_VERSION "1.0.2" #define LUA_LIB #include "lua.h" #include "lauxlib.h" #ifdef _MSC_VER /* MSVC is stuck in the last century and doesn't have C99's stdint.h. */ typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef unsigned __int64 uint64_t; #else #include #endif typedef int32_t SBits; typedef uint32_t UBits; typedef union { lua_Number n; #ifdef LUA_NUMBER_DOUBLE uint64_t b; #else UBits b; #endif } BitNum; /* Convert argument to bit type. */ static UBits barg(lua_State *L, int idx) { BitNum bn; UBits b; #if LUA_VERSION_NUM < 502 bn.n = lua_tonumber(L, idx); #else bn.n = luaL_checknumber(L, idx); #endif #if defined(LUA_NUMBER_DOUBLE) bn.n += 6755399441055744.0; /* 2^52+2^51 */ #ifdef SWAPPED_DOUBLE b = (UBits)(bn.b >> 32); #else b = (UBits)bn.b; #endif #elif defined(LUA_NUMBER_INT) || defined(LUA_NUMBER_LONG) || \ defined(LUA_NUMBER_LONGLONG) || defined(LUA_NUMBER_LONG_LONG) || \ defined(LUA_NUMBER_LLONG) if (sizeof(UBits) == sizeof(lua_Number)) b = bn.b; else b = (UBits)(SBits)bn.n; #elif defined(LUA_NUMBER_FLOAT) #error "A 'float' lua_Number type is incompatible with this library" #else #error "Unknown number type, check LUA_NUMBER_* in luaconf.h" #endif #if LUA_VERSION_NUM < 502 if (b == 0 && !lua_isnumber(L, idx)) { luaL_typerror(L, idx, "number"); } #endif return b; } /* Return bit type. */ #define BRET(b) lua_pushnumber(L, (lua_Number)(SBits)(b)); return 1; static int bit_tobit(lua_State *L) { BRET(barg(L, 1)) } static int bit_bnot(lua_State *L) { BRET(~barg(L, 1)) } #define BIT_OP(func, opr) \ static int func(lua_State *L) { int i; UBits b = barg(L, 1); \ for (i = lua_gettop(L); i > 1; i--) b opr barg(L, i); \ BRET(b) } BIT_OP(bit_band, &=) BIT_OP(bit_bor, |=) BIT_OP(bit_bxor, ^=) #define bshl(b, n) (b << n) #define bshr(b, n) (b >> n) #define bsar(b, n) ((SBits)b >> n) #define brol(b, n) ((b << n) | (b >> (32-n))) #define bror(b, n) ((b << (32-n)) | (b >> n)) #define BIT_SH(func, fn) \ static int func(lua_State *L) { \ UBits b = barg(L, 1); UBits n = barg(L, 2) & 31; BRET(fn(b, n)) } BIT_SH(bit_lshift, bshl) BIT_SH(bit_rshift, bshr) BIT_SH(bit_arshift, bsar) BIT_SH(bit_rol, brol) BIT_SH(bit_ror, bror) static int bit_bswap(lua_State *L) { UBits b = barg(L, 1); b = (b >> 24) | ((b >> 8) & 0xff00) | ((b & 0xff00) << 8) | (b << 24); BRET(b) } static int bit_tohex(lua_State *L) { UBits b = barg(L, 1); SBits n = lua_isnone(L, 2) ? 8 : (SBits)barg(L, 2); const char *hexdigits = "0123456789abcdef"; char buf[8]; int i; if (n == INT32_MIN) n = INT32_MIN+1; if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; } if (n > 8) n = 8; for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; } lua_pushlstring(L, buf, (size_t)n); return 1; } static const struct luaL_Reg bit_funcs[] = { { "tobit", bit_tobit }, { "bnot", bit_bnot }, { "band", bit_band }, { "bor", bit_bor }, { "bxor", bit_bxor }, { "lshift", bit_lshift }, { "rshift", bit_rshift }, { "arshift", bit_arshift }, { "rol", bit_rol }, { "ror", bit_ror }, { "bswap", bit_bswap }, { "tohex", bit_tohex }, { NULL, NULL } }; /* Signed right-shifts are implementation-defined per C89/C99. ** But the de facto standard are arithmetic right-shifts on two's ** complement CPUs. This behaviour is required here, so test for it. */ #define BAD_SAR (bsar(-8, 2) != (SBits)-2) LUALIB_API int luaopen_bit(lua_State *L) { UBits b; lua_pushnumber(L, (lua_Number)1437217655L); b = barg(L, -1); if (b != (UBits)1437217655L || BAD_SAR) { /* Perform a simple self-test. */ const char *msg = "compiled with incompatible luaconf.h"; #ifdef LUA_NUMBER_DOUBLE #ifdef _WIN32 if (b == (UBits)1610612736L) msg = "use D3DCREATE_FPU_PRESERVE with DirectX"; #endif if (b == (UBits)1127743488L) msg = "not compiled with SWAPPED_DOUBLE"; #endif if (BAD_SAR) msg = "arithmetic right-shift broken"; luaL_error(L, "bit library self-test failed (%s)", msg); } #if LUA_VERSION_NUM < 502 luaL_register(L, "bit", bit_funcs); #else luaL_newlib(L, bit_funcs); #endif return 1; } redis-8.0.2/deps/lua/src/lua_cjson.c000066400000000000000000001213651501533116600172640ustar00rootroot00000000000000/* Lua CJSON - JSON support for Lua * * Copyright (c) 2010-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Caveats: * - JSON "null" values are represented as lightuserdata since Lua * tables cannot contain "nil". Compare with cjson.null. * - Invalid UTF-8 characters are not detected and will be passed * untouched. If required, UTF-8 error checking should be done * outside this library. * - Javascript comments are not part of the JSON spec, and are not * currently supported. * * Note: Decoding is slower than encoding. Lua spends significant * time (30%) managing tables when parsing JSON since it is * difficult to know object/array sizes ahead of time. */ #include #include #include #include #include #include "lua.h" #include "lauxlib.h" #include "strbuf.h" #include "fpconv.h" #include "../../../src/solarisfixes.h" #ifndef CJSON_MODNAME #define CJSON_MODNAME "cjson" #endif #ifndef CJSON_VERSION #define CJSON_VERSION "2.1.0" #endif /* Workaround for Solaris platforms missing isinf() */ #if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF)) #define isinf(x) (!isnan(x) && isnan((x) - (x))) #endif #define DEFAULT_SPARSE_CONVERT 0 #define DEFAULT_SPARSE_RATIO 2 #define DEFAULT_SPARSE_SAFE 10 #define DEFAULT_ENCODE_MAX_DEPTH 1000 #define DEFAULT_DECODE_MAX_DEPTH 1000 #define DEFAULT_ENCODE_INVALID_NUMBERS 0 #define DEFAULT_DECODE_INVALID_NUMBERS 1 #define DEFAULT_ENCODE_KEEP_BUFFER 1 #define DEFAULT_ENCODE_NUMBER_PRECISION 14 #ifdef DISABLE_INVALID_NUMBERS #undef DEFAULT_DECODE_INVALID_NUMBERS #define DEFAULT_DECODE_INVALID_NUMBERS 0 #endif typedef enum { T_OBJ_BEGIN, T_OBJ_END, T_ARR_BEGIN, T_ARR_END, T_STRING, T_NUMBER, T_BOOLEAN, T_NULL, T_COLON, T_COMMA, T_END, T_WHITESPACE, T_ERROR, T_UNKNOWN } json_token_type_t; static const char *json_token_type_name[] = { "T_OBJ_BEGIN", "T_OBJ_END", "T_ARR_BEGIN", "T_ARR_END", "T_STRING", "T_NUMBER", "T_BOOLEAN", "T_NULL", "T_COLON", "T_COMMA", "T_END", "T_WHITESPACE", "T_ERROR", "T_UNKNOWN", NULL }; typedef struct { json_token_type_t ch2token[256]; char escape2char[256]; /* Decoding */ /* encode_buf is only allocated and used when * encode_keep_buffer is set */ strbuf_t encode_buf; int encode_sparse_convert; int encode_sparse_ratio; int encode_sparse_safe; int encode_max_depth; int encode_invalid_numbers; /* 2 => Encode as "null" */ int encode_number_precision; int encode_keep_buffer; int decode_invalid_numbers; int decode_max_depth; } json_config_t; typedef struct { const char *data; const char *ptr; strbuf_t *tmp; /* Temporary storage for strings */ json_config_t *cfg; int current_depth; } json_parse_t; typedef struct { json_token_type_t type; size_t index; union { const char *string; double number; int boolean; } value; size_t string_len; } json_token_t; static const char *char2escape[256] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f", NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; /* ===== CONFIGURATION ===== */ static json_config_t *json_fetch_config(lua_State *l) { json_config_t *cfg; cfg = lua_touserdata(l, lua_upvalueindex(1)); if (!cfg) luaL_error(l, "BUG: Unable to fetch CJSON configuration"); return cfg; } /* Ensure the correct number of arguments have been provided. * Pad with nil to allow other functions to simply check arg[i] * to find whether an argument was provided */ static json_config_t *json_arg_init(lua_State *l, int args) { luaL_argcheck(l, lua_gettop(l) <= args, args + 1, "found too many arguments"); while (lua_gettop(l) < args) lua_pushnil(l); return json_fetch_config(l); } /* Process integer options for configuration functions */ static int json_integer_option(lua_State *l, int optindex, int *setting, int min, int max) { char errmsg[64]; int value; if (!lua_isnil(l, optindex)) { value = luaL_checkinteger(l, optindex); snprintf(errmsg, sizeof(errmsg), "expected integer between %d and %d", min, max); luaL_argcheck(l, min <= value && value <= max, 1, errmsg); *setting = value; } lua_pushinteger(l, *setting); return 1; } /* Process enumerated arguments for a configuration function */ static int json_enum_option(lua_State *l, int optindex, int *setting, const char **options, int bool_true) { static const char *bool_options[] = { "off", "on", NULL }; if (!options) { options = bool_options; bool_true = 1; } if (!lua_isnil(l, optindex)) { if (bool_true && lua_isboolean(l, optindex)) *setting = lua_toboolean(l, optindex) * bool_true; else *setting = luaL_checkoption(l, optindex, NULL, options); } if (bool_true && (*setting == 0 || *setting == bool_true)) lua_pushboolean(l, *setting); else lua_pushstring(l, options[*setting]); return 1; } /* Configures handling of extremely sparse arrays: * convert: Convert extremely sparse arrays into objects? Otherwise error. * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio * safe: Always use an array when the max index <= safe */ static int json_cfg_encode_sparse_array(lua_State *l) { json_config_t *cfg = json_arg_init(l, 3); json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1); json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX); json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX); return 3; } /* Configures the maximum number of nested arrays/objects allowed when * encoding */ static int json_cfg_encode_max_depth(lua_State *l) { json_config_t *cfg = json_arg_init(l, 1); return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX); } /* Configures the maximum number of nested arrays/objects allowed when * encoding */ static int json_cfg_decode_max_depth(lua_State *l) { json_config_t *cfg = json_arg_init(l, 1); return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX); } /* Configures number precision when converting doubles to text */ static int json_cfg_encode_number_precision(lua_State *l) { json_config_t *cfg = json_arg_init(l, 1); return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14); } /* Configures JSON encoding buffer persistence */ static int json_cfg_encode_keep_buffer(lua_State *l) { json_config_t *cfg = json_arg_init(l, 1); int old_value; old_value = cfg->encode_keep_buffer; json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1); /* Init / free the buffer if the setting has changed */ if (old_value ^ cfg->encode_keep_buffer) { if (cfg->encode_keep_buffer) strbuf_init(&cfg->encode_buf, 0); else strbuf_free(&cfg->encode_buf); } return 1; } #if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV) void json_verify_invalid_number_setting(lua_State *l, int *setting) { if (*setting == 1) { *setting = 0; luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); } } #else #define json_verify_invalid_number_setting(l, s) do { } while(0) #endif static int json_cfg_encode_invalid_numbers(lua_State *l) { static const char *options[] = { "off", "on", "null", NULL }; json_config_t *cfg = json_arg_init(l, 1); json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1); json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers); return 1; } static int json_cfg_decode_invalid_numbers(lua_State *l) { json_config_t *cfg = json_arg_init(l, 1); json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1); json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers); return 1; } static int json_destroy_config(lua_State *l) { json_config_t *cfg; cfg = lua_touserdata(l, 1); if (cfg) strbuf_free(&cfg->encode_buf); cfg = NULL; return 0; } static void json_create_config(lua_State *l) { json_config_t *cfg; int i; cfg = lua_newuserdata(l, sizeof(*cfg)); /* Create GC method to clean up strbuf */ lua_newtable(l); lua_pushcfunction(l, json_destroy_config); lua_setfield(l, -2, "__gc"); lua_setmetatable(l, -2); cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH; cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH; cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS; cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS; cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; #if DEFAULT_ENCODE_KEEP_BUFFER > 0 strbuf_init(&cfg->encode_buf, 0); #endif /* Decoding init */ /* Tag all characters as an error */ for (i = 0; i < 256; i++) cfg->ch2token[i] = T_ERROR; /* Set tokens that require no further processing */ cfg->ch2token['{'] = T_OBJ_BEGIN; cfg->ch2token['}'] = T_OBJ_END; cfg->ch2token['['] = T_ARR_BEGIN; cfg->ch2token[']'] = T_ARR_END; cfg->ch2token[','] = T_COMMA; cfg->ch2token[':'] = T_COLON; cfg->ch2token['\0'] = T_END; cfg->ch2token[' '] = T_WHITESPACE; cfg->ch2token['\t'] = T_WHITESPACE; cfg->ch2token['\n'] = T_WHITESPACE; cfg->ch2token['\r'] = T_WHITESPACE; /* Update characters that require further processing */ cfg->ch2token['f'] = T_UNKNOWN; /* false? */ cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */ cfg->ch2token['I'] = T_UNKNOWN; cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */ cfg->ch2token['N'] = T_UNKNOWN; cfg->ch2token['t'] = T_UNKNOWN; /* true? */ cfg->ch2token['"'] = T_UNKNOWN; /* string? */ cfg->ch2token['+'] = T_UNKNOWN; /* number? */ cfg->ch2token['-'] = T_UNKNOWN; for (i = 0; i < 10; i++) cfg->ch2token['0' + i] = T_UNKNOWN; /* Lookup table for parsing escape characters */ for (i = 0; i < 256; i++) cfg->escape2char[i] = 0; /* String error */ cfg->escape2char['"'] = '"'; cfg->escape2char['\\'] = '\\'; cfg->escape2char['/'] = '/'; cfg->escape2char['b'] = '\b'; cfg->escape2char['t'] = '\t'; cfg->escape2char['n'] = '\n'; cfg->escape2char['f'] = '\f'; cfg->escape2char['r'] = '\r'; cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ } /* ===== ENCODING ===== */ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex, const char *reason) { if (!cfg->encode_keep_buffer) strbuf_free(json); luaL_error(l, "Cannot serialise %s: %s", lua_typename(l, lua_type(l, lindex)), reason); } /* json_append_string args: * - lua_State * - JSON strbuf * - String (Lua stack index) * * Returns nothing. Doesn't remove string from Lua stack */ static void json_append_string(lua_State *l, strbuf_t *json, int lindex) { const char *escstr; const char *str; size_t i, len; str = lua_tolstring(l, lindex, &len); /* Worst case is len * 6 (all unicode escapes). * This buffer is reused constantly for small strings * If there are any excess pages, they won't be hit anyway. * This gains ~5% speedup. */ if (len > SIZE_MAX / 6 - 3) abort(); /* Overflow check */ strbuf_ensure_empty_length(json, len * 6 + 2); strbuf_append_char_unsafe(json, '\"'); for (i = 0; i < len; i++) { escstr = char2escape[(unsigned char)str[i]]; if (escstr) strbuf_append_string(json, escstr); else strbuf_append_char_unsafe(json, str[i]); } strbuf_append_char_unsafe(json, '\"'); } /* Find the size of the array on the top of the Lua stack * -1 object (not a pure array) * >=0 elements in array */ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json) { double k; int max; int items; max = 0; items = 0; lua_pushnil(l); /* table, startkey */ while (lua_next(l, -2) != 0) { /* table, key, value */ if (lua_type(l, -2) == LUA_TNUMBER && (k = lua_tonumber(l, -2))) { /* Integer >= 1 ? */ if (floor(k) == k && k >= 1) { if (k > max) max = k; items++; lua_pop(l, 1); continue; } } /* Must not be an array (non integer key) */ lua_pop(l, 2); return -1; } /* Encode excessively sparse arrays as objects (if enabled) */ if (cfg->encode_sparse_ratio > 0 && max > items * cfg->encode_sparse_ratio && max > cfg->encode_sparse_safe) { if (!cfg->encode_sparse_convert) json_encode_exception(l, cfg, json, -1, "excessively sparse array"); return -1; } return max; } static void json_check_encode_depth(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json) { /* Ensure there are enough slots free to traverse a table (key, * value) and push a string for a potential error message. * * Unlike "decode", the key and value are still on the stack when * lua_checkstack() is called. Hence an extra slot for luaL_error() * below is required just in case the next check to lua_checkstack() * fails. * * While this won't cause a crash due to the EXTRA_STACK reserve * slots, it would still be an improper use of the API. */ if (current_depth <= cfg->encode_max_depth && lua_checkstack(l, 3)) return; if (!cfg->encode_keep_buffer) strbuf_free(json); luaL_error(l, "Cannot serialise, excessive nesting (%d)", current_depth); } static void json_append_data(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json); /* json_append_array args: * - lua_State * - JSON strbuf * - Size of passwd Lua array (top of stack) */ static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json, int array_length) { int comma, i; strbuf_append_char(json, '['); comma = 0; for (i = 1; i <= array_length; i++) { if (comma) strbuf_append_char(json, ','); else comma = 1; lua_rawgeti(l, -1, i); json_append_data(l, cfg, current_depth, json); lua_pop(l, 1); } strbuf_append_char(json, ']'); } static void json_append_number(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex) { double num = lua_tonumber(l, lindex); int len; if (cfg->encode_invalid_numbers == 0) { /* Prevent encoding invalid numbers */ if (isinf(num) || isnan(num)) json_encode_exception(l, cfg, json, lindex, "must not be NaN or Inf"); } else if (cfg->encode_invalid_numbers == 1) { /* Encode invalid numbers, but handle "nan" separately * since some platforms may encode as "-nan". */ if (isnan(num)) { strbuf_append_mem(json, "nan", 3); return; } } else { /* Encode invalid numbers as "null" */ if (isinf(num) || isnan(num)) { strbuf_append_mem(json, "null", 4); return; } } strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision); strbuf_extend_length(json, len); } static void json_append_object(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json) { int comma, keytype; /* Object */ strbuf_append_char(json, '{'); lua_pushnil(l); /* table, startkey */ comma = 0; while (lua_next(l, -2) != 0) { if (comma) strbuf_append_char(json, ','); else comma = 1; /* table, key, value */ keytype = lua_type(l, -2); if (keytype == LUA_TNUMBER) { strbuf_append_char(json, '"'); json_append_number(l, cfg, json, -2); strbuf_append_mem(json, "\":", 2); } else if (keytype == LUA_TSTRING) { json_append_string(l, json, -2); strbuf_append_char(json, ':'); } else { json_encode_exception(l, cfg, json, -2, "table key must be a number or string"); /* never returns */ } /* table, key, value */ json_append_data(l, cfg, current_depth, json); lua_pop(l, 1); /* table, key */ } strbuf_append_char(json, '}'); } /* Serialise Lua data into JSON string. */ static void json_append_data(lua_State *l, json_config_t *cfg, int current_depth, strbuf_t *json) { int len; switch (lua_type(l, -1)) { case LUA_TSTRING: json_append_string(l, json, -1); break; case LUA_TNUMBER: json_append_number(l, cfg, json, -1); break; case LUA_TBOOLEAN: if (lua_toboolean(l, -1)) strbuf_append_mem(json, "true", 4); else strbuf_append_mem(json, "false", 5); break; case LUA_TTABLE: current_depth++; json_check_encode_depth(l, cfg, current_depth, json); len = lua_array_length(l, cfg, json); if (len > 0) json_append_array(l, cfg, current_depth, json, len); else json_append_object(l, cfg, current_depth, json); break; case LUA_TNIL: strbuf_append_mem(json, "null", 4); break; case LUA_TLIGHTUSERDATA: if (lua_touserdata(l, -1) == NULL) { strbuf_append_mem(json, "null", 4); break; } default: /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, * and LUA_TLIGHTUSERDATA) cannot be serialised */ json_encode_exception(l, cfg, json, -1, "type not supported"); /* never returns */ } } static int json_encode(lua_State *l) { json_config_t *cfg = json_fetch_config(l); strbuf_t local_encode_buf; strbuf_t *encode_buf; char *json; size_t len; luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); if (!cfg->encode_keep_buffer) { /* Use private buffer */ encode_buf = &local_encode_buf; strbuf_init(encode_buf, 0); } else { /* Reuse existing buffer */ encode_buf = &cfg->encode_buf; strbuf_reset(encode_buf); } json_append_data(l, cfg, 0, encode_buf); json = strbuf_string(encode_buf, &len); lua_pushlstring(l, json, len); if (!cfg->encode_keep_buffer) strbuf_free(encode_buf); return 1; } /* ===== DECODING ===== */ static void json_process_value(lua_State *l, json_parse_t *json, json_token_t *token); static int hexdigit2int(char hex) { if ('0' <= hex && hex <= '9') return hex - '0'; /* Force lowercase */ hex |= 0x20; if ('a' <= hex && hex <= 'f') return 10 + hex - 'a'; return -1; } static int decode_hex4(const char *hex) { int digit[4]; int i; /* Convert ASCII hex digit to numeric digit * Note: this returns an error for invalid hex digits, including * NULL */ for (i = 0; i < 4; i++) { digit[i] = hexdigit2int(hex[i]); if (digit[i] < 0) { return -1; } } return (digit[0] << 12) + (digit[1] << 8) + (digit[2] << 4) + digit[3]; } /* Converts a Unicode codepoint to UTF-8. * Returns UTF-8 string length, and up to 4 bytes in *utf8 */ static int codepoint_to_utf8(char *utf8, int codepoint) { /* 0xxxxxxx */ if (codepoint <= 0x7F) { utf8[0] = codepoint; return 1; } /* 110xxxxx 10xxxxxx */ if (codepoint <= 0x7FF) { utf8[0] = (codepoint >> 6) | 0xC0; utf8[1] = (codepoint & 0x3F) | 0x80; return 2; } /* 1110xxxx 10xxxxxx 10xxxxxx */ if (codepoint <= 0xFFFF) { utf8[0] = (codepoint >> 12) | 0xE0; utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80; utf8[2] = (codepoint & 0x3F) | 0x80; return 3; } /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ if (codepoint <= 0x1FFFFF) { utf8[0] = (codepoint >> 18) | 0xF0; utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80; utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80; utf8[3] = (codepoint & 0x3F) | 0x80; return 4; } return 0; } /* Called when index pointing to beginning of UTF-16 code escape: \uXXXX * \u is guaranteed to exist, but the remaining hex characters may be * missing. * Translate to UTF-8 and append to temporary token string. * Must advance index to the next character to be processed. * Returns: 0 success * -1 error */ static int json_append_unicode_escape(json_parse_t *json) { char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */ int codepoint; int surrogate_low; int len; int escape_len = 6; /* Fetch UTF-16 code unit */ codepoint = decode_hex4(json->ptr + 2); if (codepoint < 0) return -1; /* UTF-16 surrogate pairs take the following 2 byte form: * 11011 x yyyyyyyyyy * When x = 0: y is the high 10 bits of the codepoint * x = 1: y is the low 10 bits of the codepoint * * Check for a surrogate pair (high or low) */ if ((codepoint & 0xF800) == 0xD800) { /* Error if the 1st surrogate is not high */ if (codepoint & 0x400) return -1; /* Ensure the next code is a unicode escape */ if (*(json->ptr + escape_len) != '\\' || *(json->ptr + escape_len + 1) != 'u') { return -1; } /* Fetch the next codepoint */ surrogate_low = decode_hex4(json->ptr + 2 + escape_len); if (surrogate_low < 0) return -1; /* Error if the 2nd code is not a low surrogate */ if ((surrogate_low & 0xFC00) != 0xDC00) return -1; /* Calculate Unicode codepoint */ codepoint = (codepoint & 0x3FF) << 10; surrogate_low &= 0x3FF; codepoint = (codepoint | surrogate_low) + 0x10000; escape_len = 12; } /* Convert codepoint to UTF-8 */ len = codepoint_to_utf8(utf8, codepoint); if (!len) return -1; /* Append bytes and advance parse index */ strbuf_append_mem_unsafe(json->tmp, utf8, len); json->ptr += escape_len; return 0; } static void json_set_token_error(json_token_t *token, json_parse_t *json, const char *errtype) { token->type = T_ERROR; token->index = json->ptr - json->data; token->value.string = errtype; } static void json_next_string_token(json_parse_t *json, json_token_t *token) { char *escape2char = json->cfg->escape2char; char ch; /* Caller must ensure a string is next */ assert(*json->ptr == '"'); /* Skip " */ json->ptr++; /* json->tmp is the temporary strbuf used to accumulate the * decoded string value. * json->tmp is sized to handle JSON containing only a string value. */ strbuf_reset(json->tmp); while ((ch = *json->ptr) != '"') { if (!ch) { /* Premature end of the string */ json_set_token_error(token, json, "unexpected end of string"); return; } /* Handle escapes */ if (ch == '\\') { /* Fetch escape character */ ch = *(json->ptr + 1); /* Translate escape code and append to tmp string */ ch = escape2char[(unsigned char)ch]; if (ch == 'u') { if (json_append_unicode_escape(json) == 0) continue; json_set_token_error(token, json, "invalid unicode escape code"); return; } if (!ch) { json_set_token_error(token, json, "invalid escape code"); return; } /* Skip '\' */ json->ptr++; } /* Append normal character or translated single character * Unicode escapes are handled above */ strbuf_append_char_unsafe(json->tmp, ch); json->ptr++; } json->ptr++; /* Eat final quote (") */ strbuf_ensure_null(json->tmp); token->type = T_STRING; token->value.string = strbuf_string(json->tmp, &token->string_len); } /* JSON numbers should take the following form: * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? * * json_next_number_token() uses strtod() which allows other forms: * - numbers starting with '+' * - NaN, -NaN, infinity, -infinity * - hexadecimal numbers * - numbers with leading zeros * * json_is_invalid_number() detects "numbers" which may pass strtod()'s * error checking, but should not be allowed with strict JSON. * * json_is_invalid_number() may pass numbers which cause strtod() * to generate an error. */ static int json_is_invalid_number(json_parse_t *json) { const char *p = json->ptr; /* Reject numbers starting with + */ if (*p == '+') return 1; /* Skip minus sign if it exists */ if (*p == '-') p++; /* Reject numbers starting with 0x, or leading zeros */ if (*p == '0') { int ch2 = *(p + 1); if ((ch2 | 0x20) == 'x' || /* Hex */ ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ return 1; return 0; } else if (*p <= '9') { return 0; /* Ordinary number */ } /* Reject inf/nan */ if (!strncasecmp(p, "inf", 3)) return 1; if (!strncasecmp(p, "nan", 3)) return 1; /* Pass all other numbers which may still be invalid, but * strtod() will catch them. */ return 0; } static void json_next_number_token(json_parse_t *json, json_token_t *token) { char *endptr; token->type = T_NUMBER; token->value.number = fpconv_strtod(json->ptr, &endptr); if (json->ptr == endptr) json_set_token_error(token, json, "invalid number"); else json->ptr = endptr; /* Skip the processed number */ return; } /* Fills in the token struct. * T_STRING will return a pointer to the json_parse_t temporary string * T_ERROR will leave the json->ptr pointer at the error. */ static void json_next_token(json_parse_t *json, json_token_t *token) { const json_token_type_t *ch2token = json->cfg->ch2token; int ch; /* Eat whitespace. */ while (1) { ch = (unsigned char)*(json->ptr); token->type = ch2token[ch]; if (token->type != T_WHITESPACE) break; json->ptr++; } /* Store location of new token. Required when throwing errors * for unexpected tokens (syntax errors). */ token->index = json->ptr - json->data; /* Don't advance the pointer for an error or the end */ if (token->type == T_ERROR) { json_set_token_error(token, json, "invalid token"); return; } if (token->type == T_END) { return; } /* Found a known single character token, advance index and return */ if (token->type != T_UNKNOWN) { json->ptr++; return; } /* Process characters which triggered T_UNKNOWN * * Must use strncmp() to match the front of the JSON string. * JSON identifier must be lowercase. * When strict_numbers if disabled, either case is allowed for * Infinity/NaN (since we are no longer following the spec..) */ if (ch == '"') { json_next_string_token(json, token); return; } else if (ch == '-' || ('0' <= ch && ch <= '9')) { if (!json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) { json_set_token_error(token, json, "invalid number"); return; } json_next_number_token(json, token); return; } else if (!strncmp(json->ptr, "true", 4)) { token->type = T_BOOLEAN; token->value.boolean = 1; json->ptr += 4; return; } else if (!strncmp(json->ptr, "false", 5)) { token->type = T_BOOLEAN; token->value.boolean = 0; json->ptr += 5; return; } else if (!strncmp(json->ptr, "null", 4)) { token->type = T_NULL; json->ptr += 4; return; } else if (json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) { /* When decode_invalid_numbers is enabled, only attempt to process * numbers we know are invalid JSON (Inf, NaN, hex) * This is required to generate an appropriate token error, * otherwise all bad tokens will register as "invalid number" */ json_next_number_token(json, token); return; } /* Token starts with t/f/n but isn't recognised above. */ json_set_token_error(token, json, "invalid token"); } /* This function does not return. * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED. * The only supported exception is the temporary parser string * json->tmp struct. * json and token should exist on the stack somewhere. * luaL_error() will long_jmp and release the stack */ static void json_throw_parse_error(lua_State *l, json_parse_t *json, const char *exp, json_token_t *token) { const char *found; strbuf_free(json->tmp); if (token->type == T_ERROR) found = token->value.string; else found = json_token_type_name[token->type]; /* Note: token->index is 0 based, display starting from 1 */ luaL_error(l, "Expected %s but found %s at character %d", exp, found, token->index + 1); } static inline void json_decode_ascend(json_parse_t *json) { json->current_depth--; } static void json_decode_descend(lua_State *l, json_parse_t *json, int slots) { json->current_depth++; if (json->current_depth <= json->cfg->decode_max_depth && lua_checkstack(l, slots)) { return; } strbuf_free(json->tmp); luaL_error(l, "Found too many nested data structures (%d) at character %d", json->current_depth, json->ptr - json->data); } static void json_parse_object_context(lua_State *l, json_parse_t *json) { json_token_t token; /* 3 slots required: * .., table, key, value */ json_decode_descend(l, json, 3); lua_newtable(l); json_next_token(json, &token); /* Handle empty objects */ if (token.type == T_OBJ_END) { json_decode_ascend(json); return; } while (1) { if (token.type != T_STRING) json_throw_parse_error(l, json, "object key string", &token); /* Push key */ lua_pushlstring(l, token.value.string, token.string_len); json_next_token(json, &token); if (token.type != T_COLON) json_throw_parse_error(l, json, "colon", &token); /* Fetch value */ json_next_token(json, &token); json_process_value(l, json, &token); /* Set key = value */ lua_rawset(l, -3); json_next_token(json, &token); if (token.type == T_OBJ_END) { json_decode_ascend(json); return; } if (token.type != T_COMMA) json_throw_parse_error(l, json, "comma or object end", &token); json_next_token(json, &token); } } /* Handle the array context */ static void json_parse_array_context(lua_State *l, json_parse_t *json) { json_token_t token; int i; /* 2 slots required: * .., table, value */ json_decode_descend(l, json, 2); lua_newtable(l); json_next_token(json, &token); /* Handle empty arrays */ if (token.type == T_ARR_END) { json_decode_ascend(json); return; } for (i = 1; ; i++) { json_process_value(l, json, &token); lua_rawseti(l, -2, i); /* arr[i] = value */ json_next_token(json, &token); if (token.type == T_ARR_END) { json_decode_ascend(json); return; } if (token.type != T_COMMA) json_throw_parse_error(l, json, "comma or array end", &token); json_next_token(json, &token); } } /* Handle the "value" context */ static void json_process_value(lua_State *l, json_parse_t *json, json_token_t *token) { switch (token->type) { case T_STRING: lua_pushlstring(l, token->value.string, token->string_len); break;; case T_NUMBER: lua_pushnumber(l, token->value.number); break;; case T_BOOLEAN: lua_pushboolean(l, token->value.boolean); break;; case T_OBJ_BEGIN: json_parse_object_context(l, json); break;; case T_ARR_BEGIN: json_parse_array_context(l, json); break;; case T_NULL: /* In Lua, setting "t[k] = nil" will delete k from the table. * Hence a NULL pointer lightuserdata object is used instead */ lua_pushlightuserdata(l, NULL); break;; default: json_throw_parse_error(l, json, "value", token); } } static int json_decode(lua_State *l) { json_parse_t json; json_token_t token; size_t json_len; luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); json.cfg = json_fetch_config(l); json.data = luaL_checklstring(l, 1, &json_len); json.current_depth = 0; json.ptr = json.data; /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) * * CJSON can support any simple data type, hence only the first * character is guaranteed to be ASCII (at worst: '"'). This is * still enough to detect whether the wrong encoding is in use. */ if (json_len >= 2 && (!json.data[0] || !json.data[1])) luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); /* Ensure the temporary buffer can hold the entire string. * This means we no longer need to do length checks since the decoded * string must be smaller than the entire json string */ json.tmp = strbuf_new(json_len); json_next_token(&json, &token); json_process_value(l, &json, &token); /* Ensure there is no more input left */ json_next_token(&json, &token); if (token.type != T_END) json_throw_parse_error(l, &json, "the end", &token); strbuf_free(json.tmp); return 1; } /* ===== INITIALISATION ===== */ #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 /* Compatibility for Lua 5.1. * * luaL_setfuncs() is used to create a module table where the functions have * json_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */ static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup) { int i; luaL_checkstack(l, nup, "too many upvalues"); for (; reg->name != NULL; reg++) { /* fill the table with given functions */ for (i = 0; i < nup; i++) /* copy upvalues to the top */ lua_pushvalue(l, -nup); lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */ lua_setfield(l, -(nup + 2), reg->name); } lua_pop(l, nup); /* remove upvalues */ } #endif /* Call target function in protected mode with all supplied args. * Assumes target function only returns a single non-nil value. * Convert and return thrown errors as: nil, "error message" */ static int json_protect_conversion(lua_State *l) { int err; /* Deliberately throw an error for invalid arguments */ luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); /* pcall() the function stored as upvalue(1) */ lua_pushvalue(l, lua_upvalueindex(1)); lua_insert(l, 1); err = lua_pcall(l, 1, 1, 0); if (!err) return 1; if (err == LUA_ERRRUN) { lua_pushnil(l); lua_insert(l, -2); return 2; } /* Since we are not using a custom error handler, the only remaining * errors are memory related */ return luaL_error(l, "Memory allocation error in CJSON protected call"); } /* Return cjson module table */ static int lua_cjson_new(lua_State *l) { luaL_Reg reg[] = { { "encode", json_encode }, { "decode", json_decode }, { "encode_sparse_array", json_cfg_encode_sparse_array }, { "encode_max_depth", json_cfg_encode_max_depth }, { "decode_max_depth", json_cfg_decode_max_depth }, { "encode_number_precision", json_cfg_encode_number_precision }, { "encode_keep_buffer", json_cfg_encode_keep_buffer }, { "encode_invalid_numbers", json_cfg_encode_invalid_numbers }, { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, { "new", lua_cjson_new }, { NULL, NULL } }; /* Initialise number conversions */ fpconv_init(); /* cjson module table */ lua_newtable(l); /* Register functions with config data as upvalue */ json_create_config(l); luaL_setfuncs(l, reg, 1); /* Set cjson.null */ lua_pushlightuserdata(l, NULL); lua_setfield(l, -2, "null"); /* Set module name / version fields */ lua_pushliteral(l, CJSON_MODNAME); lua_setfield(l, -2, "_NAME"); lua_pushliteral(l, CJSON_VERSION); lua_setfield(l, -2, "_VERSION"); return 1; } /* Return cjson.safe module table */ static int lua_cjson_safe_new(lua_State *l) { const char *func[] = { "decode", "encode", NULL }; int i; lua_cjson_new(l); /* Fix new() method */ lua_pushcfunction(l, lua_cjson_safe_new); lua_setfield(l, -2, "new"); for (i = 0; func[i]; i++) { lua_getfield(l, -1, func[i]); lua_pushcclosure(l, json_protect_conversion, 1); lua_setfield(l, -2, func[i]); } return 1; } int luaopen_cjson(lua_State *l) { lua_cjson_new(l); #ifdef ENABLE_CJSON_GLOBAL /* Register a global "cjson" table. */ lua_pushvalue(l, -1); lua_setglobal(l, CJSON_MODNAME); #endif /* Return cjson table */ return 1; } int luaopen_cjson_safe(lua_State *l) { lua_cjson_safe_new(l); /* Return cjson.safe table */ return 1; } /* vi:ai et sw=4 ts=4: */ redis-8.0.2/deps/lua/src/lua_cmsgpack.c000066400000000000000000000746201501533116600177410ustar00rootroot00000000000000#include #include #include #include #include #include "lua.h" #include "lauxlib.h" #define LUACMSGPACK_NAME "cmsgpack" #define LUACMSGPACK_SAFE_NAME "cmsgpack_safe" #define LUACMSGPACK_VERSION "lua-cmsgpack 0.4.0" #define LUACMSGPACK_COPYRIGHT "Copyright (C) 2012, Salvatore Sanfilippo" #define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua" /* Allows a preprocessor directive to override MAX_NESTING */ #ifndef LUACMSGPACK_MAX_NESTING #define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */ #endif /* Check if float or double can be an integer without loss of precision */ #define IS_INT_TYPE_EQUIVALENT(x, T) (!isinf(x) && (T)(x) == (x)) #define IS_INT64_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int64_t) #define IS_INT_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int) /* If size of pointer is equal to a 4 byte integer, we're on 32 bits. */ #if UINTPTR_MAX == UINT_MAX #define BITS_32 1 #else #define BITS_32 0 #endif #if BITS_32 #define lua_pushunsigned(L, n) lua_pushnumber(L, n) #else #define lua_pushunsigned(L, n) lua_pushinteger(L, n) #endif /* ============================================================================= * MessagePack implementation and bindings for Lua 5.1/5.2. * Copyright(C) 2012 Salvatore Sanfilippo * * http://github.com/antirez/lua-cmsgpack * * For MessagePack specification check the following web site: * http://wiki.msgpack.org/display/MSGPACK/Format+specification * * See Copyright Notice at the end of this file. * * CHANGELOG: * 19-Feb-2012 (ver 0.1.0): Initial release. * 20-Feb-2012 (ver 0.2.0): Tables encoding improved. * 20-Feb-2012 (ver 0.2.1): Minor bug fixing. * 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). * 04-Apr-2014 (ver 0.3.1): Lua 5.2 support and minor bug fix. * 07-Apr-2014 (ver 0.4.0): Multiple pack/unpack, lua allocator, efficiency. * ========================================================================== */ /* -------------------------- Endian conversion -------------------------------- * We use it only for floats and doubles, all the other conversions performed * in an endian independent fashion. So the only thing we need is a function * that swaps a binary string if arch is little endian (and left it untouched * otherwise). */ /* Reverse memory bytes if arch is little endian. Given the conceptual * simplicity of the Lua build system we prefer check for endianess at runtime. * The performance difference should be acceptable. */ void memrevifle(void *ptr, size_t len) { unsigned char *p = (unsigned char *)ptr, *e = (unsigned char *)p+len-1, aux; int test = 1; unsigned char *testp = (unsigned char*) &test; if (testp[0] == 0) return; /* Big endian, nothing to do. */ len /= 2; while(len--) { aux = *p; *p = *e; *e = aux; p++; e--; } } /* ---------------------------- String buffer ---------------------------------- * This is a simple implementation of string buffers. The only operation * supported is creating empty buffers and appending bytes to it. * The string buffer uses 2x preallocation on every realloc for O(N) append * behavior. */ typedef struct mp_buf { unsigned char *b; size_t len, free; } mp_buf; void *mp_realloc(lua_State *L, void *target, size_t osize,size_t nsize) { void *(*local_realloc) (void *, void *, size_t osize, size_t nsize) = NULL; void *ud; local_realloc = lua_getallocf(L, &ud); return local_realloc(ud, target, osize, nsize); } mp_buf *mp_buf_new(lua_State *L) { mp_buf *buf = NULL; /* Old size = 0; new size = sizeof(*buf) */ buf = (mp_buf*)mp_realloc(L, NULL, 0, sizeof(*buf)); buf->b = NULL; buf->len = buf->free = 0; return buf; } void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) { if (buf->free < len) { size_t newsize = buf->len+len; if (newsize < buf->len || newsize >= SIZE_MAX/2) abort(); newsize *= 2; buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize); buf->free = newsize - buf->len; } memcpy(buf->b+buf->len,s,len); buf->len += len; buf->free -= len; } void mp_buf_free(lua_State *L, mp_buf *buf) { mp_realloc(L, buf->b, buf->len + buf->free, 0); /* realloc to 0 = free */ mp_realloc(L, buf, sizeof(*buf), 0); } /* ---------------------------- String cursor ---------------------------------- * This simple data structure is used for parsing. Basically you create a cursor * using a string pointer and a length, then it is possible to access the * current string position with cursor->p, check the remaining length * in cursor->left, and finally consume more string using * mp_cur_consume(cursor,len), to advance 'p' and subtract 'left'. * An additional field cursor->error is set to zero on initialization and can * be used to report errors. */ #define MP_CUR_ERROR_NONE 0 #define MP_CUR_ERROR_EOF 1 /* Not enough data to complete operation. */ #define MP_CUR_ERROR_BADFMT 2 /* Bad data format */ typedef struct mp_cur { const unsigned char *p; size_t left; int err; } mp_cur; void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) { cursor->p = s; cursor->left = len; cursor->err = MP_CUR_ERROR_NONE; } #define mp_cur_consume(_c,_len) do { _c->p += _len; _c->left -= _len; } while(0) /* When there is not enough room we set an error in the cursor and return. This * is very common across the code so we have a macro to make the code look * a bit simpler. */ #define mp_cur_need(_c,_len) do { \ if (_c->left < _len) { \ _c->err = MP_CUR_ERROR_EOF; \ return; \ } \ } while(0) /* ------------------------- Low level MP encoding -------------------------- */ void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) { unsigned char hdr[5]; size_t hdrlen; if (len < 32) { hdr[0] = 0xa0 | (len&0xff); /* fix raw */ hdrlen = 1; } else if (len <= 0xff) { hdr[0] = 0xd9; hdr[1] = len; hdrlen = 2; } else if (len <= 0xffff) { hdr[0] = 0xda; hdr[1] = (len&0xff00)>>8; hdr[2] = len&0xff; hdrlen = 3; } else { hdr[0] = 0xdb; hdr[1] = (len&0xff000000)>>24; hdr[2] = (len&0xff0000)>>16; hdr[3] = (len&0xff00)>>8; hdr[4] = len&0xff; hdrlen = 5; } mp_buf_append(L,buf,hdr,hdrlen); mp_buf_append(L,buf,s,len); } /* we assume IEEE 754 internal format for single and double precision floats. */ void mp_encode_double(lua_State *L, mp_buf *buf, double d) { unsigned char b[9]; float f = d; assert(sizeof(f) == 4 && sizeof(d) == 8); if (d == (double)f) { b[0] = 0xca; /* float IEEE 754 */ memcpy(b+1,&f,4); memrevifle(b+1,4); mp_buf_append(L,buf,b,5); } else if (sizeof(d) == 8) { b[0] = 0xcb; /* double IEEE 754 */ memcpy(b+1,&d,8); memrevifle(b+1,8); mp_buf_append(L,buf,b,9); } } void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) { unsigned char b[9]; size_t enclen; if (n >= 0) { if (n <= 127) { b[0] = n & 0x7f; /* positive fixnum */ enclen = 1; } else if (n <= 0xff) { b[0] = 0xcc; /* uint 8 */ b[1] = n & 0xff; enclen = 2; } else if (n <= 0xffff) { b[0] = 0xcd; /* uint 16 */ b[1] = (n & 0xff00) >> 8; b[2] = n & 0xff; enclen = 3; } else if (n <= 0xffffffffLL) { b[0] = 0xce; /* uint 32 */ b[1] = (n & 0xff000000) >> 24; b[2] = (n & 0xff0000) >> 16; b[3] = (n & 0xff00) >> 8; b[4] = n & 0xff; enclen = 5; } else { b[0] = 0xcf; /* uint 64 */ b[1] = (n & 0xff00000000000000LL) >> 56; b[2] = (n & 0xff000000000000LL) >> 48; b[3] = (n & 0xff0000000000LL) >> 40; b[4] = (n & 0xff00000000LL) >> 32; b[5] = (n & 0xff000000) >> 24; b[6] = (n & 0xff0000) >> 16; b[7] = (n & 0xff00) >> 8; b[8] = n & 0xff; enclen = 9; } } else { if (n >= -32) { b[0] = ((signed char)n); /* negative fixnum */ enclen = 1; } else if (n >= -128) { b[0] = 0xd0; /* int 8 */ b[1] = n & 0xff; enclen = 2; } else if (n >= -32768) { b[0] = 0xd1; /* int 16 */ b[1] = (n & 0xff00) >> 8; b[2] = n & 0xff; enclen = 3; } else if (n >= -2147483648LL) { b[0] = 0xd2; /* int 32 */ b[1] = (n & 0xff000000) >> 24; b[2] = (n & 0xff0000) >> 16; b[3] = (n & 0xff00) >> 8; b[4] = n & 0xff; enclen = 5; } else { b[0] = 0xd3; /* int 64 */ b[1] = (n & 0xff00000000000000LL) >> 56; b[2] = (n & 0xff000000000000LL) >> 48; b[3] = (n & 0xff0000000000LL) >> 40; b[4] = (n & 0xff00000000LL) >> 32; b[5] = (n & 0xff000000) >> 24; b[6] = (n & 0xff0000) >> 16; b[7] = (n & 0xff00) >> 8; b[8] = n & 0xff; enclen = 9; } } mp_buf_append(L,buf,b,enclen); } void mp_encode_array(lua_State *L, mp_buf *buf, uint64_t n) { unsigned char b[5]; size_t enclen; if (n <= 15) { b[0] = 0x90 | (n & 0xf); /* fix array */ enclen = 1; } else if (n <= 65535) { b[0] = 0xdc; /* array 16 */ b[1] = (n & 0xff00) >> 8; b[2] = n & 0xff; enclen = 3; } else { b[0] = 0xdd; /* array 32 */ b[1] = (n & 0xff000000) >> 24; b[2] = (n & 0xff0000) >> 16; b[3] = (n & 0xff00) >> 8; b[4] = n & 0xff; enclen = 5; } mp_buf_append(L,buf,b,enclen); } void mp_encode_map(lua_State *L, mp_buf *buf, uint64_t n) { unsigned char b[5]; int enclen; if (n <= 15) { b[0] = 0x80 | (n & 0xf); /* fix map */ enclen = 1; } else if (n <= 65535) { b[0] = 0xde; /* map 16 */ b[1] = (n & 0xff00) >> 8; b[2] = n & 0xff; enclen = 3; } else { b[0] = 0xdf; /* map 32 */ b[1] = (n & 0xff000000) >> 24; b[2] = (n & 0xff0000) >> 16; b[3] = (n & 0xff00) >> 8; b[4] = n & 0xff; enclen = 5; } mp_buf_append(L,buf,b,enclen); } /* --------------------------- Lua types encoding --------------------------- */ void mp_encode_lua_string(lua_State *L, mp_buf *buf) { size_t len; const char *s; s = lua_tolstring(L,-1,&len); mp_encode_bytes(L,buf,(const unsigned char*)s,len); } void mp_encode_lua_bool(lua_State *L, mp_buf *buf) { unsigned char b = lua_toboolean(L,-1) ? 0xc3 : 0xc2; mp_buf_append(L,buf,&b,1); } /* Lua 5.3 has a built in 64-bit integer type */ void mp_encode_lua_integer(lua_State *L, mp_buf *buf) { #if (LUA_VERSION_NUM < 503) && BITS_32 lua_Number i = lua_tonumber(L,-1); #else lua_Integer i = lua_tointeger(L,-1); #endif mp_encode_int(L, buf, (int64_t)i); } /* Lua 5.2 and lower only has 64-bit doubles, so we need to * detect if the double may be representable as an int * for Lua < 5.3 */ void mp_encode_lua_number(lua_State *L, mp_buf *buf) { lua_Number n = lua_tonumber(L,-1); if (IS_INT64_EQUIVALENT(n)) { mp_encode_lua_integer(L, buf); } else { mp_encode_double(L,buf,(double)n); } } void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level); /* Convert a lua table into a message pack list. */ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) { #if LUA_VERSION_NUM < 502 size_t len = lua_objlen(L,-1), j; #else size_t len = lua_rawlen(L,-1), j; #endif mp_encode_array(L,buf,len); luaL_checkstack(L, 1, "in function mp_encode_lua_table_as_array"); for (j = 1; j <= len; j++) { lua_pushnumber(L,j); lua_gettable(L,-2); mp_encode_lua_type(L,buf,level+1); } } /* Convert a lua table into a message pack key-value map. */ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) { size_t len = 0; /* First step: count keys into table. No other way to do it with the * Lua API, we need to iterate a first time. Note that an alternative * would be to do a single run, and then hack the buffer to insert the * map opcodes for message pack. Too hackish for this lib. */ luaL_checkstack(L, 3, "in function mp_encode_lua_table_as_map"); lua_pushnil(L); while(lua_next(L,-2)) { lua_pop(L,1); /* remove value, keep key for next iteration. */ len++; } /* Step two: actually encoding of the map. */ mp_encode_map(L,buf,len); lua_pushnil(L); while(lua_next(L,-2)) { /* Stack: ... key value */ lua_pushvalue(L,-2); /* Stack: ... key value key */ mp_encode_lua_type(L,buf,level+1); /* encode key */ mp_encode_lua_type(L,buf,level+1); /* encode val */ } } /* Returns true if the Lua table on top of the stack is exclusively composed * of keys from numerical keys from 1 up to N, with N being the total number * of elements, without any hole in the middle. */ int table_is_an_array(lua_State *L) { int count = 0, max = 0; #if LUA_VERSION_NUM < 503 lua_Number n; #else lua_Integer n; #endif /* Stack top on function entry */ int stacktop; stacktop = lua_gettop(L); luaL_checkstack(L, 2, "in function table_is_an_array"); lua_pushnil(L); while(lua_next(L,-2)) { /* Stack: ... key value */ lua_pop(L,1); /* Stack: ... key */ /* The <= 0 check is valid here because we're comparing indexes. */ #if LUA_VERSION_NUM < 503 if ((LUA_TNUMBER != lua_type(L,-1)) || (n = lua_tonumber(L, -1)) <= 0 || !IS_INT_EQUIVALENT(n)) #else if (!lua_isinteger(L,-1) || (n = lua_tointeger(L, -1)) <= 0) #endif { lua_settop(L, stacktop); return 0; } max = (n > max ? n : max); count++; } /* We have the total number of elements in "count". Also we have * the max index encountered in "max". We can't reach this code * if there are indexes <= 0. If you also note that there can not be * repeated keys into a table, you have that if max==count you are sure * that there are all the keys form 1 to count (both included). */ lua_settop(L, stacktop); return max == count; } /* If the length operator returns non-zero, that is, there is at least * an object at key '1', we serialize to message pack list. Otherwise * we use a map. */ void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) { if (table_is_an_array(L)) mp_encode_lua_table_as_array(L,buf,level); else mp_encode_lua_table_as_map(L,buf,level); } void mp_encode_lua_null(lua_State *L, mp_buf *buf) { unsigned char b[1]; b[0] = 0xc0; mp_buf_append(L,buf,b,1); } void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) { int t = lua_type(L,-1); /* Limit the encoding of nested tables to a specified maximum depth, so that * we survive when called against circular references in tables. */ if (t == LUA_TTABLE && level == LUACMSGPACK_MAX_NESTING) t = LUA_TNIL; switch(t) { case LUA_TSTRING: mp_encode_lua_string(L,buf); break; case LUA_TBOOLEAN: mp_encode_lua_bool(L,buf); break; case LUA_TNUMBER: #if LUA_VERSION_NUM < 503 mp_encode_lua_number(L,buf); break; #else if (lua_isinteger(L, -1)) { mp_encode_lua_integer(L, buf); } else { mp_encode_lua_number(L, buf); } break; #endif case LUA_TTABLE: mp_encode_lua_table(L,buf,level); break; default: mp_encode_lua_null(L,buf); break; } lua_pop(L,1); } /* * Packs all arguments as a stream for multiple upacking later. * Returns error if no arguments provided. */ int mp_pack(lua_State *L) { int nargs = lua_gettop(L); int i; mp_buf *buf; if (nargs == 0) return luaL_argerror(L, 0, "MessagePack pack needs input."); if (!lua_checkstack(L, nargs)) return luaL_argerror(L, 0, "Too many arguments for MessagePack pack."); buf = mp_buf_new(L); for(i = 1; i <= nargs; i++) { /* Copy argument i to top of stack for _encode processing; * the encode function pops it from the stack when complete. */ luaL_checkstack(L, 1, "in function mp_check"); lua_pushvalue(L, i); mp_encode_lua_type(L,buf,0); lua_pushlstring(L,(char*)buf->b,buf->len); /* Reuse the buffer for the next operation by * setting its free count to the total buffer size * and the current position to zero. */ buf->free += buf->len; buf->len = 0; } mp_buf_free(L, buf); /* Concatenate all nargs buffers together */ lua_concat(L, nargs); return 1; } /* ------------------------------- Decoding --------------------------------- */ void mp_decode_to_lua_type(lua_State *L, mp_cur *c); void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) { assert(len <= UINT_MAX); int index = 1; lua_newtable(L); luaL_checkstack(L, 1, "in function mp_decode_to_lua_array"); while(len--) { lua_pushnumber(L,index++); mp_decode_to_lua_type(L,c); if (c->err) return; lua_settable(L,-3); } } void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) { assert(len <= UINT_MAX); lua_newtable(L); while(len--) { mp_decode_to_lua_type(L,c); /* key */ if (c->err) return; mp_decode_to_lua_type(L,c); /* value */ if (c->err) return; lua_settable(L,-3); } } /* Decode a Message Pack raw object pointed by the string cursor 'c' to * a Lua type, that is left as the only result on the stack. */ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { mp_cur_need(c,1); /* If we return more than 18 elements, we must resize the stack to * fit all our return values. But, there is no way to * determine how many objects a msgpack will unpack to up front, so * we request a +1 larger stack on each iteration (noop if stack is * big enough, and when stack does require resize it doubles in size) */ luaL_checkstack(L, 1, "too many return values at once; " "use unpack_one or unpack_limit instead."); switch(c->p[0]) { case 0xcc: /* uint 8 */ mp_cur_need(c,2); lua_pushunsigned(L,c->p[1]); mp_cur_consume(c,2); break; case 0xd0: /* int 8 */ mp_cur_need(c,2); lua_pushinteger(L,(signed char)c->p[1]); mp_cur_consume(c,2); break; case 0xcd: /* uint 16 */ mp_cur_need(c,3); lua_pushunsigned(L, (c->p[1] << 8) | c->p[2]); mp_cur_consume(c,3); break; case 0xd1: /* int 16 */ mp_cur_need(c,3); lua_pushinteger(L,(int16_t) (c->p[1] << 8) | c->p[2]); mp_cur_consume(c,3); break; case 0xce: /* uint 32 */ mp_cur_need(c,5); lua_pushunsigned(L, ((uint32_t)c->p[1] << 24) | ((uint32_t)c->p[2] << 16) | ((uint32_t)c->p[3] << 8) | (uint32_t)c->p[4]); mp_cur_consume(c,5); break; case 0xd2: /* int 32 */ mp_cur_need(c,5); lua_pushinteger(L, ((int32_t)c->p[1] << 24) | ((int32_t)c->p[2] << 16) | ((int32_t)c->p[3] << 8) | (int32_t)c->p[4]); mp_cur_consume(c,5); break; case 0xcf: /* uint 64 */ mp_cur_need(c,9); lua_pushunsigned(L, ((uint64_t)c->p[1] << 56) | ((uint64_t)c->p[2] << 48) | ((uint64_t)c->p[3] << 40) | ((uint64_t)c->p[4] << 32) | ((uint64_t)c->p[5] << 24) | ((uint64_t)c->p[6] << 16) | ((uint64_t)c->p[7] << 8) | (uint64_t)c->p[8]); mp_cur_consume(c,9); break; case 0xd3: /* int 64 */ mp_cur_need(c,9); #if LUA_VERSION_NUM < 503 lua_pushnumber(L, #else lua_pushinteger(L, #endif ((int64_t)c->p[1] << 56) | ((int64_t)c->p[2] << 48) | ((int64_t)c->p[3] << 40) | ((int64_t)c->p[4] << 32) | ((int64_t)c->p[5] << 24) | ((int64_t)c->p[6] << 16) | ((int64_t)c->p[7] << 8) | (int64_t)c->p[8]); mp_cur_consume(c,9); break; case 0xc0: /* nil */ lua_pushnil(L); mp_cur_consume(c,1); break; case 0xc3: /* true */ lua_pushboolean(L,1); mp_cur_consume(c,1); break; case 0xc2: /* false */ lua_pushboolean(L,0); mp_cur_consume(c,1); break; case 0xca: /* float */ mp_cur_need(c,5); assert(sizeof(float) == 4); { float f; memcpy(&f,c->p+1,4); memrevifle(&f,4); lua_pushnumber(L,f); mp_cur_consume(c,5); } break; case 0xcb: /* double */ mp_cur_need(c,9); assert(sizeof(double) == 8); { double d; memcpy(&d,c->p+1,8); memrevifle(&d,8); lua_pushnumber(L,d); mp_cur_consume(c,9); } break; case 0xd9: /* raw 8 */ mp_cur_need(c,2); { size_t l = c->p[1]; mp_cur_need(c,2+l); lua_pushlstring(L,(char*)c->p+2,l); mp_cur_consume(c,2+l); } break; case 0xda: /* raw 16 */ mp_cur_need(c,3); { size_t l = (c->p[1] << 8) | c->p[2]; mp_cur_need(c,3+l); lua_pushlstring(L,(char*)c->p+3,l); mp_cur_consume(c,3+l); } break; case 0xdb: /* raw 32 */ mp_cur_need(c,5); { size_t l = ((size_t)c->p[1] << 24) | ((size_t)c->p[2] << 16) | ((size_t)c->p[3] << 8) | (size_t)c->p[4]; mp_cur_consume(c,5); mp_cur_need(c,l); lua_pushlstring(L,(char*)c->p,l); mp_cur_consume(c,l); } break; case 0xdc: /* array 16 */ mp_cur_need(c,3); { size_t l = (c->p[1] << 8) | c->p[2]; mp_cur_consume(c,3); mp_decode_to_lua_array(L,c,l); } break; case 0xdd: /* array 32 */ mp_cur_need(c,5); { size_t l = ((size_t)c->p[1] << 24) | ((size_t)c->p[2] << 16) | ((size_t)c->p[3] << 8) | (size_t)c->p[4]; mp_cur_consume(c,5); mp_decode_to_lua_array(L,c,l); } break; case 0xde: /* map 16 */ mp_cur_need(c,3); { size_t l = (c->p[1] << 8) | c->p[2]; mp_cur_consume(c,3); mp_decode_to_lua_hash(L,c,l); } break; case 0xdf: /* map 32 */ mp_cur_need(c,5); { size_t l = ((size_t)c->p[1] << 24) | ((size_t)c->p[2] << 16) | ((size_t)c->p[3] << 8) | (size_t)c->p[4]; mp_cur_consume(c,5); mp_decode_to_lua_hash(L,c,l); } break; default: /* types that can't be idenitified by first byte value. */ if ((c->p[0] & 0x80) == 0) { /* positive fixnum */ lua_pushunsigned(L,c->p[0]); mp_cur_consume(c,1); } else if ((c->p[0] & 0xe0) == 0xe0) { /* negative fixnum */ lua_pushinteger(L,(signed char)c->p[0]); mp_cur_consume(c,1); } else if ((c->p[0] & 0xe0) == 0xa0) { /* fix raw */ size_t l = c->p[0] & 0x1f; mp_cur_need(c,1+l); lua_pushlstring(L,(char*)c->p+1,l); mp_cur_consume(c,1+l); } else if ((c->p[0] & 0xf0) == 0x90) { /* fix map */ size_t l = c->p[0] & 0xf; mp_cur_consume(c,1); mp_decode_to_lua_array(L,c,l); } else if ((c->p[0] & 0xf0) == 0x80) { /* fix map */ size_t l = c->p[0] & 0xf; mp_cur_consume(c,1); mp_decode_to_lua_hash(L,c,l); } else { c->err = MP_CUR_ERROR_BADFMT; } } } int mp_unpack_full(lua_State *L, lua_Integer limit, lua_Integer offset) { size_t len; const char *s; mp_cur c; int cnt; /* Number of objects unpacked */ int decode_all = (!limit && !offset); s = luaL_checklstring(L,1,&len); /* if no match, exits */ if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */ return luaL_error(L, "Invalid request to unpack with offset of %d and limit of %d.", (int) offset, (int) len); else if (offset > len) return luaL_error(L, "Start offset %d greater than input length %d.", (int) offset, (int) len); if (decode_all) limit = INT_MAX; mp_cur_init(&c,(const unsigned char *)s+offset,len-offset); /* We loop over the decode because this could be a stream * of multiple top-level values serialized together */ for(cnt = 0; c.left > 0 && cnt < limit; cnt++) { mp_decode_to_lua_type(L,&c); if (c.err == MP_CUR_ERROR_EOF) { return luaL_error(L,"Missing bytes in input."); } else if (c.err == MP_CUR_ERROR_BADFMT) { return luaL_error(L,"Bad data format in input."); } } if (!decode_all) { /* c->left is the remaining size of the input buffer. * subtract the entire buffer size from the unprocessed size * to get our next start offset */ size_t new_offset = len - c.left; if (new_offset > LONG_MAX) abort(); luaL_checkstack(L, 1, "in function mp_unpack_full"); /* Return offset -1 when we have have processed the entire buffer. */ lua_pushinteger(L, c.left == 0 ? -1 : (lua_Integer) new_offset); /* Results are returned with the arg elements still * in place. Lua takes care of only returning * elements above the args for us. * In this case, we have one arg on the stack * for this function, so we insert our first return * value at position 2. */ lua_insert(L, 2); cnt += 1; /* increase return count by one to make room for offset */ } return cnt; } int mp_unpack(lua_State *L) { return mp_unpack_full(L, 0, 0); } int mp_unpack_one(lua_State *L) { lua_Integer offset = luaL_optinteger(L, 2, 0); /* Variable pop because offset may not exist */ lua_pop(L, lua_gettop(L)-1); return mp_unpack_full(L, 1, offset); } int mp_unpack_limit(lua_State *L) { lua_Integer limit = luaL_checkinteger(L, 2); lua_Integer offset = luaL_optinteger(L, 3, 0); /* Variable pop because offset may not exist */ lua_pop(L, lua_gettop(L)-1); return mp_unpack_full(L, limit, offset); } int mp_safe(lua_State *L) { int argc, err, total_results; argc = lua_gettop(L); /* This adds our function to the bottom of the stack * (the "call this function" position) */ lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); err = lua_pcall(L, argc, LUA_MULTRET, 0); total_results = lua_gettop(L); if (!err) { return total_results; } else { lua_pushnil(L); lua_insert(L,-2); return 2; } } /* -------------------------------------------------------------------------- */ const struct luaL_Reg cmds[] = { {"pack", mp_pack}, {"unpack", mp_unpack}, {"unpack_one", mp_unpack_one}, {"unpack_limit", mp_unpack_limit}, {0} }; int luaopen_create(lua_State *L) { int i; /* Manually construct our module table instead of * relying on _register or _newlib */ lua_newtable(L); for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) { lua_pushcfunction(L, cmds[i].func); lua_setfield(L, -2, cmds[i].name); } /* Add metadata */ lua_pushliteral(L, LUACMSGPACK_NAME); lua_setfield(L, -2, "_NAME"); lua_pushliteral(L, LUACMSGPACK_VERSION); lua_setfield(L, -2, "_VERSION"); lua_pushliteral(L, LUACMSGPACK_COPYRIGHT); lua_setfield(L, -2, "_COPYRIGHT"); lua_pushliteral(L, LUACMSGPACK_DESCRIPTION); lua_setfield(L, -2, "_DESCRIPTION"); return 1; } LUALIB_API int luaopen_cmsgpack(lua_State *L) { luaopen_create(L); #if LUA_VERSION_NUM < 502 /* Register name globally for 5.1 */ lua_pushvalue(L, -1); lua_setglobal(L, LUACMSGPACK_NAME); #endif return 1; } LUALIB_API int luaopen_cmsgpack_safe(lua_State *L) { int i; luaopen_cmsgpack(L); /* Wrap all functions in the safe handler */ for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) { lua_getfield(L, -1, cmds[i].name); lua_pushcclosure(L, mp_safe, 1); lua_setfield(L, -2, cmds[i].name); } #if LUA_VERSION_NUM < 502 /* Register name globally for 5.1 */ lua_pushvalue(L, -1); lua_setglobal(L, LUACMSGPACK_SAFE_NAME); #endif return 1; } /****************************************************************************** * Copyright (C) 2012 Salvatore Sanfilippo. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************/ redis-8.0.2/deps/lua/src/lua_struct.c000066400000000000000000000263311501533116600174710ustar00rootroot00000000000000/* ** {====================================================== ** Library for packing/unpacking structures. ** $Id: struct.c,v 1.7 2018/05/11 22:04:31 roberto Exp $ ** See Copyright Notice at the end of this file ** ======================================================= */ /* ** Valid formats: ** > - big endian ** < - little endian ** ![num] - alignment ** x - pading ** b/B - signed/unsigned byte ** h/H - signed/unsigned short ** l/L - signed/unsigned long ** T - size_t ** i/In - signed/unsigned integer with size 'n' (default is size of int) ** cn - sequence of 'n' chars (from/to a string); when packing, n==0 means the whole string; when unpacking, n==0 means use the previous read number as the string length ** s - zero-terminated string ** f - float ** d - double ** ' ' - ignored */ #include #include #include #include #include #include "lua.h" #include "lauxlib.h" #if (LUA_VERSION_NUM >= 502) #define luaL_register(L,n,f) luaL_newlib(L,f) #endif /* basic integer type */ #if !defined(STRUCT_INT) #define STRUCT_INT long #endif typedef STRUCT_INT Inttype; /* corresponding unsigned version */ typedef unsigned STRUCT_INT Uinttype; /* maximum size (in bytes) for integral types */ #define MAXINTSIZE 32 /* is 'x' a power of 2? */ #define isp2(x) ((x) > 0 && ((x) & ((x) - 1)) == 0) /* dummy structure to get alignment requirements */ struct cD { char c; double d; }; #define PADDING (sizeof(struct cD) - sizeof(double)) #define MAXALIGN (PADDING > sizeof(int) ? PADDING : sizeof(int)) /* endian options */ #define BIG 0 #define LITTLE 1 static union { int dummy; char endian; } const native = {1}; typedef struct Header { int endian; int align; } Header; static int getnum (lua_State *L, const char **fmt, int df) { if (!isdigit(**fmt)) /* no number? */ return df; /* return default value */ else { int a = 0; do { if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0'))) luaL_error(L, "integral size overflow"); a = a*10 + *((*fmt)++) - '0'; } while (isdigit(**fmt)); return a; } } #define defaultoptions(h) ((h)->endian = native.endian, (h)->align = 1) static size_t optsize (lua_State *L, char opt, const char **fmt) { switch (opt) { case 'B': case 'b': return sizeof(char); case 'H': case 'h': return sizeof(short); case 'L': case 'l': return sizeof(long); case 'T': return sizeof(size_t); case 'f': return sizeof(float); case 'd': return sizeof(double); case 'x': return 1; case 'c': return getnum(L, fmt, 1); case 'i': case 'I': { int sz = getnum(L, fmt, sizeof(int)); if (sz > MAXINTSIZE) luaL_error(L, "integral size %d is larger than limit of %d", sz, MAXINTSIZE); return sz; } default: return 0; /* other cases do not need alignment */ } } /* ** return number of bytes needed to align an element of size 'size' ** at current position 'len' */ static int gettoalign (size_t len, Header *h, int opt, size_t size) { if (size == 0 || opt == 'c') return 0; if (size > (size_t)h->align) size = h->align; /* respect max. alignment */ return (size - (len & (size - 1))) & (size - 1); } /* ** options to control endianess and alignment */ static void controloptions (lua_State *L, int opt, const char **fmt, Header *h) { switch (opt) { case ' ': return; /* ignore white spaces */ case '>': h->endian = BIG; return; case '<': h->endian = LITTLE; return; case '!': { int a = getnum(L, fmt, MAXALIGN); if (!isp2(a)) luaL_error(L, "alignment %d is not a power of 2", a); h->align = a; return; } default: { const char *msg = lua_pushfstring(L, "invalid format option '%c'", opt); luaL_argerror(L, 1, msg); } } } static void putinteger (lua_State *L, luaL_Buffer *b, int arg, int endian, int size) { lua_Number n = luaL_checknumber(L, arg); Uinttype value; char buff[MAXINTSIZE]; if (n < 0) value = (Uinttype)(Inttype)n; else value = (Uinttype)n; if (endian == LITTLE) { int i; for (i = 0; i < size; i++) { buff[i] = (value & 0xff); value >>= 8; } } else { int i; for (i = size - 1; i >= 0; i--) { buff[i] = (value & 0xff); value >>= 8; } } luaL_addlstring(b, buff, size); } static void correctbytes (char *b, int size, int endian) { if (endian != native.endian) { int i = 0; while (i < --size) { char temp = b[i]; b[i++] = b[size]; b[size] = temp; } } } static int b_pack (lua_State *L) { luaL_Buffer b; const char *fmt = luaL_checkstring(L, 1); Header h; int arg = 2; size_t totalsize = 0; defaultoptions(&h); lua_pushnil(L); /* mark to separate arguments from string buffer */ luaL_buffinit(L, &b); while (*fmt != '\0') { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); int toalign = gettoalign(totalsize, &h, opt, size); totalsize += toalign; while (toalign-- > 0) luaL_addchar(&b, '\0'); switch (opt) { case 'b': case 'B': case 'h': case 'H': case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */ putinteger(L, &b, arg++, h.endian, size); break; } case 'x': { luaL_addchar(&b, '\0'); break; } case 'f': { float f = (float)luaL_checknumber(L, arg++); correctbytes((char *)&f, size, h.endian); luaL_addlstring(&b, (char *)&f, size); break; } case 'd': { double d = luaL_checknumber(L, arg++); correctbytes((char *)&d, size, h.endian); luaL_addlstring(&b, (char *)&d, size); break; } case 'c': case 's': { size_t l; const char *s = luaL_checklstring(L, arg++, &l); if (size == 0) size = l; luaL_argcheck(L, l >= (size_t)size, arg, "string too short"); luaL_addlstring(&b, s, size); if (opt == 's') { luaL_addchar(&b, '\0'); /* add zero at the end */ size++; } break; } default: controloptions(L, opt, &fmt, &h); } totalsize += size; } luaL_pushresult(&b); return 1; } static lua_Number getinteger (const char *buff, int endian, int issigned, int size) { Uinttype l = 0; int i; if (endian == BIG) { for (i = 0; i < size; i++) { l <<= 8; l |= (Uinttype)(unsigned char)buff[i]; } } else { for (i = size - 1; i >= 0; i--) { l <<= 8; l |= (Uinttype)(unsigned char)buff[i]; } } if (!issigned) return (lua_Number)l; else { /* signed format */ Uinttype mask = (Uinttype)(~((Uinttype)0)) << (size*8 - 1); if (l & mask) /* negative value? */ l |= mask; /* signal extension */ return (lua_Number)(Inttype)l; } } static int b_unpack (lua_State *L) { Header h; const char *fmt = luaL_checkstring(L, 1); size_t ld; const char *data = luaL_checklstring(L, 2, &ld); size_t pos = luaL_optinteger(L, 3, 1); luaL_argcheck(L, pos > 0, 3, "offset must be 1 or greater"); pos--; /* Lua indexes are 1-based, but here we want 0-based for C * pointer math. */ int n = 0; /* number of results */ defaultoptions(&h); while (*fmt) { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); pos += gettoalign(pos, &h, opt, size); luaL_argcheck(L, size <= ld && pos <= ld - size, 2, "data string too short"); /* stack space for item + next position */ luaL_checkstack(L, 2, "too many results"); switch (opt) { case 'b': case 'B': case 'h': case 'H': case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */ int issigned = islower(opt); lua_Number res = getinteger(data+pos, h.endian, issigned, size); lua_pushnumber(L, res); n++; break; } case 'x': { break; } case 'f': { float f; memcpy(&f, data+pos, size); correctbytes((char *)&f, sizeof(f), h.endian); lua_pushnumber(L, f); n++; break; } case 'd': { double d; memcpy(&d, data+pos, size); correctbytes((char *)&d, sizeof(d), h.endian); lua_pushnumber(L, d); n++; break; } case 'c': { if (size == 0) { if (n == 0 || !lua_isnumber(L, -1)) luaL_error(L, "format 'c0' needs a previous size"); size = lua_tonumber(L, -1); lua_pop(L, 1); n--; luaL_argcheck(L, size <= ld && pos <= ld - size, 2, "data string too short"); } lua_pushlstring(L, data+pos, size); n++; break; } case 's': { const char *e = (const char *)memchr(data+pos, '\0', ld - pos); if (e == NULL) luaL_error(L, "unfinished string in data"); size = (e - (data+pos)) + 1; lua_pushlstring(L, data+pos, size - 1); n++; break; } default: controloptions(L, opt, &fmt, &h); } pos += size; } lua_pushinteger(L, pos + 1); /* next position */ return n + 1; } static int b_size (lua_State *L) { Header h; const char *fmt = luaL_checkstring(L, 1); size_t pos = 0; defaultoptions(&h); while (*fmt) { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); pos += gettoalign(pos, &h, opt, size); if (opt == 's') luaL_argerror(L, 1, "option 's' has no fixed size"); else if (opt == 'c' && size == 0) luaL_argerror(L, 1, "option 'c0' has no fixed size"); if (!isalnum(opt)) controloptions(L, opt, &fmt, &h); pos += size; } lua_pushinteger(L, pos); return 1; } /* }====================================================== */ static const struct luaL_Reg thislib[] = { {"pack", b_pack}, {"unpack", b_unpack}, {"size", b_size}, {NULL, NULL} }; LUALIB_API int luaopen_struct (lua_State *L); LUALIB_API int luaopen_struct (lua_State *L) { luaL_register(L, "struct", thislib); return 1; } /****************************************************************************** * Copyright (C) 2010-2018 Lua.org, PUC-Rio. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************/ redis-8.0.2/deps/lua/src/luac.c000066400000000000000000000110651501533116600162260ustar00rootroot00000000000000/* ** $Id: luac.c,v 1.54 2006/06/02 17:37:11 lhf Exp $ ** Lua compiler (saves bytecodes to files; also list bytecodes) ** See Copyright Notice in lua.h */ #include #include #include #include #define luac_c #define LUA_CORE #include "lua.h" #include "lauxlib.h" #include "ldo.h" #include "lfunc.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lstring.h" #include "lundump.h" #define PROGNAME "luac" /* default program name */ #define OUTPUT PROGNAME ".out" /* default output file */ static int listing=0; /* list bytecodes? */ static int dumping=1; /* dump bytecodes? */ static int stripping=0; /* strip debug information? */ static char Output[]={ OUTPUT }; /* default output file name */ static const char* output=Output; /* actual output file name */ static const char* progname=PROGNAME; /* actual program name */ static void fatal(const char* message) { fprintf(stderr,"%s: %s\n",progname,message); exit(EXIT_FAILURE); } static void cannot(const char* what) { fprintf(stderr,"%s: cannot %s %s: %s\n",progname,what,output,strerror(errno)); exit(EXIT_FAILURE); } static void usage(const char* message) { if (*message=='-') fprintf(stderr,"%s: unrecognized option " LUA_QS "\n",progname,message); else fprintf(stderr,"%s: %s\n",progname,message); fprintf(stderr, "usage: %s [options] [filenames].\n" "Available options are:\n" " - process stdin\n" " -l list\n" " -o name output to file " LUA_QL("name") " (default is \"%s\")\n" " -p parse only\n" " -s strip debug information\n" " -v show version information\n" " -- stop handling options\n", progname,Output); exit(EXIT_FAILURE); } #define IS(s) (strcmp(argv[i],s)==0) static int doargs(int argc, char* argv[]) { int i; int version=0; if (argv[0]!=NULL && *argv[0]!=0) progname=argv[0]; for (i=1; itop+(i))->l.p) static const Proto* combine(lua_State* L, int n) { if (n==1) return toproto(L,-1); else { int i,pc; Proto* f=luaF_newproto(L); setptvalue2s(L,L->top,f); incr_top(L); f->source=luaS_newliteral(L,"=(" PROGNAME ")"); f->maxstacksize=1; pc=2*n+1; f->code=luaM_newvector(L,pc,Instruction); f->sizecode=pc; f->p=luaM_newvector(L,n,Proto*); f->sizep=n; pc=0; for (i=0; ip[i]=toproto(L,i-n-1); f->code[pc++]=CREATE_ABx(OP_CLOSURE,0,i); f->code[pc++]=CREATE_ABC(OP_CALL,0,1,1); } f->code[pc++]=CREATE_ABC(OP_RETURN,0,1,0); return f; } } static int writer(lua_State* L, const void* p, size_t size, void* u) { UNUSED(L); return (fwrite(p,size,1,(FILE*)u)!=1) && (size!=0); } struct Smain { int argc; char** argv; }; static int pmain(lua_State* L) { struct Smain* s = (struct Smain*)lua_touserdata(L, 1); int argc=s->argc; char** argv=s->argv; const Proto* f; int i; if (!lua_checkstack(L,argc)) fatal("too many input files"); for (i=0; i1); if (dumping) { FILE* D= (output==NULL) ? stdout : fopen(output,"wb"); if (D==NULL) cannot("open"); lua_lock(L); luaU_dump(L,f,writer,D,stripping); lua_unlock(L); if (ferror(D)) cannot("write"); if (fclose(D)) cannot("close"); } return 0; } int main(int argc, char* argv[]) { lua_State* L; struct Smain s; int i=doargs(argc,argv); argc-=i; argv+=i; if (argc<=0) usage("no input files given"); L=lua_open(); if (L==NULL) fatal("not enough memory for state"); s.argc=argc; s.argv=argv; if (lua_cpcall(L,pmain,&s)!=0) fatal(lua_tostring(L,-1)); lua_close(L); return EXIT_SUCCESS; } redis-8.0.2/deps/lua/src/luaconf.h000066400000000000000000000534331501533116600167430ustar00rootroot00000000000000/* ** $Id: luaconf.h,v 1.82.1.7 2008/02/11 16:25:08 roberto Exp $ ** Configuration file for Lua ** See Copyright Notice in lua.h */ #ifndef lconfig_h #define lconfig_h #include #include /* ** ================================================================== ** Search for "@@" to find all configurable definitions. ** =================================================================== */ /* @@ LUA_ANSI controls the use of non-ansi features. ** CHANGE it (define it) if you want Lua to avoid the use of any ** non-ansi feature or library. */ #if defined(__STRICT_ANSI__) #define LUA_ANSI #endif #if !defined(LUA_ANSI) && defined(_WIN32) #define LUA_WIN #endif #if defined(LUA_USE_LINUX) #define LUA_USE_POSIX #define LUA_USE_DLOPEN /* needs an extra library: -ldl */ #define LUA_USE_READLINE /* needs some extra libraries */ #endif #if defined(LUA_USE_MACOSX) #define LUA_USE_POSIX #define LUA_DL_DYLD /* does not need extra library */ #endif /* @@ LUA_USE_POSIX includes all functionallity listed as X/Open System @* Interfaces Extension (XSI). ** CHANGE it (define it) if your system is XSI compatible. */ #if defined(LUA_USE_POSIX) #define LUA_USE_MKSTEMP #define LUA_USE_ISATTY #define LUA_USE_POPEN #define LUA_USE_ULONGJMP #endif /* @@ LUA_PATH and LUA_CPATH are the names of the environment variables that @* Lua check to set its paths. @@ LUA_INIT is the name of the environment variable that Lua @* checks for initialization code. ** CHANGE them if you want different names. */ #define LUA_PATH "LUA_PATH" #define LUA_CPATH "LUA_CPATH" #define LUA_INIT "LUA_INIT" /* @@ LUA_PATH_DEFAULT is the default path that Lua uses to look for @* Lua libraries. @@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for @* C libraries. ** CHANGE them if your machine has a non-conventional directory ** hierarchy or if you want to install your libraries in ** non-conventional directories. */ #if defined(_WIN32) /* ** In Windows, any exclamation mark ('!') in the path is replaced by the ** path of the directory of the executable file of the current process. */ #define LUA_LDIR "!\\lua\\" #define LUA_CDIR "!\\" #define LUA_PATH_DEFAULT \ ".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua" #define LUA_CPATH_DEFAULT \ ".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll" #else #define LUA_ROOT "/usr/local/" #define LUA_LDIR LUA_ROOT "share/lua/5.1/" #define LUA_CDIR LUA_ROOT "lib/lua/5.1/" #define LUA_PATH_DEFAULT \ "./?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua" #define LUA_CPATH_DEFAULT \ "./?.so;" LUA_CDIR"?.so;" LUA_CDIR"loadall.so" #endif /* @@ LUA_DIRSEP is the directory separator (for submodules). ** CHANGE it if your machine does not use "/" as the directory separator ** and is not Windows. (On Windows Lua automatically uses "\".) */ #if defined(_WIN32) #define LUA_DIRSEP "\\" #else #define LUA_DIRSEP "/" #endif /* @@ LUA_PATHSEP is the character that separates templates in a path. @@ LUA_PATH_MARK is the string that marks the substitution points in a @* template. @@ LUA_EXECDIR in a Windows path is replaced by the executable's @* directory. @@ LUA_IGMARK is a mark to ignore all before it when bulding the @* luaopen_ function name. ** CHANGE them if for some reason your system cannot use those ** characters. (E.g., if one of those characters is a common character ** in file/directory names.) Probably you do not need to change them. */ #define LUA_PATHSEP ";" #define LUA_PATH_MARK "?" #define LUA_EXECDIR "!" #define LUA_IGMARK "-" /* @@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger. ** CHANGE that if ptrdiff_t is not adequate on your machine. (On most ** machines, ptrdiff_t gives a good choice between int or long.) */ #define LUA_INTEGER ptrdiff_t /* @@ LUA_API is a mark for all core API functions. @@ LUALIB_API is a mark for all standard library functions. ** CHANGE them if you need to define those functions in some special way. ** For instance, if you want to create one Windows DLL with the core and ** the libraries, you may want to use the following definition (define ** LUA_BUILD_AS_DLL to get it). */ #if defined(LUA_BUILD_AS_DLL) #if defined(LUA_CORE) || defined(LUA_LIB) #define LUA_API __declspec(dllexport) #else #define LUA_API __declspec(dllimport) #endif #else #define LUA_API extern #endif /* more often than not the libs go together with the core */ #define LUALIB_API LUA_API /* @@ LUAI_FUNC is a mark for all extern functions that are not to be @* exported to outside modules. @@ LUAI_DATA is a mark for all extern (const) variables that are not to @* be exported to outside modules. ** CHANGE them if you need to mark them in some special way. Elf/gcc ** (versions 3.2 and later) mark them as "hidden" to optimize access ** when Lua is compiled as a shared library. */ #if defined(luaall_c) #define LUAI_FUNC static #define LUAI_DATA /* empty */ #elif defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ defined(__ELF__) #define LUAI_FUNC __attribute__((visibility("hidden"))) extern #define LUAI_DATA LUAI_FUNC #else #define LUAI_FUNC extern #define LUAI_DATA extern #endif /* @@ LUA_QL describes how error messages quote program elements. ** CHANGE it if you want a different appearance. */ #define LUA_QL(x) "'" x "'" #define LUA_QS LUA_QL("%s") /* @@ LUA_IDSIZE gives the maximum size for the description of the source @* of a function in debug information. ** CHANGE it if you want a different size. */ #define LUA_IDSIZE 60 /* ** {================================================================== ** Stand-alone configuration ** =================================================================== */ #if defined(lua_c) || defined(luaall_c) /* @@ lua_stdin_is_tty detects whether the standard input is a 'tty' (that @* is, whether we're running lua interactively). ** CHANGE it if you have a better definition for non-POSIX/non-Windows ** systems. */ #if defined(LUA_USE_ISATTY) #include #define lua_stdin_is_tty() isatty(0) #elif defined(LUA_WIN) #include #include #define lua_stdin_is_tty() _isatty(_fileno(stdin)) #else #define lua_stdin_is_tty() 1 /* assume stdin is a tty */ #endif /* @@ LUA_PROMPT is the default prompt used by stand-alone Lua. @@ LUA_PROMPT2 is the default continuation prompt used by stand-alone Lua. ** CHANGE them if you want different prompts. (You can also change the ** prompts dynamically, assigning to globals _PROMPT/_PROMPT2.) */ #define LUA_PROMPT "> " #define LUA_PROMPT2 ">> " /* @@ LUA_PROGNAME is the default name for the stand-alone Lua program. ** CHANGE it if your stand-alone interpreter has a different name and ** your system is not able to detect that name automatically. */ #define LUA_PROGNAME "lua" /* @@ LUA_MAXINPUT is the maximum length for an input line in the @* stand-alone interpreter. ** CHANGE it if you need longer lines. */ #define LUA_MAXINPUT 512 /* @@ lua_readline defines how to show a prompt and then read a line from @* the standard input. @@ lua_saveline defines how to "save" a read line in a "history". @@ lua_freeline defines how to free a line read by lua_readline. ** CHANGE them if you want to improve this functionality (e.g., by using ** GNU readline and history facilities). */ #if defined(LUA_USE_READLINE) #include #include #include #define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) #define lua_saveline(L,idx) \ if (lua_strlen(L,idx) > 0) /* non-empty line? */ \ add_history(lua_tostring(L, idx)); /* add it to history */ #define lua_freeline(L,b) ((void)L, free(b)) #else #define lua_readline(L,b,p) \ ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ #define lua_saveline(L,idx) { (void)L; (void)idx; } #define lua_freeline(L,b) { (void)L; (void)b; } #endif #endif /* }================================================================== */ /* @@ LUAI_GCPAUSE defines the default pause between garbage-collector cycles @* as a percentage. ** CHANGE it if you want the GC to run faster or slower (higher values ** mean larger pauses which mean slower collection.) You can also change ** this value dynamically. */ #define LUAI_GCPAUSE 200 /* 200% (wait memory to double before next GC) */ /* @@ LUAI_GCMUL defines the default speed of garbage collection relative to @* memory allocation as a percentage. ** CHANGE it if you want to change the granularity of the garbage ** collection. (Higher values mean coarser collections. 0 represents ** infinity, where each step performs a full collection.) You can also ** change this value dynamically. */ #define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ /* @@ LUA_COMPAT_GETN controls compatibility with old getn behavior. ** CHANGE it (define it) if you want exact compatibility with the ** behavior of setn/getn in Lua 5.0. */ #undef LUA_COMPAT_GETN /* @@ LUA_COMPAT_LOADLIB controls compatibility about global loadlib. ** CHANGE it to undefined as soon as you do not need a global 'loadlib' ** function (the function is still available as 'package.loadlib'). */ #undef LUA_COMPAT_LOADLIB /* @@ LUA_COMPAT_VARARG controls compatibility with old vararg feature. ** CHANGE it to undefined as soon as your programs use only '...' to ** access vararg parameters (instead of the old 'arg' table). */ #define LUA_COMPAT_VARARG /* @@ LUA_COMPAT_MOD controls compatibility with old math.mod function. ** CHANGE it to undefined as soon as your programs use 'math.fmod' or ** the new '%' operator instead of 'math.mod'. */ #define LUA_COMPAT_MOD /* @@ LUA_COMPAT_LSTR controls compatibility with old long string nesting @* facility. ** CHANGE it to 2 if you want the old behaviour, or undefine it to turn ** off the advisory error when nesting [[...]]. */ #define LUA_COMPAT_LSTR 1 /* @@ LUA_COMPAT_GFIND controls compatibility with old 'string.gfind' name. ** CHANGE it to undefined as soon as you rename 'string.gfind' to ** 'string.gmatch'. */ #define LUA_COMPAT_GFIND /* @@ LUA_COMPAT_OPENLIB controls compatibility with old 'luaL_openlib' @* behavior. ** CHANGE it to undefined as soon as you replace to 'luaL_register' ** your uses of 'luaL_openlib' */ #define LUA_COMPAT_OPENLIB /* @@ luai_apicheck is the assert macro used by the Lua-C API. ** CHANGE luai_apicheck if you want Lua to perform some checks in the ** parameters it gets from API calls. This may slow down the interpreter ** a bit, but may be quite useful when debugging C code that interfaces ** with Lua. A useful redefinition is to use assert.h. */ #if defined(LUA_USE_APICHECK) #include #define luai_apicheck(L,o) { (void)L; assert(o); } #else #define luai_apicheck(L,o) { (void)L; } #endif /* @@ LUAI_BITSINT defines the number of bits in an int. ** CHANGE here if Lua cannot automatically detect the number of bits of ** your machine. Probably you do not need to change this. */ /* avoid overflows in comparison */ #if INT_MAX-20 < 32760 #define LUAI_BITSINT 16 #elif INT_MAX > 2147483640L /* int has at least 32 bits */ #define LUAI_BITSINT 32 #else #error "you must define LUA_BITSINT with number of bits in an integer" #endif /* @@ LUAI_UINT32 is an unsigned integer with at least 32 bits. @@ LUAI_INT32 is an signed integer with at least 32 bits. @@ LUAI_UMEM is an unsigned integer big enough to count the total @* memory used by Lua. @@ LUAI_MEM is a signed integer big enough to count the total memory @* used by Lua. ** CHANGE here if for some weird reason the default definitions are not ** good enough for your machine. (The definitions in the 'else' ** part always works, but may waste space on machines with 64-bit ** longs.) Probably you do not need to change this. */ #if LUAI_BITSINT >= 32 #define LUAI_UINT32 unsigned int #define LUAI_INT32 int #define LUAI_MAXINT32 INT_MAX #define LUAI_UMEM size_t #define LUAI_MEM ptrdiff_t #else /* 16-bit ints */ #define LUAI_UINT32 unsigned long #define LUAI_INT32 long #define LUAI_MAXINT32 LONG_MAX #define LUAI_UMEM unsigned long #define LUAI_MEM long #endif /* @@ LUAI_MAXCALLS limits the number of nested calls. ** CHANGE it if you need really deep recursive calls. This limit is ** arbitrary; its only purpose is to stop infinite recursion before ** exhausting memory. */ #define LUAI_MAXCALLS 20000 /* @@ LUAI_MAXCSTACK limits the number of Lua stack slots that a C function @* can use. ** CHANGE it if you need lots of (Lua) stack space for your C ** functions. This limit is arbitrary; its only purpose is to stop C ** functions to consume unlimited stack space. (must be smaller than ** -LUA_REGISTRYINDEX) */ #define LUAI_MAXCSTACK 8000 /* ** {================================================================== ** CHANGE (to smaller values) the following definitions if your system ** has a small C stack. (Or you may want to change them to larger ** values if your system has a large C stack and these limits are ** too rigid for you.) Some of these constants control the size of ** stack-allocated arrays used by the compiler or the interpreter, while ** others limit the maximum number of recursive calls that the compiler ** or the interpreter can perform. Values too large may cause a C stack ** overflow for some forms of deep constructs. ** =================================================================== */ /* @@ LUAI_MAXCCALLS is the maximum depth for nested C calls (short) and @* syntactical nested non-terminals in a program. */ #define LUAI_MAXCCALLS 200 /* @@ LUAI_MAXVARS is the maximum number of local variables per function @* (must be smaller than 250). */ #define LUAI_MAXVARS 200 /* @@ LUAI_MAXUPVALUES is the maximum number of upvalues per function @* (must be smaller than 250). */ #define LUAI_MAXUPVALUES 60 /* @@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. */ #define LUAL_BUFFERSIZE BUFSIZ /* }================================================================== */ /* ** {================================================================== @@ LUA_NUMBER is the type of numbers in Lua. ** CHANGE the following definitions only if you want to build Lua ** with a number type different from double. You may also need to ** change lua_number2int & lua_number2integer. ** =================================================================== */ #define LUA_NUMBER_DOUBLE #define LUA_NUMBER double /* @@ LUAI_UACNUMBER is the result of an 'usual argument conversion' @* over a number. */ #define LUAI_UACNUMBER double /* @@ LUA_NUMBER_SCAN is the format for reading numbers. @@ LUA_NUMBER_FMT is the format for writing numbers. @@ lua_number2str converts a number to a string. @@ LUAI_MAXNUMBER2STR is maximum size of previous conversion. @@ lua_str2number converts a string to a number. */ #define LUA_NUMBER_SCAN "%lf" #define LUA_NUMBER_FMT "%.14g" #define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) #define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */ #define lua_str2number(s,p) strtod((s), (p)) /* @@ The luai_num* macros define the primitive operations over numbers. */ #if defined(LUA_CORE) #include #define luai_numadd(a,b) ((a)+(b)) #define luai_numsub(a,b) ((a)-(b)) #define luai_nummul(a,b) ((a)*(b)) #define luai_numdiv(a,b) ((a)/(b)) #define luai_nummod(a,b) ((a) - floor((a)/(b))*(b)) #define luai_numpow(a,b) (pow(a,b)) #define luai_numunm(a) (-(a)) #define luai_numeq(a,b) ((a)==(b)) #define luai_numlt(a,b) ((a)<(b)) #define luai_numle(a,b) ((a)<=(b)) #define luai_numisnan(a) (!luai_numeq((a), (a))) #endif /* @@ lua_number2int is a macro to convert lua_Number to int. @@ lua_number2integer is a macro to convert lua_Number to lua_Integer. ** CHANGE them if you know a faster way to convert a lua_Number to ** int (with any rounding method and without throwing errors) in your ** system. In Pentium machines, a naive typecast from double to int ** in C is extremely slow, so any alternative is worth trying. */ /* On a Pentium, resort to a trick */ #if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) && !defined(__SSE2__) && \ (defined(__i386) || defined (_M_IX86) || defined(__i386__)) /* On a Microsoft compiler, use assembler */ #if defined(_MSC_VER) #define lua_number2int(i,d) __asm fld d __asm fistp i #define lua_number2integer(i,n) lua_number2int(i, n) /* the next trick should work on any Pentium, but sometimes clashes with a DirectX idiosyncrasy */ #else union luai_Cast { double l_d; long l_l; }; #define lua_number2int(i,d) \ { volatile union luai_Cast u; u.l_d = (d) + 6755399441055744.0; (i) = u.l_l; } #define lua_number2integer(i,n) lua_number2int(i, n) #endif /* this option always works, but may be slow */ #else #define lua_number2int(i,d) ((i)=(int)(d)) #define lua_number2integer(i,d) ((i)=(lua_Integer)(d)) #endif /* }================================================================== */ /* @@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment. ** CHANGE it if your system requires alignments larger than double. (For ** instance, if your system supports long doubles and they must be ** aligned in 16-byte boundaries, then you should add long double in the ** union.) Probably you do not need to change this. */ #define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; } /* @@ LUAI_THROW/LUAI_TRY define how Lua does exception handling. ** CHANGE them if you prefer to use longjmp/setjmp even with C++ ** or if want/don't to use _longjmp/_setjmp instead of regular ** longjmp/setjmp. By default, Lua handles errors with exceptions when ** compiling as C++ code, with _longjmp/_setjmp when asked to use them, ** and with longjmp/setjmp otherwise. */ #if defined(__cplusplus) /* C++ exceptions */ #define LUAI_THROW(L,c) throw(c) #define LUAI_TRY(L,c,a) try { a } catch(...) \ { if ((c)->status == 0) (c)->status = -1; } #define luai_jmpbuf int /* dummy variable */ #elif defined(LUA_USE_ULONGJMP) /* in Unix, try _longjmp/_setjmp (more efficient) */ #define LUAI_THROW(L,c) _longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf #else /* default handling with long jumps */ #define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf #endif /* @@ LUA_MAXCAPTURES is the maximum number of captures that a pattern @* can do during pattern-matching. ** CHANGE it if you need more captures. This limit is arbitrary. */ #define LUA_MAXCAPTURES 32 /* @@ lua_tmpnam is the function that the OS library uses to create a @* temporary name. @@ LUA_TMPNAMBUFSIZE is the maximum size of a name created by lua_tmpnam. ** CHANGE them if you have an alternative to tmpnam (which is considered ** insecure) or if you want the original tmpnam anyway. By default, Lua ** uses tmpnam except when POSIX is available, where it uses mkstemp. */ #if defined(loslib_c) || defined(luaall_c) #if defined(LUA_USE_MKSTEMP) #include #define LUA_TMPNAMBUFSIZE 32 #define lua_tmpnam(b,e) { \ strcpy(b, "/tmp/lua_XXXXXX"); \ e = mkstemp(b); \ if (e != -1) close(e); \ e = (e == -1); } #else #define LUA_TMPNAMBUFSIZE L_tmpnam #define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } #endif #endif /* @@ lua_popen spawns a new process connected to the current one through @* the file streams. ** CHANGE it if you have a way to implement it in your system. */ #if defined(LUA_USE_POPEN) #define lua_popen(L,c,m) ((void)L, fflush(NULL), popen(c,m)) #define lua_pclose(L,file) ((void)L, (pclose(file) != -1)) #elif defined(LUA_WIN) #define lua_popen(L,c,m) ((void)L, _popen(c,m)) #define lua_pclose(L,file) ((void)L, (_pclose(file) != -1)) #else #define lua_popen(L,c,m) ((void)((void)c, m), \ luaL_error(L, LUA_QL("popen") " not supported"), (FILE*)0) #define lua_pclose(L,file) ((void)((void)L, file), 0) #endif /* @@ LUA_DL_* define which dynamic-library system Lua should use. ** CHANGE here if Lua has problems choosing the appropriate ** dynamic-library system for your platform (either Windows' DLL, Mac's ** dyld, or Unix's dlopen). If your system is some kind of Unix, there ** is a good chance that it has dlopen, so LUA_DL_DLOPEN will work for ** it. To use dlopen you also need to adapt the src/Makefile (probably ** adding -ldl to the linker options), so Lua does not select it ** automatically. (When you change the makefile to add -ldl, you must ** also add -DLUA_USE_DLOPEN.) ** If you do not want any kind of dynamic library, undefine all these ** options. ** By default, _WIN32 gets LUA_DL_DLL and MAC OS X gets LUA_DL_DYLD. */ #if defined(LUA_USE_DLOPEN) #define LUA_DL_DLOPEN #endif #if defined(LUA_WIN) #define LUA_DL_DLL #endif /* @@ LUAI_EXTRASPACE allows you to add user-specific data in a lua_State @* (the data goes just *before* the lua_State pointer). ** CHANGE (define) this if you really need that. This value must be ** a multiple of the maximum alignment required for your machine. */ #define LUAI_EXTRASPACE 0 /* @@ luai_userstate* allow user-specific actions on threads. ** CHANGE them if you defined LUAI_EXTRASPACE and need to do something ** extra when a thread is created/deleted/resumed/yielded. */ #define luai_userstateopen(L) ((void)L) #define luai_userstateclose(L) ((void)L) #define luai_userstatethread(L,L1) ((void)L) #define luai_userstatefree(L) ((void)L) #define luai_userstateresume(L,n) ((void)L) #define luai_userstateyield(L,n) ((void)L) /* @@ LUA_INTFRMLEN is the length modifier for integer conversions @* in 'string.format'. @@ LUA_INTFRM_T is the integer type correspoding to the previous length @* modifier. ** CHANGE them if your system supports long long or does not support long. */ #if defined(LUA_USELONGLONG) #define LUA_INTFRMLEN "ll" #define LUA_INTFRM_T long long #else #define LUA_INTFRMLEN "l" #define LUA_INTFRM_T long #endif /* =================================================================== */ /* ** Local configuration. You can use this space to add your redefinitions ** without modifying the main part of the file. */ #endif redis-8.0.2/deps/lua/src/lualib.h000066400000000000000000000020021501533116600165460ustar00rootroot00000000000000/* ** $Id: lualib.h,v 1.36.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua standard libraries ** See Copyright Notice in lua.h */ #ifndef lualib_h #define lualib_h #include "lua.h" /* Key to file-handle type */ #define LUA_FILEHANDLE "FILE*" #define LUA_COLIBNAME "coroutine" LUALIB_API int (luaopen_base) (lua_State *L); #define LUA_TABLIBNAME "table" LUALIB_API int (luaopen_table) (lua_State *L); #define LUA_IOLIBNAME "io" LUALIB_API int (luaopen_io) (lua_State *L); #define LUA_OSLIBNAME "os" LUALIB_API int (luaopen_os) (lua_State *L); #define LUA_STRLIBNAME "string" LUALIB_API int (luaopen_string) (lua_State *L); #define LUA_MATHLIBNAME "math" LUALIB_API int (luaopen_math) (lua_State *L); #define LUA_DBLIBNAME "debug" LUALIB_API int (luaopen_debug) (lua_State *L); #define LUA_LOADLIBNAME "package" LUALIB_API int (luaopen_package) (lua_State *L); /* open all previous libraries */ LUALIB_API void (luaL_openlibs) (lua_State *L); #ifndef lua_assert #define lua_assert(x) ((void)0) #endif #endif redis-8.0.2/deps/lua/src/lundump.c000066400000000000000000000110251501533116600167620ustar00rootroot00000000000000/* ** $Id: lundump.c,v 2.7.1.4 2008/04/04 19:51:41 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ #include #define lundump_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lmem.h" #include "lobject.h" #include "lstring.h" #include "lundump.h" #include "lzio.h" typedef struct { lua_State* L; ZIO* Z; Mbuffer* b; const char* name; } LoadState; #ifdef LUAC_TRUST_BINARIES #define IF(c,s) #define error(S,s) #else #define IF(c,s) if (c) error(S,s) static void error(LoadState* S, const char* why) { luaO_pushfstring(S->L,"%s: %s in precompiled chunk",S->name,why); luaD_throw(S->L,LUA_ERRSYNTAX); } #endif #define LoadMem(S,b,n,size) LoadBlock(S,b,(n)*(size)) #define LoadByte(S) (lu_byte)LoadChar(S) #define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x)) #define LoadVector(S,b,n,size) LoadMem(S,b,n,size) static void LoadBlock(LoadState* S, void* b, size_t size) { size_t r=luaZ_read(S->Z,b,size); IF (r!=0, "unexpected end"); } static int LoadChar(LoadState* S) { char x; LoadVar(S,x); return x; } static int LoadInt(LoadState* S) { int x; LoadVar(S,x); IF (x<0, "bad integer"); return x; } static lua_Number LoadNumber(LoadState* S) { lua_Number x; LoadVar(S,x); return x; } static TString* LoadString(LoadState* S) { size_t size; LoadVar(S,size); if (size==0) return NULL; else { char* s=luaZ_openspace(S->L,S->b,size); LoadBlock(S,s,size); return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */ } } static void LoadCode(LoadState* S, Proto* f) { int n=LoadInt(S); f->code=luaM_newvector(S->L,n,Instruction); f->sizecode=n; LoadVector(S,f->code,n,sizeof(Instruction)); } static Proto* LoadFunction(LoadState* S, TString* p); static void LoadConstants(LoadState* S, Proto* f) { int i,n; n=LoadInt(S); f->k=luaM_newvector(S->L,n,TValue); f->sizek=n; for (i=0; ik[i]); for (i=0; ik[i]; int t=LoadChar(S); switch (t) { case LUA_TNIL: setnilvalue(o); break; case LUA_TBOOLEAN: setbvalue(o,LoadChar(S)!=0); break; case LUA_TNUMBER: setnvalue(o,LoadNumber(S)); break; case LUA_TSTRING: setsvalue2n(S->L,o,LoadString(S)); break; default: error(S,"bad constant"); break; } } n=LoadInt(S); f->p=luaM_newvector(S->L,n,Proto*); f->sizep=n; for (i=0; ip[i]=NULL; for (i=0; ip[i]=LoadFunction(S,f->source); } static void LoadDebug(LoadState* S, Proto* f) { int i,n; n=LoadInt(S); f->lineinfo=luaM_newvector(S->L,n,int); f->sizelineinfo=n; LoadVector(S,f->lineinfo,n,sizeof(int)); n=LoadInt(S); f->locvars=luaM_newvector(S->L,n,LocVar); f->sizelocvars=n; for (i=0; ilocvars[i].varname=NULL; for (i=0; ilocvars[i].varname=LoadString(S); f->locvars[i].startpc=LoadInt(S); f->locvars[i].endpc=LoadInt(S); } n=LoadInt(S); f->upvalues=luaM_newvector(S->L,n,TString*); f->sizeupvalues=n; for (i=0; iupvalues[i]=NULL; for (i=0; iupvalues[i]=LoadString(S); } static Proto* LoadFunction(LoadState* S, TString* p) { Proto* f; if (++S->L->nCcalls > LUAI_MAXCCALLS) error(S,"code too deep"); f=luaF_newproto(S->L); setptvalue2s(S->L,S->L->top,f); incr_top(S->L); f->source=LoadString(S); if (f->source==NULL) f->source=p; f->linedefined=LoadInt(S); f->lastlinedefined=LoadInt(S); f->nups=LoadByte(S); f->numparams=LoadByte(S); f->is_vararg=LoadByte(S); f->maxstacksize=LoadByte(S); LoadCode(S,f); LoadConstants(S,f); LoadDebug(S,f); IF (!luaG_checkcode(f), "bad code"); S->L->top--; S->L->nCcalls--; return f; } static void LoadHeader(LoadState* S) { char h[LUAC_HEADERSIZE]; char s[LUAC_HEADERSIZE]; luaU_header(h); LoadBlock(S,s,LUAC_HEADERSIZE); IF (memcmp(h,s,LUAC_HEADERSIZE)!=0, "bad header"); } /* ** load precompiled chunk */ Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name) { LoadState S; if (*name=='@' || *name=='=') S.name=name+1; else if (*name==LUA_SIGNATURE[0]) S.name="binary string"; else S.name=name; S.L=L; S.Z=Z; S.b=buff; LoadHeader(&S); return LoadFunction(&S,luaS_newliteral(L,"=?")); } /* * make header */ void luaU_header (char* h) { int x=1; memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-1); h+=sizeof(LUA_SIGNATURE)-1; *h++=(char)LUAC_VERSION; *h++=(char)LUAC_FORMAT; *h++=(char)*(char*)&x; /* endianness */ *h++=(char)sizeof(int); *h++=(char)sizeof(size_t); *h++=(char)sizeof(Instruction); *h++=(char)sizeof(lua_Number); *h++=(char)(((lua_Number)0.5)==0); /* is lua_Number integral? */ } redis-8.0.2/deps/lua/src/lundump.h000066400000000000000000000015721501533116600167750ustar00rootroot00000000000000/* ** $Id: lundump.h,v 1.37.1.1 2007/12/27 13:02:25 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ #ifndef lundump_h #define lundump_h #include "lobject.h" #include "lzio.h" /* load one chunk; from lundump.c */ LUAI_FUNC Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name); /* make header; from lundump.c */ LUAI_FUNC void luaU_header (char* h); /* dump one chunk; from ldump.c */ LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip); #ifdef luac_c /* print one chunk; from print.c */ LUAI_FUNC void luaU_print (const Proto* f, int full); #endif /* for header of binary files -- this is Lua 5.1 */ #define LUAC_VERSION 0x51 /* for header of binary files -- this is the official format */ #define LUAC_FORMAT 0 /* size of header of binary files */ #define LUAC_HEADERSIZE 12 #endif redis-8.0.2/deps/lua/src/lvm.c000066400000000000000000000554411501533116600161060ustar00rootroot00000000000000/* ** $Id: lvm.c,v 2.63.1.5 2011/08/17 20:43:11 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ #include #include #include #define lvm_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lobject.h" #include "lopcodes.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lvm.h" /* limit for table tag-method chains (to avoid loops) */ #define MAXTAGLOOP 100 const TValue *luaV_tonumber (const TValue *obj, TValue *n) { lua_Number num; if (ttisnumber(obj)) return obj; if (ttisstring(obj) && luaO_str2d(svalue(obj), &num)) { setnvalue(n, num); return n; } else return NULL; } int luaV_tostring (lua_State *L, StkId obj) { if (!ttisnumber(obj)) return 0; else { char s[LUAI_MAXNUMBER2STR]; lua_Number n = nvalue(obj); lua_number2str(s, n); setsvalue2s(L, obj, luaS_new(L, s)); return 1; } } static void traceexec (lua_State *L, const Instruction *pc) { lu_byte mask = L->hookmask; const Instruction *oldpc = L->savedpc; L->savedpc = pc; if ((mask & LUA_MASKCOUNT) && L->hookcount == 0) { resethookcount(L); luaD_callhook(L, LUA_HOOKCOUNT, -1); } if (mask & LUA_MASKLINE) { Proto *p = ci_func(L->ci)->l.p; int npc = pcRel(pc, p); int newline = getline(p, npc); /* call linehook when enter a new function, when jump back (loop), or when enter a new line */ if (npc == 0 || pc <= oldpc || newline != getline(p, pcRel(oldpc, p))) luaD_callhook(L, LUA_HOOKLINE, newline); } } static void callTMres (lua_State *L, StkId res, const TValue *f, const TValue *p1, const TValue *p2) { ptrdiff_t result = savestack(L, res); setobj2s(L, L->top, f); /* push function */ setobj2s(L, L->top+1, p1); /* 1st argument */ setobj2s(L, L->top+2, p2); /* 2nd argument */ luaD_checkstack(L, 3); L->top += 3; luaD_call(L, L->top - 3, 1); res = restorestack(L, result); L->top--; setobjs2s(L, res, L->top); } static void callTM (lua_State *L, const TValue *f, const TValue *p1, const TValue *p2, const TValue *p3) { setobj2s(L, L->top, f); /* push function */ setobj2s(L, L->top+1, p1); /* 1st argument */ setobj2s(L, L->top+2, p2); /* 2nd argument */ setobj2s(L, L->top+3, p3); /* 3th argument */ luaD_checkstack(L, 4); L->top += 4; luaD_call(L, L->top - 4, 0); } void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { int loop; for (loop = 0; loop < MAXTAGLOOP; loop++) { const TValue *tm; if (ttistable(t)) { /* `t' is a table? */ Table *h = hvalue(t); const TValue *res = luaH_get(h, key); /* do a primitive get */ if (!ttisnil(res) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ setobj2s(L, val, res); return; } /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) luaG_typeerror(L, t, "index"); if (ttisfunction(tm)) { callTMres(L, val, tm, t, key); return; } t = tm; /* else repeat with `tm' */ } luaG_runerror(L, "loop in gettable"); } void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { int loop; TValue temp; for (loop = 0; loop < MAXTAGLOOP; loop++) { const TValue *tm; if (ttistable(t)) { /* `t' is a table? */ Table *h = hvalue(t); if (h->readonly) luaG_runerror(L, "Attempt to modify a readonly table"); TValue *oldval = luaH_set(L, h, key); /* do a primitive set */ if (!ttisnil(oldval) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */ setobj2t(L, oldval, val); h->flags = 0; luaC_barriert(L, h, val); return; } /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_typeerror(L, t, "index"); if (ttisfunction(tm)) { callTM(L, tm, t, key, val); return; } /* else repeat with `tm' */ setobj(L, &temp, tm); /* avoid pointing inside table (may rehash) */ t = &temp; } luaG_runerror(L, "loop in settable"); } static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, StkId res, TMS event) { const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ if (ttisnil(tm)) tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ if (ttisnil(tm)) return 0; callTMres(L, res, tm, p1, p2); return 1; } static const TValue *get_compTM (lua_State *L, Table *mt1, Table *mt2, TMS event) { const TValue *tm1 = fasttm(L, mt1, event); const TValue *tm2; if (tm1 == NULL) return NULL; /* no metamethod */ if (mt1 == mt2) return tm1; /* same metatables => same metamethods */ tm2 = fasttm(L, mt2, event); if (tm2 == NULL) return NULL; /* no metamethod */ if (luaO_rawequalObj(tm1, tm2)) /* same metamethods? */ return tm1; return NULL; } static int call_orderTM (lua_State *L, const TValue *p1, const TValue *p2, TMS event) { const TValue *tm1 = luaT_gettmbyobj(L, p1, event); const TValue *tm2; if (ttisnil(tm1)) return -1; /* no metamethod? */ tm2 = luaT_gettmbyobj(L, p2, event); if (!luaO_rawequalObj(tm1, tm2)) /* different metamethods? */ return -1; callTMres(L, L->top, tm1, p1, p2); return !l_isfalse(L->top); } static int l_strcmp (const TString *ls, const TString *rs) { const char *l = getstr(ls); size_t ll = ls->tsv.len; const char *r = getstr(rs); size_t lr = rs->tsv.len; for (;;) { int temp = strcoll(l, r); if (temp != 0) return temp; else { /* strings are equal up to a `\0' */ size_t len = strlen(l); /* index of first `\0' in both strings */ if (len == lr) /* r is finished? */ return (len == ll) ? 0 : 1; else if (len == ll) /* l is finished? */ return -1; /* l is smaller than r (because r is not finished) */ /* both strings longer than `len'; go on comparing (after the `\0') */ len++; l += len; ll -= len; r += len; lr -= len; } } } int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { int res; if (ttype(l) != ttype(r)) return luaG_ordererror(L, l, r); else if (ttisnumber(l)) return luai_numlt(nvalue(l), nvalue(r)); else if (ttisstring(l)) return l_strcmp(rawtsvalue(l), rawtsvalue(r)) < 0; else if ((res = call_orderTM(L, l, r, TM_LT)) != -1) return res; return luaG_ordererror(L, l, r); } static int lessequal (lua_State *L, const TValue *l, const TValue *r) { int res; if (ttype(l) != ttype(r)) return luaG_ordererror(L, l, r); else if (ttisnumber(l)) return luai_numle(nvalue(l), nvalue(r)); else if (ttisstring(l)) return l_strcmp(rawtsvalue(l), rawtsvalue(r)) <= 0; else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) /* first try `le' */ return res; else if ((res = call_orderTM(L, r, l, TM_LT)) != -1) /* else try `lt' */ return !res; return luaG_ordererror(L, l, r); } int luaV_equalval (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; lua_assert(ttype(t1) == ttype(t2)); switch (ttype(t1)) { case LUA_TNIL: return 1; case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); case LUA_TUSERDATA: { if (uvalue(t1) == uvalue(t2)) return 1; tm = get_compTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } case LUA_TTABLE: { if (hvalue(t1) == hvalue(t2)) return 1; tm = get_compTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } default: return gcvalue(t1) == gcvalue(t2); } if (tm == NULL) return 0; /* no TM? */ callTMres(L, L->top, tm, t1, t2); /* call TM */ return !l_isfalse(L->top); } void luaV_concat (lua_State *L, int total, int last) { do { StkId top = L->base + last + 1; int n = 2; /* number of elements handled in this pass (at least 2) */ if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) luaG_concaterror(L, top-2, top-1); } else if (tsvalue(top-1)->len == 0) /* second op is empty? */ (void)tostring(L, top - 2); /* result is first op (as string) */ else { /* at least two string values; get as many as possible */ size_t tl = tsvalue(top-1)->len; char *buffer; int i; /* collect total length */ for (n = 1; n < total && tostring(L, top-n-1); n++) { size_t l = tsvalue(top-n-1)->len; if (l >= MAX_SIZET - tl) luaG_runerror(L, "string length overflow"); tl += l; } buffer = luaZ_openspace(L, &G(L)->buff, tl); tl = 0; for (i=n; i>0; i--) { /* concat all strings */ size_t l = tsvalue(top-i)->len; memcpy(buffer+tl, svalue(top-i), l); tl += l; } setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); } total -= n-1; /* got `n' strings to create 1 new */ last -= n-1; } while (total > 1); /* repeat until only 1 result left */ } static void Arith (lua_State *L, StkId ra, const TValue *rb, const TValue *rc, TMS op) { TValue tempb, tempc; const TValue *b, *c; if ((b = luaV_tonumber(rb, &tempb)) != NULL && (c = luaV_tonumber(rc, &tempc)) != NULL) { lua_Number nb = nvalue(b), nc = nvalue(c); switch (op) { case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break; case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break; case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break; case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break; case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break; case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break; case TM_UNM: setnvalue(ra, luai_numunm(nb)); break; default: lua_assert(0); break; } } else if (!call_binTM(L, rb, rc, ra, op)) luaG_aritherror(L, rb, rc); } /* ** some macros for common tasks in `luaV_execute' */ #define runtime_check(L, c) { if (!(c)) break; } #define RA(i) (base+GETARG_A(i)) /* to be used after possible stack reallocation */ #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) #define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i)) #define dojump(L,pc,i) {(pc) += (i); luai_threadyield(L);} #define Protect(x) { L->savedpc = pc; {x;}; base = L->base; } #define arith_op(op,tm) { \ TValue *rb = RKB(i); \ TValue *rc = RKC(i); \ if (ttisnumber(rb) && ttisnumber(rc)) { \ lua_Number nb = nvalue(rb), nc = nvalue(rc); \ setnvalue(ra, op(nb, nc)); \ } \ else \ Protect(Arith(L, ra, rb, rc, tm)); \ } void luaV_execute (lua_State *L, int nexeccalls) { LClosure *cl; StkId base; TValue *k; const Instruction *pc; reentry: /* entry point */ lua_assert(isLua(L->ci)); pc = L->savedpc; cl = &clvalue(L->ci->func)->l; base = L->base; k = cl->p->k; /* main loop of interpreter */ for (;;) { const Instruction i = *pc++; StkId ra; if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { traceexec(L, pc); if (L->status == LUA_YIELD) { /* did hook yield? */ L->savedpc = pc - 1; return; } base = L->base; } /* warning!! several calls may realloc the stack and invalidate `ra' */ ra = RA(i); lua_assert(base == L->base && L->base == L->ci->base); lua_assert(base <= L->top && L->top <= L->stack + L->stacksize); lua_assert(L->top == L->ci->top || luaG_checkopenop(i)); switch (GET_OPCODE(i)) { case OP_MOVE: { setobjs2s(L, ra, RB(i)); continue; } case OP_LOADK: { setobj2s(L, ra, KBx(i)); continue; } case OP_LOADBOOL: { setbvalue(ra, GETARG_B(i)); if (GETARG_C(i)) pc++; /* skip next instruction (if C) */ continue; } case OP_LOADNIL: { TValue *rb = RB(i); do { setnilvalue(rb--); } while (rb >= ra); continue; } case OP_GETUPVAL: { int b = GETARG_B(i); setobj2s(L, ra, cl->upvals[b]->v); continue; } case OP_GETGLOBAL: { TValue g; TValue *rb = KBx(i); sethvalue(L, &g, cl->env); lua_assert(ttisstring(rb)); Protect(luaV_gettable(L, &g, rb, ra)); continue; } case OP_GETTABLE: { Protect(luaV_gettable(L, RB(i), RKC(i), ra)); continue; } case OP_SETGLOBAL: { TValue g; sethvalue(L, &g, cl->env); lua_assert(ttisstring(KBx(i))); Protect(luaV_settable(L, &g, KBx(i), ra)); continue; } case OP_SETUPVAL: { UpVal *uv = cl->upvals[GETARG_B(i)]; setobj(L, uv->v, ra); luaC_barrier(L, uv, ra); continue; } case OP_SETTABLE: { Protect(luaV_settable(L, ra, RKB(i), RKC(i))); continue; } case OP_NEWTABLE: { int b = GETARG_B(i); int c = GETARG_C(i); sethvalue(L, ra, luaH_new(L, luaO_fb2int(b), luaO_fb2int(c))); Protect(luaC_checkGC(L)); continue; } case OP_SELF: { StkId rb = RB(i); setobjs2s(L, ra+1, rb); Protect(luaV_gettable(L, rb, RKC(i), ra)); continue; } case OP_ADD: { arith_op(luai_numadd, TM_ADD); continue; } case OP_SUB: { arith_op(luai_numsub, TM_SUB); continue; } case OP_MUL: { arith_op(luai_nummul, TM_MUL); continue; } case OP_DIV: { arith_op(luai_numdiv, TM_DIV); continue; } case OP_MOD: { arith_op(luai_nummod, TM_MOD); continue; } case OP_POW: { arith_op(luai_numpow, TM_POW); continue; } case OP_UNM: { TValue *rb = RB(i); if (ttisnumber(rb)) { lua_Number nb = nvalue(rb); setnvalue(ra, luai_numunm(nb)); } else { Protect(Arith(L, ra, rb, rb, TM_UNM)); } continue; } case OP_NOT: { int res = l_isfalse(RB(i)); /* next assignment may change this value */ setbvalue(ra, res); continue; } case OP_LEN: { const TValue *rb = RB(i); switch (ttype(rb)) { case LUA_TTABLE: { setnvalue(ra, cast_num(luaH_getn(hvalue(rb)))); break; } case LUA_TSTRING: { setnvalue(ra, cast_num(tsvalue(rb)->len)); break; } default: { /* try metamethod */ Protect( if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN)) luaG_typeerror(L, rb, "get length of"); ) } } continue; } case OP_CONCAT: { int b = GETARG_B(i); int c = GETARG_C(i); Protect(luaV_concat(L, c-b+1, c); luaC_checkGC(L)); setobjs2s(L, RA(i), base+b); continue; } case OP_JMP: { dojump(L, pc, GETARG_sBx(i)); continue; } case OP_EQ: { TValue *rb = RKB(i); TValue *rc = RKC(i); Protect( if (equalobj(L, rb, rc) == GETARG_A(i)) dojump(L, pc, GETARG_sBx(*pc)); ) pc++; continue; } case OP_LT: { Protect( if (luaV_lessthan(L, RKB(i), RKC(i)) == GETARG_A(i)) dojump(L, pc, GETARG_sBx(*pc)); ) pc++; continue; } case OP_LE: { Protect( if (lessequal(L, RKB(i), RKC(i)) == GETARG_A(i)) dojump(L, pc, GETARG_sBx(*pc)); ) pc++; continue; } case OP_TEST: { if (l_isfalse(ra) != GETARG_C(i)) dojump(L, pc, GETARG_sBx(*pc)); pc++; continue; } case OP_TESTSET: { TValue *rb = RB(i); if (l_isfalse(rb) != GETARG_C(i)) { setobjs2s(L, ra, rb); dojump(L, pc, GETARG_sBx(*pc)); } pc++; continue; } case OP_CALL: { int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra+b; /* else previous instruction set top */ L->savedpc = pc; switch (luaD_precall(L, ra, nresults)) { case PCRLUA: { nexeccalls++; goto reentry; /* restart luaV_execute over new Lua function */ } case PCRC: { /* it was a C function (`precall' called it); adjust results */ if (nresults >= 0) L->top = L->ci->top; base = L->base; continue; } default: { return; /* yield */ } } } case OP_TAILCALL: { int b = GETARG_B(i); if (b != 0) L->top = ra+b; /* else previous instruction set top */ L->savedpc = pc; lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); switch (luaD_precall(L, ra, LUA_MULTRET)) { case PCRLUA: { /* tail call: put new frame in place of previous one */ CallInfo *ci = L->ci - 1; /* previous frame */ int aux; StkId func = ci->func; StkId pfunc = (ci+1)->func; /* previous function index */ if (L->openupval) luaF_close(L, ci->base); L->base = ci->base = ci->func + ((ci+1)->base - pfunc); for (aux = 0; pfunc+aux < L->top; aux++) /* move frame down */ setobjs2s(L, func+aux, pfunc+aux); ci->top = L->top = func+aux; /* correct top */ lua_assert(L->top == L->base + clvalue(func)->l.p->maxstacksize); ci->savedpc = L->savedpc; ci->tailcalls++; /* one more call lost */ L->ci--; /* remove new frame */ goto reentry; } case PCRC: { /* it was a C function (`precall' called it) */ base = L->base; continue; } default: { return; /* yield */ } } } case OP_RETURN: { int b = GETARG_B(i); if (b != 0) L->top = ra+b-1; if (L->openupval) luaF_close(L, base); L->savedpc = pc; b = luaD_poscall(L, ra); if (--nexeccalls == 0) /* was previous function running `here'? */ return; /* no: return */ else { /* yes: continue its execution */ if (b) L->top = L->ci->top; lua_assert(isLua(L->ci)); lua_assert(GET_OPCODE(*((L->ci)->savedpc - 1)) == OP_CALL); goto reentry; } } case OP_FORLOOP: { lua_Number step = nvalue(ra+2); lua_Number idx = luai_numadd(nvalue(ra), step); /* increment index */ lua_Number limit = nvalue(ra+1); if (luai_numlt(0, step) ? luai_numle(idx, limit) : luai_numle(limit, idx)) { dojump(L, pc, GETARG_sBx(i)); /* jump back */ setnvalue(ra, idx); /* update internal index... */ setnvalue(ra+3, idx); /* ...and external index */ } continue; } case OP_FORPREP: { const TValue *init = ra; const TValue *plimit = ra+1; const TValue *pstep = ra+2; L->savedpc = pc; /* next steps may throw errors */ if (!tonumber(init, ra)) luaG_runerror(L, LUA_QL("for") " initial value must be a number"); else if (!tonumber(plimit, ra+1)) luaG_runerror(L, LUA_QL("for") " limit must be a number"); else if (!tonumber(pstep, ra+2)) luaG_runerror(L, LUA_QL("for") " step must be a number"); setnvalue(ra, luai_numsub(nvalue(ra), nvalue(pstep))); dojump(L, pc, GETARG_sBx(i)); continue; } case OP_TFORLOOP: { StkId cb = ra + 3; /* call base */ setobjs2s(L, cb+2, ra+2); setobjs2s(L, cb+1, ra+1); setobjs2s(L, cb, ra); L->top = cb+3; /* func. + 2 args (state and index) */ Protect(luaD_call(L, cb, GETARG_C(i))); L->top = L->ci->top; cb = RA(i) + 3; /* previous call may change the stack */ if (!ttisnil(cb)) { /* continue loop? */ setobjs2s(L, cb-1, cb); /* save control variable */ dojump(L, pc, GETARG_sBx(*pc)); /* jump back */ } pc++; continue; } case OP_SETLIST: { int n = GETARG_B(i); int c = GETARG_C(i); int last; Table *h; if (n == 0) { n = cast_int(L->top - ra) - 1; L->top = L->ci->top; } if (c == 0) c = cast_int(*pc++); runtime_check(L, ttistable(ra)); h = hvalue(ra); last = ((c-1)*LFIELDS_PER_FLUSH) + n; if (last > h->sizearray) /* needs more space? */ luaH_resizearray(L, h, last); /* pre-alloc it at once */ for (; n > 0; n--) { TValue *val = ra+n; setobj2t(L, luaH_setnum(L, h, last--), val); luaC_barriert(L, h, val); } continue; } case OP_CLOSE: { luaF_close(L, ra); continue; } case OP_CLOSURE: { Proto *p; Closure *ncl; int nup, j; p = cl->p->p[GETARG_Bx(i)]; nup = p->nups; ncl = luaF_newLclosure(L, nup, cl->env); ncl->l.p = p; for (j=0; jl.upvals[j] = cl->upvals[GETARG_B(*pc)]; else { lua_assert(GET_OPCODE(*pc) == OP_MOVE); ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc)); } } setclvalue(L, ra, ncl); Protect(luaC_checkGC(L)); continue; } case OP_VARARG: { int b = GETARG_B(i) - 1; int j; CallInfo *ci = L->ci; int n = cast_int(ci->base - ci->func) - cl->p->numparams - 1; if (b == LUA_MULTRET) { Protect(luaD_checkstack(L, n)); ra = RA(i); /* previous call may change the stack */ b = n; L->top = ra + n; } for (j = 0; j < b; j++) { if (j < n) { setobjs2s(L, ra + j, ci->base - n + j); } else { setnilvalue(ra + j); } } continue; } } } } redis-8.0.2/deps/lua/src/lvm.h000066400000000000000000000022071501533116600161030ustar00rootroot00000000000000/* ** $Id: lvm.h,v 2.5.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ #ifndef lvm_h #define lvm_h #include "ldo.h" #include "lobject.h" #include "ltm.h" #define tostring(L,o) ((ttype(o) == LUA_TSTRING) || (luaV_tostring(L, o))) #define tonumber(o,n) (ttype(o) == LUA_TNUMBER || \ (((o) = luaV_tonumber(o,n)) != NULL)) #define equalobj(L,o1,o2) \ (ttype(o1) == ttype(o2) && luaV_equalval(L, o1, o2)) LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_equalval (lua_State *L, const TValue *t1, const TValue *t2); LUAI_FUNC const TValue *luaV_tonumber (const TValue *obj, TValue *n); LUAI_FUNC int luaV_tostring (lua_State *L, StkId obj); LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val); LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val); LUAI_FUNC void luaV_execute (lua_State *L, int nexeccalls); LUAI_FUNC void luaV_concat (lua_State *L, int total, int last); #endif redis-8.0.2/deps/lua/src/lzio.c000066400000000000000000000031341501533116600162550ustar00rootroot00000000000000/* ** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $ ** a generic input stream interface ** See Copyright Notice in lua.h */ #include #define lzio_c #define LUA_CORE #include "lua.h" #include "llimits.h" #include "lmem.h" #include "lstate.h" #include "lzio.h" int luaZ_fill (ZIO *z) { size_t size; lua_State *L = z->L; const char *buff; lua_unlock(L); buff = z->reader(L, z->data, &size); lua_lock(L); if (buff == NULL || size == 0) return EOZ; z->n = size - 1; z->p = buff; return char2int(*(z->p++)); } int luaZ_lookahead (ZIO *z) { if (z->n == 0) { if (luaZ_fill(z) == EOZ) return EOZ; else { z->n++; /* luaZ_fill removed first byte; put back it */ z->p--; } } return char2int(*z->p); } void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { z->L = L; z->reader = reader; z->data = data; z->n = 0; z->p = NULL; } /* --------------------------------------------------------------- read --- */ size_t luaZ_read (ZIO *z, void *b, size_t n) { while (n) { size_t m; if (luaZ_lookahead(z) == EOZ) return n; /* return number of missing bytes */ m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ memcpy(b, z->p, m); z->n -= m; z->p += m; b = (char *)b + m; n -= m; } return 0; } /* ------------------------------------------------------------------------ */ char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { if (n > buff->buffsize) { if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; luaZ_resizebuffer(L, buff, n); } return buff->buffer; } redis-8.0.2/deps/lua/src/lzio.h000066400000000000000000000030241501533116600162600ustar00rootroot00000000000000/* ** $Id: lzio.h,v 1.21.1.1 2007/12/27 13:02:25 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ #ifndef lzio_h #define lzio_h #include "lua.h" #include "lmem.h" #define EOZ (-1) /* end of stream */ typedef struct Zio ZIO; #define char2int(c) cast(int, cast(unsigned char, (c))) #define zgetc(z) (((z)->n--)>0 ? char2int(*(z)->p++) : luaZ_fill(z)) typedef struct Mbuffer { char *buffer; size_t n; size_t buffsize; } Mbuffer; #define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) #define luaZ_buffer(buff) ((buff)->buffer) #define luaZ_sizebuffer(buff) ((buff)->buffsize) #define luaZ_bufflen(buff) ((buff)->n) #define luaZ_resetbuffer(buff) ((buff)->n = 0) #define luaZ_resizebuffer(L, buff, size) \ (luaM_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ (buff)->buffsize = size) #define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data); LUAI_FUNC size_t luaZ_read (ZIO* z, void* b, size_t n); /* read next n bytes */ LUAI_FUNC int luaZ_lookahead (ZIO *z); /* --------- Private Part ------------------ */ struct Zio { size_t n; /* bytes still unread */ const char *p; /* current position in buffer */ lua_Reader reader; void* data; /* additional data */ lua_State *L; /* Lua state (for reader) */ }; LUAI_FUNC int luaZ_fill (ZIO *z); #endif redis-8.0.2/deps/lua/src/print.c000066400000000000000000000115201501533116600164320ustar00rootroot00000000000000/* ** $Id: print.c,v 1.55a 2006/05/31 13:30:05 lhf Exp $ ** print bytecodes ** See Copyright Notice in lua.h */ #include #include #define luac_c #define LUA_CORE #include "ldebug.h" #include "lobject.h" #include "lopcodes.h" #include "lundump.h" #define PrintFunction luaU_print #define Sizeof(x) ((int)sizeof(x)) #define VOID(p) ((const void*)(p)) static void PrintString(const TString* ts) { const char* s=getstr(ts); size_t i,n=ts->tsv.len; putchar('"'); for (i=0; ik[i]; switch (ttype(o)) { case LUA_TNIL: printf("nil"); break; case LUA_TBOOLEAN: printf(bvalue(o) ? "true" : "false"); break; case LUA_TNUMBER: printf(LUA_NUMBER_FMT,nvalue(o)); break; case LUA_TSTRING: PrintString(rawtsvalue(o)); break; default: /* cannot happen */ printf("? type=%d",ttype(o)); break; } } static void PrintCode(const Proto* f) { const Instruction* code=f->code; int pc,n=f->sizecode; for (pc=0; pc0) printf("[%d]\t",line); else printf("[-]\t"); printf("%-9s\t",luaP_opnames[o]); switch (getOpMode(o)) { case iABC: printf("%d",a); if (getBMode(o)!=OpArgN) printf(" %d",ISK(b) ? (-1-INDEXK(b)) : b); if (getCMode(o)!=OpArgN) printf(" %d",ISK(c) ? (-1-INDEXK(c)) : c); break; case iABx: if (getBMode(o)==OpArgK) printf("%d %d",a,-1-bx); else printf("%d %d",a,bx); break; case iAsBx: if (o==OP_JMP) printf("%d",sbx); else printf("%d %d",a,sbx); break; } switch (o) { case OP_LOADK: printf("\t; "); PrintConstant(f,bx); break; case OP_GETUPVAL: case OP_SETUPVAL: printf("\t; %s", (f->sizeupvalues>0) ? getstr(f->upvalues[b]) : "-"); break; case OP_GETGLOBAL: case OP_SETGLOBAL: printf("\t; %s",svalue(&f->k[bx])); break; case OP_GETTABLE: case OP_SELF: if (ISK(c)) { printf("\t; "); PrintConstant(f,INDEXK(c)); } break; case OP_SETTABLE: case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_POW: case OP_EQ: case OP_LT: case OP_LE: if (ISK(b) || ISK(c)) { printf("\t; "); if (ISK(b)) PrintConstant(f,INDEXK(b)); else printf("-"); printf(" "); if (ISK(c)) PrintConstant(f,INDEXK(c)); else printf("-"); } break; case OP_JMP: case OP_FORLOOP: case OP_FORPREP: printf("\t; to %d",sbx+pc+2); break; case OP_CLOSURE: printf("\t; %p",VOID(f->p[bx])); break; case OP_SETLIST: if (c==0) printf("\t; %d",(int)code[++pc]); else printf("\t; %d",c); break; default: break; } printf("\n"); } } #define SS(x) (x==1)?"":"s" #define S(x) x,SS(x) static void PrintHeader(const Proto* f) { const char* s=getstr(f->source); if (*s=='@' || *s=='=') s++; else if (*s==LUA_SIGNATURE[0]) s="(bstring)"; else s="(string)"; printf("\n%s <%s:%d,%d> (%d instruction%s, %d bytes at %p)\n", (f->linedefined==0)?"main":"function",s, f->linedefined,f->lastlinedefined, S(f->sizecode),f->sizecode*Sizeof(Instruction),VOID(f)); printf("%d%s param%s, %d slot%s, %d upvalue%s, ", f->numparams,f->is_vararg?"+":"",SS(f->numparams), S(f->maxstacksize),S(f->nups)); printf("%d local%s, %d constant%s, %d function%s\n", S(f->sizelocvars),S(f->sizek),S(f->sizep)); } static void PrintConstants(const Proto* f) { int i,n=f->sizek; printf("constants (%d) for %p:\n",n,VOID(f)); for (i=0; isizelocvars; printf("locals (%d) for %p:\n",n,VOID(f)); for (i=0; ilocvars[i].varname),f->locvars[i].startpc+1,f->locvars[i].endpc+1); } } static void PrintUpvalues(const Proto* f) { int i,n=f->sizeupvalues; printf("upvalues (%d) for %p:\n",n,VOID(f)); if (f->upvalues==NULL) return; for (i=0; iupvalues[i])); } } void PrintFunction(const Proto* f, int full) { int i,n=f->sizep; PrintHeader(f); PrintCode(f); if (full) { PrintConstants(f); PrintLocals(f); PrintUpvalues(f); } for (i=0; ip[i],full); } redis-8.0.2/deps/lua/src/strbuf.c000066400000000000000000000106261501533116600166110ustar00rootroot00000000000000/* strbuf - String buffer routines * * Copyright (c) 2010-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include "strbuf.h" static void die(const char *fmt, ...) { va_list arg; va_start(arg, fmt); vfprintf(stderr, fmt, arg); va_end(arg); fprintf(stderr, "\n"); abort(); } void strbuf_init(strbuf_t *s, size_t len) { size_t size; if (!len) size = STRBUF_DEFAULT_SIZE; else size = len + 1; if (size < len) die("Overflow, len: %zu", len); s->buf = NULL; s->size = size; s->length = 0; s->dynamic = 0; s->reallocs = 0; s->debug = 0; s->buf = malloc(size); if (!s->buf) die("Out of memory"); strbuf_ensure_null(s); } strbuf_t *strbuf_new(size_t len) { strbuf_t *s; s = malloc(sizeof(strbuf_t)); if (!s) die("Out of memory"); strbuf_init(s, len); /* Dynamic strbuf allocation / deallocation */ s->dynamic = 1; return s; } static inline void debug_stats(strbuf_t *s) { if (s->debug) { fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n", (long)s, s->reallocs, s->length, s->size); } } /* If strbuf_t has not been dynamically allocated, strbuf_free() can * be called any number of times strbuf_init() */ void strbuf_free(strbuf_t *s) { debug_stats(s); if (s->buf) { free(s->buf); s->buf = NULL; } if (s->dynamic) free(s); } char *strbuf_free_to_string(strbuf_t *s, size_t *len) { char *buf; debug_stats(s); strbuf_ensure_null(s); buf = s->buf; if (len) *len = s->length; if (s->dynamic) free(s); return buf; } static size_t calculate_new_size(strbuf_t *s, size_t len) { size_t reqsize, newsize; if (len <= 0) die("BUG: Invalid strbuf length requested"); /* Ensure there is room for optional NULL termination */ reqsize = len + 1; if (reqsize < len) die("Overflow, len: %zu", len); /* If the user has requested to shrink the buffer, do it exactly */ if (s->size > reqsize) return reqsize; newsize = s->size; if (reqsize >= SIZE_MAX / 2) { newsize = reqsize; } else { /* Exponential sizing */ while (newsize < reqsize) newsize *= 2; } if (newsize < reqsize) die("BUG: strbuf length would overflow, len: %zu", len); return newsize; } /* Ensure strbuf can handle a string length bytes long (ignoring NULL * optional termination). */ void strbuf_resize(strbuf_t *s, size_t len) { size_t newsize; newsize = calculate_new_size(s, len); if (s->debug > 1) { fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n", (long)s, s->size, newsize); } s->size = newsize; s->buf = realloc(s->buf, s->size); if (!s->buf) die("Out of memory, len: %zu", len); s->reallocs++; } void strbuf_append_string(strbuf_t *s, const char *str) { size_t i, space; space = strbuf_empty_length(s); for (i = 0; str[i]; i++) { if (space < 1) { strbuf_resize(s, s->length + 1); space = strbuf_empty_length(s); } s->buf[s->length] = str[i]; s->length++; space--; } } /* vi:ai et sw=4 ts=4: */ redis-8.0.2/deps/lua/src/strbuf.h000066400000000000000000000076761501533116600166310ustar00rootroot00000000000000/* strbuf - String buffer routines * * Copyright (c) 2010-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include /* Size: Total bytes allocated to *buf * Length: String length, excluding optional NULL terminator. * Dynamic: True if created via strbuf_new() */ typedef struct { char *buf; size_t size; size_t length; int dynamic; int reallocs; int debug; } strbuf_t; #ifndef STRBUF_DEFAULT_SIZE #define STRBUF_DEFAULT_SIZE 1023 #endif /* Initialise */ extern strbuf_t *strbuf_new(size_t len); extern void strbuf_init(strbuf_t *s, size_t len); /* Release */ extern void strbuf_free(strbuf_t *s); extern char *strbuf_free_to_string(strbuf_t *s, size_t *len); /* Management */ extern void strbuf_resize(strbuf_t *s, size_t len); static size_t strbuf_empty_length(strbuf_t *s); static size_t strbuf_length(strbuf_t *s); static char *strbuf_string(strbuf_t *s, size_t *len); static void strbuf_ensure_empty_length(strbuf_t *s, size_t len); static char *strbuf_empty_ptr(strbuf_t *s); static void strbuf_extend_length(strbuf_t *s, size_t len); /* Update */ static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len); extern void strbuf_append_string(strbuf_t *s, const char *str); static void strbuf_append_char(strbuf_t *s, const char c); static void strbuf_ensure_null(strbuf_t *s); /* Reset string for before use */ static inline void strbuf_reset(strbuf_t *s) { s->length = 0; } static inline int strbuf_allocated(strbuf_t *s) { return s->buf != NULL; } /* Return bytes remaining in the string buffer * Ensure there is space for a NULL terminator. */ static inline size_t strbuf_empty_length(strbuf_t *s) { return s->size - s->length - 1; } static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len) { if (len > strbuf_empty_length(s)) strbuf_resize(s, s->length + len); } static inline char *strbuf_empty_ptr(strbuf_t *s) { return s->buf + s->length; } static inline void strbuf_extend_length(strbuf_t *s, size_t len) { s->length += len; } static inline size_t strbuf_length(strbuf_t *s) { return s->length; } static inline void strbuf_append_char(strbuf_t *s, const char c) { strbuf_ensure_empty_length(s, 1); s->buf[s->length++] = c; } static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) { s->buf[s->length++] = c; } static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len) { strbuf_ensure_empty_length(s, len); memcpy(s->buf + s->length, c, len); s->length += len; } static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len) { memcpy(s->buf + s->length, c, len); s->length += len; } static inline void strbuf_ensure_null(strbuf_t *s) { s->buf[s->length] = 0; } static inline char *strbuf_string(strbuf_t *s, size_t *len) { if (len) *len = s->length; return s->buf; } /* vi:ai et sw=4 ts=4: */ redis-8.0.2/deps/lua/test/000077500000000000000000000000001501533116600153235ustar00rootroot00000000000000redis-8.0.2/deps/lua/test/README000066400000000000000000000022311501533116600162010ustar00rootroot00000000000000These are simple tests for Lua. Some of them contain useful code. They are meant to be run to make sure Lua is built correctly and also to be read, to see how Lua programs look. Here is a one-line summary of each program: bisect.lua bisection method for solving non-linear equations cf.lua temperature conversion table (celsius to farenheit) echo.lua echo command line arguments env.lua environment variables as automatic global variables factorial.lua factorial without recursion fib.lua fibonacci function with cache fibfor.lua fibonacci numbers with coroutines and generators globals.lua report global variable usage hello.lua the first program in every language life.lua Conway's Game of Life luac.lua bare-bones luac printf.lua an implementation of printf readonly.lua make global variables readonly sieve.lua the sieve of of Eratosthenes programmed with coroutines sort.lua two implementations of a sort function table.lua make table, grouping all data for the same item trace-calls.lua trace calls trace-globals.lua trace assigments to global variables xd.lua hex dump redis-8.0.2/deps/lua/test/bisect.lua000066400000000000000000000012051501533116600172750ustar00rootroot00000000000000-- bisection method for solving non-linear equations delta=1e-6 -- tolerance function bisect(f,a,b,fa,fb) local c=(a+b)/2 io.write(n," c=",c," a=",a," b=",b,"\n") if c==a or c==b or math.abs(a-b) posted to lua-l -- modified to use ANSI terminal escape sequences -- modified to use for instead of while local write=io.write ALIVE="¥" DEAD="þ" ALIVE="O" DEAD="-" function delay() -- NOTE: SYSTEM-DEPENDENT, adjust as necessary for i=1,10000 do end -- local i=os.clock()+1 while(os.clock() 0 do local xm1,x,xp1,xi=self.w-1,self.w,1,self.w while xi > 0 do local sum = self[ym1][xm1] + self[ym1][x] + self[ym1][xp1] + self[y][xm1] + self[y][xp1] + self[yp1][xm1] + self[yp1][x] + self[yp1][xp1] next[y][x] = ((sum==2) and self[y][x]) or ((sum==3) and 1) or 0 xm1,x,xp1,xi = x,xp1,xp1+1,xi-1 end ym1,y,yp1,yi = y,yp1,yp1+1,yi-1 end end -- output the array to screen function _CELLS:draw() local out="" -- accumulate to reduce flicker for y=1,self.h do for x=1,self.w do out=out..(((self[y][x]>0) and ALIVE) or DEAD) end out=out.."\n" end write(out) end -- constructor function CELLS(w,h) local c = ARRAY2D(w,h) c.spawn = _CELLS.spawn c.evolve = _CELLS.evolve c.draw = _CELLS.draw return c end -- -- shapes suitable for use with spawn() above -- HEART = { 1,0,1,1,0,1,1,1,1; w=3,h=3 } GLIDER = { 0,0,1,1,0,1,0,1,1; w=3,h=3 } EXPLODE = { 0,1,0,1,1,1,1,0,1,0,1,0; w=3,h=4 } FISH = { 0,1,1,1,1,1,0,0,0,1,0,0,0,0,1,1,0,0,1,0; w=5,h=4 } BUTTERFLY = { 1,0,0,0,1,0,1,1,1,0,1,0,0,0,1,1,0,1,0,1,1,0,0,0,1; w=5,h=5 } -- the main routine function LIFE(w,h) -- create two arrays local thisgen = CELLS(w,h) local nextgen = CELLS(w,h) -- create some life -- about 1000 generations of fun, then a glider steady-state thisgen:spawn(GLIDER,5,4) thisgen:spawn(EXPLODE,25,10) thisgen:spawn(FISH,4,12) -- run until break local gen=1 write("\027[2J") -- ANSI clear screen while 1 do thisgen:evolve(nextgen) thisgen,nextgen = nextgen,thisgen write("\027[H") -- ANSI home cursor thisgen:draw() write("Life - generation ",gen,"\n") gen=gen+1 if gen>2000 then break end --delay() -- no delay end end LIFE(40,20) redis-8.0.2/deps/lua/test/luac.lua000066400000000000000000000003521501533116600167520ustar00rootroot00000000000000-- bare-bones luac in Lua -- usage: lua luac.lua file.lua assert(arg[1]~=nil and arg[2]==nil,"usage: lua luac.lua file.lua") f=assert(io.open("luac.out","wb")) assert(f:write(string.dump(assert(loadfile(arg[1]))))) assert(f:close()) redis-8.0.2/deps/lua/test/printf.lua000066400000000000000000000002511501533116600173260ustar00rootroot00000000000000-- an implementation of printf function printf(...) io.write(string.format(...)) end printf("Hello %s from %s on %s\n",os.getenv"USER" or "there",_VERSION,os.date()) redis-8.0.2/deps/lua/test/readonly.lua000066400000000000000000000004041501533116600176410ustar00rootroot00000000000000-- make global variables readonly local f=function (t,i) error("cannot redefine global variable `"..i.."'",2) end local g={} local G=getfenv() setmetatable(g,{__index=G,__newindex=f}) setfenv(1,g) -- an example rawset(g,"x",3) x=2 y=1 -- cannot redefine `y' redis-8.0.2/deps/lua/test/sieve.lua000066400000000000000000000014061501533116600171420ustar00rootroot00000000000000-- the sieve of of Eratosthenes programmed with coroutines -- typical usage: lua -e N=1000 sieve.lua | column -- generate all the numbers from 2 to n function gen (n) return coroutine.wrap(function () for i=2,n do coroutine.yield(i) end end) end -- filter the numbers generated by `g', removing multiples of `p' function filter (p, g) return coroutine.wrap(function () while 1 do local n = g() if n == nil then return end if math.mod(n, p) ~= 0 then coroutine.yield(n) end end end) end N=N or 1000 -- from command line x = gen(N) -- generate primes up to N while 1 do local n = x() -- pick a number until done if n == nil then break end print(n) -- must be a prime number x = filter(n, x) -- now remove its multiples end redis-8.0.2/deps/lua/test/sort.lua000066400000000000000000000027261501533116600170240ustar00rootroot00000000000000-- two implementations of a sort function -- this is an example only. Lua has now a built-in function "sort" -- extracted from Programming Pearls, page 110 function qsort(x,l,u,f) if ly end) show("after reverse selection sort",x) qsort(x,1,n,function (x,y) return x>> ",string.rep(" ",level)) if t~=nil and t.currentline>=0 then io.write(t.short_src,":",t.currentline," ") end t=debug.getinfo(2) if event=="call" then level=level+1 else level=level-1 if level<0 then level=0 end end if t.what=="main" then if event=="call" then io.write("begin ",t.short_src) else io.write("end ",t.short_src) end elseif t.what=="Lua" then -- table.foreach(t,print) io.write(event," ",t.name or "(Lua)"," <",t.linedefined,":",t.short_src,">") else io.write(event," ",t.name or "(C)"," [",t.what,"] ") end io.write("\n") end debug.sethook(hook,"cr") level=0 redis-8.0.2/deps/lua/test/trace-globals.lua000066400000000000000000000013301501533116600205420ustar00rootroot00000000000000-- trace assigments to global variables do -- a tostring that quotes strings. note the use of the original tostring. local _tostring=tostring local tostring=function(a) if type(a)=="string" then return string.format("%q",a) else return _tostring(a) end end local log=function (name,old,new) local t=debug.getinfo(3,"Sl") local line=t.currentline io.write(t.short_src) if line>=0 then io.write(":",line) end io.write(": ",name," is now ",tostring(new)," (was ",tostring(old),")","\n") end local g={} local set=function (t,name,value) log(name,g[name],value) g[name]=value end setmetatable(getfenv(),{__index=g,__newindex=set}) end -- an example a=1 b=2 a=10 b=20 b=nil b=200 print(a,b,c) redis-8.0.2/deps/lua/test/xd.lua000066400000000000000000000005541501533116600164450ustar00rootroot00000000000000-- hex dump -- usage: lua xd.lua < file local offset=0 while true do local s=io.read(16) if s==nil then return end io.write(string.format("%08X ",offset)) string.gsub(s,"(.)", function (c) io.write(string.format("%02X ",string.byte(c))) end) io.write(string.rep(" ",3*(16-string.len(s)))) io.write(" ",string.gsub(s,"%c","."),"\n") offset=offset+16 end redis-8.0.2/modules/000077500000000000000000000000001501533116600143005ustar00rootroot00000000000000redis-8.0.2/modules/Makefile000066400000000000000000000055211501533116600157430ustar00rootroot00000000000000 SUBDIRS = redisjson redistimeseries redisbloom redisearch define submake for dir in $(SUBDIRS); do $(MAKE) -C $$dir $(1); done endef all: prepare_source $(call submake,$@) get_source: $(call submake,$@) prepare_source: get_source handle-werrors setup_environment clean: $(call submake,$@) distclean: clean_environment $(call submake,$@) pristine: $(call submake,$@) install: $(call submake,$@) setup_environment: install-rust handle-werrors clean_environment: uninstall-rust # Keep all of the Rust stuff in one place install-rust: ifeq ($(INSTALL_RUST_TOOLCHAIN),yes) @RUST_VERSION=1.80.1; \ 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}"; \ case "$${ARCH}" in \ 'x86_64') \ if [ "$${LIBC_TYPE}" = "musl" ]; then \ RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-musl"; \ RUST_SHA256="37bbec6a7b9f55fef79c451260766d281a7a5b9d2e65c348bbc241127cf34c8d"; \ else \ RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-gnu"; \ RUST_SHA256="85e936d5d36970afb80756fa122edcc99bd72a88155f6bdd514f5d27e778e00a"; \ fi ;; \ 'aarch64') \ if [ "$${LIBC_TYPE}" = "musl" ]; then \ RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-musl"; \ RUST_SHA256="dd668c2d82f77c5458deb023932600fae633fff8d7f876330e01bc47e9976d17"; \ else \ RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-gnu"; \ RUST_SHA256="2e89bad7857711a1c11d017ea28fbfeec54076317763901194f8f5decbac1850"; \ 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; }; \ tar -xf $${RUST_INSTALLER}.tar.xz; \ (cd $${RUST_INSTALLER} && ./install.sh); \ rm -rf $${RUST_INSTALLER} endif uninstall-rust: ifeq ($(INSTALL_RUST_TOOLCHAIN),yes) @if [ -x "/usr/local/lib/rustlib/uninstall.sh" ]; then \ echo "Uninstalling Rust using uninstall.sh script"; \ rm -rf ~/.cargo; \ /usr/local/lib/rustlib/uninstall.sh; \ else \ echo "WARNING: Rust toolchain not found or uninstall script is missing."; \ fi endif handle-werrors: get_source ifeq ($(DISABLE_WERRORS),yes) @echo "Disabling -Werror for all modules" @for dir in $(SUBDIRS); do \ echo "Processing $$dir"; \ find $$dir/src -type f \ \( -name "Makefile" \ -o -name "*.mk" \ -o -name "CMakeLists.txt" \) \ -exec sed -i 's/-Werror//g' {} +; \ done endif .PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors redis-8.0.2/modules/common.mk000066400000000000000000000022771501533116600161310ustar00rootroot00000000000000PREFIX ?= /usr/local INSTALL_DIR ?= $(DESTDIR)$(PREFIX)/lib/redis/modules INSTALL ?= install # This logic *partially* follows the current module build system. It is a bit awkward and # should be changed if/when the modules' build process is refactored. ARCH_MAP_x86_64 := x64 ARCH_MAP_i386 := x86 ARCH_MAP_i686 := x86 ARCH_MAP_aarch64 := arm64v8 ARCH_MAP_arm64 := arm64v8 OS := $(shell uname -s | tr '[:upper:]' '[:lower:]') ARCH := $(ARCH_MAP_$(shell uname -m)) ifeq ($(ARCH),) $(error Unrecognized CPU architecture $(shell uname -m)) endif FULL_VARIANT := $(OS)-$(ARCH)-release # Common rules for all modules, based on per-module configuration all: $(TARGET_MODULE) $(TARGET_MODULE): get_source $(MAKE) -C $(SRC_DIR) cp ${TARGET_MODULE} ./ get_source: $(SRC_DIR)/.prepared $(SRC_DIR)/.prepared: mkdir -p $(SRC_DIR) git clone --recursive --depth 1 --branch $(MODULE_VERSION) $(MODULE_REPO) $(SRC_DIR) touch $@ clean: -$(MAKE) -C $(SRC_DIR) clean -rm -f ./*.so distclean: clean -$(MAKE) -C $(SRC_DIR) distclean pristine: -rm -rf $(SRC_DIR) install: $(TARGET_MODULE) mkdir -p $(INSTALL_DIR) $(INSTALL) -m 0755 -D $(TARGET_MODULE) $(INSTALL_DIR) .PHONY: all clean distclean pristine install redis-8.0.2/modules/redisbloom/000077500000000000000000000000001501533116600164375ustar00rootroot00000000000000redis-8.0.2/modules/redisbloom/Makefile000066400000000000000000000002601501533116600200750ustar00rootroot00000000000000SRC_DIR = src MODULE_VERSION = v8.0.1 MODULE_REPO = https://github.com/redisbloom/redisbloom TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so include ../common.mk redis-8.0.2/modules/redisearch/000077500000000000000000000000001501533116600164115ustar00rootroot00000000000000redis-8.0.2/modules/redisearch/Makefile000066400000000000000000000003021501533116600200440ustar00rootroot00000000000000SRC_DIR = src MODULE_VERSION = v8.0.1 MODULE_REPO = https://github.com/redisearch/redisearch TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so include ../common.mk redis-8.0.2/modules/redisjson/000077500000000000000000000000001501533116600163005ustar00rootroot00000000000000redis-8.0.2/modules/redisjson/Makefile000066400000000000000000000004131501533116600177360ustar00rootroot00000000000000SRC_DIR = src MODULE_VERSION = v8.0.1 MODULE_REPO = https://github.com/redisjson/redisjson TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so include ../common.mk $(SRC_DIR)/.cargo_fetched: cd $(SRC_DIR) && cargo fetch get_source: $(SRC_DIR)/.cargo_fetched redis-8.0.2/modules/redistimeseries/000077500000000000000000000000001501533116600175005ustar00rootroot00000000000000redis-8.0.2/modules/redistimeseries/Makefile000066400000000000000000000002771501533116600211460ustar00rootroot00000000000000SRC_DIR = src MODULE_VERSION = v8.0.1 MODULE_REPO = https://github.com/redistimeseries/redistimeseries TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so include ../common.mk redis-8.0.2/modules/vector-sets/000077500000000000000000000000001501533116600165565ustar00rootroot00000000000000redis-8.0.2/modules/vector-sets/.gitignore000066400000000000000000000001131501533116600205410ustar00rootroot00000000000000__pycache__ misc *.so *.xo *.o .DS_Store w2v word2vec.bin TODO *.txt *.rdb redis-8.0.2/modules/vector-sets/Makefile000066400000000000000000000035501501533116600202210ustar00rootroot00000000000000# Compiler settings CC = cc ifdef SANITIZER ifeq ($(SANITIZER),address) SAN=-fsanitize=address else ifeq ($(SANITIZER),undefined) SAN=-fsanitize=undefined else ifeq ($(SANITIZER),thread) SAN=-fsanitize=thread else $(error "unknown sanitizer=${SANITIZER}") endif endif endif endif CFLAGS = -O2 -Wall -Wextra -g $(SAN) -std=c11 LDFLAGS = -lm $(SAN) # Detect OS uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') # Shared library compile flags for linux / osx ifeq ($(uname_S),Linux) SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c11 -O2 SHOBJ_LDFLAGS ?= -shared ifneq (,$(findstring armv,$(uname_M))) SHOBJ_LDFLAGS += -latomic endif ifneq (,$(findstring aarch64,$(uname_M))) SHOBJ_LDFLAGS += -latomic endif else SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c11 -O3 SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup endif # OS X 11.x doesn't have /usr/lib/libSystem.dylib and needs an explicit setting. ifeq ($(uname_S),Darwin) ifeq ("$(wildcard /usr/lib/libSystem.dylib)","") LIBS = -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lsystem endif endif .SUFFIXES: .c .so .xo .o all: vset.so .c.xo: $(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@ vset.xo: ../../src/redismodule.h expr.c vset.so: vset.xo hnsw.xo $(CC) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) $(SAN) -lc # Example sources / objects SRCS = hnsw.c w2v.c OBJS = $(SRCS:.c=.o) TARGET = w2v MODULE = vset.so # Default target all: $(TARGET) $(MODULE) # Example linking rule $(TARGET): $(OBJS) $(CC) $(OBJS) $(LDFLAGS) -o $(TARGET) # Compilation rule for object files %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ expr-test: expr.c fastjson.c fastjson_test.c $(CC) $(CFLAGS) expr.c -o expr-test -DTEST_MAIN -lm # Clean rule clean: rm -f $(TARGET) $(OBJS) *.xo *.so # Declare phony targets .PHONY: all clean redis-8.0.2/modules/vector-sets/README.md000066400000000000000000001120701501533116600200360ustar00rootroot00000000000000**IMPORTANT:** *Please note that this is a merged module, it's part of the Redis binary now, and you don't need to build it and load it into Redis. Compiling Redis version 8 or greater will result into having the Vector Sets commands available. However, you could compile this module as a shared library in order to load it in older versions of Redis.* This module implements Vector Sets for Redis, a new Redis data type similar to Sorted Sets but having string elements associated to a vector instead of a score. The fundamental goal of Vector Sets is to make possible adding items, and later get a subset of the added items that are the most similar to a specified vector (often a learned embedding), or the most similar to the vector of an element that is already part of the Vector Set. Moreover, Vector sets implement optional filtered search capabilities: it is possible to associate attributes to all or to a subset of elements in the set, and then, using the `FILTER` option of the `VSIM` command, to ask for items similar to a given vector but also passing a filter specified as a simple mathematical expression (Like `".year > 1950"` or similar). This means that **you can have vector similarity and scalar filters at the same time**. ## Installation **WARNING:** If you are running **Redis 8.0 RC1 or greater** you don't need to install anything, just compile Redis, and the Vector Sets commands will be part of the default install. Otherwise to test Vector Sets with older Redis versions follow the following instructions. Build with: make Then load the module with the following command line, or by inserting the needed directives in the `redis.conf` file. ./redis-server --loadmodule vset.so To run tests, I suggest using this: ./redis-server --save "" --enable-debug-command yes The execute the tests with: ./test.py ## Reference of available commands **VADD: add items into a vector set** VADD key [REDUCE dim] FP32|VALUES vector element [CAS] [NOQUANT | Q8 | BIN] [EF build-exploration-factor] [SETATTR ] [M ] Add a new element into the vector set specified by the key. The vector can be provided as FP32 blob of values, or as floating point numbers as strings, prefixed by the number of elements (3 in the example): VADD mykey VALUES 3 0.1 1.2 0.5 my-element Meaning of the options: `REDUCE` implements random projection, in order to reduce the dimensionality of the vector. The projection matrix is saved and reloaded along with the vector set. **Please note that** the `REDUCE` option must be passed immediately before the vector, like in `REDUCE 50 VALUES ...`. `CAS` performs the operation partially using threads, in a check-and-set style. The neighbor candidates collection, which is slow, is 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. `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. `EF` plays a role in the effort made to find good candidates when connecting the new node to the existing HNSW graph. The default is 200. Using a larger value, may help to have a better recall. To improve the recall it is also possible to increase `EF` during `VSIM` searches. `SETATTR` associates attributes to the newly created entry or update the entry attributes (if it already exists). It is the same as calling the `VSETATTR` attribute separately, so please check the documentation of that command in the filtered search section of this documentation. `M` defaults to 16 and is the HNSW famous `M` parameters. It is the maximum number of connections that each node of the graph have with other nodes: more connections mean more memory, but a better ability to explore the graph. Nodes at layer zero (every node exists at least at layer zero) have `M*2` connections, while the other layers only have `M` connections. This means that, for instance, an `M` of 64 will use at least 1024 bytes of memory for each node! That is, `64 links * 2 times * 8 bytes pointers`, and even more, since on average each node has something like 1.33 layers (but the other layers have just `M` connections, instead of `M*2`). If you don't have a recall quality problem, the default is fine, and uses a limited amount of memory. **VSIM: return elements by vector similarity** VSIM key [ELE|FP32|VALUES] [WITHSCORES] [COUNT num] [EF search-exploration-factor] [FILTER expression] [FILTER-EF max-filtering-effort] [TRUTH] [NOTHREAD] The command returns similar vectors, for simplicity (and verbosity) in the following example, instead of providing a vector using FP32 or VALUES (like in `VADD`), we will ask for elements having a vector similar to a given element already in the sorted set: > VSIM word_embeddings ELE apple 1) "apple" 2) "apples" 3) "pear" 4) "fruit" 5) "berry" 6) "pears" 7) "strawberry" 8) "peach" 9) "potato" 10) "grape" It is possible to specify a `COUNT` and also to get the similarity score (from 1 to 0, where 1 is identical, 0 is opposite vector) between the query and the returned items. > VSIM word_embeddings ELE apple WITHSCORES COUNT 3 1) "apple" 2) "0.9998867657923256" 3) "apples" 4) "0.8598527610301971" 5) "pear" 6) "0.8226882219314575" The `EF` argument is the exploration factor: the higher it is, the slower the command becomes, but the better the index is explored to find nodes that are near to our query. Sensible values are from 50 to 1000. The `TRUTH` option forces the command to perform a linear scan of all the entries inside the set, without using the graph search inside the HNSW, so it returns the best matching elements (the perfect result set) that can be used in order to easily calculate the recall. Of course the linear scan is `O(N)`, so it is much slower than the `log(N)` (considering a small `COUNT`) provided by the HNSW index. The `NOTHREAD` option forces the command to execute the search on the data structure in the main thread. Normally `VSIM` spawns a thread instead. This may be useful for benchmarking purposes, or when we work with extremely small vector sets and don't want to pay the cost of spawning a thread. It is possible that in the future this option will be automatically used by Redis when we detect small vector sets. Note that this option blocks the server for all the time needed to complete the command, so it is a source of potential latency issues: if you are in doubt, never use it. For `FILTER` and `FILTER-EF` options, please check the filtered search section of this documentation. **VDIM: return the dimension of the vectors inside the vector set** VDIM keyname Example: > VDIM word_embeddings (integer) 300 Note that in the case of vectors that were populated using the `REDUCE` option, for random projection, the vector set will report the size of the projected (reduced) dimension. Yet the user should perform all the queries using full-size vectors. **VCARD: return the number of elements in a vector set** VCARD key Example: > VCARD word_embeddings (integer) 3000000 **VREM: remove elements from vector set** VREM key element Example: > VADD vset VALUES 3 1 0 1 bar (integer) 1 > VREM vset bar (integer) 1 > VREM vset bar (integer) 0 VREM does not perform thumstone / logical deletion, but will actually reclaim the memory from the vector set, so it is save to add and remove elements in a vector set in the context of long running applications that continuously update the same index. **VEMB: return the approximated vector of an element** VEMB key element Example: > VEMB word_embeddings SQL 1) "0.18208661675453186" 2) "0.08535309880971909" 3) "0.1365649551153183" 4) "-0.16501599550247192" 5) "0.14225517213344574" ... 295 more elements ... Because vector sets perform insertion time normalization and optional quantization, the returned vector could be approximated. `VEMB` will take care to de-quantized and de-normalize the vector before returning it. It is possible to ask VEMB to return raw data, that is, the internal representation used by the vector: fp32, int8, or a bitmap for binary quantization. This behavior is triggered by the `RAW` option of of VEMB: VEMB word_embedding apple RAW In this case the return value of the command is an array of three or more elements: 1. The name of the quantization used, that is one of: "fp32", "bin", "q8". 2. The a string blob containing the raw data, 4 bytes fp32 floats for fp32, a bitmap for binary quants, or int8 bytes array for q8 quants. 3. A float representing the l2 of the vector before normalization. You need to multiply by this vector if you want to de-normalize the value for any reason. For q8 quantization, an additional elements is also returned: the quantization range, so the integers from -127 to 127 represent (normalized) components in the range `-range`, `+range`. **VISMEMBER: test if a given element already exists** This command will return 1 (or true) if the specified element is already in the vector set, otherwise 0 (or false) is returned. VISMEMBER key element As with other existence check Redis commands, if the key does not exist it is considered as if it was empty, thus the element is reported as non existing. **VLINKS: introspection command that shows neighbors for a node** VLINKS key element [WITHSCORES] The command reports the neighbors for each level. **VINFO: introspection command that shows info about a vector set** VINFO key Example: > VINFO word_embeddings 1) quant-type 2) int8 3) vector-dim 4) (integer) 300 5) size 6) (integer) 3000000 7) max-level 8) (integer) 12 9) vset-uid 10) (integer) 1 11) hnsw-max-node-uid 12) (integer) 3000000 **VSETATTR: associate or remove the JSON attributes of elements** VSETATTR key element "{... json ...}" Each element of a vector set can be optionally associated with a JSON string in order to use the `FILTER` option of `VSIM` to filter elements by scalars (see the filtered search section for more information). This command can set, update (if already set) or delete (if you set to an empty string) the associated JSON attributes of an element. The command returns 0 if the element or the key don't exist, without raising an error, otherwise 1 is returned, and the element attributes are set or updated. **VGETATTR: retrieve the JSON attributes of elements** VGETATTR key element The command returns the JSON attribute associated with an element, or null if there is no element associated, or no element at all, or no key. **VRANDMEMBER: return random members from a vector set** VRANDMEMBER key [count] Return one or more random elements from a vector set. The semantics of this command are similar to Redis's native SRANDMEMBER command: - When called without count, returns a single random element from the set, as a single string (no array reply). - When called with a positive count, returns up to count distinct random elements (no duplicates). - When called with a negative count, returns count random elements, potentially with duplicates. - If the count value is larger than the set size (and positive), only the entire set is returned. If the key doesn't exist, returns a Null reply if count is not given, or an empty array if a count is provided. Examples: > VADD vset VALUES 3 1 0 0 elem1 (integer) 1 > VADD vset VALUES 3 0 1 0 elem2 (integer) 1 > VADD vset VALUES 3 0 0 1 elem3 (integer) 1 # Return a single random element > VRANDMEMBER vset "elem2" # Return 2 distinct random elements > VRANDMEMBER vset 2 1) "elem1" 2) "elem3" # Return 3 random elements with possible duplicates > VRANDMEMBER vset -3 1) "elem2" 2) "elem2" 3) "elem1" # Return more elements than in the set (returns all elements) > VRANDMEMBER vset 10 1) "elem1" 2) "elem2" 3) "elem3" # When key doesn't exist > VRANDMEMBER nonexistent (nil) > VRANDMEMBER nonexistent 3 (empty array) This command is particularly useful for: 1. Selecting random samples from a vector set for testing or training. 2. Performance testing by retrieving random elements for subsequent similarity searches. When the user asks for unique elements (positev count) the implementation optimizes for two scenarios: - For small sample sizes (less than 20% of the set size), it uses a dictionary to avoid duplicates, and performs a real random walk inside the graph. - For large sample sizes (more than 20% of the set size), it starts from a random node and sequentially traverses the internal list, providing faster performances but not really "random" elements. The command has `O(N)` worst-case time complexity when requesting many unique elements (it uses linear scanning), or `O(M*log(N))` complexity when the users asks for `M` random elements in a sorted set of `N` elements, with `M` much smaller than `N`. # Filtered search Each element of the vector set can be associated with a set of attributes specified as a JSON blob: > VADD vset VALUES 3 1 1 1 a SETATTR '{"year": 1950}' (integer) 1 > VADD vset VALUES 3 -1 -1 -1 b SETATTR '{"year": 1951}' (integer) 1 Specifying an attribute with the `SETATTR` option of `VADD` is exactly equivalent to adding an element and then setting (or updating, if already set) the attributes JSON string. Also the symmetrical `VGETATTR` command returns the attribute associated to a given element. > VADD vset VALUES 3 0 1 0 c (integer) 1 > VSETATTR vset c '{"year": 1952}' (integer) 1 > VGETATTR vset c "{\"year\": 1952}" At this point, I may use the FILTER option of VSIM to only ask for the subset of elements that are verified by my expression: > VSIM vset VALUES 3 0 0 0 FILTER '.year > 1950' 1) "c" 2) "b" The items will be returned again in order of similarity (most similar first), but only the items with the year field matching the expression is returned. The expressions are similar to what you would write inside the `if` statement of JavaScript or other familiar programming languages: you can use `and`, `or`, the obvious math operators like `+`, `-`, `/`, `>=`, `<`, ... and so forth (see the expressions section for more info). The selectors of the JSON object attributes start with a dot followed by the name of the key inside the JSON objects. Elements with invalid JSON or not having a given specified field **are considered as not matching** the expression, but will not generate any error at runtime. ## FILTER expressions capabilities FILTER expressions allow you to perform complex filtering on vector similarity results using a JavaScript-like syntax. The expression is evaluated against each element's JSON attributes, with only elements that satisfy the expression being included in the results. ### Expression Syntax Expressions support the following operators and capabilities: 1. **Arithmetic operators**: `+`, `-`, `*`, `/`, `%` (modulo), `**` (exponentiation) 2. **Comparison operators**: `>`, `>=`, `<`, `<=`, `==`, `!=` 3. **Logical operators**: `and`/`&&`, `or`/`||`, `!`/`not` 4. **Containment operator**: `in` 5. **Parentheses** for grouping: `(...)` ### Selector Notation Attributes are accessed using dot notation: - `.year` references the "year" attribute - `.movie.year` would **NOT** reference the "year" field inside a "movie" object, only keys that are at the first level of the JSON object are accessible. ### JSON and expressions data types Expressions can work with: - Numbers (dobule precision floats) - Strings (enclosed in single or double quotes) - Booleans (no native type: they are represented as 1 for true, 0 for false) - Arrays (for use with the `in` operator: `value in [1, 2, 3]`) JSON attributes are converted in this way: - Numbers will be converted to numbers. - Strings to strings. - Booleans to 0 or 1 number. - Arrays to tuples (for "in" operator), but only if composed of just numbers and strings. Any other type is ignored, and accessig it will make the expression evaluate to false. ### Examples ``` # Find items from the 1980s VSIM movies VALUES 3 0.5 0.8 0.2 FILTER '.year >= 1980 and .year < 1990' # Find action movies with high ratings VSIM movies VALUES 3 0.5 0.8 0.2 FILTER '.genre == "action" and .rating > 8.0' # Find movies directed by either Spielberg or Nolan VSIM movies VALUES 3 0.5 0.8 0.2 FILTER '.director in ["Spielberg", "Nolan"]' # Complex condition with numerical operations VSIM movies VALUES 3 0.5 0.8 0.2 FILTER '(.year - 2000) ** 2 < 100 and .rating / 2 > 4' ``` ### Error Handling Elements with any of the following conditions are considered not matching: - Missing the queried JSON attribute - Having invalid JSON in their attributes - Having a JSON value that cannot be converted to the expected type This behavior allows you to safely filter on optional attributes without generating errors. ### FILTER effort The `FILTER-EF` option controls the maximum effort spent when filtering vector search results. When performing vector similarity search with filtering, Vector Sets perform the standard similarity search as they apply the filter expression to each node. Since many results might be filtered out, Vector Sets may need to examine a lot more candidates than the requested `COUNT` to ensure sufficient matching results are returned. Actually, if the elements matching the filter are very rare or if there are less than elements matching than the specified count, this would trigger a full scan of the HNSW graph. For this reason, by default, the maximum effort is limited to a reasonable amount of nodes explored. ### Modifying the FILTER effort 1. By default, Vector Sets will explore up to `COUNT * 100` candidates to find matching results. 2. You can control this exploration with the `FILTER-EF` parameter. 3. A higher `FILTER-EF` value increases the chances of finding all relevant matches at the cost of increased processing time. 4. A `FILTER-EF` of zero will explore as many nodes as needed in order to actually return the number of elements specified by `COUNT`. 5. Even when a high `FILTER-EF` value is specified **the implementation will do a lot less work** if the elements passing the filter are very common, because of the early stop conditions of the HNSW implementation (once the specified amount of elements is reached and the quality check of the other candidates trigger an early stop). ``` VSIM key [ELE|FP32|VALUES] COUNT 10 FILTER '.year > 2000' FILTER-EF 500 ``` In this example, Vector Sets will examine up to 500 potential nodes. Of course if count is reached before exploring 500 nodes, and the quality checks show that it is not possible to make progresses on similarity, the search is ended sooner. ### Performance Considerations - If you have highly selective filters (few items match), use a higher `FILTER-EF`, or just design your application in order to handle a result set that is smaller than the requested count. Note that anyway the additional elements may be too distant than the query vector. - For less selective filters, the default should be sufficient. - Very selective filters with low `FILTER-EF` values may return fewer items than requested. - Extremely high values may impact performance without significantly improving results. The optimal `FILTER-EF` value depends on: 1. The selectivity of your filter. 2. The distribution of your data. 3. The required recall quality. A good practice is to start with the default and increase if needed when you observe fewer results than expected. ### Testing a larg-ish data set To really see how things work at scale, you can [download](https://antirez.com/word2vec_with_attribs.rdb) the following dataset: wget https://antirez.com/word2vec_with_attribs.rdb It contains the 3 million words in Word2Vec having as attribute a JSON with just the length of the word. Because of the length distribution of words in large amounts of texts, where longer words become less and less common, this is ideal to check how filtering behaves with a filter verifying as true with less and less elements in a vector set. For instance: > VSIM word_embeddings_bin ele "pasta" FILTER ".len == 6" 1) "pastas" 2) "rotini" 3) "gnocci" 4) "panino" 5) "salads" 6) "breads" 7) "salame" 8) "sauces" 9) "cheese" 10) "fritti" This will easily retrieve the desired amount of items (`COUNT` is 10 by default) since there are many items of length 6. However: > VSIM word_embeddings_bin ele "pasta" FILTER ".len == 33" 1) "skinless_boneless_chicken_breasts" 2) "boneless_skinless_chicken_breasts" 3) "Boneless_skinless_chicken_breasts" This time even if we asked for 10 items, we only get 3, since the default filter effort will be `10*100 = 1000`. We can tune this giving the effort in an explicit way, with the risk of our query being slower, of course: > VSIM word_embeddings_bin ele "pasta" FILTER ".len == 33" FILTER-EF 10000 1) "skinless_boneless_chicken_breasts" 2) "boneless_skinless_chicken_breasts" 3) "Boneless_skinless_chicken_breasts" 4) "mozzarella_feta_provolone_cheddar" 5) "Greatfood.com_R_www.greatfood.com" 6) "Pepperidge_Farm_Goldfish_crackers" 7) "Prosecuted_Mobsters_Rebuilt_Dying" 8) "Crispy_Snacker_Sandwiches_Popcorn" 9) "risultati_delle_partite_disputate" 10) "Peppermint_Mocha_Twist_Gingersnap" This time we get all the ten items, even if the last one will be quite far from our query vector. We encourage to experiment with this test dataset in order to understand better the dynamics of the implementation and the natural tradeoffs of filtered search. **Keep in mind** that by default, Redis Vector Sets will try to avoid a likely very useless huge scan of the HNSW graph, and will be more happy to return few or no elements at all, since this is almost always what the user actually wants in the context of retrieving *similar* items to the query. # Single Instance Scalability and Latency Vector Sets implement a threading model that allows Redis to handle many concurrent requests: by default `VSIM` is always threaded, and `VADD` is not (but can be partially threaded using the `CAS` option). This section explains how the threading and locking mechanisms work, and what to expect in terms of performance. ## Threading Model - The `VSIM` command runs in a separate thread by default, allowing Redis to continue serving other commands. - A maximum of 32 threads can run concurrently (defined by `HNSW_MAX_THREADS`). - When this limit is reached, additional `VSIM` requests are queued - Redis remains responsive, no latency event is generated. - The `VADD` command with the `CAS` option also leverages threading for the computation-heavy candidate search phase, but the insertion itself is performed in the main thread. `VADD` always runs in a sub-millisecond time, so this is not a source of latency, but having too many hundreds of writes per second can be challenging to handle with a single instance. Please, look at the next section about multiple instances scalability. - Commands run within Lua scripts, MULTI/EXEC blocks, or from replication are executed in the main thread to ensure consistency. ``` > VSIM vset VALUES 3 1 1 1 FILTER '.year > 2000' # This runs in a thread. > VADD vset VALUES 3 1 1 1 element CAS # Candidate search runs in a thread. ``` ## Locking Mechanism Vector Sets use a read/write locking mechanism to coordinate access: - Reads (`VSIM`, `VEMB`, etc.) acquire a read lock, allowing multiple concurrent reads. - Writes (`VADD`, `VREM`, etc.) acquire a write lock, temporarily blocking all reads. - When a write lock is requested while reads are in progress, the write operation waits for all reads to complete. - Once a write lock is granted, all reads are blocked until the write completes. - Each thread has a dedicated slot for tracking visited nodes during graph traversal, avoiding contention. This improves performances but limits the maximum number of concurrent threads, since each node has a memory cost proportional to the number of slots. ## DEL latency Deleting a very large vector set (millions of elements) can cause latency spikes, as deletion rebuilds connections between nodes. This may change in the future. The deletion latency is most noticeable when using `DEL` on a key containing a large vector set or when the key expires. ## Performance Characteristics - Search operations (`VSIM`) scale almost linearly with the number of CPU cores available, up to the thread limit. You can expect a Vector Set composed of million of items associated with components of dimension 300, with the default int8 quantization, to deliver around 50k VSIM operations per second in a single host. - Insertion operations (`VADD`) are more computationally expensive than searches, and can't be threaded: expect much lower throughput, in the range of a few thousands inserts per second. - Binary quantization offers significantly faster search performance at the cost of some recall quality, while int8 quantization, the default, seems to have very small impacts on recall quality, while it significantly improves performances and space efficiency. - The `EF` parameter has a major impact on both search quality and performance - higher values mean better recall but slower searches. - Graph traversal time scales logarithmically with the number of elements, making Vector Sets efficient even with millions of vectors ## Loading / Saving performances Vector Sets are able to serialize on disk the graph structure as it is in memory, so loading back the data does not need to rebuild the HNSW graph. This means that Redis can load millions of items per minute. For instance 3 million items with 300 components vectors can be loaded back into memory into around 15 seconds. # Scaling vector sets to multiple instances The fundamental way vector sets can be scaled to very large data sets and to many Redis instances is that a given very large set of vectors can be partitioned into N different Redis keys, that can also live into different Redis instances. For instance, I could add my elements into `key0`, `key1`, `key2`, by hashing the item in some way, like doing `crc32(item)%3`, effectively splitting the dataset into three different parts. However once I want all the vectors of my dataset near to a given query vector, I could simply perform the `VSIM` command against all the three keys, merging the results by score (so the commands must be called using the `WITHSCORES` option) on the client side: once the union of the results are ordered by the similarity score, the query is equivalent to having a single key `key1+2+3` containing all the items. There are a few interesting facts to note about this pattern: 1. It is possible to have a logical sorted set that is as big as the sum of all the Redis instances we are using. 2. Deletion operations remain simple, we can hash the key and select the key where our item belongs. 3. However, even if I use 10 different Redis instances, I'm not going to reach 10x the **read** operations per second, compared to using a single server: for each logical query, I need to query all the instances. Yet, smaller graphs are faster to navigate, so there is some win even from the point of view of CPU usage. 4. Insertions, so **write** queries, will be scaled linearly: I can add N items against N instances at the same time, splitting the insertion load evenly. This is very important since vector sets, being based on HNSW data structures, are slower to add items than to query similar items, by a very big factor. 5. While it cannot guarantee always the best results, with proper timeout management this system may be considered *highly available*, since if a subset of N instances are reachable, I'll be still be able to return similar items to my query vector. Notably, this pattern can be implemented in a way that avoids paying the sum of the round trip time with all the servers: it is possible to send the queries at the same time to all the instances, so that latency will be equal the slower reply out of of the N servers queries. # Optimizing memory usage Vector Sets, or better, HNSWs, the underlying data structure used by Vector Sets, combined with the features provided by the Vector Sets themselves (quantization, random projection, filtering, ...) form an implementation that has a non-trivial space of parameters that can be tuned. Despite to the complexity of the implementation and of vector similarity problems, here there is a list of simple ideas that can drive the user to pick the best settings: * 8 bit quantization (the default) is almost always a win. It reduces the memory usage of vectors by a factor of 4, yet the performance penalty in terms of recall is minimal. It also reduces insertion and search time by around 2 times or more. * Binary quantization is much more extreme: it makes vector sets a lot faster, but increases the recall error in a sensible way, for instance from 95% to 80% if all the parameters remain the same. Yet, the speedup is really big, and the memory usage of vectors, compaerd to full precision vectors, 32 times smaller. * Vectors memory usage are not the only responsible for Vector Set high memory usage per entry: nodes contain, on average `M*2 + M*0.33` pointers, where M is by default 16 (but can be tuned in `VADD`, see the `M` option). Also each node has the string item and the optional JSON attributes: those should be as small as possible in order to avoid contributing more to the memory usage. * The `M` parameter should be increased to 32 or more only when a near perfect recall is really needed. * It is possible to gain space (less memory usage) sacrificing time (more CPU time) by using a low `M` (the default of 16, for instance) and a high `EF` (the effort parameter of `VSIM`) in order to scan the graph more deeply. * When memory usage is seriosu concern, and there is the suspect the vectors we are storing don't contain as much information - at least for our use case - to justify the number of components they feature, random projection (the `REDUCE` option of `VADD`) could be tested to see if dimensionality reduction is possible with acceptable precision loss. ## Random projection tradeoffs Sometimes learned vectors are not as information dense as we could guess, that is there are components having similar meanings in the space, and components having values that don't really represent features that matter in our use case. At the same time, certain vectors are very big, 1024 components or more. In this cases, it is possible to use the random projection feature of Redis Vector Sets in order to reduce both space (less RAM used) and space (more operstions per second). The feature is accessible via the `REDUCE` option of the `VADD` command. However, keep in mind that you need to test how much reduction impacts the performances of your vectors in term of recall and quality of the results you get back. ## What is a random projection? The concept of Random Projection is relatively simple to grasp. For instance, a projection that turns a 100 components vector into a 10 components vector will perform a different linear transformation between the 100 components and each of the target 10 components. Please note that *each of the target components* will get some random amount of all the 100 original components. It is mathematically proved that this process results in a vector space where elements still have similar distances among them, but still some information will get lost. ## Examples of projections and loss of precision To show you a bit of a extreme case, let's take Word2Vec 3 million items and compress them from 300 to 100, 50 and 25 components vectors. Then, we check the recall compared to the ground truth against each of the vector sets produced in this way (using different `REDUCE` parameters of `VADD`). This is the result, obtained asking for the top 10 elements. ``` ---------------------------------------------------------------------- Key Average Recall % Std Dev ---------------------------------------------------------------------- word_embeddings_int8 95.98 12.14 ^ This is the same key used for ground truth, but without TRUTH option word_embeddings_reduced_100 40.20 20.13 word_embeddings_reduced_50 24.42 16.89 word_embeddings_reduced_25 14.31 9.99 ``` Here the dimensionality reduction we are using is quite extreme: from 300 to 100 means that 66.6% of the original information is lost. The recall drops from 96% to 40%, down to 24% and 14% for even more extreme dimension reduction. Reducing the dimension of vectors that are already relatively small, like the above example, of 300 components, will provide only relatively small memory savings, especially because by default Vector Sets use `int8` quantization, that will use only one byte per component: ``` > MEMORY USAGE word_embeddings_int8 (integer) 3107002888 > MEMORY USAGE word_embeddings_reduced_100 (integer) 2507122888 ``` Of course going, for example, from 2048 component vectors to 1024 would provide a much more sensible memory saving, even with the `int8` quantization used by Vector Sets, assuming the recall loss is acceptable. Other than the memory saving, there is also the reduction in CPU time, translating to more operations per second. Another thing to note is that, with certain embedding models, binary quantization (that offers a 8x reduction of memory usage compared to 8 bit quants, and a very big speedup in computation) performs much better than reducing the dimension of vectors of the same amount via random projections: ``` word_embeddings_bin 35.48 19.78 ``` Here in the same test did above: we have a 35% recall which is not too far than the 40% obtained with a random projection from 300 to 100 components. However, while the first technique reduces the size by 3 times, the size reduced of binary quantization is by 8 times. ``` > memory usage word_embeddings_bin (integer) 2327002888 ``` In this specific case the key uses JSON attributes and has a graph connection overhead that is much bigger than the 300 bits each vector takes, but, as already said, for big vectors (1024 components, for instance) or for lower values of `M` (see `VADD`, the `M` parameter connects the level of connectivity, so it changes the amount of pointers used per node) the memory saving is much stronger. # Vector Sets troubleshooting and understandability ## Debugging poor recall or unexpected results Vector graphs and similarity queries pose many challenges mainly due to the following three problems: 1. The error due to the approximated nature of Vector Sets is hard to evaluate. 2. The error added by the quantization is often depends on the exact vector space (the embedding we are using **and** how far apart the elements we represent into such embeddings are). 3. We live in the illusion that learned embeddings capture the best similarity possible among elements, which is obviously not always true, and highly application dependent. The only way to debug such problems, is the ability to inspect step by step what is happening inside our application, and the structure of the HNSW graph itself. To do so, we suggest to consider the following tools: 1. The `TRUTH` option of the `VSIM` command is able to return the ground truth of the most similar elements, without using the HNSW graph, but doing a linear scan. 2. The `VLINKS` command allows to explore the graph to see if the connections among nodes make sense, and to investigate why a given node may be more isolated than expected. Such command can also be used in a different way, when we want very fast "similar items" without paying the HNSW traversal time. It exploits the fact that we have a direct reference from each element in our vector set to each node in our HNSW graph. 3. The `WITHSCORES` option, in the supported commands, return a value that is directly related to the *cosine similarity* between the query and the items vectors, the interval of the similarity is simply rescaled from the -1, 1 original range to 0, 1, otherwise the metric is identical. ## Clients, latency and bandwidth usage During Vector Sets testing, we discovered that often clients introduce considerable latecy and CPU usage (in the client side, not in Redis) for two main reasons: 1. Often the serialization to `VALUES ... list of floats ...` can be very slow. 2. The vector payload of floats represented as strings is very large, resulting in high bandwidth usage and latency, compared to other Redis commands. 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. The main features are: * Proper nodes deletion with relinking. * 8 bits and binary quantization. * Threaded queries. * Filtered search with predicate callback. redis-8.0.2/modules/vector-sets/commands.json000066400000000000000000000175031501533116600212600ustar00rootroot00000000000000{ "VADD": { "summary": "Add one or more elements to a vector set, or update its vector if it already exists", "complexity": "O(log(N)) for each element added, where N is the number of elements in the vector set.", "group": "vector_set", "since": "1.0.0", "arity": -1, "function": "vaddCommand", "arguments": [ { "name": "key", "type": "key" }, { "token": "REDUCE", "name": "reduce", "type": "pure-token", "optional": true }, { "name": "dim", "type": "integer", "optional": true }, { "name": "format", "type": "oneof", "arguments": [ { "name": "fp32", "type": "pure-token", "token": "FP32" }, { "name": "values", "type": "pure-token", "token": "VALUES" } ] }, { "name": "vector", "type": "string" }, { "name": "element", "type": "string" }, { "token": "CAS", "name": "cas", "type": "pure-token", "optional": true }, { "name": "quant_type", "type": "oneof", "optional": true, "arguments": [ { "name": "noquant", "type": "pure-token", "token": "NOQUANT" }, { "name": "bin", "type": "pure-token", "token": "BIN" }, { "name": "q8", "type": "pure-token", "token": "Q8" } ] }, { "token": "EF", "name": "build-exploration-factor", "type": "integer", "optional": true }, { "token": "SETATTR", "name": "attributes", "type": "string", "optional": true }, { "token": "M", "name": "numlinks", "type": "integer", "optional": true } ], "command_flags": [ "WRITE", "DENYOOM" ] }, "VREM": { "summary": "Remove one or more elements from a vector set", "complexity": "O(log(N)) for each element removed, where N is the number of elements in the vector set.", "group": "vector_set", "since": "1.0.0", "arity": -2, "function": "vremCommand", "command_flags": [ "WRITE" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "element", "type": "string", "multiple": true } ] }, "VSIM": { "summary": "Return elements by vector similarity", "complexity": "O(log(N)) where N is the number of elements in the vector set.", "group": "vector_set", "since": "1.0.0", "arity": -3, "function": "vsimCommand", "command_flags": [ "READONLY" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "format", "type": "oneof", "arguments": [ { "name": "ele", "type": "pure-token", "token": "ELE" }, { "name": "fp32", "type": "pure-token", "token": "FP32" }, { "name": "values", "type": "pure-token", "token": "VALUES" } ] }, { "name": "vector_or_element", "type": "string" }, { "token": "WITHSCORES", "name": "withscores", "type": "pure-token", "optional": true }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true }, { "token": "EPSILON", "name": "max_distance", "type": "double", "optional": true }, { "token": "EF", "name": "search-exploration-factor", "type": "integer", "optional": true }, { "token": "FILTER", "name": "expression", "type": "string", "optional": true }, { "token": "FILTER-EF", "name": "max-filtering-effort", "type": "integer", "optional": true }, { "token": "TRUTH", "name": "truth", "type": "pure-token", "optional": true } ] }, "VDIM": { "summary": "Return the dimension of vectors in the vector set", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": 2, "function": "vdimCommand", "command_flags": [ "READONLY", "FAST" ], "arguments": [ { "name": "key", "type": "key" } ] }, "VCARD": { "summary": "Return the number of elements in a vector set", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": 2, "function": "vcardCommand", "command_flags": [ "READONLY", "FAST" ], "arguments": [ { "name": "key", "type": "key" } ] }, "VEMB": { "summary": "Return the vector associated with an element", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": -3, "function": "vembCommand", "command_flags": [ "READONLY" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "element", "type": "string" }, { "token": "RAW", "name": "raw", "type": "pure-token", "optional": true } ] }, "VLINKS": { "summary": "Return the neighbors of an element at each layer in the HNSW graph", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": -3, "function": "vlinksCommand", "command_flags": [ "READONLY" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "element", "type": "string" }, { "token": "WITHSCORES", "name": "withscores", "type": "pure-token", "optional": true } ] }, "VINFO": { "summary": "Return information about a vector set", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": 2, "function": "vinfoCommand", "command_flags": [ "READONLY", "FAST" ], "arguments": [ { "name": "key", "type": "key" } ] }, "VSETATTR": { "summary": "Associate or remove the JSON attributes of elements", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": 4, "function": "vsetattrCommand", "command_flags": [ "WRITE" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "element", "type": "string" }, { "name": "json", "type": "string" } ] }, "VGETATTR": { "summary": "Retrieve the JSON attributes of elements", "complexity": "O(1)", "group": "vector_set", "since": "1.0.0", "arity": 3, "function": "vgetattrCommand", "command_flags": [ "READONLY" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "element", "type": "string" } ] }, "VRANDMEMBER": { "summary": "Return one or multiple random members from a vector set", "complexity": "O(N) where N is the absolute value of the count argument.", "group": "vector_set", "since": "1.0.0", "arity": -2, "function": "vrandmemberCommand", "command_flags": [ "READONLY" ], "arguments": [ { "name": "key", "type": "key" }, { "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/modules/vector-sets/examples/000077500000000000000000000000001501533116600203745ustar00rootroot00000000000000redis-8.0.2/modules/vector-sets/examples/cli-tool/000077500000000000000000000000001501533116600221165ustar00rootroot00000000000000redis-8.0.2/modules/vector-sets/examples/cli-tool/.gitignore000066400000000000000000000000051501533116600241010ustar00rootroot00000000000000venv redis-8.0.2/modules/vector-sets/examples/cli-tool/README.md000066400000000000000000000031711501533116600233770ustar00rootroot00000000000000This tool is similar to redis-cli (but very basic) but allows to specify arguments that are expanded as vectors by calling ollama to get the embedding. Whatever is passed as !"foo bar" gets expanded into VALUES ... embedding ... You must have ollama running with the mxbai-emb-large model already installed for this to work. Example: redis> KEYS * 1) food_items 2) glove_embeddings_bin 3) many_movies_mxbai-embed-large_BIN 4) many_movies_mxbai-embed-large_NOQUANT 5) word_embeddings 6) word_embeddings_bin 7) glove_embeddings_fp32 redis> VSIM food_items !"drinks with fruit" 1) (Fruit)Juices,Lemonade,100ml,50 cal,210 kJ 2) (Fruit)Juices,Limeade,100ml,128 cal,538 kJ 3) CannedFruit,Canned Fruit Cocktail,100g,81 cal,340 kJ 4) (Fruit)Juices,Energy-Drink,100ml,87 cal,365 kJ 5) Fruits,Lime,100g,30 cal,126 kJ 6) (Fruit)Juices,Coconut Water,100ml,19 cal,80 kJ 7) Fruits,Lemon,100g,29 cal,122 kJ 8) (Fruit)Juices,Clamato,100ml,60 cal,252 kJ 9) Fruits,Fruit salad,100g,50 cal,210 kJ 10) (Fruit)Juices,Capri-Sun,100ml,41 cal,172 kJ redis> vsim food_items !"barilla" 1) Pasta&Noodles,Spirelli,100g,367 cal,1541 kJ 2) Pasta&Noodles,Farfalle,100g,358 cal,1504 kJ 3) Pasta&Noodles,Capellini,100g,353 cal,1483 kJ 4) Pasta&Noodles,Spaetzle,100g,368 cal,1546 kJ 5) Pasta&Noodles,Cappelletti,100g,164 cal,689 kJ 6) Pasta&Noodles,Penne,100g,351 cal,1474 kJ 7) Pasta&Noodles,Shells,100g,353 cal,1483 kJ 8) Pasta&Noodles,Linguine,100g,357 cal,1499 kJ 9) Pasta&Noodles,Rotini,100g,353 cal,1483 kJ 10) Pasta&Noodles,Rigatoni,100g,353 cal,1483 kJ redis-8.0.2/modules/vector-sets/examples/cli-tool/cli.py000077500000000000000000000114621501533116600232460ustar00rootroot00000000000000# # Copyright (c) 2009-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). # #!/usr/bin/env python3 import redis import requests import re import shlex from prompt_toolkit import PromptSession from prompt_toolkit.history import InMemoryHistory def get_embedding(text): """Get embedding from local Ollama API""" url = "http://localhost:11434/api/embeddings" payload = { "model": "mxbai-embed-large", "prompt": text } try: response = requests.post(url, json=payload) response.raise_for_status() return response.json()['embedding'] except requests.exceptions.RequestException as e: raise Exception(f"Failed to get embedding: {str(e)}") def process_embedding_patterns(text): """Process !"text" and !!"text" patterns in the command""" def replace_with_embedding(match): text = match.group(1) embedding = get_embedding(text) return f"VALUES {len(embedding)} {' '.join(map(str, embedding))}" def replace_with_embedding_and_text(match): text = match.group(1) embedding = get_embedding(text) # Return both the embedding values and the original text as next argument return f'VALUES {len(embedding)} {" ".join(map(str, embedding))} "{text}"' # First handle !!"text" pattern (must be done before !"text") text = re.sub(r'!!"([^"]*)"', replace_with_embedding_and_text, text) # Then handle !"text" pattern text = re.sub(r'!"([^"]*)"', replace_with_embedding, text) return text def parse_command(command): """Parse command respecting quoted strings""" try: # Use shlex to properly handle quoted strings return shlex.split(command) except ValueError as e: raise Exception(f"Invalid command syntax: {str(e)}") def format_response(response): """Format the response to match Redis protocol style""" if response is None: return "(nil)" elif isinstance(response, bool): return "+OK" if response else "(error) Operation failed" elif isinstance(response, (list, set)): if not response: return "(empty list or set)" return "\n".join(f"{i+1}) {item}" for i, item in enumerate(response)) elif isinstance(response, int): return f"(integer) {response}" else: return str(response) def main(): # Default connection to localhost:6379 r = redis.Redis(host='localhost', port=6379, decode_responses=True) try: # Test connection r.ping() print("Connected to Redis. Type your commands (CTRL+D to exit):") print("Special syntax:") print(" !\"text\" - Replace with embedding") print(" !!\"text\" - Replace with embedding and append text as value") print(" \"text\" - Quote strings containing spaces") except redis.ConnectionError: print("Error: Could not connect to Redis server") return # Setup prompt session with history session = PromptSession(history=InMemoryHistory()) # Main loop while True: try: # Read input with line editing support command = session.prompt("redis> ") # Skip empty commands if not command.strip(): continue # Process any embedding patterns before parsing try: processed_command = process_embedding_patterns(command) except Exception as e: print(f"(error) Embedding processing failed: {str(e)}") continue # Parse the command respecting quoted strings try: parts = parse_command(processed_command) except Exception as e: print(f"(error) {str(e)}") continue if not parts: continue cmd = parts[0].lower() args = parts[1:] # Execute command try: method = getattr(r, cmd, None) if method is not None: result = method(*args) else: # Use execute_command for unknown commands result = r.execute_command(cmd, *args) print(format_response(result)) except AttributeError: print(f"(error) Unknown command '{cmd}'") except EOFError: print("\nGoodbye!") break except KeyboardInterrupt: continue # Allow Ctrl+C to clear current line except redis.RedisError as e: print(f"(error) {str(e)}") except Exception as e: print(f"(error) {str(e)}") if __name__ == "__main__": main() redis-8.0.2/modules/vector-sets/examples/glove-100/000077500000000000000000000000001501533116600220065ustar00rootroot00000000000000redis-8.0.2/modules/vector-sets/examples/glove-100/README000066400000000000000000000002051501533116600226630ustar00rootroot00000000000000wget http://ann-benchmarks.com/glove-100-angular.hdf5 python insert.py python recall.py (use --k optionally, default top-10) redis-8.0.2/modules/vector-sets/examples/glove-100/insert.py000066400000000000000000000034521501533116600236700ustar00rootroot00000000000000# # Copyright (c) 2009-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). # import h5py import redis from tqdm import tqdm # Initialize Redis connection redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True, encoding='utf-8') def add_to_redis(index, embedding): """Add embedding to Redis using VADD command""" args = ["VADD", "glove_embeddings", "VALUES", "100"] # 100 is vector dimension args.extend(map(str, embedding)) args.append(f"{index}") # Using index as identifier since we don't have words args.append("EF") args.append("200") # args.append("NOQUANT") # args.append("BIN") redis_client.execute_command(*args) def main(): with h5py.File('glove-100-angular.hdf5', 'r') as f: # Get the train dataset train_vectors = f['train'] total_vectors = train_vectors.shape[0] print(f"Starting to process {total_vectors} vectors...") # Process in batches to avoid memory issues batch_size = 1000 for i in tqdm(range(0, total_vectors, batch_size)): batch_end = min(i + batch_size, total_vectors) batch = train_vectors[i:batch_end] for j, vector in enumerate(batch): try: current_index = i + j add_to_redis(current_index, vector) except Exception as e: print(f"Error processing vector {current_index}: {str(e)}") continue if (i + batch_size) % 10000 == 0: print(f"Processed {i + batch_size} vectors") if __name__ == "__main__": main() redis-8.0.2/modules/vector-sets/examples/glove-100/recall.py000066400000000000000000000061251501533116600236260ustar00rootroot00000000000000# # Copyright (c) 2009-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). # import h5py import redis import numpy as np from tqdm import tqdm import argparse # Initialize Redis connection redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True, encoding='utf-8') def get_redis_neighbors(query_vector, k): """Get nearest neighbors using Redis VSIM command""" args = ["VSIM", "glove_embeddings_bin", "VALUES", "100"] args.extend(map(str, query_vector)) args.extend(["COUNT", str(k)]) args.extend(["EF", 100]) if False: print(args) exit(1) results = redis_client.execute_command(*args) return [int(res) for res in results] def calculate_recall(ground_truth, predicted, k): """Calculate recall@k""" relevant = set(ground_truth[:k]) retrieved = set(predicted[:k]) return len(relevant.intersection(retrieved)) / len(relevant) def main(): parser = argparse.ArgumentParser(description='Evaluate Redis VSIM recall') parser.add_argument('--k', type=int, default=10, help='Number of neighbors to evaluate (default: 10)') parser.add_argument('--batch', type=int, default=100, help='Progress update frequency (default: 100)') args = parser.parse_args() k = args.k batch_size = args.batch with h5py.File('glove-100-angular.hdf5', 'r') as f: test_vectors = f['test'][:] ground_truth_neighbors = f['neighbors'][:] num_queries = len(test_vectors) recalls = [] print(f"Evaluating recall@{k} for {num_queries} test queries...") for i in tqdm(range(num_queries)): try: # Get Redis results redis_neighbors = get_redis_neighbors(test_vectors[i], k) # Get ground truth for this query true_neighbors = ground_truth_neighbors[i] # Calculate recall recall = calculate_recall(true_neighbors, redis_neighbors, k) recalls.append(recall) if (i + 1) % batch_size == 0: current_avg_recall = np.mean(recalls) print(f"Current average recall@{k} after {i+1} queries: {current_avg_recall:.4f}") except Exception as e: print(f"Error processing query {i}: {str(e)}") continue final_recall = np.mean(recalls) print("\nFinal Results:") print(f"Average recall@{k}: {final_recall:.4f}") print(f"Total queries evaluated: {len(recalls)}") # Save detailed results with open(f'recall_evaluation_results_k{k}.txt', 'w') as f: f.write(f"Average recall@{k}: {final_recall:.4f}\n") f.write(f"Total queries evaluated: {len(recalls)}\n") f.write(f"Individual query recalls: {recalls}\n") if __name__ == "__main__": main() redis-8.0.2/modules/vector-sets/examples/movies/000077500000000000000000000000001501533116600216765ustar00rootroot00000000000000redis-8.0.2/modules/vector-sets/examples/movies/.gitignore000066400000000000000000000000421501533116600236620ustar00rootroot00000000000000mpst_full_data.csv partition.json redis-8.0.2/modules/vector-sets/examples/movies/README000066400000000000000000000015601501533116600225600ustar00rootroot00000000000000This example maps long form movies plots to movies titles. It will create fp32 and binary vectors (the two extremes). 1. Install ollama, and install the embedding model "mxbai-embed-large" 2. Download mpst_full_data.csv from https://www.kaggle.com/datasets/cryptexcode/mpst-movie-plot-synopses-with-tags 3. python insert.py 127.0.0.1:6379> VSIM many_movies_mxbai-embed-large_NOQUANT ELE "The Matrix" 1) "The Matrix" 2) "The Matrix Reloaded" 3) "The Matrix Revolutions" 4) "Commando" 5) "Avatar" 6) "Forbidden Planet" 7) "Terminator Salvation" 8) "Mandroid" 9) "The Omega Code" 10) "Coherence" 127.0.0.1:6379> VSIM many_movies_mxbai-embed-large_BIN ELE "The Matrix" 1) "The Matrix" 2) "The Matrix Reloaded" 3) "The Matrix Revolutions" 4) "The Omega Code" 5) "Forbidden Planet" 6) "Avatar" 7) "John Carter" 8) "System Shock 2" 9) "Coherence" 10) "Tomorrowland" redis-8.0.2/modules/vector-sets/examples/movies/insert.py000066400000000000000000000035331501533116600235600ustar00rootroot00000000000000# # Copyright (c) 2009-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). # import csv import requests import redis ModelName="mxbai-embed-large" # Initialize Redis connection, setting encoding to utf-8 redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True, encoding='utf-8') def get_embedding(text): """Get embedding from local API""" url = "http://localhost:11434/api/embeddings" payload = { "model": ModelName, "prompt": "Represent this movie plot and genre: "+text } response = requests.post(url, json=payload) return response.json()['embedding'] def add_to_redis(title, embedding, quant_type): """Add embedding to Redis using VADD command""" args = ["VADD", "many_movies_"+ModelName+"_"+quant_type, "VALUES", str(len(embedding))] args.extend(map(str, embedding)) args.append(title) args.append(quant_type) redis_client.execute_command(*args) def main(): with open('mpst_full_data.csv', 'r', encoding='utf-8') as file: reader = csv.DictReader(file) for movie in reader: try: text_to_embed = f"{movie['title']} {movie['plot_synopsis']} {movie['tags']}" print(f"Getting embedding for: {movie['title']}") embedding = get_embedding(text_to_embed) add_to_redis(movie['title'], embedding, "BIN") add_to_redis(movie['title'], embedding, "NOQUANT") print(f"Successfully processed: {movie['title']}") except Exception as e: print(f"Error processing {movie['title']}: {str(e)}") continue if __name__ == "__main__": main() redis-8.0.2/modules/vector-sets/expr.c000066400000000000000000000742761501533116600177200ustar00rootroot00000000000000/* Filtering of objects based on simple expressions. * This powers the FILTER option of Vector Sets, but it is otherwise * general code to be used when we want to tell if a given object (with fields) * passes or fails a given test for scalars, strings, ... * * Copyright (c) 2009-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). * Originally authored by: Salvatore Sanfilippo. */ #ifdef TEST_MAIN #define RedisModule_Alloc malloc #define RedisModule_Realloc realloc #define RedisModule_Free free #define RedisModule_Strdup strdup #define RedisModule_Assert assert #define _DEFAULT_SOURCE #define _USE_MATH_DEFINES #define _POSIX_C_SOURCE 200809L #include #include #endif #include #include #include #include #include #define EXPR_TOKEN_EOF 0 #define EXPR_TOKEN_NUM 1 #define EXPR_TOKEN_STR 2 #define EXPR_TOKEN_TUPLE 3 #define EXPR_TOKEN_SELECTOR 4 #define EXPR_TOKEN_OP 5 #define EXPR_TOKEN_NULL 6 #define EXPR_OP_OPAREN 0 /* ( */ #define EXPR_OP_CPAREN 1 /* ) */ #define EXPR_OP_NOT 2 /* ! */ #define EXPR_OP_POW 3 /* ** */ #define EXPR_OP_MULT 4 /* * */ #define EXPR_OP_DIV 5 /* / */ #define EXPR_OP_MOD 6 /* % */ #define EXPR_OP_SUM 7 /* + */ #define EXPR_OP_DIFF 8 /* - */ #define EXPR_OP_GT 9 /* > */ #define EXPR_OP_GTE 10 /* >= */ #define EXPR_OP_LT 11 /* < */ #define EXPR_OP_LTE 12 /* <= */ #define EXPR_OP_EQ 13 /* == */ #define EXPR_OP_NEQ 14 /* != */ #define EXPR_OP_IN 15 /* in */ #define EXPR_OP_AND 16 /* and */ #define EXPR_OP_OR 17 /* or */ /* This structure represents a token in our expression. It's either * literals like 4, "foo", or operators like "+", "-", "and", or * json selectors, that start with a dot: ".age", ".properties.somearray[1]" */ typedef struct exprtoken { int refcount; // Reference counting for memory reclaiming. int token_type; // Token type of the just parsed token. int offset; // Chars offset in expression. union { double num; // Value for EXPR_TOKEN_NUM. struct { char *start; // String pointer for EXPR_TOKEN_STR / SELECTOR. size_t len; // String len for EXPR_TOKEN_STR / SELECTOR. char *heapstr; // True if we have a private allocation for this // string. When possible, it just references to the // string expression we compiled, exprstate->expr. } str; int opcode; // Opcode ID for EXPR_TOKEN_OP. struct { struct exprtoken **ele; size_t len; } tuple; // Tuples are like [1, 2, 3] for "in" operator. }; } exprtoken; /* Simple stack of expr tokens. This is used both to represent the stack * of values and the stack of operands during VM execution. */ typedef struct exprstack { exprtoken **items; int numitems; int allocsize; } exprstack; typedef struct exprstate { char *expr; /* Expression string to compile. Note that * expression token strings point directly to this * string. */ char *p; // Current position inside 'expr', while parsing. // Virtual machine state. exprstack values_stack; exprstack ops_stack; // Operator stack used during compilation. exprstack tokens; // Expression processed into a sequence of tokens. exprstack program; // Expression compiled into opcodes and values. } exprstate; /* Valid operators. */ struct { char *opname; int oplen; int opcode; int precedence; int arity; } ExprOptable[] = { {"(", 1, EXPR_OP_OPAREN, 7, 0}, {")", 1, EXPR_OP_CPAREN, 7, 0}, {"!", 1, EXPR_OP_NOT, 6, 1}, {"not", 3, EXPR_OP_NOT, 6, 1}, {"**", 2, EXPR_OP_POW, 5, 2}, {"*", 1, EXPR_OP_MULT, 4, 2}, {"/", 1, EXPR_OP_DIV, 4, 2}, {"%", 1, EXPR_OP_MOD, 4, 2}, {"+", 1, EXPR_OP_SUM, 3, 2}, {"-", 1, EXPR_OP_DIFF, 3, 2}, {">", 1, EXPR_OP_GT, 2, 2}, {">=", 2, EXPR_OP_GTE, 2, 2}, {"<", 1, EXPR_OP_LT, 2, 2}, {"<=", 2, EXPR_OP_LTE, 2, 2}, {"==", 2, EXPR_OP_EQ, 2, 2}, {"!=", 2, EXPR_OP_NEQ, 2, 2}, {"in", 2, EXPR_OP_IN, 2, 2}, {"and", 3, EXPR_OP_AND, 1, 2}, {"&&", 2, EXPR_OP_AND, 1, 2}, {"or", 2, EXPR_OP_OR, 0, 2}, {"||", 2, EXPR_OP_OR, 0, 2}, {NULL, 0, 0, 0, 0} // Terminator. }; #define EXPR_OP_SPECIALCHARS "+-*%/!()<>=|&" #define EXPR_SELECTOR_SPECIALCHARS "_-" /* ================================ Expr token ============================== */ /* Return an heap allocated token of the specified type, setting the * reference count to 1. */ exprtoken *exprNewToken(int type) { exprtoken *t = RedisModule_Alloc(sizeof(exprtoken)); memset(t,0,sizeof(*t)); t->token_type = type; t->refcount = 1; return t; } /* Generic free token function, can be used to free stack allocated * objects (in this case the pointer itself will not be freed) or * heap allocated objects. See the wrappers below. */ void exprTokenRelease(exprtoken *t) { if (t == NULL) return; RedisModule_Assert(t->refcount > 0); // Catch double free & more. t->refcount--; if (t->refcount > 0) return; // We reached refcount 0: free the object. if (t->token_type == EXPR_TOKEN_STR) { if (t->str.heapstr != NULL) RedisModule_Free(t->str.heapstr); } else if (t->token_type == EXPR_TOKEN_TUPLE) { for (size_t j = 0; j < t->tuple.len; j++) exprTokenRelease(t->tuple.ele[j]); if (t->tuple.ele) RedisModule_Free(t->tuple.ele); } RedisModule_Free(t); } void exprTokenRetain(exprtoken *t) { t->refcount++; } /* ============================== Stack handling ============================ */ #include #include #define EXPR_STACK_INITIAL_SIZE 16 /* Initialize a new expression stack. */ void exprStackInit(exprstack *stack) { stack->items = RedisModule_Alloc(sizeof(exprtoken*) * EXPR_STACK_INITIAL_SIZE); stack->numitems = 0; stack->allocsize = EXPR_STACK_INITIAL_SIZE; } /* Push a token pointer onto the stack. Does not increment the refcount * of the token: it is up to the caller doing this. */ void exprStackPush(exprstack *stack, exprtoken *token) { /* Check if we need to grow the stack. */ if (stack->numitems == stack->allocsize) { size_t newsize = stack->allocsize * 2; exprtoken **newitems = RedisModule_Realloc(stack->items, sizeof(exprtoken*) * newsize); stack->items = newitems; stack->allocsize = newsize; } stack->items[stack->numitems] = token; stack->numitems++; } /* Pop a token pointer from the stack. Return NULL if the stack is * empty. Does NOT recrement the refcount of the token, it's up to the * caller to do so, as the new owner of the reference. */ exprtoken *exprStackPop(exprstack *stack) { if (stack->numitems == 0) return NULL; stack->numitems--; return stack->items[stack->numitems]; } /* Just return the last element pushed, without consuming it nor altering * the reference count. */ exprtoken *exprStackPeek(exprstack *stack) { if (stack->numitems == 0) return NULL; return stack->items[stack->numitems-1]; } /* Free the stack structure state, including the items it contains, that are * assumed to be heap allocated. The passed pointer itself is not freed. */ void exprStackFree(exprstack *stack) { for (int j = 0; j < stack->numitems; j++) exprTokenRelease(stack->items[j]); RedisModule_Free(stack->items); } /* Just reset the stack removing all the items, but leaving it in a state * that makes it still usable for new elements. */ void exprStackReset(exprstack *stack) { for (int j = 0; j < stack->numitems; j++) exprTokenRelease(stack->items[j]); stack->numitems = 0; } /* =========================== Expression compilation ======================= */ void exprConsumeSpaces(exprstate *es) { while(es->p[0] && isspace(es->p[0])) es->p++; } /* Parse an operator or a literal (just "null" currently). * When parsing operators, the function will try to match the longest match * in the operators table. */ exprtoken *exprParseOperatorOrLiteral(exprstate *es) { exprtoken *t = exprNewToken(EXPR_TOKEN_OP); char *start = es->p; while(es->p[0] && (isalpha(es->p[0]) || strchr(EXPR_OP_SPECIALCHARS,es->p[0]) != NULL)) { es->p++; } int matchlen = es->p - start; int bestlen = 0; int j; // Check if it's a literal. if (matchlen == 4 && !memcmp("null",start,4)) { t->token_type = EXPR_TOKEN_NULL; return t; } // Find the longest matching operator. for (j = 0; ExprOptable[j].opname != NULL; j++) { if (ExprOptable[j].oplen > matchlen) continue; if (memcmp(ExprOptable[j].opname, start, ExprOptable[j].oplen) != 0) { continue; } if (ExprOptable[j].oplen > bestlen) { t->opcode = ExprOptable[j].opcode; bestlen = ExprOptable[j].oplen; } } if (bestlen == 0) { exprTokenRelease(t); return NULL; } else { es->p = start + bestlen; } return t; } // Valid selector charset. static int is_selector_char(int c) { return (isalpha(c) || isdigit(c) || strchr(EXPR_SELECTOR_SPECIALCHARS,c) != NULL); } /* Parse selectors, they start with a dot and can have alphanumerical * or few special chars. */ exprtoken *exprParseSelector(exprstate *es) { exprtoken *t = exprNewToken(EXPR_TOKEN_SELECTOR); es->p++; // Skip dot. char *start = es->p; while(es->p[0] && is_selector_char(es->p[0])) es->p++; int matchlen = es->p - start; t->str.start = start; t->str.len = matchlen; return t; } exprtoken *exprParseNumber(exprstate *es) { exprtoken *t = exprNewToken(EXPR_TOKEN_NUM); char num[256]; int idx = 0; while(isdigit(es->p[0]) || es->p[0] == '.' || es->p[0] == 'e' || es->p[0] == 'E' || (idx == 0 && es->p[0] == '-')) { if (idx >= (int)sizeof(num)-1) { exprTokenRelease(t); return NULL; } num[idx++] = es->p[0]; es->p++; } num[idx] = 0; char *endptr; t->num = strtod(num, &endptr); if (*endptr != '\0') { exprTokenRelease(t); return NULL; } return t; } exprtoken *exprParseString(exprstate *es) { char quote = es->p[0]; /* Store the quote type (' or "). */ es->p++; /* Skip opening quote. */ exprtoken *t = exprNewToken(EXPR_TOKEN_STR); t->str.start = es->p; while(es->p[0] != '\0') { if (es->p[0] == '\\' && es->p[1] != '\0') { es->p += 2; // Skip escaped char. continue; } if (es->p[0] == quote) { t->str.len = es->p - t->str.start; es->p++; // Skip closing quote. return t; } es->p++; } /* If we reach here, string was not terminated. */ exprTokenRelease(t); return NULL; } /* Parse a tuple of the form [1, "foo", 42]. No nested tuples are * supported. This type is useful mostly to be used with the "IN" * operator. */ exprtoken *exprParseTuple(exprstate *es) { exprtoken *t = exprNewToken(EXPR_TOKEN_TUPLE); t->tuple.ele = NULL; t->tuple.len = 0; es->p++; /* Skip opening '['. */ size_t allocated = 0; while(1) { exprConsumeSpaces(es); /* Check for empty tuple or end. */ if (es->p[0] == ']') { es->p++; break; } /* Grow tuple array if needed. */ if (t->tuple.len == allocated) { size_t newsize = allocated == 0 ? 4 : allocated * 2; exprtoken **newele = RedisModule_Realloc(t->tuple.ele, sizeof(exprtoken*) * newsize); t->tuple.ele = newele; allocated = newsize; } /* Parse tuple element. */ exprtoken *ele = NULL; if (isdigit(es->p[0]) || es->p[0] == '-') { ele = exprParseNumber(es); } else if (es->p[0] == '"' || es->p[0] == '\'') { ele = exprParseString(es); } else { exprTokenRelease(t); return NULL; } /* Error parsing number/string? */ if (ele == NULL) { exprTokenRelease(t); return NULL; } /* Store element if no error was detected. */ t->tuple.ele[t->tuple.len] = ele; t->tuple.len++; /* Check for next element. */ exprConsumeSpaces(es); if (es->p[0] == ']') { es->p++; break; } if (es->p[0] != ',') { exprTokenRelease(t); return NULL; } es->p++; /* Skip comma. */ } return t; } /* Deallocate the object returned by exprCompile(). */ void exprFree(exprstate *es) { if (es == NULL) return; /* Free the original expression string. */ if (es->expr) RedisModule_Free(es->expr); /* Free all stacks. */ exprStackFree(&es->values_stack); exprStackFree(&es->ops_stack); exprStackFree(&es->tokens); exprStackFree(&es->program); /* Free the state object itself. */ RedisModule_Free(es); } /* Split the provided expression into a stack of tokens. Returns * 0 on success, 1 on error. */ int exprTokenize(exprstate *es, int *errpos) { /* Main parsing loop. */ while(1) { exprConsumeSpaces(es); /* Set a flag to see if we can consider the - part of the * number, or an operator. */ int minus_is_number = 0; // By default is an operator. exprtoken *last = exprStackPeek(&es->tokens); if (last == NULL) { /* If we are at the start of an expression, the minus is * considered a number. */ minus_is_number = 1; } else if (last->token_type == EXPR_TOKEN_OP && last->opcode != EXPR_OP_CPAREN) { /* Also, if the previous token was an operator, the minus * is considered a number, unless the previous operator is * a closing parens. In such case it's like (...) -5, or alike * and we want to emit an operator. */ minus_is_number = 1; } /* Parse based on the current character. */ exprtoken *current = NULL; if (*es->p == '\0') { current = exprNewToken(EXPR_TOKEN_EOF); } else if (isdigit(*es->p) || (minus_is_number && *es->p == '-' && isdigit(es->p[1]))) { current = exprParseNumber(es); } else if (*es->p == '"' || *es->p == '\'') { current = exprParseString(es); } else if (*es->p == '.' && is_selector_char(es->p[1])) { current = exprParseSelector(es); } else if (*es->p == '[') { current = exprParseTuple(es); } else if (isalpha(*es->p) || strchr(EXPR_OP_SPECIALCHARS, *es->p)) { current = exprParseOperatorOrLiteral(es); } if (current == NULL) { if (errpos) *errpos = es->p - es->expr; return 1; // Syntax Error. } /* Push the current token to tokens stack. */ exprStackPush(&es->tokens, current); if (current->token_type == EXPR_TOKEN_EOF) break; } return 0; } /* Helper function to get operator precedence from the operator table. */ int exprGetOpPrecedence(int opcode) { for (int i = 0; ExprOptable[i].opname != NULL; i++) { if (ExprOptable[i].opcode == opcode) return ExprOptable[i].precedence; } return -1; } /* Helper function to get operator arity from the operator table. */ int exprGetOpArity(int opcode) { for (int i = 0; ExprOptable[i].opname != NULL; i++) { if (ExprOptable[i].opcode == opcode) return ExprOptable[i].arity; } return -1; } /* Process an operator during compilation. Returns 0 on success, 1 on error. * This function will retain a reference of the operator 'op' in case it * is pushed on the operators stack. */ int exprProcessOperator(exprstate *es, exprtoken *op, int *stack_items, int *errpos) { if (op->opcode == EXPR_OP_OPAREN) { // This is just a marker for us. Do nothing. exprStackPush(&es->ops_stack, op); exprTokenRetain(op); return 0; } if (op->opcode == EXPR_OP_CPAREN) { /* Process operators until we find the matching opening parenthesis. */ while (1) { exprtoken *top_op = exprStackPop(&es->ops_stack); if (top_op == NULL) { if (errpos) *errpos = op->offset; return 1; } if (top_op->opcode == EXPR_OP_OPAREN) { /* Open parethesis found. Our work finished. */ exprTokenRelease(top_op); return 0; } int arity = exprGetOpArity(top_op->opcode); if (*stack_items < arity) { exprTokenRelease(top_op); if (errpos) *errpos = top_op->offset; return 1; } /* Move the operator on the program stack. */ exprStackPush(&es->program, top_op); *stack_items = *stack_items - arity + 1; } } int curr_prec = exprGetOpPrecedence(op->opcode); /* Process operators with higher or equal precedence. */ while (1) { exprtoken *top_op = exprStackPeek(&es->ops_stack); if (top_op == NULL || top_op->opcode == EXPR_OP_OPAREN) break; int top_prec = exprGetOpPrecedence(top_op->opcode); if (top_prec < curr_prec) break; /* Special case for **: only pop if precedence is strictly higher * so that the operator is right associative, that is: * 2 ** 3 ** 2 is evaluated as 2 ** (3 ** 2) == 512 instead * of (2 ** 3) ** 2 == 64. */ if (op->opcode == EXPR_OP_POW && top_prec <= curr_prec) break; /* Pop and add to program. */ top_op = exprStackPop(&es->ops_stack); int arity = exprGetOpArity(top_op->opcode); if (*stack_items < arity) { exprTokenRelease(top_op); if (errpos) *errpos = top_op->offset; return 1; } /* Move to the program stack. */ exprStackPush(&es->program, top_op); *stack_items = *stack_items - arity + 1; } /* Push current operator. */ exprStackPush(&es->ops_stack, op); exprTokenRetain(op); return 0; } /* Compile the expression into a set of push-value and exec-operator * that exprRun() can execute. The function returns an expstate object * that can be used for execution of the program. On error, NULL * is returned, and optionally the position of the error into the * expression is returned by reference. */ exprstate *exprCompile(char *expr, int *errpos) { /* Initialize expression state. */ exprstate *es = RedisModule_Alloc(sizeof(exprstate)); es->expr = RedisModule_Strdup(expr); es->p = es->expr; /* Initialize all stacks. */ exprStackInit(&es->values_stack); exprStackInit(&es->ops_stack); exprStackInit(&es->tokens); exprStackInit(&es->program); /* Tokenization. */ if (exprTokenize(es, errpos)) { exprFree(es); return NULL; } /* Compile the expression into a sequence of operations. */ int stack_items = 0; // Track # of items that would be on the stack // during execution. This way we can detect arity // issues at compile time. /* Process each token. */ for (int i = 0; i < es->tokens.numitems; i++) { exprtoken *token = es->tokens.items[i]; if (token->token_type == EXPR_TOKEN_EOF) break; /* Handle values (numbers, strings, selectors). */ if (token->token_type == EXPR_TOKEN_NUM || token->token_type == EXPR_TOKEN_STR || token->token_type == EXPR_TOKEN_TUPLE || token->token_type == EXPR_TOKEN_SELECTOR) { exprStackPush(&es->program, token); exprTokenRetain(token); stack_items++; continue; } /* Handle operators. */ if (token->token_type == EXPR_TOKEN_OP) { if (exprProcessOperator(es, token, &stack_items, errpos)) { exprFree(es); return NULL; } continue; } } /* Process remaining operators on the stack. */ while (es->ops_stack.numitems > 0) { exprtoken *op = exprStackPop(&es->ops_stack); if (op->opcode == EXPR_OP_OPAREN) { if (errpos) *errpos = op->offset; exprTokenRelease(op); exprFree(es); return NULL; } int arity = exprGetOpArity(op->opcode); if (stack_items < arity) { if (errpos) *errpos = op->offset; exprTokenRelease(op); exprFree(es); return NULL; } exprStackPush(&es->program, op); stack_items = stack_items - arity + 1; } /* Verify that exactly one value would remain on the stack after * execution. We could also check that such value is a number, but this * would make the code more complex without much gains. */ if (stack_items != 1) { if (errpos) { /* Point to the last token's offset for error reporting. */ exprtoken *last = es->tokens.items[es->tokens.numitems - 1]; *errpos = last->offset; } exprFree(es); return NULL; } return es; } /* ============================ Expression execution ======================== */ /* Convert a token to its numeric value. For strings we attempt to parse them * as numbers, returning 0 if conversion fails. */ double exprTokenToNum(exprtoken *t) { char buf[256]; if (t->token_type == EXPR_TOKEN_NUM) { return t->num; } else if (t->token_type == EXPR_TOKEN_STR && t->str.len < sizeof(buf)) { memcpy(buf, t->str.start, t->str.len); buf[t->str.len] = '\0'; char *endptr; double val = strtod(buf, &endptr); return *endptr == '\0' ? val : 0; } else { return 0; } } /* Convert object to true/false (0 or 1) */ double exprTokenToBool(exprtoken *t) { if (t->token_type == EXPR_TOKEN_NUM) { return t->num != 0; } else if (t->token_type == EXPR_TOKEN_STR && t->str.len == 0) { return 0; // Empty string are false, like in Javascript. } else if (t->token_type == EXPR_TOKEN_NULL) { return 0; // Null is surely more false than true... } else { return 1; // Every non numerical type is true. } } /* Compare two tokens. Returns true if they are equal. */ int exprTokensEqual(exprtoken *a, exprtoken *b) { // If both are strings, do string comparison. if (a->token_type == EXPR_TOKEN_STR && b->token_type == EXPR_TOKEN_STR) { return a->str.len == b->str.len && memcmp(a->str.start, b->str.start, a->str.len) == 0; } // If both are numbers, do numeric comparison. if (a->token_type == EXPR_TOKEN_NUM && b->token_type == EXPR_TOKEN_NUM) { return a->num == b->num; } /* If one of the two is null, the expression is true only if * both are null. */ if (a->token_type == EXPR_TOKEN_NULL || b->token_type == EXPR_TOKEN_NULL) { return a->token_type == b->token_type; } // Mixed types - convert to numbers and compare. return exprTokenToNum(a) == exprTokenToNum(b); } #include "fastjson.c" // JSON parser implementation used by exprRun(). /* Execute the compiled expression program. Returns 1 if the final stack value * evaluates to true, 0 otherwise. Also returns 0 if any selector callback * fails. */ int exprRun(exprstate *es, char *json, size_t json_len) { exprStackReset(&es->values_stack); // Execute each instruction in the program. for (int i = 0; i < es->program.numitems; i++) { exprtoken *t = es->program.items[i]; // Handle selectors by calling the callback. if (t->token_type == EXPR_TOKEN_SELECTOR) { exprtoken *obj = NULL; if (t->str.len > 0) obj = jsonExtractField(json,json_len,t->str.start,t->str.len); // Selector not found or JSON object not convertible to // expression tokens. Evaluate the expression to false. if (obj == NULL) return 0; exprStackPush(&es->values_stack, obj); continue; } // Push non-operator values directly onto the stack. if (t->token_type != EXPR_TOKEN_OP) { exprStackPush(&es->values_stack, t); exprTokenRetain(t); continue; } // Handle operators. exprtoken *result = exprNewToken(EXPR_TOKEN_NUM); // Pop operands - we know we have enough from compile-time checks. exprtoken *b = exprStackPop(&es->values_stack); exprtoken *a = NULL; if (exprGetOpArity(t->opcode) == 2) { a = exprStackPop(&es->values_stack); } switch(t->opcode) { case EXPR_OP_NOT: result->num = exprTokenToBool(b) == 0 ? 1 : 0; break; case EXPR_OP_POW: { double base = exprTokenToNum(a); double exp = exprTokenToNum(b); result->num = pow(base, exp); break; } case EXPR_OP_MULT: result->num = exprTokenToNum(a) * exprTokenToNum(b); break; case EXPR_OP_DIV: result->num = exprTokenToNum(a) / exprTokenToNum(b); break; case EXPR_OP_MOD: { double va = exprTokenToNum(a); double vb = exprTokenToNum(b); result->num = fmod(va, vb); break; } case EXPR_OP_SUM: result->num = exprTokenToNum(a) + exprTokenToNum(b); break; case EXPR_OP_DIFF: result->num = exprTokenToNum(a) - exprTokenToNum(b); break; case EXPR_OP_GT: result->num = exprTokenToNum(a) > exprTokenToNum(b) ? 1 : 0; break; case EXPR_OP_GTE: result->num = exprTokenToNum(a) >= exprTokenToNum(b) ? 1 : 0; break; case EXPR_OP_LT: result->num = exprTokenToNum(a) < exprTokenToNum(b) ? 1 : 0; break; case EXPR_OP_LTE: result->num = exprTokenToNum(a) <= exprTokenToNum(b) ? 1 : 0; break; case EXPR_OP_EQ: result->num = exprTokensEqual(a, b) ? 1 : 0; break; case EXPR_OP_NEQ: result->num = !exprTokensEqual(a, b) ? 1 : 0; break; case EXPR_OP_IN: { // For 'in' operator, b must be a tuple. result->num = 0; // Default to false. if (b->token_type == EXPR_TOKEN_TUPLE) { for (size_t j = 0; j < b->tuple.len; j++) { if (exprTokensEqual(a, b->tuple.ele[j])) { result->num = 1; // Found a match. break; } } } break; } case EXPR_OP_AND: result->num = exprTokenToBool(a) != 0 && exprTokenToBool(b) != 0 ? 1 : 0; break; case EXPR_OP_OR: result->num = exprTokenToBool(a) != 0 || exprTokenToBool(b) != 0 ? 1 : 0; break; default: // Do nothing: we don't want runtime errors. break; } // Free operands and push result. if (a) exprTokenRelease(a); exprTokenRelease(b); exprStackPush(&es->values_stack, result); } // Get final result from stack. exprtoken *final = exprStackPop(&es->values_stack); if (final == NULL) return 0; // Convert result to boolean. int retval = exprTokenToBool(final); exprTokenRelease(final); return retval; } /* ============================ Simple test main ============================ */ #ifdef TEST_MAIN #include "fastjson_test.c" void exprPrintToken(exprtoken *t) { switch(t->token_type) { case EXPR_TOKEN_EOF: printf("EOF"); break; case EXPR_TOKEN_NUM: printf("NUM:%g", t->num); break; case EXPR_TOKEN_STR: printf("STR:\"%.*s\"", (int)t->str.len, t->str.start); break; case EXPR_TOKEN_SELECTOR: printf("SEL:%.*s", (int)t->str.len, t->str.start); break; case EXPR_TOKEN_OP: printf("OP:"); for (int i = 0; ExprOptable[i].opname != NULL; i++) { if (ExprOptable[i].opcode == t->opcode) { printf("%s", ExprOptable[i].opname); break; } } break; default: printf("UNKNOWN"); break; } } void exprPrintStack(exprstack *stack, const char *name) { printf("%s (%d items):", name, stack->numitems); for (int j = 0; j < stack->numitems; j++) { printf(" "); exprPrintToken(stack->items[j]); } printf("\n"); } int main(int argc, char **argv) { /* Check for JSON parser test mode. */ if (argc >= 2 && strcmp(argv[1], "--test-json-parser") == 0) { run_fastjson_test(); return 0; } char *testexpr = "(5+2)*3 and .year > 1980 and 'foo' == 'foo'"; char *testjson = "{\"year\": 1984, \"name\": \"The Matrix\"}"; if (argc >= 2) testexpr = argv[1]; if (argc >= 3) testjson = argv[2]; printf("Compiling expression: %s\n", testexpr); int errpos = 0; exprstate *es = exprCompile(testexpr,&errpos); if (es == NULL) { printf("Compilation failed near \"...%s\"\n", testexpr+errpos); return 1; } exprPrintStack(&es->tokens, "Tokens"); exprPrintStack(&es->program, "Program"); printf("Running against object: %s\n", testjson); int result = exprRun(es,testjson,strlen(testjson)); printf("Result1: %s\n", result ? "True" : "False"); result = exprRun(es,testjson,strlen(testjson)); printf("Result2: %s\n", result ? "True" : "False"); exprFree(es); return 0; } #endif redis-8.0.2/modules/vector-sets/fastjson.c000066400000000000000000000374401501533116600205610ustar00rootroot00000000000000/* Ultra‑lightweight top‑level JSON field extractor. * Return the element directly as an expr.c token. * This code is directly included inside expr.c. * * Copyright (c) 2025-Present, Redis Ltd. * All rights reserved. * * Licensed under your choice of the Redis Source Available License 2.0 * (RSALv2) or the Server Side Public License v1 (SSPLv1). * * Originally authored by: Salvatore Sanfilippo. * * ------------------------------------------------------------------ * * DESIGN GOALS: * * 1. Zero heap allocations while seeking the requested key. * 2. A single parse (and therefore a single allocation, if needed) * when the key finally matches. * 3. Same subset‑of‑JSON coverage needed by expr.c: * - Strings (escapes: \" \\ \n \r \t). * - Numbers (double). * - Booleans. * - Null. * - Flat arrays of the above primitives. * * Any other value (nested object, unicode escape, etc.) returns NULL. * Should be very easy to extend it in case in the future we want * more for the FILTER option of VSIM. * 4. No global state, so this file can be #included directly in expr.c. * * The only API expr.c uses directly is: * * exprtoken *jsonExtractField(const char *json, size_t json_len, * const char *field, size_t field_len); * ------------------------------------------------------------------ */ #include #include // Forward declarations. static int jsonSkipValue(const char **p, const char *end); static exprtoken *jsonParseValueToken(const char **p, const char *end); /* Similar to ctype.h isdigit() but covers the whole JSON number charset, * including exp form. */ static int jsonIsNumberChar(int c) { return isdigit(c) || c=='-' || c=='+' || c=='.' || c=='e' || c=='E'; } /* ========================== Fast skipping of JSON ========================= * The helpers here are designed to skip values without performing any * allocation. This way, for the use case of this JSON parser, we are able * to easily (and with good speed) skip fields and values we are not * interested in. Then, later in the code, when we find the field we want * to obtain, we finally call the functions that turn a given JSON value * associated to a field into our of our expressions token. * ========================================================================== */ /* Advance *p consuming all the spaces. */ static inline void jsonSkipWhiteSpaces(const char **p, const char *end) { while (*p < end && isspace((unsigned char)**p)) (*p)++; } /* Advance *p past a JSON string. Returns 1 on success, 0 on error. */ static int jsonSkipString(const char **p, const char *end) { if (*p >= end || **p != '"') return 0; (*p)++; /* Skip opening quote. */ while (*p < end) { if (**p == '\\') { (*p) += 2; continue; } if (**p == '"') { (*p)++; /* Skip closing quote. */ return 1; } (*p)++; } return 0; /* unterminated */ } /* Skip an array or object generically using depth counter. * Opener and closer tells the function how the aggregated * data type starts/stops, basically [] or {}. */ static int jsonSkipBracketed(const char **p, const char *end, char opener, char closer) { int depth = 1; (*p)++; /* Skip opener. */ /* Loop until we reach the end of the input or find the matching * closer (depth becomes 0). */ while (*p < end && depth > 0) { char c = **p; if (c == '"') { // Found a string, delegate skipping to jsonSkipString(). if (!jsonSkipString(p, end)) { return 0; // String skipping failed (e.g., unterminated) } /* jsonSkipString() advances *p past the closing quote. * Continue the loop to process the character *after* the string. */ continue; } /* If it's not a string, check if it affects the depth for the * specific brackets we are currently tracking. */ if (c == opener) { depth++; } else if (c == closer) { depth--; } /* Always advance the pointer for any non-string character. * This handles commas, colons, whitespace, numbers, literals, * and even nested brackets of a *different* type than the * one we are currently skipping (e.g. skipping a { inside []). */ (*p)++; } /* Return 1 (true) if we successfully found the matching closer, * otherwise there is a parse error and we return 0. */ return depth == 0; } /* Skip a single JSON literal (true, null, ...) starting at *p. * Returns 1 on success, 0 on failure. */ static int jsonSkipLiteral(const char **p, const char *end, const char *lit) { size_t l = strlen(lit); if (*p + l > end) return 0; if (strncmp(*p, lit, l) == 0) { *p += l; return 1; } return 0; } /* Skip number, don't check that number format is correct, just consume * number-alike characters. * * Note: More robust number skipping might check validity, * but for skipping, just consuming plausible characters is enough. */ static int jsonSkipNumber(const char **p, const char *end) { const char *num_start = *p; while (*p < end && jsonIsNumberChar(**p)) (*p)++; return *p > num_start; // Any progress made? Otherwise no number found. } /* Skip any JSON value. 1 = success, 0 = error. */ static int jsonSkipValue(const char **p, const char *end) { jsonSkipWhiteSpaces(p, end); if (*p >= end) return 0; switch (**p) { case '"': return jsonSkipString(p, end); case '{': return jsonSkipBracketed(p, end, '{', '}'); case '[': return jsonSkipBracketed(p, end, '[', ']'); case 't': return jsonSkipLiteral(p, end, "true"); case 'f': return jsonSkipLiteral(p, end, "false"); case 'n': return jsonSkipLiteral(p, end, "null"); default: return jsonSkipNumber(p, end); } } /* =========================== JSON to exprtoken ============================ * The functions below convert a given json value to the equivalent * expression token structure. * ========================================================================== */ static exprtoken *jsonParseStringToken(const char **p, const char *end) { if (*p >= end || **p != '"') return NULL; const char *start = ++(*p); int esc = 0; size_t len = 0; int has_esc = 0; const char *q = *p; while (q < end) { if (esc) { esc = 0; q++; len++; has_esc = 1; continue; } if (*q == '\\') { esc = 1; q++; continue; } if (*q == '"') break; q++; len++; } if (q >= end || *q != '"') return NULL; // Unterminated string exprtoken *t = exprNewToken(EXPR_TOKEN_STR); if (!has_esc) { // No escapes, we can point directly into the original JSON string. t->str.start = (char*)start; t->str.len = len; t->str.heapstr = NULL; } else { // Escapes present, need to allocate and copy/process escapes. char *dst = RedisModule_Alloc(len + 1); t->str.start = t->str.heapstr = dst; t->str.len = len; const char *r = start; esc = 0; while (r < q) { if (esc) { switch (*r) { // Supported escapes from Goal 3. case 'n': *dst='\n'; break; case 'r': *dst='\r'; break; case 't': *dst='\t'; break; case '\\': *dst='\\'; break; case '"': *dst='\"'; break; // Escapes (like \uXXXX, \b, \f) are not supported for now, // we just copy them verbatim. default: *dst=*r; break; } dst++; esc = 0; r++; continue; } if (*r == '\\') { esc = 1; r++; continue; } *dst++ = *r++; } *dst = '\0'; // Null-terminate the allocated string. } *p = q + 1; // Advance the main pointer past the closing quote. return t; } static exprtoken *jsonParseNumberToken(const char **p, const char *end) { // Use a buffer to extract the number literal for parsing with strtod(). char buf[256]; int idx = 0; const char *start = *p; // For strtod partial failures check. // Copy potential number characters to buffer. while (*p < end && idx < (int)sizeof(buf)-1 && jsonIsNumberChar(**p)) { buf[idx++] = **p; (*p)++; } buf[idx]='\0'; // Null-terminate buffer. if (idx==0) return NULL; // No number characters found. char *ep; // End pointer for strtod validation. double v = strtod(buf, &ep); /* Check if strtod() consumed the entire buffer content. * If not, the number format was invalid. */ if (*ep!='\0') { // strtod() failed; rewind p to the start and return NULL *p = start; return NULL; } // If strtod() succeeded, create and return the token.. exprtoken *t = exprNewToken(EXPR_TOKEN_NUM); t->num = v; return t; } static exprtoken *jsonParseLiteralToken(const char **p, const char *end, const char *lit, int type, double num) { size_t l = strlen(lit); // Ensure we don't read past 'end'. if ((*p + l) > end) return NULL; if (strncmp(*p, lit, l) != 0) return NULL; // Literal doesn't match. // Check that the character *after* the literal is a valid JSON delimiter // (whitespace, comma, closing bracket/brace, or end of input) // This prevents matching "trueblabla" as "true". if ((*p + l) < end) { char next_char = *(*p + l); if (!isspace((unsigned char)next_char) && next_char!=',' && next_char!=']' && next_char!='}') { return NULL; // Invalid character following literal. } } // Literal matched and is correctly terminated. *p += l; exprtoken *t = exprNewToken(type); t->num = num; return t; } static exprtoken *jsonParseArrayToken(const char **p, const char *end) { if (*p >= end || **p != '[') return NULL; (*p)++; // Skip '['. jsonSkipWhiteSpaces(p,end); exprtoken *t = exprNewToken(EXPR_TOKEN_TUPLE); t->tuple.len = 0; t->tuple.ele = NULL; size_t alloc = 0; // Handle empty array []. if (*p < end && **p == ']') { (*p)++; // Skip ']'. return t; } // Parse array elements. while (1) { exprtoken *ele = jsonParseValueToken(p,end); if (!ele) { exprTokenRelease(t); // Clean up partially built array token. return NULL; } // Grow allocated space for elements if needed. if (t->tuple.len == alloc) { size_t newsize = alloc ? alloc * 2 : 4; // Check for potential overflow if newsize becomes huge. if (newsize < alloc) { exprTokenRelease(ele); exprTokenRelease(t); return NULL; } exprtoken **newele = RedisModule_Realloc(t->tuple.ele, sizeof(exprtoken*)*newsize); t->tuple.ele = newele; alloc = newsize; } t->tuple.ele[t->tuple.len++] = ele; // Add element. jsonSkipWhiteSpaces(p,end); if (*p>=end) { // Unterminated array. Note that this check is crucial because // previous value parsed may seek 'p' to 'end'. exprTokenRelease(t); return NULL; } // Check for comma (more elements) or closing bracket. if (**p == ',') { (*p)++; // Skip ',' jsonSkipWhiteSpaces(p,end); // Skip whitespace before next element continue; // Parse next element } else if (**p == ']') { (*p)++; // Skip ']' return t; // End of array } else { // Unexpected character (not ',' or ']') exprTokenRelease(t); return NULL; } } } /* Turn a JSON value into an expr token. */ static exprtoken *jsonParseValueToken(const char **p, const char *end) { jsonSkipWhiteSpaces(p,end); if (*p >= end) return NULL; switch (**p) { case '"': return jsonParseStringToken(p,end); case '[': return jsonParseArrayToken(p,end); case '{': return NULL; // No nested elements support for now. case 't': return jsonParseLiteralToken(p,end,"true",EXPR_TOKEN_NUM,1); case 'f': return jsonParseLiteralToken(p,end,"false",EXPR_TOKEN_NUM,0); case 'n': return jsonParseLiteralToken(p,end,"null",EXPR_TOKEN_NULL,0); default: // Check if it starts like a number. if (isdigit((unsigned char)**p) || **p=='-' || **p=='+') { return jsonParseNumberToken(p,end); } // Anything else is an unsupported type or malformed JSON. return NULL; } } /* ============================== Fast key seeking ========================== */ /* Finds the start of the value for a given field key within a JSON object. * Returns pointer to the first char of the value, or NULL if not found/error. * This function does not perform any allocation and is optimized to seek * the specified *toplevel* filed as fast as possible. */ static const char *jsonSeekField(const char *json, const char *end, const char *field, size_t flen) { const char *p = json; jsonSkipWhiteSpaces(&p,end); if (p >= end || *p != '{') return NULL; // Must start with '{'. p++; // skip '{'. while (1) { jsonSkipWhiteSpaces(&p,end); if (p >= end) return NULL; // Reached end within object. if (*p == '}') return NULL; // End of object, field not found. // Expecting a key (string). if (*p != '"') return NULL; // Key must be a string. // --- Key Matching using jsonSkipString --- const char *key_start = p + 1; // Start of key content. const char *key_end_p = p; // Will later contain the end. // Use jsonSkipString() to find the end. if (!jsonSkipString(&key_end_p, end)) { // Unterminated / invalid key string. return NULL; } // Calculate the length of the key's content. size_t klen = (key_end_p - 1) - key_start; /* Perform the comparison using the raw key content. * WARNING: This uses memcmp(), so we don't handle escaped chars * within the key matching against unescaped chars in 'field'. */ int match = klen == flen && !memcmp(key_start, field, flen); // Update the main pointer 'p' to be after the key string. p = key_end_p; // Now we expect to find a ":" followed by a value. jsonSkipWhiteSpaces(&p,end); if (p>=end || *p!=':') return NULL; // Expect ':' after key p++; // Skip ':'. // Seek value. jsonSkipWhiteSpaces(&p,end); if (p>=end) return NULL; // Expect value after ':' if (match) { // Found the matching key, p now points to the start of the value. return p; } else { // Key didn't match, skip the corresponding value. if (!jsonSkipValue(&p,end)) return NULL; // Syntax error. } // Look for comma or a closing brace. jsonSkipWhiteSpaces(&p,end); if (p>=end) return NULL; // Reached end after value. if (*p == ',') { p++; // Skip comma, continue loop to find next key. continue; } else if (*p == '}') { return NULL; // Reached end of object, field not found. } return NULL; // Malformed JSON (unexpected char after value). } } /* This is the only real API that this file conceptually exports (it is * inlined, actually). */ exprtoken *jsonExtractField(const char *json, size_t json_len, const char *field, size_t field_len) { const char *end = json + json_len; const char *valptr = jsonSeekField(json,end,field,field_len); if (!valptr) return NULL; /* Key found, valptr points to the start of the value. * Convert it into an expression token object. */ return jsonParseValueToken(&valptr,end); } redis-8.0.2/modules/vector-sets/fastjson_test.c000066400000000000000000000322651501533116600216200ustar00rootroot00000000000000/* fastjson_test.c - Stress test for fastjson.c * * This performs boundary and corruption tests to ensure * the JSON parser handles edge cases without accessing * memory outside the bounds of the input. */ #include #include #include #include #include #include #include #include #include #include #include /* Page size constant - typically 4096 or 16k bytes (Apple Silicon). * We use 16k so that it will work on both, but not with Linux huge pages. */ #define PAGE_SIZE 4096*4 #define MAX_JSON_SIZE (PAGE_SIZE - 128) /* Keep some margin */ #define MAX_FIELD_SIZE 64 #define NUM_TEST_ITERATIONS 100000 #define NUM_CORRUPTION_TESTS 10000 #define NUM_BOUNDARY_TESTS 10000 /* Test state tracking */ static char *safe_page = NULL; /* Start of readable/writable page */ static char *unsafe_page = NULL; /* Start of inaccessible guard page */ static int boundary_violation = 0; /* Flag for boundary violations */ static jmp_buf jmpbuf; /* For signal handling */ static int tests_passed = 0; static int tests_failed = 0; static int corruptions_passed = 0; static int boundary_tests_passed = 0; /* Test metadata for tracking */ typedef struct { char *json; size_t json_len; char field[MAX_FIELD_SIZE]; size_t field_len; int expected_result; } test_case_t; /* Forward declarations for test JSON generation */ char *generate_random_json(size_t *len, char *field, size_t *field_len, int *has_field); void corrupt_json(char *json, size_t len); void setup_test_memory(void); void cleanup_test_memory(void); void run_normal_tests(void); void run_corruption_tests(void); void run_boundary_tests(void); void print_test_summary(void); /* Signal handler for segmentation violations */ static void sigsegv_handler(int sig) { boundary_violation = 1; printf("Boundary violation detected! Caught signal %d\n", sig); longjmp(jmpbuf, 1); } /* Wrapper for jsonExtractField to check for boundary violations */ exprtoken *safe_extract_field(const char *json, size_t json_len, const char *field, size_t field_len) { boundary_violation = 0; if (setjmp(jmpbuf) == 0) { return jsonExtractField(json, json_len, field, field_len); } else { return NULL; /* Return NULL if boundary violation occurred */ } } /* Setup two adjacent memory pages - one readable/writable, one inaccessible */ void setup_test_memory(void) { /* Request a page of memory, with specific alignment. We rely on the * fact that hopefully the page after that will cause a segfault if * accessed. */ void *region = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (region == MAP_FAILED) { perror("mmap failed"); exit(EXIT_FAILURE); } safe_page = (char*)region; unsafe_page = safe_page + PAGE_SIZE; // Uncomment to make sure it crashes :D // printf("%d\n", unsafe_page[5]); /* Set up signal handlers for memory access violations */ struct sigaction sa; sa.sa_handler = sigsegv_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); } void cleanup_test_memory(void) { if (safe_page != NULL) { munmap(safe_page, PAGE_SIZE); safe_page = NULL; unsafe_page = NULL; } } /* Generate random strings with proper escaping for JSON */ void generate_random_string(char *buffer, size_t max_len) { static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; size_t len = 1 + rand() % (max_len - 2); /* Ensure at least 1 char */ for (size_t i = 0; i < len; i++) { buffer[i] = charset[rand() % (sizeof(charset) - 1)]; } buffer[len] = '\0'; } /* Generate random numbers as strings */ void generate_random_number(char *buffer, size_t max_len) { double num = (double)rand() / RAND_MAX * 1000.0; /* Occasionally make it negative or add decimal places */ if (rand() % 5 == 0) num = -num; if (rand() % 3 != 0) num += (double)(rand() % 100) / 100.0; snprintf(buffer, max_len, "%.6g", num); } /* Generate a random field name */ void generate_random_field(char *field, size_t *field_len) { generate_random_string(field, MAX_FIELD_SIZE / 2); *field_len = strlen(field); } /* Generate a random JSON object with fields */ char *generate_random_json(size_t *len, char *field, size_t *field_len, int *has_field) { char *json = malloc(MAX_JSON_SIZE); if (json == NULL) { perror("malloc"); exit(EXIT_FAILURE); } char buffer[MAX_JSON_SIZE / 4]; /* Buffer for generating values */ int pos = 0; int num_fields = 1 + rand() % 10; /* Random number of fields */ int target_field_index = rand() % num_fields; /* Which field to return */ /* Start the JSON object */ pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "{"); /* Generate random field/value pairs */ for (int i = 0; i < num_fields; i++) { /* Add a comma if not the first field */ if (i > 0) { pos += snprintf(json + pos, MAX_JSON_SIZE - pos, ", "); } /* Generate a field name */ if (i == target_field_index) { /* This is our target field - save it for the caller */ generate_random_field(field, field_len); pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\": ", field); *has_field = 1; /* Sometimes change the last char so that it will not match. */ if (rand() % 2) { *has_field = 0; field[*field_len-1] = '!'; } } else { generate_random_string(buffer, MAX_FIELD_SIZE / 4); pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\": ", buffer); } /* Generate a random value type */ int value_type = rand() % 5; switch (value_type) { case 0: /* String */ generate_random_string(buffer, MAX_JSON_SIZE / 8); pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\"", buffer); break; case 1: /* Number */ generate_random_number(buffer, MAX_JSON_SIZE / 8); pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "%s", buffer); break; case 2: /* Boolean: true */ pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "true"); break; case 3: /* Boolean: false */ pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "false"); break; case 4: /* Null */ pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "null"); break; case 5: /* Array (simple) */ pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "["); int array_items = 1 + rand() % 5; for (int j = 0; j < array_items; j++) { if (j > 0) pos += snprintf(json + pos, MAX_JSON_SIZE - pos, ", "); /* Array items - either number or string */ if (rand() % 2) { generate_random_number(buffer, MAX_JSON_SIZE / 16); pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "%s", buffer); } else { generate_random_string(buffer, MAX_JSON_SIZE / 16); pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "\"%s\"", buffer); } } pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "]"); break; } } /* Close the JSON object */ pos += snprintf(json + pos, MAX_JSON_SIZE - pos, "}"); *len = pos; return json; } /* Corrupt JSON by replacing random characters */ void corrupt_json(char *json, size_t len) { if (len < 2) return; /* Too short to corrupt safely */ /* Corrupt 1-3 characters */ int num_corruptions = 1 + rand() % 3; for (int i = 0; i < num_corruptions; i++) { size_t pos = rand() % len; char corruption = " \t\n{}[]\":,0123456789abcdefXYZ"[rand() % 30]; json[pos] = corruption; } } /* Run standard parser tests with generated valid JSON */ void run_normal_tests(void) { printf("Running normal JSON extraction tests...\n"); for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { char field[MAX_FIELD_SIZE] = {0}; size_t field_len = 0; size_t json_len = 0; int has_field = 0; /* Generate random JSON */ char *json = generate_random_json(&json_len, field, &field_len, &has_field); /* Use valid field to test parser */ exprtoken *token = safe_extract_field(json, json_len, field, field_len); /* Check if we got a token as expected */ if (has_field && token != NULL) { exprTokenRelease(token); tests_passed++; } else if (!has_field && token == NULL) { tests_passed++; } else { tests_failed++; } /* Test with a non-existent field */ char nonexistent_field[MAX_FIELD_SIZE] = "nonexistent_field"; token = safe_extract_field(json, json_len, nonexistent_field, strlen(nonexistent_field)); if (token == NULL) { tests_passed++; } else { exprTokenRelease(token); tests_failed++; } free(json); } } /* Run tests with corrupted JSON */ void run_corruption_tests(void) { printf("Running JSON corruption tests...\n"); for (int i = 0; i < NUM_CORRUPTION_TESTS; i++) { char field[MAX_FIELD_SIZE] = {0}; size_t field_len = 0; size_t json_len = 0; int has_field = 0; /* Generate random JSON */ char *json = generate_random_json(&json_len, field, &field_len, &has_field); /* Make a copy and corrupt it */ char *corrupted = malloc(json_len + 1); if (!corrupted) { perror("malloc"); free(json); exit(EXIT_FAILURE); } memcpy(corrupted, json, json_len + 1); corrupt_json(corrupted, json_len); /* Test with corrupted JSON */ exprtoken *token = safe_extract_field(corrupted, json_len, field, field_len); /* We're just testing that it doesn't crash or access invalid memory */ if (boundary_violation) { printf("Boundary violation with corrupted JSON!\n"); tests_failed++; } else { if (token != NULL) { exprTokenRelease(token); } corruptions_passed++; } free(corrupted); free(json); } } /* Run tests at memory boundaries */ void run_boundary_tests(void) { printf("Running memory boundary tests...\n"); for (int i = 0; i < NUM_BOUNDARY_TESTS; i++) { char field[MAX_FIELD_SIZE] = {0}; size_t field_len = 0; size_t json_len = 0; int has_field = 0; /* Generate random JSON */ char *temp_json = generate_random_json(&json_len, field, &field_len, &has_field); /* Truncate the JSON to a random length */ size_t truncated_len = 1 + rand() % json_len; /* Place at the edge of the safe page */ size_t offset = PAGE_SIZE - truncated_len; memcpy(safe_page + offset, temp_json, truncated_len); /* Test parsing with non-existent field (forcing it to scan to end) */ char nonexistent_field[MAX_FIELD_SIZE] = "nonexistent_field"; exprtoken *token = safe_extract_field(safe_page + offset, truncated_len, nonexistent_field, strlen(nonexistent_field)); /* We're just testing that it doesn't access memory beyond the boundary */ if (boundary_violation) { printf("Boundary violation at edge of memory page!\n"); tests_failed++; } else { if (token != NULL) { exprTokenRelease(token); } boundary_tests_passed++; } free(temp_json); } } /* Print summary of test results */ void print_test_summary(void) { printf("\n===== FASTJSON PARSER TEST SUMMARY =====\n"); printf("Normal tests passed: %d/%d\n", tests_passed, NUM_TEST_ITERATIONS * 2); printf("Corruption tests passed: %d/%d\n", corruptions_passed, NUM_CORRUPTION_TESTS); printf("Boundary tests passed: %d/%d\n", boundary_tests_passed, NUM_BOUNDARY_TESTS); printf("Failed tests: %d\n", tests_failed); if (tests_failed == 0) { printf("\nALL TESTS PASSED! The JSON parser appears to be robust.\n"); } else { printf("\nSome tests FAILED. The JSON parser may be vulnerable.\n"); } } /* Entry point for fastjson parser test */ void run_fastjson_test(void) { printf("Starting fastjson parser stress test...\n"); /* Seed the random number generator */ srand(time(NULL)); /* Setup test memory environment */ setup_test_memory(); /* Run the various test phases */ run_normal_tests(); run_corruption_tests(); run_boundary_tests(); /* Print summary */ print_test_summary(); /* Cleanup */ cleanup_test_memory(); } redis-8.0.2/modules/vector-sets/hnsw.c000066400000000000000000003217411501533116600177110ustar00rootroot00000000000000/* HNSW (Hierarchical Navigable Small World) Implementation. * * Based on the paper by Yu. A. Malkov, D. A. Yashunin. * * Many details of this implementation, not covered in the paper, were * obtained simulating different workloads and checking the connection * quality of the graph. * * Notably, this implementation: * * 1. Only uses bi-directional links, implementing strategies in order to * link new nodes even when candidates are full, and our new node would * be not close enough to replace old links in candidate. * * 2. We normalize on-insert, making cosine similarity and dot product the * same. This means we can't use euclidean distance or alike here. * Together with quantization, this provides an important speedup that * makes HNSW more practical. * * 3. The quantization used is int8. And it is performed per-vector, so the * "range" (max abs value) is also stored alongside with the quantized data. * * 4. This library implements true elements deletion, not just marking the * element as deleted, but removing it (we can do it since our links are * bidirectional), and reliking the nodes orphaned of one link among * them. * * Copyright (c) 2009-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). * Originally authored by: Salvatore Sanfilippo. */ #define _DEFAULT_SOURCE #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include /* for INFINITY if not in math.h */ #include #include "hnsw.h" #if 0 #define debugmsg printf #else #define debugmsg if(0) printf #endif #ifndef INFINITY #define INFINITY (1.0/0.0) #endif #define MIN(a,b) ((a) < (b) ? (a) : (b)) /* Algorithm parameters. */ #define HNSW_P 0.25 /* Probability of level increase. */ #define HNSW_MAX_LEVEL 16 /* Max level nodes can reach. */ #define HNSW_EF_C 200 /* Default size of dynamic candidate list while * inserting a new node, in case 0 is passed to * the 'ef' argument while inserting. This is also * used when deleting nodes for the search step * needed sometimes to reconnect nodes that remain * orphaned of one link. */ static void (*hfree)(void *p) = free; static void *(*hmalloc)(size_t s) = malloc; static void *(*hrealloc)(void *old, size_t s) = realloc; void hnsw_set_allocator(void (*free_ptr)(void*), void *(*malloc_ptr)(size_t), void *(*realloc_ptr)(void*, size_t)) { hfree = free_ptr; hmalloc = malloc_ptr; hrealloc = realloc_ptr; } // Get a warning if you use the libc allocator functions for mistake. #define malloc use_hmalloc_instead #define realloc use_hrealloc_instead #define free use_hfree_instead /* ============================== Prototypes ================================ */ void hnsw_cursor_element_deleted(HNSW *index, hnswNode *deleted); /* ============================ Priority queue ================================ * We need a priority queue to take an ordered list of candidates. Right now * it is implemented as a linear array, since it is relatively small. * * You may find it to be odd that we take the best element (smaller distance) * at the end of the array, but this way popping from the pqueue is O(1), as * we need to just decrement the count, and this is a very used operation * in a critical code path. This makes the priority queue implementation a * bit more complex in the insertion, but for good reasons. */ /* Maximum number of candidates we'll ever need (cit. Bill Gates). */ #define HNSW_MAX_CANDIDATES 256 typedef struct { hnswNode *node; float distance; } pqitem; typedef struct { pqitem *items; /* Array of items. */ uint32_t count; /* Current number of items. */ uint32_t cap; /* Maximum capacity. */ } pqueue; /* The HNSW algorithms access the pqueue conceptually from nearest (index 0) * to farthest (larger indexes) node, so the following macros are used to * access the pqueue in this fashion, even if the internal order is * actually reversed. */ #define pq_get_node(q,i) ((q)->items[(q)->count-(i+1)].node) #define pq_get_distance(q,i) ((q)->items[(q)->count-(i+1)].distance) /* Create a new priority queue with given capacity. Adding to the * pqueue only retains 'capacity' elements with the shortest distance. */ pqueue *pq_new(uint32_t capacity) { pqueue *pq = hmalloc(sizeof(*pq)); if (!pq) return NULL; pq->items = hmalloc(sizeof(pqitem) * capacity); if (!pq->items) { hfree(pq); return NULL; } pq->count = 0; pq->cap = capacity; return pq; } /* Free a priority queue. */ void pq_free(pqueue *pq) { if (!pq) return; hfree(pq->items); hfree(pq); } /* Insert maintaining distance order (higher distances first). */ void pq_push(pqueue *pq, hnswNode *node, float distance) { if (pq->count < pq->cap) { /* Queue not full: shift right from high distances to make room. */ uint32_t i = pq->count; while (i > 0 && pq->items[i-1].distance < distance) { pq->items[i] = pq->items[i-1]; i--; } pq->items[i].node = node; pq->items[i].distance = distance; pq->count++; } else { /* Queue full: if new item is worse than worst, ignore it. */ if (distance >= pq->items[0].distance) return; /* Otherwise shift left from low distances to drop worst. */ uint32_t i = 0; while (i < pq->cap-1 && pq->items[i+1].distance > distance) { pq->items[i] = pq->items[i+1]; i++; } pq->items[i].node = node; pq->items[i].distance = distance; } } /* Remove and return the top (closest) element, which is at count-1 * since we store elements with higher distances first. * Runs in constant time. */ hnswNode *pq_pop(pqueue *pq, float *distance) { if (pq->count == 0) return NULL; pq->count--; *distance = pq->items[pq->count].distance; return pq->items[pq->count].node; } /* Get distance of the furthest element. * An empty priority queue has infinite distance as its furthest element, * note that this behavior is needed by the algorithms below. */ float pq_max_distance(pqueue *pq) { if (pq->count == 0) return INFINITY; return pq->items[0].distance; } /* ============================ HNSW algorithm ============================== */ /* Dot product: our vectors are already normalized. * Version for not quantized vectors of floats. */ float vectors_distance_float(const float *x, const float *y, uint32_t dim) { /* Use two accumulators to reduce dependencies among multiplications. * This provides a clear speed boost in Apple silicon, but should be * help in general. */ float dot0 = 0.0f, dot1 = 0.0f; uint32_t i; // Process 8 elements per iteration, 50/50 with the two accumulators. for (i = 0; i + 7 < dim; i += 8) { dot0 += x[i] * y[i] + x[i+1] * y[i+1] + x[i+2] * y[i+2] + x[i+3] * y[i+3]; dot1 += x[i+4] * y[i+4] + x[i+5] * y[i+5] + x[i+6] * y[i+6] + x[i+7] * y[i+7]; } /* Handle the remaining elements. These are a minority in the case * of a small vector, don't optimize this part. */ for (; i < dim; i++) dot0 += x[i] * y[i]; /* The following line may be counter intuitive. The dot product of * normalized vectors is equivalent to their cosine similarity. The * cosine will be from -1 (vectors facing opposite directions in the * N-dim space) to 1 (vectors are facing in the same direction). * * We kinda want a "score" of distance from 0 to 2 (this is a distance * function and we want minimize the distance for K-NN searches), so we * can't just add 1: that would return a number in the 0-2 range, with * 0 meaning opposite vectors and 2 identical vectors, so this is * similarity, not distance. * * Returning instead (1 - dotprod) inverts the meaning: 0 is identical * and 2 is opposite, hence it is their distance. * * Why don't normalize the similarity right now, and return from 0 to * 1? Because division is costly. */ return 1.0f - (dot0 + dot1); } /* Q8 quants dotproduct. We do integer math and later fix it by range. */ float vectors_distance_q8(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) { /* Zero vector distance from anything is 1.0 * (since 1.0 - dot_product where dot_product = 0). */ return 1.0f; } /* Each vector is quantized from [-max_abs, +max_abs] to [-127, 127] * where range = 2*max_abs. */ const float scale_product = (range_a/127) * (range_b/127); int32_t dot0 = 0, dot1 = 0; uint32_t i; // Process 8 elements at a time for better pipeline utilization. for (i = 0; i + 7 < dim; i += 8) { dot0 += ((int32_t)x[i]) * ((int32_t)y[i]) + ((int32_t)x[i+1]) * ((int32_t)y[i+1]) + ((int32_t)x[i+2]) * ((int32_t)y[i+2]) + ((int32_t)x[i+3]) * ((int32_t)y[i+3]); dot1 += ((int32_t)x[i+4]) * ((int32_t)y[i+4]) + ((int32_t)x[i+5]) * ((int32_t)y[i+5]) + ((int32_t)x[i+6]) * ((int32_t)y[i+6]) + ((int32_t)x[i+7]) * ((int32_t)y[i+7]); } // Handle remaining elements. for (; i < dim; i++) dot0 += ((int32_t)x[i]) * ((int32_t)y[i]); // Convert to original range. float dotf = (dot0 + dot1) * 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; } 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) { 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); } return (float)opposite*2/dim; } /* Dot product between nodes. Will call the right version depending on the * quantization used. */ float hnsw_distance(HNSW *index, hnswNode *a, hnswNode *b) { switch(index->quant_type) { case HNSW_QUANT_NONE: return vectors_distance_float(a->vector,b->vector,index->vector_dim); case HNSW_QUANT_Q8: return vectors_distance_q8(a->vector,b->vector,index->vector_dim,a->quants_range,b->quants_range); case HNSW_QUANT_BIN: return vectors_distance_bin(a->vector,b->vector,index->vector_dim); default: assert(1 != 1); return 0; } } /* This do Q8 'range' quantization. * For people looking at this code thinking: Oh, I could use min/max * quants instead! Well: I tried with min/max normalization but the dot * product needs to accumulate the sum for later correction, and it's slower. */ void quantize_to_q8(float *src, int8_t *dst, uint32_t dim, float *rangeptr) { float max_abs = 0; for (uint32_t j = 0; j < dim; j++) { if (src[j] > max_abs) max_abs = src[j]; if (-src[j] > max_abs) max_abs = -src[j]; } if (max_abs == 0) { if (rangeptr) *rangeptr = 0; memset(dst, 0, dim); return; } const float scale = 127.0f / max_abs; // Scale to map to [-127, 127]. for (uint32_t j = 0; j < dim; j++) { dst[j] = (int8_t)roundf(src[j] * scale); } if (rangeptr) *rangeptr = max_abs; // Return max_abs instead of 2*max_abs. } /* Binary quantization of vector 'src' to 'dst'. We use full words of * 64 bit as smallest unit, we will just set all the unused bits to 0 * so that they'll be the same in all the vectors, and when xor+popcount * is used to compute the distance, such bits are not considered. This * allows to go faster. */ void quantize_to_bin(float *src, uint64_t *dst, uint32_t dim) { memset(dst,0,(dim+63)/64*sizeof(uint64_t)); for (uint32_t j = 0; j < dim; j++) { uint32_t word = j/64; uint32_t bit = j&63; /* Since cosine similarity checks the vector direction and * not magnitudo, we do likewise in the binary quantization and * just remember if the component is positive or negative. */ if (src[j] > 0) dst[word] |= 1ULL< HNSW_MAX_M) m = HNSW_MAX_M; index->M = m; index->quant_type = quant_type; index->enter_point = NULL; index->max_level = 0; index->vector_dim = vector_dim; index->node_count = 0; index->last_id = 0; index->head = NULL; index->cursors = NULL; /* Initialize epochs array. */ for (int i = 0; i < HNSW_MAX_THREADS; i++) index->current_epoch[i] = 0; /* Initialize locks. */ if (pthread_rwlock_init(&index->global_lock, NULL) != 0) { hfree(index); return NULL; } for (int i = 0; i < HNSW_MAX_THREADS; i++) { if (pthread_mutex_init(&index->slot_locks[i], NULL) != 0) { /* Clean up previously initialized mutexes. */ for (int j = 0; j < i; j++) pthread_mutex_destroy(&index->slot_locks[j]); pthread_rwlock_destroy(&index->global_lock); hfree(index); return NULL; } } /* Initialize atomic variables. */ index->next_slot = 0; index->version = 0; return index; } /* Fill 'vec' with the node vector, de-normalizing and de-quantizing it * as needed. Note that this function will return an approximated version * of the original vector. */ void hnsw_get_node_vector(HNSW *index, hnswNode *node, float *vec) { if (index->quant_type == HNSW_QUANT_NONE) { memcpy(vec,node->vector,index->vector_dim*sizeof(float)); } else if (index->quant_type == HNSW_QUANT_Q8) { int8_t *quants = node->vector; for (uint32_t j = 0; j < index->vector_dim; j++) vec[j] = (quants[j]*node->quants_range)/127; } else if (index->quant_type == HNSW_QUANT_BIN) { uint64_t *bits = node->vector; for (uint32_t j = 0; j < index->vector_dim; j++) { uint32_t word = j/64; uint32_t bit = j&63; vec[j] = (bits[word] & (1ULL<quant_type != HNSW_QUANT_BIN) { for (uint32_t j = 0; j < index->vector_dim; j++) vec[j] *= node->l2; } } /* Return the number of bytes needed to represent a vector in the index, * that is function of the dimension of the vectors and the quantization * type used. */ uint32_t hnsw_quants_bytes(HNSW *index) { switch(index->quant_type) { case HNSW_QUANT_NONE: return index->vector_dim * sizeof(float); case HNSW_QUANT_Q8: return index->vector_dim; case HNSW_QUANT_BIN: return (index->vector_dim+63)/64*8; default: assert(0 && "Quantization type not supported."); } } /* Create new node. Returns NULL on out of memory. * It is possible to pass the vector as floats or, in case this index * was already stored on disk and is being loaded, or serialized and * transmitted in any form, the already quantized version in * 'qvector'. * * Only vector or qvector should be non-NULL. The reason why passing * a quantized vector is useful, is that because re-normalizing and * re-quantizing several times the same vector may accumulate rounding * errors. So if you work with quantized indexes, you should save * the quantized indexes. * * Note that, together with qvector, the quantization range is needed, * since this library uses per-vector quantization. In case of quantized * vectors the l2 is considered to be '1', so if you want to restore * the right l2 (to use the API that returns an approximation of the * original vector) make sure to save the l2 on disk and set it back * after the node creation (see later for the serialization API that * handles this and more). */ hnswNode *hnsw_node_new(HNSW *index, uint64_t id, const float *vector, const int8_t *qvector, float qrange, uint32_t level, int normalize) { hnswNode *node = hmalloc(sizeof(hnswNode)+(sizeof(hnswNodeLayer)*(level+1))); if (!node) return NULL; if (id == 0) id = ++index->last_id; node->level = level; node->id = id; node->next = NULL; node->vector = NULL; node->l2 = 1; // Default in case of already quantized vectors. It is // up to the caller to fill this later, if needed. /* Initialize visited epoch array. */ for (int i = 0; i < HNSW_MAX_THREADS; i++) node->visited_epoch[i] = 0; if (qvector == NULL) { /* Copy input vector. */ node->vector = hmalloc(sizeof(float) * index->vector_dim); if (!node->vector) { hfree(node); return NULL; } memcpy(node->vector, vector, sizeof(float) * index->vector_dim); if (normalize) hnsw_normalize_vector(node->vector,&node->l2,index->vector_dim); /* Handle quantization. */ if (index->quant_type != HNSW_QUANT_NONE) { void *quants = hmalloc(hnsw_quants_bytes(index)); if (quants == NULL) { hfree(node->vector); hfree(node); return NULL; } // Quantize. switch(index->quant_type) { case HNSW_QUANT_Q8: quantize_to_q8(node->vector,quants,index->vector_dim,&node->quants_range); break; case HNSW_QUANT_BIN: quantize_to_bin(node->vector,quants,index->vector_dim); break; default: assert(0 && "Quantization type not handled."); break; } // Discard the full precision vector. hfree(node->vector); node->vector = quants; } } else { // We got the already quantized vector. Just copy it. assert(index->quant_type != HNSW_QUANT_NONE); uint32_t vector_bytes = hnsw_quants_bytes(index); node->vector = hmalloc(vector_bytes); node->quants_range = qrange; if (node->vector == NULL) { hfree(node); return NULL; } memcpy(node->vector,qvector,vector_bytes); } /* Initialize each layer. */ for (uint32_t i = 0; i <= level; i++) { uint32_t max_links = (i == 0) ? index->M*2 : index->M; node->layers[i].max_links = max_links; node->layers[i].num_links = 0; node->layers[i].worst_distance = 0; node->layers[i].worst_idx = 0; node->layers[i].links = hmalloc(sizeof(hnswNode*) * max_links); if (!node->layers[i].links) { for (uint32_t j = 0; j < i; j++) hfree(node->layers[j].links); hfree(node->vector); hfree(node); return NULL; } } return node; } /* Free a node. */ void hnsw_node_free(hnswNode *node) { if (!node) return; for (uint32_t i = 0; i <= node->level; i++) hfree(node->layers[i].links); hfree(node->vector); hfree(node); } /* Free the entire index. */ void hnsw_free(HNSW *index,void(*free_value)(void*value)) { if (!index) return; hnswNode *current = index->head; while (current) { hnswNode *next = current->next; if (free_value) free_value(current->value); hnsw_node_free(current); current = next; } /* Destroy locks */ pthread_rwlock_destroy(&index->global_lock); for (int i = 0; i < HNSW_MAX_THREADS; i++) { pthread_mutex_destroy(&index->slot_locks[i]); } hfree(index); } /* Add node to linked list of nodes. We may need to scan the whole * HNSW graph for several reasons. The list is doubly linked since we * also need the ability to remove a node without scanning the whole thing. */ void hnsw_add_node(HNSW *index, hnswNode *node) { node->next = index->head; node->prev = NULL; if (index->head) index->head->prev = node; index->head = node; index->node_count++; } /* Search the specified layer starting from the specified entry point * to collect 'ef' nodes that are near to 'query'. * * This function implements optional hybrid search, so that each node * can be accepted or not based on its associated value. In this case * a callback 'filter_callback' should be passed, together with a maximum * effort for the search (number of candidates to evaluate), since even * with a a low "EF" value we risk that there are too few nodes that satisfy * the provided filter, and we could trigger a full scan. */ pqueue *search_layer_with_filter( HNSW *index, hnswNode *query, hnswNode *entry_point, uint32_t ef, uint32_t layer, uint32_t slot, int (*filter_callback)(void *value, void *privdata), void *filter_privdata, uint32_t max_candidates) { // Mark visited nodes with a never seen epoch. index->current_epoch[slot]++; pqueue *candidates = pq_new(HNSW_MAX_CANDIDATES); pqueue *results = pq_new(ef); if (!candidates || !results) { if (candidates) pq_free(candidates); if (results) pq_free(results); return NULL; } // Take track of the total effort: only used when filtering via // a callback to have a bound effort. uint32_t evaluated_candidates = 1; // Add entry point. float dist = hnsw_distance(index, query, entry_point); pq_push(candidates, entry_point, dist); if (filter_callback == NULL || filter_callback(entry_point->value, filter_privdata)) { pq_push(results, entry_point, dist); } entry_point->visited_epoch[slot] = index->current_epoch[slot]; // Process candidates. while (candidates->count > 0) { // Max effort. If zero, we keep scanning. if (filter_callback && max_candidates && evaluated_candidates >= max_candidates) break; float cur_dist; hnswNode *current = pq_pop(candidates, &cur_dist); evaluated_candidates++; float furthest = pq_max_distance(results); if (results->count >= ef && cur_dist > furthest) break; /* Check neighbors. */ for (uint32_t i = 0; i < current->layers[layer].num_links; i++) { hnswNode *neighbor = current->layers[layer].links[i]; if (neighbor->visited_epoch[slot] == index->current_epoch[slot]) continue; // Already visited during this scan. neighbor->visited_epoch[slot] = index->current_epoch[slot]; float neighbor_dist = hnsw_distance(index, query, neighbor); furthest = pq_max_distance(results); if (filter_callback == NULL) { /* Original HNSW logic when no filtering: * Add to results if better than current max or * results not full. */ if (neighbor_dist < furthest || results->count < ef) { pq_push(candidates, neighbor, neighbor_dist); pq_push(results, neighbor, neighbor_dist); } } else { /* With filtering: we add candidates even if doesn't match * the filter, in order to continue to explore the graph. */ if (neighbor_dist < furthest || candidates->count < ef) { pq_push(candidates, neighbor, neighbor_dist); } /* Add results only if passes filter. */ if (filter_callback(neighbor->value, filter_privdata)) { if (neighbor_dist < furthest || results->count < ef) { pq_push(results, neighbor, neighbor_dist); } } } } } pq_free(candidates); return results; } /* Just a wrapper without hybrid search callback. */ pqueue *search_layer(HNSW *index, hnswNode *query, hnswNode *entry_point, uint32_t ef, uint32_t layer, uint32_t slot) { return search_layer_with_filter(index, query, entry_point, ef, layer, slot, NULL, NULL, 0); } /* This function is used in order to initialize a node allocated in the * function stack with the specified vector. The idea is that we can * easily use hnsw_distance() from a vector and the HNSW nodes this way: * * hnswNode myQuery; * hnsw_init_tmp_node(myIndex,&myQuery,0,some_vector); * hnsw_distance(&myQuery, some_hnsw_node); * * Make sure to later free the node with: * * hnsw_free_tmp_node(&myQuery,some_vector); * You have to pass the vector to the free function, because sometimes * hnsw_init_tmp_node() may just avoid allocating a vector at all, * just reusing 'some_vector' pointer. * * Return 0 on out of memory, 1 on success. */ int hnsw_init_tmp_node(HNSW *index, hnswNode *node, int is_normalized, const float *vector) { node->vector = NULL; /* Work on a normalized query vector if the input vector is * not normalized. */ if (!is_normalized) { node->vector = hmalloc(sizeof(float)*index->vector_dim); if (node->vector == NULL) return 0; memcpy(node->vector,vector,sizeof(float)*index->vector_dim); hnsw_normalize_vector(node->vector,NULL,index->vector_dim); } else { node->vector = (float*)vector; } /* If quantization is enabled, our query fake node should be * quantized as well. */ if (index->quant_type != HNSW_QUANT_NONE) { void *quants = hmalloc(hnsw_quants_bytes(index)); if (quants == NULL) { if (node->vector != vector) hfree(node->vector); return 0; } switch(index->quant_type) { case HNSW_QUANT_Q8: quantize_to_q8(node->vector, quants, index->vector_dim, &node->quants_range); break; case HNSW_QUANT_BIN: quantize_to_bin(node->vector, quants, index->vector_dim); } if (node->vector != vector) hfree(node->vector); node->vector = quants; } return 1; } /* Free the stack allocated node initialized by hnsw_init_tmp_node(). */ void hnsw_free_tmp_node(hnswNode *node, const float *vector) { if (node->vector != vector) hfree(node->vector); } /* Return approximated K-NN items. Note that neighbors and distances * arrays must have space for at least 'k' items. * norm_query should be set to 1 if the query vector is already * normalized, otherwise, if 0, the function will copy the vector, * L2-normalize the copy and search using the normalized version. * * If the filter_privdata callback is passed, only elements passing the * specified filter (invoked with privdata and the value associated * to the node as arguments) are returned. In such case, if max_candidates * is not NULL, it represents the maximum number of nodes to explore, since * the search may be otherwise unbound if few or no elements pass the * filter. */ int hnsw_search_with_filter (HNSW *index, const float *query_vector, uint32_t k, hnswNode **neighbors, float *distances, uint32_t slot, int query_vector_is_normalized, int (*filter_callback)(void *value, void *privdata), void *filter_privdata, uint32_t max_candidates) { if (!index || !query_vector || !neighbors || k == 0) return -1; if (!index->enter_point) return 0; // Empty index. /* Use a fake node that holds the query vector, this way we can * use our normal node to node distance functions when checking * the distance between query and graph nodes. */ hnswNode query; if (hnsw_init_tmp_node(index,&query,query_vector_is_normalized,query_vector) == 0) return -1; // Start searching from the entry point. hnswNode *curr_ep = index->enter_point; /* Start from higher layer to layer 1 (layer 0 is handled later) * in the next section. Descend to the most similar node found * so far. */ for (int lc = index->max_level; lc > 0; lc--) { pqueue *results = search_layer(index, &query, curr_ep, 1, lc, slot); if (!results) continue; if (results->count > 0) { curr_ep = pq_get_node(results,0); } pq_free(results); } /* Search bottom layer (the most densely populated) with ef = k */ pqueue *results = search_layer_with_filter( index, &query, curr_ep, k, 0, slot, filter_callback, filter_privdata, max_candidates); if (!results) { hnsw_free_tmp_node(&query, query_vector); return -1; } /* Copy results. */ uint32_t found = MIN(k, results->count); for (uint32_t i = 0; i < found; i++) { neighbors[i] = pq_get_node(results,i); if (distances) { distances[i] = pq_get_distance(results,i); } } pq_free(results); hnsw_free_tmp_node(&query, query_vector); return found; } /* Wrapper to hnsw_search_with_filter() when no filter is needed. */ int hnsw_search(HNSW *index, const float *query_vector, uint32_t k, hnswNode **neighbors, float *distances, uint32_t slot, int query_vector_is_normalized) { return hnsw_search_with_filter(index,query_vector,k,neighbors, distances,slot,query_vector_is_normalized, NULL,NULL,0); } /* Rescan a node and update the wortst neighbor index. * The followinng two functions are variants of this function to be used * when links are added or removed: they may do less work than a full scan. */ void hnsw_update_worst_neighbor(HNSW *index, hnswNode *node, uint32_t layer) { float worst_dist = 0; uint32_t worst_idx = 0; for (uint32_t i = 0; i < node->layers[layer].num_links; i++) { float dist = hnsw_distance(index, node, node->layers[layer].links[i]); if (dist > worst_dist) { worst_dist = dist; worst_idx = i; } } node->layers[layer].worst_distance = worst_dist; node->layers[layer].worst_idx = worst_idx; } /* Update node worst neighbor distance information when a new neighbor * is added. */ void hnsw_update_worst_neighbor_on_add(HNSW *index, hnswNode *node, uint32_t layer, uint32_t added_index, float distance) { (void) index; // Unused but here for API symmetry. if (node->layers[layer].num_links == 1 || // First neighbor? distance > node->layers[layer].worst_distance) // New worst? { node->layers[layer].worst_distance = distance; node->layers[layer].worst_idx = added_index; } } /* Update node worst neighbor distance information when a linked neighbor * is removed. */ void hnsw_update_worst_neighbor_on_remove(HNSW *index, hnswNode *node, uint32_t layer, uint32_t removed_idx) { if (node->layers[layer].num_links == 0) { node->layers[layer].worst_distance = 0; node->layers[layer].worst_idx = 0; } else if (removed_idx == node->layers[layer].worst_idx) { hnsw_update_worst_neighbor(index,node,layer); } else if (removed_idx < node->layers[layer].worst_idx) { // Just update index if we removed element before worst. node->layers[layer].worst_idx--; } } /* We have a list of candidate nodes to link to the new node, when inserting * one. This function selects which nodes to link and performs the linking. * * Parameters: * * - 'candidates' is the priority queue of potential good nodes to link to the * new node 'new_node'. * - 'required_links' is as many links we would like our new_node to get * at the specified layer. * - 'aggressive' changes the strategy used to find good neighbors as follows: * * This function is called with aggressive=0 for all the layers, including * layer 0. When called like that, it will use the diversity of links and * quality of links checks before linking our new node with some candidate. * * However if the insert function finds that at layer 0, with aggressive=0, * few connections were made, it calls this function again with aggressiveness * levels greater up to 2. * * At aggressive=1, the diversity checks are disabled, and the candidate * node for linking is accepted even if it is nearest to an already accepted * neighbor than it is to the new node. * * When we link our new node by replacing the link of a candidate neighbor * that already has the max number of links, inevitably some other node loses * a connection (to make space for our new node link). In this case: * * 1. If such "dropped" node would remain with too little links, we try with * some different neighbor instead, however as the 'aggressive' parameter * has incremental values (0, 1, 2) we are more and more willing to leave * the dropped node with fever connections. * 2. If aggressive=2, we will scan the candidate neighbor node links to * find a different linked-node to replace, one better connected even if * its distance is not the worse. * * Note: this function is also called during deletion of nodes in order to * provide certain nodes with additional links. */ void select_neighbors(HNSW *index, pqueue *candidates, hnswNode *new_node, uint32_t layer, uint32_t required_links, int aggressive) { for (uint32_t i = 0; i < candidates->count; i++) { hnswNode *neighbor = pq_get_node(candidates,i); if (neighbor == new_node) continue; // Don't link node with itself. /* Use our cached distance among the new node and the candidate. */ float dist = pq_get_distance(candidates,i); /* First of all, since our links are all bidirectional, if the * new node for any reason has no longer room, or if it accumulated * the required number of links, return ASAP. */ if (new_node->layers[layer].num_links >= new_node->layers[layer].max_links || new_node->layers[layer].num_links >= required_links) return; /* If aggressive is true, it is possible that the new node * already got some link among the candidates (see the top comment, * this function gets re-called in case of too few links). * So we need to check if this candidate is already linked to * the new node. */ if (aggressive) { int duplicated = 0; for (uint32_t j = 0; j < new_node->layers[layer].num_links; j++) { if (new_node->layers[layer].links[j] == neighbor) { duplicated = 1; break; } } if (duplicated) continue; } /* Diversity check. We accept new candidates * only if there is no element already accepted that is nearest * to the candidate than the new element itself. * However this check is disabled if we have pressure to find * new links (aggressive != 0) */ if (!aggressive) { int diversity_failed = 0; for (uint32_t j = 0; j < new_node->layers[layer].num_links; j++) { float link_dist = hnsw_distance(index, neighbor, new_node->layers[layer].links[j]); if (link_dist < dist) { diversity_failed = 1; break; } } if (diversity_failed) continue; } /* If potential neighbor node has space, simply add the new link. * We will have space as well. */ uint32_t n = neighbor->layers[layer].num_links; if (n < neighbor->layers[layer].max_links) { /* Link candidate to new node. */ neighbor->layers[layer].links[n] = new_node; neighbor->layers[layer].num_links++; /* Update candidate worst link info. */ hnsw_update_worst_neighbor_on_add(index,neighbor,layer,n,dist); /* Link new node to candidate. */ uint32_t new_links = new_node->layers[layer].num_links; new_node->layers[layer].links[new_links] = neighbor; new_node->layers[layer].num_links++; /* Update new node worst link info. */ hnsw_update_worst_neighbor_on_add(index,new_node,layer,new_links,dist); continue; } /* ==================================================================== * Replacing existing candidate neighbor link step. * ================================================================== */ /* If we are here, our accepted candidate for linking is full. * * If new node is more distant to candidate than its current worst link * then we skip it: we would not be able to establish a bidirectional * connection without compromising link quality of candidate. * * At aggressiveness > 0 we don't care about this check. */ if (!aggressive && dist >= neighbor->layers[layer].worst_distance) continue; /* We can add it: we are ready to replace the candidate neighbor worst * link with the new node, assuming certain conditions are met. */ hnswNode *worst_node = neighbor->layers[layer].links[neighbor->layers[layer].worst_idx]; /* The worst node linked to our candidate may remain too disconnected * if we remove the candidate node as its link. Let's check if * this is the case: */ if (aggressive == 0 && worst_node->layers[layer].num_links <= index->M/2) continue; /* Aggressive level = 1. It's ok if the node remains with just * HNSW_M/4 links. */ else if (aggressive == 1 && worst_node->layers[layer].num_links <= index->M/4) continue; /* If aggressive is set to 2, then the new node we are adding failed * to find enough neighbors. We can't insert an almost orphaned new * node, so let's see if the target node has some other link * that is well connected in the graph: we could drop it instead * of the worst link. */ if (aggressive == 2 && worst_node->layers[layer].num_links <= index->M/4) { /* Let's see if we can find at least a candidate link that * would remain with a few connections. Track the one * that is the farthest away (worst distance) from our candidate * neighbor (in order to remove the less interesting link). */ worst_node = NULL; uint32_t worst_idx = 0; float max_dist = 0; for (uint32_t j = 0; j < neighbor->layers[layer].num_links; j++) { hnswNode *to_drop = neighbor->layers[layer].links[j]; /* Skip this if it would remain too disconnected as well. * * NOTE about index->M/4 min connections requirement: * * It is not too strict, since leaving a node with just a * single link does not just leave it too weakly connected, but * also sometimes creates cycles with few disconnected * nodes linked among them. */ if (to_drop->layers[layer].num_links <= index->M/4) continue; float link_dist = hnsw_distance(index, neighbor, to_drop); if (worst_node == NULL || link_dist > max_dist) { worst_node = to_drop; max_dist = link_dist; worst_idx = j; } } if (worst_node != NULL) { /* We found a node that we can drop. Let's pretend this is * the worst node of the candidate to unify the following * code path. Later we will fix the worst node info anyway. */ neighbor->layers[layer].worst_distance = max_dist; neighbor->layers[layer].worst_idx = worst_idx; } else { /* Otherwise we have no other option than reallocating * the max number of links for this target node, and * ensure at least a few connections for our new node. */ uint32_t reallocation_limit = layer == 0 ? index->M * 3 : index->M *2; if (neighbor->layers[layer].max_links >= reallocation_limit) continue; uint32_t new_max_links = neighbor->layers[layer].max_links+1; hnswNode **new_links = hrealloc(neighbor->layers[layer].links, sizeof(hnswNode*) * new_max_links); if (new_links == NULL) continue; // Non critical. /* Update neighbor's link capacity. */ neighbor->layers[layer].links = new_links; neighbor->layers[layer].max_links = new_max_links; /* Establish bidirectional link. */ uint32_t n = neighbor->layers[layer].num_links; neighbor->layers[layer].links[n] = new_node; neighbor->layers[layer].num_links++; hnsw_update_worst_neighbor_on_add(index, neighbor, layer, n, dist); n = new_node->layers[layer].num_links; new_node->layers[layer].links[n] = neighbor; new_node->layers[layer].num_links++; hnsw_update_worst_neighbor_on_add(index, new_node, layer, n, dist); continue; } } // Remove backlink from the worst node of our candidate. for (uint64_t j = 0; j < worst_node->layers[layer].num_links; j++) { if (worst_node->layers[layer].links[j] == neighbor) { memmove(&worst_node->layers[layer].links[j], &worst_node->layers[layer].links[j+1], (worst_node->layers[layer].num_links - j - 1) * sizeof(hnswNode*)); worst_node->layers[layer].num_links--; hnsw_update_worst_neighbor_on_remove(index,worst_node,layer,j); break; } } /* Replace worst link with the new node. */ neighbor->layers[layer].links[neighbor->layers[layer].worst_idx] = new_node; /* Update the worst link in the target node, at this point * the link that we replaced may no longer be the worst. */ hnsw_update_worst_neighbor(index,neighbor,layer); // Add new node -> candidate link. uint32_t new_links = new_node->layers[layer].num_links; new_node->layers[layer].links[new_links] = neighbor; new_node->layers[layer].num_links++; // Update new node worst link. hnsw_update_worst_neighbor_on_add(index,new_node,layer,new_links,dist); } } /* This function implements node reconnection after a node deletion in HNSW. * When a node is deleted, other nodes at the specified layer lose one * connection (all the neighbors of the deleted node). This function attempts * to pair such nodes together in a way that maximizes connection quality * among the M nodes that were former neighbors of our deleted node. * * The algorithm works by first building a distance matrix among the nodes: * * N0 N1 N2 N3 * N0 0 1.2 0.4 0.9 * N1 1.2 0 0.8 0.5 * N2 0.4 0.8 0 1.1 * N3 0.9 0.5 1.1 0 * * For each potential pairing (i,j) we compute a score that combines: * 1. The direct cosine distance between the two nodes * 2. The average distance to other nodes that would no longer be * available for pairing if we select this pair * * We want to balance local node-to-node requirements and global requirements. * For instance sometimes connecting A with B, while optimal, would leave * C and D to be connected without other choices, and this could be a very * bad connection. Maybe instead A and C and B and D are both relatively high * quality connections. * * The formula used to calculate the score of each connection is: * * score[i,j] = W1*(2-distance[i,j]) + W2*((new_avg_i + new_avg_j)/2) * where new_avg_x is the average of distances in row x excluding distance[i,j] * * So the score is directly proportional to the SIMILARITY of the two nodes * and also directly proportional to the DISTANCE of the potential other * connections that we lost by pairign i,j. So we have a cost for missed * opportunities, or better, in this case, a reward if the missing * opportunities are not so good (big average distance). * * W1 and W2 are weights (defaults: 0.7 and 0.3) that determine the relative * importance of immediate connection quality vs future pairing potential. * * After the initial pairing phase, any nodes that couldn't be paired * (due to odd count or existing connections) are handled by searching * the broader graph using the standard HNSW neighbor selection logic. */ void hnsw_reconnect_nodes(HNSW *index, hnswNode **nodes, int count, uint32_t layer) { if (count <= 0) return; debugmsg("Reconnecting %d nodes\n", count); /* Step 1: Build the distance matrix between all nodes. * Since distance(i,j) = distance(j,i), we only compute the upper triangle * and mirror it to the lower triangle. */ float *distances = hmalloc((unsigned long) count * count * sizeof(float)); if (!distances) return; for (int i = 0; i < count; i++) { distances[i*count + i] = 0; // Distance to self is 0 for (int j = i+1; j < count; j++) { float dist = hnsw_distance(index, nodes[i], nodes[j]); distances[i*count + j] = dist; // Upper triangle. distances[j*count + i] = dist; // Lower triangle. } } /* Step 2: Calculate row averages (will be used in scoring): * please note that we just calculate row averages and not * columns averages since the matrix is symmetrical, so those * are the same: check the image in the top comment if you have any * doubt about this. */ float *row_avgs = hmalloc(count * sizeof(float)); if (!row_avgs) { hfree(distances); return; } for (int i = 0; i < count; i++) { float sum = 0; int valid_count = 0; for (int j = 0; j < count; j++) { if (i != j) { sum += distances[i*count + j]; valid_count++; } } row_avgs[i] = valid_count ? sum / valid_count : 0; } /* Step 3: Build scoring matrix. What we do here is to combine how * good is a given i,j nodes connection, with how badly connecting * i,j will affect the remaining quality of connections left to * pair the other nodes. */ float *scores = hmalloc((unsigned long) count * count * sizeof(float)); if (!scores) { hfree(distances); hfree(row_avgs); return; } /* Those weights were obtained manually... No guarantee that they * are optimal. However with these values the algorithm is certain * better than its greedy version that just attempts to pick the * best pair each time (verified experimentally). */ const float W1 = 0.7; // Weight for immediate distance. const float W2 = 0.3; // Weight for future potential. for (int i = 0; i < count; i++) { for (int j = 0; j < count; j++) { if (i == j) { scores[i*count + j] = -1; // Invalid pairing. continue; } // Check for existing connection between i and j. int already_linked = 0; for (uint32_t k = 0; k < nodes[i]->layers[layer].num_links; k++) { if (nodes[i]->layers[layer].links[k] == nodes[j]) { scores[i*count + j] = -1; // Already linked. already_linked = 1; break; } } if (already_linked) continue; float dist = distances[i*count + j]; /* Calculate new averages excluding this pair. * Handle edge case where we might have too few elements. * Note that it would be not very smart to recompute the average * each time scanning the row, we can remove the element * and adjust the average without it. */ float new_avg_i = 0, new_avg_j = 0; if (count > 2) { new_avg_i = (row_avgs[i] * (count-1) - dist) / (count-2); new_avg_j = (row_avgs[j] * (count-1) - dist) / (count-2); } /* Final weighted score: the more similar i,j, the better * the score. The more distant are the pairs we lose by * connecting i,j, the better the score. */ scores[i*count + j] = W1*(2-dist) + W2*((new_avg_i + new_avg_j)/2); } } // Step 5: Pair nodes greedily based on scores. int *used = hmalloc(count*sizeof(int)); memset(used,0,count*sizeof(int)); if (!used) { hfree(distances); hfree(row_avgs); hfree(scores); return; } /* Scan the matrix looking each time for the potential * link with the best score. */ while(1) { float max_score = -1; int best_j = -1, best_i = -1; // Seek best score i,j values. for (int i = 0; i < count; i++) { if (used[i]) continue; // Already connected. /* No space left? Not possible after a node deletion but makes * this function more future-proof. */ if (nodes[i]->layers[layer].num_links >= nodes[i]->layers[layer].max_links) continue; for (int j = 0; j < count; j++) { if (i == j) continue; // Same node, skip. if (used[j]) continue; // Already connected. float score = scores[i*count + j]; if (score < 0) continue; // Invalid link. /* If the target node has space, and its score is better * than any other seen so far... remember it is the best. */ if (score > max_score && nodes[j]->layers[layer].num_links < nodes[j]->layers[layer].max_links) { // Track the best connection found so far. max_score = score; best_j = j; best_i = i; } } } // Possible link found? Connect i and j. if (best_j != -1) { debugmsg("[%d] linking %d with %d: %f\n", layer, (int)best_i, (int)best_j, max_score); // Link i -> j. int link_idx = nodes[best_i]->layers[layer].num_links; nodes[best_i]->layers[layer].links[link_idx] = nodes[best_j]; nodes[best_i]->layers[layer].num_links++; // Update worst distance if needed. float dist = distances[best_i*count + best_j]; hnsw_update_worst_neighbor_on_add(index,nodes[best_i],layer,link_idx,dist); // Link j -> i. link_idx = nodes[best_j]->layers[layer].num_links; nodes[best_j]->layers[layer].links[link_idx] = nodes[best_i]; nodes[best_j]->layers[layer].num_links++; // Update worst distance if needed. hnsw_update_worst_neighbor_on_add(index,nodes[best_j],layer,link_idx,dist); // Mark connection as used. used[best_i] = used[best_j] = 1; } else { break; // No more valid connections available. } } /* Step 6: Handle remaining unpaired nodes using the standard HNSW * neighbor selection. */ for (int i = 0; i < count; i++) { if (used[i]) continue; // Skip if node is already at max connections. if (nodes[i]->layers[layer].num_links >= nodes[i]->layers[layer].max_links) continue; debugmsg("[%d] Force linking %d\n", layer, i); /* First, try with local nodes as candidates. * Some candidate may have space. */ pqueue *candidates = pq_new(count); if (!candidates) continue; /* Add all the local nodes having some space as candidates * to be linked with this node. */ for (int j = 0; j < count; j++) { if (i != j && // Must not be itself. nodes[j]->layers[layer].num_links < // Must not be full. nodes[j]->layers[layer].max_links) { float dist = distances[i*count + j]; pq_push(candidates, nodes[j], dist); } } /* Try local candidates first with aggressive = 1. * So we will link only if there is space. * We want one link more than the links we already have. */ uint32_t wanted_links = nodes[i]->layers[layer].num_links+1; if (candidates->count > 0) { select_neighbors(index, candidates, nodes[i], layer, wanted_links, 1); debugmsg("Final links after attempt with local nodes: %d (wanted: %d)\n", (int)nodes[i]->layers[layer].num_links, wanted_links); } // If still no connection, search the broader graph. if (nodes[i]->layers[layer].num_links != wanted_links) { debugmsg("No force linking possible with local candidates\n"); pq_free(candidates); // Find entry point for target layer by descending through levels. hnswNode *curr_ep = index->enter_point; for (uint32_t lc = index->max_level; lc > layer; lc--) { pqueue *results = search_layer(index, nodes[i], curr_ep, 1, lc, 0); if (results) { if (results->count > 0) { curr_ep = pq_get_node(results,0); } pq_free(results); } } if (curr_ep) { /* Search this layer for candidates. * Use the default EF_C in this case, since it's not an * "insert" operation, and we don't know the user * specified "EF". */ candidates = search_layer(index, nodes[i], curr_ep, HNSW_EF_C, layer, 0); if (candidates) { /* Try to connect with aggressiveness proportional to the * node linking condition. */ int aggressiveness = (nodes[i]->layers[layer].num_links > index->M / 2) ? 1 : 2; select_neighbors(index, candidates, nodes[i], layer, wanted_links, aggressiveness); debugmsg("Final links with broader search: %d (wanted: %d)\n", (int)nodes[i]->layers[layer].num_links, wanted_links); pq_free(candidates); } } } else { pq_free(candidates); } } // Cleanup. hfree(distances); hfree(row_avgs); hfree(scores); hfree(used); } /* This is an helper function in order to support node deletion. * It's goal is just to: * * 1. Remove the node from the bidirectional links of neighbors in the graph. * 2. Remove the node from the linked list of nodes. * 3. Fix the entry point in the graph. We just select one of the neighbors * of the deleted node at a lower level. If none is found, we do * a full scan. * 4. The node itself amd its aux value field are NOT freed. It's up to the * caller to do it, by using hnsw_node_free(). * 5. The node associated value (node->value) is NOT freed. * * Why this function will not free the node? Because in node updates it * could be a good idea to reuse the node allocation for different reasons * (currently not implemented). * In general it is more future-proof to be able to reuse the node if * needed. Right now this library reuses the node only when links are * not touched (see hnsw_update() for more information). */ void hnsw_unlink_node(HNSW *index, hnswNode *node) { if (!index || !node) return; index->version++; // This node may be missing in an already compiled list // of neighbors. Make optimistic concurrent inserts fail. /* Remove all bidirectional links at each level. * Note that in this implementation all the * links are guaranteed to be bedirectional. */ /* For each level of the deleted node... */ for (uint32_t level = 0; level <= node->level; level++) { /* For each linked node of the deleted node... */ for (uint32_t i = 0; i < node->layers[level].num_links; i++) { hnswNode *linked = node->layers[level].links[i]; /* Find and remove the backlink in the linked node */ for (uint32_t j = 0; j < linked->layers[level].num_links; j++) { if (linked->layers[level].links[j] == node) { /* Remove by shifting remaining links left */ memmove(&linked->layers[level].links[j], &linked->layers[level].links[j + 1], (linked->layers[level].num_links - j - 1) * sizeof(hnswNode*)); linked->layers[level].num_links--; hnsw_update_worst_neighbor_on_remove(index,linked,level,j); break; } } } } /* Update cursors pointing at this element. */ if (index->cursors) hnsw_cursor_element_deleted(index,node); /* Update the previous node's next pointer. */ if (node->prev) { node->prev->next = node->next; } else { /* If there's no previous node, this is the head. */ index->head = node->next; } /* Update the next node's prev pointer. */ if (node->next) node->next->prev = node->prev; /* Update node count. */ index->node_count--; /* If this node was the enter_point, we need to update it. */ if (node == index->enter_point) { /* Reset entry point - we'll find a new one (unless the HNSW is * now empty) */ index->enter_point = NULL; index->max_level = 0; /* Step 1: Try to find a replacement by scanning levels * from top to bottom. Under normal conditions, if there is * any other node at the same level, we have a link. Anyway * we descend levels to find any neighbor at the higher level * possible. */ for (int level = node->level; level >= 0; level--) { if (node->layers[level].num_links > 0) { index->enter_point = node->layers[level].links[0]; break; } } /* Step 2: If no links were found at any level, do a full scan. * This should never happen in practice if the HNSW is not * empty. */ if (!index->enter_point) { uint32_t new_max_level = 0; hnswNode *current = index->head; while (current) { if (current != node && current->level >= new_max_level) { new_max_level = current->level; index->enter_point = current; } current = current->next; } } /* Update max_level. */ if (index->enter_point) index->max_level = index->enter_point->level; } /* Clear the node's links but don't free the node itself */ node->prev = node->next = NULL; } /* Higher level API for hnsw_unlink_node() + hnsw_reconnect_nodes() actual work. * This will get the write lock, will delete the node, free it, * reconnect the node neighbors among themselves, and unlock again. * If free_value function pointer is not NULL, then the function provided is * used to free node->value. * * The function returns 0 on error (inability to acquire the lock), otherwise * 1 is returned. */ int hnsw_delete_node(HNSW *index, hnswNode *node, void(*free_value)(void*value)) { if (pthread_rwlock_wrlock(&index->global_lock) != 0) return 0; hnsw_unlink_node(index,node); if (free_value && node->value) free_value(node->value); /* Relink all the nodes orphaned of this node link. * Do it for all the levels. */ for (unsigned int j = 0; j <= node->level; j++) { hnsw_reconnect_nodes(index, node->layers[j].links, node->layers[j].num_links, j); } hnsw_node_free(node); pthread_rwlock_unlock(&index->global_lock); return 1; } /* ============================ Threaded API ================================ * Concurrent readers should use the following API to get a slot assigned * (and a lock, too), do their read-only call, and unlock the slot. * * There is a reason why read operations don't implement opaque transparent * locking directly on behalf of the user: when we return a result set * with hnsw_search(), we report a set of nodes. The caller will do something * with the nodes and the associated values, so the unlocking of the * slot should happen AFTER the result was already used, otherwise we may * have changes to the HNSW nodes as the result is being accessed. */ /* Try to acquire a read slot. Returns the slot number (0 to HNSW_MAX_THREADS-1) * on success, -1 on error (pthread mutex errors). */ int hnsw_acquire_read_slot(HNSW *index) { /* First try a non-blocking approach on all slots. */ for (uint32_t i = 0; i < HNSW_MAX_THREADS; i++) { if (pthread_mutex_trylock(&index->slot_locks[i]) == 0) { if (pthread_rwlock_rdlock(&index->global_lock) != 0) { pthread_mutex_unlock(&index->slot_locks[i]); return -1; } return i; } } /* All trylock attempts failed, use atomic increment to select slot. */ uint32_t slot = index->next_slot++ % HNSW_MAX_THREADS; /* Try to lock the selected slot. */ if (pthread_mutex_lock(&index->slot_locks[slot]) != 0) return -1; /* Get read lock. */ if (pthread_rwlock_rdlock(&index->global_lock) != 0) { pthread_mutex_unlock(&index->slot_locks[slot]); return -1; } return slot; } /* Release a previously acquired read slot: note that it is important that * nodes returned by hnsw_search() are accessed while the read lock is * still active, to be sure that nodes are not freed. */ void hnsw_release_read_slot(HNSW *index, int slot) { if (slot < 0 || slot >= HNSW_MAX_THREADS) return; pthread_rwlock_unlock(&index->global_lock); pthread_mutex_unlock(&index->slot_locks[slot]); } /* ============================ Nodes insertion ============================= * We have an optimistic API separating the read-only candidates search * and the write side (actual node insertion). We internally also use * this API to provide the plain hnsw_insert() function for code unification. */ struct InsertContext { pqueue *level_queues[HNSW_MAX_LEVEL]; /* Candidates for each level. */ hnswNode *node; /* Pre-allocated node ready for insertion */ uint64_t version; /* Index version at preparation time. This is used * for CAS-like locking during change commit. */ }; /* Optimistic insertion API. * * WARNING: Note that this is an internal function: users should call * hnsw_prepare_insert() instead. * * This is how it works: you use hnsw_prepare_insert() and it will return * a context where good candidate neighbors are already pre-selected. * This step only uses read locks. * * Then finally you try to actually commit the new node with * hnsw_try_commit_insert(): this time we will require a write lock, but * for less time than it would be otherwise needed if using directly * hnsw_insert(). When you try to commit the write, if no node was deleted in * the meantime, your operation will succeed, otherwise it will fail, and * you should try to just use the hnsw_insert() API, since there is * contention. * * See hnsw_node_new() for information about 'vector' and 'qvector' * arguments, and which one to pass. */ InsertContext *hnsw_prepare_insert_nolock(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, int slot, int ef) { InsertContext *ctx = hmalloc(sizeof(*ctx)); if (!ctx) return NULL; memset(ctx, 0, sizeof(*ctx)); ctx->version = index->version; /* Crete a new node that we may be able to insert into the * graph later, when calling the commit function. */ uint32_t level = random_level(); ctx->node = hnsw_node_new(index, id, vector, qvector, qrange, level, 1); if (!ctx->node) { hfree(ctx); return NULL; } hnswNode *curr_ep = index->enter_point; /* Empty graph, no need to collect candidates. */ if (curr_ep == NULL) return ctx; /* Phase 1: Find good entry point on the highest level of the new * node we are going to insert. */ for (unsigned int lc = index->max_level; lc > level; lc--) { pqueue *results = search_layer(index, ctx->node, curr_ep, 1, lc, slot); if (results) { if (results->count > 0) curr_ep = pq_get_node(results,0); pq_free(results); } } /* Phase 2: Collect a set of potential connections for each layer of * the new node. */ for (int lc = MIN(level, index->max_level); lc >= 0; lc--) { pqueue *candidates = search_layer(index, ctx->node, curr_ep, ef, lc, slot); if (!candidates) continue; curr_ep = (candidates->count > 0) ? pq_get_node(candidates,0) : curr_ep; ctx->level_queues[lc] = candidates; } return ctx; } /* External API for hnsw_prepare_insert_nolock(), handling locking. */ InsertContext *hnsw_prepare_insert(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, int ef) { InsertContext *ctx; int slot = hnsw_acquire_read_slot(index); ctx = hnsw_prepare_insert_nolock(index,vector,qvector,qrange,id,slot,ef); hnsw_release_read_slot(index,slot); return ctx; } /* Free an insert context and all its resources. */ void hnsw_free_insert_context(InsertContext *ctx) { if (!ctx) return; for (uint32_t i = 0; i < HNSW_MAX_LEVEL; i++) { if (ctx->level_queues[i]) pq_free(ctx->level_queues[i]); } if (ctx->node) hnsw_node_free(ctx->node); hfree(ctx); } /* Commit a prepared insert operation. This function is a low level API that * should not be called by the user. See instead hnsw_try_commit_insert(), that * will perform the CAS check and acquire the write lock. * * See the top comment in hnsw_prepare_insert() for more information * on the optimistic insertion API. * * This function can't fail and always returns the pointer to the * just inserted node. Out of memory is not possible since no critical * allocation is never performed in this code path: we populate links * on already allocated nodes. */ hnswNode *hnsw_commit_insert_nolock(HNSW *index, InsertContext *ctx, void *value) { hnswNode *node = ctx->node; node->value = value; /* Handle first node case. */ if (index->enter_point == NULL) { index->version++; // First node, make concurrent inserts fail. index->enter_point = node; index->max_level = node->level; hnsw_add_node(index, node); ctx->node = NULL; // So hnsw_free_insert_context() will not free it. hnsw_free_insert_context(ctx); return node; } /* Connect the node with near neighbors at each level. */ for (int lc = MIN(node->level,index->max_level); lc >= 0; lc--) { if (ctx->level_queues[lc] == NULL) continue; /* Try to provide index->M connections to our node. The call * is not guaranteed to be able to provide all the links we would * like to have for the new node: they must be bi-directional, obey * certain quality checks, and so forth, so later there are further * calls to force the hand a bit if needed. * * Let's start with aggressiveness = 0. */ select_neighbors(index, ctx->level_queues[lc], node, lc, index->M, 0); /* Layer 0 and too few connections? Let's be more aggressive. */ if (lc == 0 && node->layers[0].num_links < index->M/2) { select_neighbors(index, ctx->level_queues[lc], node, lc, index->M, 1); /* Still too few connections? Let's go to * aggressiveness level '2' in linking strategy. */ if (node->layers[0].num_links < index->M/4) { select_neighbors(index, ctx->level_queues[lc], node, lc, index->M/4, 2); } } } /* If new node level is higher than current max, update entry point. */ if (node->level > index->max_level) { index->version++; // Entry point changed, make concurrent inserts fail. index->enter_point = node; index->max_level = node->level; } /* Add node to the linked list. */ hnsw_add_node(index, node); ctx->node = NULL; // So hnsw_free_insert_context() will not free the node. hnsw_free_insert_context(ctx); return node; } /* If the context obtained with hnsw_prepare_insert() is still valid * (nodes not deleted in the meantime) then add the new node to the HNSW * index and return its pointer. Otherwise NULL is returned and the operation * should be either performed with the blocking API hnsw_insert() or attempted * again. */ hnswNode *hnsw_try_commit_insert(HNSW *index, InsertContext *ctx, void *value) { /* Check if the version changed since preparation. Note that we * should access index->version under the write lock in order to * be sure we can safely commit the write: this is just a fast-path * in order to return ASAP without acquiring the write lock in case * the version changed. */ if (ctx->version != index->version) { hnsw_free_insert_context(ctx); return NULL; } /* Try to acquire write lock. */ if (pthread_rwlock_wrlock(&index->global_lock) != 0) { hnsw_free_insert_context(ctx); return NULL; } /* Check version again under write lock. */ if (ctx->version != index->version) { pthread_rwlock_unlock(&index->global_lock); hnsw_free_insert_context(ctx); return NULL; } /* Commit the change: note that it's up to hnsw_commit_insert_nolock() * to free the insertion context. */ hnswNode *node = hnsw_commit_insert_nolock(index, ctx, value); /* Release the write lock. */ pthread_rwlock_unlock(&index->global_lock); return node; } /* Insert a new element into the graph. * See hnsw_node_new() for information about 'vector' and 'qvector' * arguments, and which one to pass. * * Return NULL on out of memory during insert. Otherwise the newly * inserted node pointer is returned. */ hnswNode *hnsw_insert(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, void *value, int ef) { /* Write lock. We acquire the write lock even for the prepare() * operation (that is a read-only operation) since we want this function * to don't fail in the check-and-set stage of commit(). * * Basically here we are using the optimistic API in a non-optimistinc * way in order to have a single insertion code in the implementation. */ if (pthread_rwlock_wrlock(&index->global_lock) != 0) return NULL; // Prepare the insertion - note we pass slot 0 since we're single threaded. InsertContext *ctx = hnsw_prepare_insert_nolock(index, vector, qvector, qrange, id, 0, ef); if (!ctx) { pthread_rwlock_unlock(&index->global_lock); return NULL; } // Commit the prepared insertion without version checking. hnswNode *node = hnsw_commit_insert_nolock(index, ctx, value); // Release write lock and return our node pointer. pthread_rwlock_unlock(&index->global_lock); return node; } /* Helper function for qsort call in hnsw_should_reuse_node(). */ static int compare_floats(const float *a, const float *b) { if (*a < *b) return 1; if (*a > *b) return -1; return 0; } /* This function determines if a node can be reused with a new vector by: * * 1. Computing average of worst 25% of current distances. * 2. Checking if at least 50% of new distances stay below this threshold. * 3. Requiring a minimum number of links for the check to be meaningful. * * This check is useful when we want to just update a node that already * exists in the graph. Often the new vector is a learned embedding generated * by some model, and the embedding represents some document that perhaps * changed just slightly compared to the past, so the new embedding will * be very nearby. We need to find a way do determine if the current node * neighbors (practically speaking its location in the grapb) are good * enough even with the new vector. * * XXX: this function needs improvements: successive updates to the same * node with more and more distant vectors will make the node drift away * from its neighbors. One of the additional metrics used could be * neighbor-to-neighbor distance, that represents a more absolute check * of fit for the new vector. */ int hnsw_should_reuse_node(HNSW *index, hnswNode *node, int is_normalized, const float *new_vector) { /* Step 1: Not enough links? Advice to avoid reuse. */ const uint32_t min_links_for_reuse = 4; uint32_t layer0_connections = node->layers[0].num_links; if (layer0_connections < min_links_for_reuse) return 0; /* Step2: get all current distances and run our heuristic. */ float *old_distances = hmalloc(sizeof(float) * layer0_connections); if (!old_distances) return 0; // Temporary node with the new vector, to simplify the next logic. hnswNode tmp_node; if (hnsw_init_tmp_node(index,&tmp_node,is_normalized,new_vector) == 0) { hfree(old_distances); return 0; } /* Get old dinstances and sort them to access the 25% worst * (bigger) ones. */ for (uint32_t i = 0; i < layer0_connections; i++) { old_distances[i] = hnsw_distance(index, node, node->layers[0].links[i]); } qsort(old_distances, layer0_connections, sizeof(float), (int (*)(const void*, const void*))(&compare_floats)); uint32_t count = (layer0_connections+3)/4; // 25% approx to larger int. if (count > layer0_connections) count = layer0_connections; // Futureproof. float worst_avg = 0; // Compute average of 25% worst dinstances. for (uint32_t i = 0; i < count; i++) worst_avg += old_distances[i]; worst_avg /= count; hfree(old_distances); // Count how many new distances stay below the threshold. uint32_t good_distances = 0; for (uint32_t i = 0; i < layer0_connections; i++) { float new_dist = hnsw_distance(index, &tmp_node, node->layers[0].links[i]); if (new_dist <= worst_avg) good_distances++; } hnsw_free_tmp_node(&tmp_node,new_vector); /* At least 50% of the nodes should pass our quality test, for the * node to be reused. */ return good_distances >= layer0_connections/2; } /** * Return a random node from the HNSW graph. * * This function performs a random walk starting from the entry point, * using only level 0 connections for navigation. It uses log^2(N) steps * to ensure proper mixing time. */ hnswNode *hnsw_random_node(HNSW *index, int slot) { if (index->node_count == 0 || index->enter_point == NULL) return NULL; (void)slot; // Unused, but we need the caller to acquire the lock. /* First phase: descend from max level to level 0 taking random paths. * Note that we don't need a more conservative log^2(N) steps for * proper mixing, since we already descend to a random cluster here. */ hnswNode *current = index->enter_point; for (uint32_t level = index->max_level; level > 0; level--) { /* If current node doesn't have this level or no links, continue * to lower level. */ if (current->level < level || current->layers[level].num_links == 0) continue; /* Choose random neighbor at this level. */ uint32_t rand_neighbor = rand() % current->layers[level].num_links; current = current->layers[level].links[rand_neighbor]; } /* Second phase: at level 0, take log(N) * c random steps. */ const int c = 3; // Multiplier for more thorough exploration. double logN = log2(index->node_count + 1); uint32_t num_walks = (uint32_t)(logN * c); // Perform random walk at level 0. for (uint32_t i = 0; i < num_walks; i++) { if (current->layers[0].num_links == 0) return current; // Choose random neighbor. uint32_t rand_neighbor = rand() % current->layers[0].num_links; current = current->layers[0].links[rand_neighbor]; } return current; } /* ============================= Serialization ============================== * * TO SERIALIZE * ============ * * To serialize on disk, you need to persist the vector dimension, number * of elements, and the quantization type index->quant_type. These are * global values for the whole index. * * Then, to serialize each node: * * call hnsw_serialize_node() with each node you find in the linked list * of nodes, starting at index->head (each node has a next pointer). * The function will return an hnswSerNode structure, you will need * to store the following on disk (for each node): * * - The sernode->vector data, that is sernode->vector_size bytes. * - The sernode->params array, that points to an array of uint64_t * integers. There are sernode->params_count total items. These * parameters contain everything there is to need about your node: how * many levels it has, its ID, the list of neighbors for each level (as node * IDs), and so forth. * * You need to to save your own node->value in some way as well, but it already * belongs to the user of the API, since, for this library, it's just a pointer, * so the user should know how to serialized its private data. * * RELOADING FROM DISK / NET * ========================= * * When reloading nodes, you first load the index vector dimension and * quantization type, and create the index with: * * HNSW *hnsw_new(uint32_t vector_dim, uint32_t quant_type); * * Then you load back, for each node (you stored how many nodes you had) * the vector and the params array / count. * You also load the value associated with your node. * * At this point you add back the loaded elements into the index with: * * hnsw_insert_serialized(HNSW *index, void *vector, uint64_t params, * uint32_t params_len, void *value); * * Once you added all the nodes back, you need to resolve the pointers * (since so far they are added just with the node IDs as reference), so * you call: * * hnsw_deserialize_index(index); * * The index is now ready to be used like if it has been always in memory. * * DESIGN NOTES * ============ * * Why this API does not just give you a binary blob to save? Because in * many systems (and in Redis itself) to save integers / floats can have * more interesting encodings that just storing a 64 bit value. Many vector * indexes will be small, and their IDs will be small numbers, so the storage * system can exploit that and use less disk space, less network bandwidth * and so forth. * * How is the data stored in these arrays of numbers? Oh well, we have * things that are obviously numbers like node ID, number of levels for the * node and so forth. Also each of our nodes have an unique incremental ID, * so we can store a node set of links in terms of linked node IDs. This * data is put directly in the loaded node pointer space! We just cast the * integer to the pointer (so THIS IS NOT SAFE for 32 bit systems). Then * we want to translate such IDs into pointers. To do that, we build an * hash table, then scan all the nodes again and fix all the links converting * the ID to the pointer. */ /* Return the serialized node information as specified in the top comment * above. Note that the returned information is true as long as the node * provided is not deleted or modified, so this function should be called * when there are no concurrent writes. * * The function hnsw_serialize_node() should be called in order to * free the result of this function. */ hnswSerNode *hnsw_serialize_node(HNSW *index, hnswNode *node) { /* The first step is calculating the number of uint64_t parameters * that we need in order to serialize the node. */ uint32_t num_params = 0; num_params += 2; // node ID, number of layers. for (uint32_t i = 0; i <= node->level; i++) { num_params += 2; // max_links and num_links info for this layer. num_params += node->layers[i].num_links; // The IDs of linked nodes. } /* We use another 64bit value to store two floats that are about * the vector: l2 and quantization range (that is only used if the * vector is quantized). */ num_params++; /* Allocate the return object and the parameters array. */ hnswSerNode *sn = hmalloc(sizeof(hnswSerNode)); if (sn == NULL) return NULL; sn->params = hmalloc(sizeof(uint64_t)*num_params); if (sn->params == NULL) { hfree(sn); return NULL; } /* Fill data. */ sn->params_count = num_params; sn->vector = node->vector; sn->vector_size = hnsw_quants_bytes(index); uint32_t param_idx = 0; sn->params[param_idx++] = node->id; sn->params[param_idx++] = node->level; for (uint32_t i = 0; i <= node->level; i++) { sn->params[param_idx++] = node->layers[i].num_links; sn->params[param_idx++] = node->layers[i].max_links; for (uint32_t j = 0; j < node->layers[i].num_links; j++) { sn->params[param_idx++] = node->layers[i].links[j]->id; } } uint64_t l2_and_range = 0; unsigned char *aux = (unsigned char*)&l2_and_range; memcpy(aux,&node->l2,sizeof(float)); memcpy(aux+4,&node->quants_range,sizeof(float)); sn->params[param_idx++] = l2_and_range; /* Better safe than sorry: */ assert(param_idx == num_params); return sn; } /* This is needed in order to free hnsw_serialize_node() returned * structure. */ void hnsw_free_serialized_node(hnswSerNode *sn) { hfree(sn->params); hfree(sn); } /* Load a serialized node. See the top comment in this section of code * for the documentation about how to use this. * * The function returns NULL both on out of memory and if the remaining * parameters length does not match the number of links or other items * to load. */ hnswNode *hnsw_insert_serialized(HNSW *index, void *vector, uint64_t *params, uint32_t params_len, void *value) { if (params_len < 2) return NULL; uint64_t id = params[0]; uint32_t level = params[1]; /* Keep track of maximum ID seen while loading. */ if (id >= index->last_id) index->last_id = id; /* Create node, passing vector data directly based on quantization type. */ hnswNode *node; if (index->quant_type != HNSW_QUANT_NONE) { node = hnsw_node_new(index, id, NULL, vector, 0, level, 0); } else { node = hnsw_node_new(index, id, vector, NULL, 0, level, 0); } if (!node) return NULL; /* Load params array into the node. */ uint32_t param_idx = 2; for (uint32_t i = 0; i <= level; i++) { /* Sanity check. */ if (param_idx + 2 > params_len) { hnsw_node_free(node); return NULL; } uint32_t num_links = params[param_idx++]; uint32_t max_links = params[param_idx++]; /* If max_links is larger than current allocation, reallocate. * It could happen in select_neighbors() that we over-allocate the * node under very unlikely to happen conditions. */ if (max_links > node->layers[i].max_links) { hnswNode **new_links = hrealloc(node->layers[i].links, sizeof(hnswNode*) * max_links); if (!new_links) { hnsw_node_free(node); return NULL; } node->layers[i].links = new_links; node->layers[i].max_links = max_links; } node->layers[i].num_links = num_links; /* Sanity check. */ if (param_idx + num_links > params_len) { hnsw_node_free(node); return NULL; } /* Fill links for this layer with the IDs. Note that this * is going to not work in 32 bit systems. Deleting / adding-back * nodes can produce IDs larger than 2^32-1 even if we can't never * fit more than 2^32 nodes in a 32 bit system. */ for (uint32_t j = 0; j < num_links; j++) node->layers[i].links[j] = (hnswNode*)params[param_idx++]; } /* Get l2 and quantization range. */ if (param_idx >= params_len) { hnsw_node_free(node); return NULL; } uint64_t l2_and_range = params[param_idx]; unsigned char *aux = (unsigned char*)&l2_and_range; memcpy(&node->l2, aux, sizeof(float)); memcpy(&node->quants_range, aux+4, sizeof(float)); node->value = value; hnsw_add_node(index, node); /* Keep track of higher node level and set the entry point to the * greatest level node seen so far: thanks to this check we don't * need to remember what our entry point was during serialization. */ if (index->enter_point == NULL || level > index->max_level) { index->max_level = level; index->enter_point = node; } return node; } /* Integer hashing, used by hnsw_deserialize_index(). * MurmurHash3's 64-bit finalizer function. */ uint64_t hnsw_hash_node_id(uint64_t id) { id ^= id >> 33; id *= 0xff51afd7ed558ccd; id ^= id >> 33; id *= 0xc4ceb9fe1a85ec53; id ^= id >> 33; return id; } /* Fix pointers of neighbors nodes: after loading the serialized nodes, the * neighbors links are just IDs (casted to pointers), instead of the actual * pointers. We need to resolve IDs into pointers. * * Return 0 on error (out of memory or some ID that can't be resolved), 1 on * success. */ int hnsw_deserialize_index(HNSW *index) { /* We will use simple linear probing, so over-allocating is a good * idea: anyway this flat array of pointers will consume a fraction * of the memory of the loaded index. */ uint64_t min_size = index->node_count*2; uint64_t table_size = 1; while(table_size < min_size) table_size <<= 1; hnswNode **table = hmalloc(sizeof(hnswNode*) * table_size); if (table == NULL) return 0; memset(table,0,sizeof(hnswNode*) * table_size); /* First pass: populate the ID -> pointer hash table. */ hnswNode *node = index->head; while(node) { uint64_t bucket = hnsw_hash_node_id(node->id) & (table_size-1); for (uint64_t j = 0; j < table_size; j++) { if (table[bucket] == NULL) { table[bucket] = node; break; } bucket = (bucket+1) & (table_size-1); } node = node->next; } /* Second pass: fix pointers of all the neighbors links. */ node = index->head; // Rewind. while(node) { for (uint32_t i = 0; i <= node->level; i++) { for (uint32_t j = 0; j < node->layers[i].num_links; j++) { uint64_t linked_id = (uint64_t) node->layers[i].links[j]; uint64_t bucket = hnsw_hash_node_id(linked_id) & (table_size-1); hnswNode *neighbor = NULL; for (uint64_t k = 0; k < table_size; k++) { if (table[bucket] && table[bucket]->id == linked_id) { neighbor = table[bucket]; break; } bucket = (bucket+1) & (table_size-1); } if (neighbor == NULL) { /* Unresolved link! Either a bug in this code * or broken serialization data. */ hfree(table); return 0; } node->layers[i].links[j] = neighbor; } } node = node->next; } hfree(table); return 1; } /* ================================ Iterator ================================ */ /* Get a cursor that can be used as argument of hnsw_cursor_next() to iterate * all the elements that remain there from the start to the end of the * iteration, excluding newly added elements. * * The function returns NULL on out of memory. */ hnswCursor *hnsw_cursor_init(HNSW *index) { if (pthread_rwlock_wrlock(&index->global_lock) != 0) return NULL; hnswCursor *cursor = hmalloc(sizeof(*cursor)); if (cursor == NULL) { pthread_rwlock_unlock(&index->global_lock); return NULL; } cursor->index = index; cursor->next = index->cursors; cursor->current = index->head; index->cursors = cursor; pthread_rwlock_unlock(&index->global_lock); return cursor; } /* 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) { // No easy way to recover from that. We will leak memory. return; } hnswCursor *x = cursor->index->cursors; hnswCursor *prev = NULL; while(x) { if (x == cursor) { if (prev) prev->next = cursor->next; else cursor->index->cursors = cursor->next; hfree(cursor); break; } x = x->next; } pthread_rwlock_unlock(&cursor->index->global_lock); } /* Acquire a lock to use the cursor. Returns 1 if the lock was acquired * with success, otherwise zero is returned. The returned element is * protected after calling hnsw_cursor_next() for all the time required to * access it, then hnsw_cursor_release_lock() should be called in order * to unlock the HNSW index. */ int hnsw_cursor_acquire_lock(hnswCursor *cursor) { return pthread_rwlock_rdlock(&cursor->index->global_lock) == 0; } /* Release the cursor lock, see hnsw_cursor_acquire_lock() top comment * for more information. */ void hnsw_cursor_release_lock(hnswCursor *cursor) { pthread_rwlock_unlock(&cursor->index->global_lock); } /* Return the next element of the HNSW. See hnsw_cursor_init() for * the guarantees of the function. */ hnswNode *hnsw_cursor_next(hnswCursor *cursor) { hnswNode *ret = cursor->current; if (ret) cursor->current = ret->next; return ret; } /* Called by hnsw_unlink_node() if there is at least an active cursor. * Will scan the cursors to see if any cursor is going to yield this * one, and in this case, updates the current element to the next. */ void hnsw_cursor_element_deleted(HNSW *index, hnswNode *deleted) { hnswCursor *x = index->cursors; while(x) { if (x->current == deleted) x->current = deleted->next; x = x->next; } } /* ============================ Debugging stuff ============================= */ /* Show stats about nodes connections. */ void hnsw_print_stats(HNSW *index) { if (!index || !index->head) { printf("Empty index or NULL pointer passed\n"); return; } long long total_links = 0; int min_links = -1; // We'll set this to first node's count. int isolated_nodes = 0; uint32_t node_count = 0; // Iterate through all nodes using the linked list. hnswNode *current = index->head; while (current) { // Count total links for this node across all layers. int node_total_links = 0; for (uint32_t layer = 0; layer <= current->level; layer++) node_total_links += current->layers[layer].num_links; // Update statistics. total_links += node_total_links; // Initialize or update minimum links. if (min_links == -1 || node_total_links < min_links) { min_links = node_total_links; } // Check if node is isolated (no links at all). if (node_total_links == 0) isolated_nodes++; node_count++; current = current->next; } // Print statistics printf("HNSW Graph Statistics:\n"); printf("----------------------\n"); printf("Total nodes: %u\n", node_count); if (node_count > 0) { printf("Average links per node: %.2f\n", (float)total_links / node_count); printf("Minimum links in a single node: %d\n", min_links); printf("Number of isolated nodes: %d (%.1f%%)\n", isolated_nodes, (float)isolated_nodes * 100 / node_count); } } /* Validate graph connectivity and link reciprocity. Takes pointers to store results: * - connected_nodes: will contain number of reachable nodes from entry point. * - reciprocal_links: will contain 1 if all links are reciprocal, 0 otherwise. * Returns 0 on success, -1 on error (NULL parameters and such). */ int hnsw_validate_graph(HNSW *index, uint64_t *connected_nodes, int *reciprocal_links) { if (!index || !connected_nodes || !reciprocal_links) return -1; if (!index->enter_point) { *connected_nodes = 0; *reciprocal_links = 1; // Empty graph is valid. return 0; } // Initialize connectivity check. index->current_epoch[0]++; *connected_nodes = 0; *reciprocal_links = 1; // Initialize node stack. uint64_t stack_size = index->node_count; hnswNode **stack = hmalloc(sizeof(hnswNode*) * stack_size); if (!stack) return -1; uint64_t stack_top = 0; // Start from entry point. index->enter_point->visited_epoch[0] = index->current_epoch[0]; (*connected_nodes)++; stack[stack_top++] = index->enter_point; // Process all reachable nodes. while (stack_top > 0) { hnswNode *current = stack[--stack_top]; // Explore all neighbors at each level. for (uint32_t level = 0; level <= current->level; level++) { for (uint64_t i = 0; i < current->layers[level].num_links; i++) { hnswNode *neighbor = current->layers[level].links[i]; // Check reciprocity. int found_backlink = 0; for (uint64_t j = 0; j < neighbor->layers[level].num_links; j++) { if (neighbor->layers[level].links[j] == current) { found_backlink = 1; break; } } if (!found_backlink) { *reciprocal_links = 0; } // If we haven't visited this neighbor yet. if (neighbor->visited_epoch[0] != index->current_epoch[0]) { neighbor->visited_epoch[0] = index->current_epoch[0]; (*connected_nodes)++; if (stack_top < stack_size) { stack[stack_top++] = neighbor; } else { // This should never happen in a valid graph. hfree(stack); return -1; } } } } } hfree(stack); // Now scan for unreachable nodes and print debug info. printf("\nUnreachable nodes debug information:\n"); printf("=====================================\n"); hnswNode *current = index->head; while (current) { if (current->visited_epoch[0] != index->current_epoch[0]) { printf("\nUnreachable node found:\n"); printf("- Node pointer: %p\n", (void*)current); printf("- Node ID: %llu\n", (unsigned long long)current->id); printf("- Node level: %u\n", current->level); // Print info about all its links at each level. for (uint32_t level = 0; level <= current->level; level++) { printf(" Level %u links (%u):\n", level, current->layers[level].num_links); for (uint64_t i = 0; i < current->layers[level].num_links; i++) { hnswNode *neighbor = current->layers[level].links[i]; // Check reciprocity for this specific link int found_backlink = 0; for (uint64_t j = 0; j < neighbor->layers[level].num_links; j++) { if (neighbor->layers[level].links[j] == current) { found_backlink = 1; break; } } printf(" - Link %llu: pointer=%p, id=%llu, visited=%s,recpr=%s\n", (unsigned long long)i, (void*)neighbor, (unsigned long long)neighbor->id, neighbor->visited_epoch[0] == index->current_epoch[0] ? "yes" : "no", found_backlink ? "yes" : "no"); } } } current = current->next; } printf("Total connected nodes: %llu\n", (unsigned long long)*connected_nodes); printf("All links are bi-directiona? %s\n", (*reciprocal_links)?"yes":"no"); return 0; } /* Test graph recall ability by verifying each node can be found searching * for its own vector. This helps validate that the majority of nodes are * properly connected and easily reachable in the graph structure. Every * unreachable node is reported. * * Normally only a small percentage of nodes will be not reachable when * visited. This is expected and part of the statistical properties * of HNSW. This happens especially with entries that have an ambiguous * meaning in the represented space, and are across two or multiple clusters * of items. * * The function works by: * 1. Iterating through all nodes in the linked list * 2. Using each node's vector to perform a search with specified EF * 3. Verifying the node can find itself as nearest neighbor * 4. Collecting and reporting statistics about reachability * * This is just a debugging function that reports stuff in the standard * output, part of the implementation because this kind of functions * provide some visibility on what happens inside the HNSW. */ void hnsw_test_graph_recall(HNSW *index, int test_ef, int verbose) { // Stats uint32_t total_nodes = 0; uint32_t unreachable_nodes = 0; uint32_t perfectly_reachable = 0; // Node finds itself as first result // For storing search results hnswNode **neighbors = hmalloc(sizeof(hnswNode*) * test_ef); float *distances = hmalloc(sizeof(float) * test_ef); float *test_vector = hmalloc(sizeof(float) * index->vector_dim); if (!neighbors || !distances || !test_vector) { hfree(neighbors); hfree(distances); hfree(test_vector); return; } // Get a read slot for searching (even if it's highly unlikely that // this test will be run threaded...). int slot = hnsw_acquire_read_slot(index); if (slot < 0) { hfree(neighbors); hfree(distances); return; } printf("\nTesting graph recall\n"); printf("====================\n"); // Process one node at a time using the linked list hnswNode *current = index->head; while (current) { total_nodes++; // If using quantization, we need to reconstruct the normalized vector if (index->quant_type == HNSW_QUANT_Q8) { int8_t *quants = current->vector; // Reconstruct normalized vector from quantized data for (uint32_t j = 0; j < index->vector_dim; j++) { test_vector[j] = (quants[j] * current->quants_range) / 127; } } else if (index->quant_type == HNSW_QUANT_NONE) { memcpy(test_vector,current->vector,sizeof(float)*index->vector_dim); } else { assert(0 && "Quantization type not supported."); } // Search using the node's own vector with high ef int found = hnsw_search(index, test_vector, test_ef, neighbors, distances, slot, 1); if (found == 0) continue; // Empty HNSW? // Look for the node itself in the results int found_self = 0; int self_position = -1; for (int i = 0; i < found; i++) { if (neighbors[i] == current) { found_self = 1; self_position = i; break; } } if (!found_self || self_position != 0) { unreachable_nodes++; if (verbose) { if (!found_self) printf("\nNode %s cannot find itself:\n", (char*)current->value); else printf("\nNode %s is not top result:\n", (char*)current->value); printf("- Node ID: %llu\n", (unsigned long long)current->id); printf("- Node level: %u\n", current->level); printf("- Found %d neighbors but self not among them\n", found); printf("- Closest neighbor distance: %f\n", distances[0]); printf("- Neighbors: "); for (uint32_t i = 0; i < current->layers[0].num_links; i++) { printf("%s ", (char*)current->layers[0].links[i]->value); } printf("\n"); printf("\nFound instead: "); for (int j = 0; j < found && j < 10; j++) { printf("%s ", (char*)neighbors[j]->value); } printf("\n"); } } else { perfectly_reachable++; } current = current->next; } // Release read slot hnsw_release_read_slot(index, slot); // Free resources hfree(neighbors); hfree(distances); hfree(test_vector); // Print final statistics printf("Total nodes tested: %u\n", total_nodes); printf("Perfectly reachable nodes: %u (%.1f%%)\n", perfectly_reachable, total_nodes ? (float)perfectly_reachable * 100 / total_nodes : 0); printf("Unreachable/suboptimal nodes: %u (%.1f%%)\n", unreachable_nodes, total_nodes ? (float)unreachable_nodes * 100 / total_nodes : 0); } /* Return exact K-NN items by performing a linear scan of all nodes. * This function has the same signature as hnsw_search_with_filter() but * instead of using the graph structure, it scans all nodes to find the * true nearest neighbors. * * Note that neighbors and distances arrays must have space for at least 'k' items. * norm_query should be set to 1 if the query vector is already normalized. * * If the filter_callback is passed, only elements passing the specified filter * are returned. The slot parameter is ignored but kept for API consistency. */ int hnsw_ground_truth_with_filter (HNSW *index, const float *query_vector, uint32_t k, hnswNode **neighbors, float *distances, uint32_t slot, int query_vector_is_normalized, int (*filter_callback)(void *value, void *privdata), void *filter_privdata) { /* Note that we don't really use the slot here: it's a linear scan. * Yet we want the user to acquire the slot as this will hold the * global lock in read only mode. */ (void) slot; /* Take our query vector into a temporary node. */ hnswNode query; if (hnsw_init_tmp_node(index, &query, query_vector_is_normalized, query_vector) == 0) return -1; /* Accumulate best results into a priority queue. */ pqueue *results = pq_new(k); if (!results) { hnsw_free_tmp_node(&query, query_vector); return -1; } /* Scan all nodes linearly. */ hnswNode *current = index->head; while (current) { /* Apply filter if needed. */ if (filter_callback && !filter_callback(current->value, filter_privdata)) { current = current->next; continue; } /* Calculate distance to query. */ float dist = hnsw_distance(index, &query, current); /* Add to results to pqueue. Will be accepted only if better than * the current worse or pqueue not full. */ pq_push(results, current, dist); current = current->next; } /* Copy results to output arrays. */ uint32_t found = MIN(k, results->count); for (uint32_t i = 0; i < found; i++) { neighbors[i] = pq_get_node(results, i); if (distances) distances[i] = pq_get_distance(results, i); } /* Clean up. */ pq_free(results); hnsw_free_tmp_node(&query, query_vector); return found; } redis-8.0.2/modules/vector-sets/hnsw.h000066400000000000000000000207161501533116600177140ustar00rootroot00000000000000/* * HNSW (Hierarchical Navigable Small World) Implementation * Based on the paper by Yu. A. Malkov, D. A. Yashunin * * Copyright (c) 2009-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). * Originally authored by: Salvatore Sanfilippo. */ #ifndef HNSW_H #define HNSW_H #include #include #define HNSW_DEFAULT_M 16 /* Used when 0 is given at creation time. */ #define HNSW_MIN_M 4 /* Probably even too low already. */ #define HNSW_MAX_M 4096 /* Safeguard sanity limit. */ #define HNSW_MAX_THREADS 32 /* Maximum number of concurrent threads */ /* Quantization types you can enable at creation time in hnsw_new() */ #define HNSW_QUANT_NONE 0 // No quantization. #define HNSW_QUANT_Q8 1 // Q8 quantization. #define HNSW_QUANT_BIN 2 // Binary quantization. /* Layer structure for HNSW nodes. Each node will have from one to a few * of this depending on its level. */ typedef struct { struct hnswNode **links; /* Array of neighbors for this layer */ uint32_t num_links; /* Number of used links */ uint32_t max_links; /* Maximum links for this layer. We may * reallocate the node in very particular * conditions in order to allow linking of * new inserted nodes, so this may change * dynamically and be > M*2 for a small set of * nodes. */ float worst_distance; /* Distance to the worst neighbor */ uint32_t worst_idx; /* Index of the worst neighbor */ } hnswNodeLayer; /* Node structure for HNSW graph */ typedef struct hnswNode { uint32_t level; /* Node's maximum level */ uint64_t id; /* Unique identifier, may be useful in order to * have a bitmap of visited notes to use as * alternative to epoch / visited_epoch. * Also used in serialization in order to retain * links specifying IDs. */ void *vector; /* The vector, quantized or not. */ float quants_range; /* Quantization range for this vector: * min/max values will be in the range * -quants_range, +quants_range */ float l2; /* L2 before normalization. */ /* Last time (epoch) this node was visited. We need one per thread. * This avoids having a different data structure where we track * visited nodes, but costs memory per node. */ uint64_t visited_epoch[HNSW_MAX_THREADS]; void *value; /* Associated value */ struct hnswNode *prev, *next; /* Prev/Next node in the list starting at * HNSW->head. */ /* Links (and links info) per each layer. Note that this is part * of the node allocation to be more cache friendly: reliable 3% speedup * on Apple silicon, and does not make anything more complex. */ hnswNodeLayer layers[]; } hnswNode; struct HNSW; /* It is possible to navigate an HNSW with a cursor that guarantees * visiting all the elements that remain in the HNSW from the start to the * end of the process (but not the new ones, so that the process will * eventually finish). Check hnsw_cursor_init(), hnsw_cursor_next() and * hnsw_cursor_free(). */ typedef struct hnswCursor { struct HNSW *index; // Reference to the index of this cursor. hnswNode *current; // Element to report when hnsw_cursor_next() is called. struct hnswCursor *next; // Next cursor active. } hnswCursor; /* Main HNSW index structure */ typedef struct HNSW { hnswNode *enter_point; /* Entry point for the graph */ uint32_t M; /* M as in the paper: layer 0 has M*2 max neighbors (M populated at insertion time) while all the other layers have M neighbors. */ uint32_t max_level; /* Current maximum level in the graph */ uint32_t vector_dim; /* Dimensionality of stored vectors */ uint64_t node_count; /* Total number of nodes */ _Atomic uint64_t last_id; /* Last node ID used */ uint64_t current_epoch[HNSW_MAX_THREADS]; /* Current epoch for visit tracking */ hnswNode *head; /* Linked list of nodes. Last first */ /* We have two locks here: * 1. A global_lock that is used to perform write operations blocking all * the readers. * 2. One mutex per epoch slot, in order for read operations to acquire * a lock on a specific slot to use epochs tracking of visited nodes. */ pthread_rwlock_t global_lock; /* Global read-write lock */ pthread_mutex_t slot_locks[HNSW_MAX_THREADS]; /* Per-slot locks */ _Atomic uint32_t next_slot; /* Next thread slot to try */ _Atomic uint64_t version; /* Version for optimistic concurrency, this is * incremented on deletions and entry point * updates. */ uint32_t quant_type; /* Quantization used. HNSW_QUANT_... */ hnswCursor *cursors; } HNSW; /* Serialized node. This structure is used as return value of * hnsw_serialize_node(). */ typedef struct hnswSerNode { void *vector; uint32_t vector_size; uint64_t *params; uint32_t params_count; } hnswSerNode; /* Insert preparation context */ typedef struct InsertContext InsertContext; /* Core HNSW functions */ HNSW *hnsw_new(uint32_t vector_dim, uint32_t quant_type, uint32_t m); void hnsw_free(HNSW *index,void(*free_value)(void*value)); void hnsw_node_free(hnswNode *node); void hnsw_print_stats(HNSW *index); hnswNode *hnsw_insert(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, void *value, int ef); int hnsw_search(HNSW *index, const float *query, uint32_t k, hnswNode **neighbors, float *distances, uint32_t slot, int query_vector_is_normalized); int hnsw_search_with_filter (HNSW *index, const float *query_vector, uint32_t k, hnswNode **neighbors, float *distances, uint32_t slot, int query_vector_is_normalized, int (*filter_callback)(void *value, void *privdata), void *filter_privdata, uint32_t max_candidates); void hnsw_get_node_vector(HNSW *index, hnswNode *node, float *vec); int hnsw_delete_node(HNSW *index, hnswNode *node, void(*free_value)(void*value)); hnswNode *hnsw_random_node(HNSW *index, int slot); /* Thread safety functions. */ int hnsw_acquire_read_slot(HNSW *index); void hnsw_release_read_slot(HNSW *index, int slot); /* Optimistic insertion API. */ InsertContext *hnsw_prepare_insert(HNSW *index, const float *vector, const int8_t *qvector, float qrange, uint64_t id, int ef); hnswNode *hnsw_try_commit_insert(HNSW *index, InsertContext *ctx, void *value); void hnsw_free_insert_context(InsertContext *ctx); /* Serialization. */ hnswSerNode *hnsw_serialize_node(HNSW *index, hnswNode *node); void hnsw_free_serialized_node(hnswSerNode *sn); hnswNode *hnsw_insert_serialized(HNSW *index, void *vector, uint64_t *params, uint32_t params_len, void *value); int hnsw_deserialize_index(HNSW *index); // Helper function in case the user wants to directly copy // the vector bytes. uint32_t hnsw_quants_bytes(HNSW *index); /* Cursors. */ hnswCursor *hnsw_cursor_init(HNSW *index); void hnsw_cursor_free(hnswCursor *cursor); hnswNode *hnsw_cursor_next(hnswCursor *cursor); int hnsw_cursor_acquire_lock(hnswCursor *cursor); void hnsw_cursor_release_lock(hnswCursor *cursor); /* Allocator selection. */ void hnsw_set_allocator(void (*free_ptr)(void*), void *(*malloc_ptr)(size_t), void *(*realloc_ptr)(void*, size_t)); /* Testing. */ int hnsw_validate_graph(HNSW *index, uint64_t *connected_nodes, int *reciprocal_links); void hnsw_test_graph_recall(HNSW *index, int test_ef, int verbose); float hnsw_distance(HNSW *index, hnswNode *a, hnswNode *b); int hnsw_ground_truth_with_filter (HNSW *index, const float *query_vector, uint32_t k, hnswNode **neighbors, float *distances, uint32_t slot, int query_vector_is_normalized, int (*filter_callback)(void *value, void *privdata), void *filter_privdata); #endif /* HNSW_H */ redis-8.0.2/modules/vector-sets/test.py000077500000000000000000000230701501533116600201140ustar00rootroot00000000000000#!/usr/bin/env python3 # # Vector set tests. # A Redis instance should be running in the default port. # # Copyright (c) 2009-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). # import redis import random import struct import math import time import sys import os import importlib import inspect import argparse from typing import List, Tuple, Optional from dataclasses import dataclass def colored(text: str, color: str) -> str: colors = { 'red': '\033[91m', 'green': '\033[92m', 'yellow': '\033[93m' } reset = '\033[0m' return f"{colors.get(color, '')}{text}{reset}" @dataclass class VectorData: vectors: List[List[float]] names: List[str] def find_k_nearest(self, query_vector: List[float], k: int) -> List[Tuple[str, float]]: """Find k-nearest neighbors using the same scoring as Redis VSIM WITHSCORES.""" similarities = [] query_norm = math.sqrt(sum(x*x for x in query_vector)) if query_norm == 0: return [] for i, vec in enumerate(self.vectors): vec_norm = math.sqrt(sum(x*x for x in vec)) if vec_norm == 0: continue dot_product = sum(a*b for a,b in zip(query_vector, vec)) cosine_sim = dot_product / (query_norm * vec_norm) distance = 1.0 - cosine_sim redis_similarity = 1.0 - (distance/2.0) similarities.append((self.names[i], redis_similarity)) similarities.sort(key=lambda x: x[1], reverse=True) return similarities[:k] def generate_random_vector(dim: int) -> List[float]: """Generate a random normalized vector.""" vec = [random.gauss(0, 1) for _ in range(dim)] norm = math.sqrt(sum(x*x for x in vec)) return [x/norm for x in vec] def fill_redis_with_vectors(r: redis.Redis, key: str, count: int, dim: int, with_reduce: Optional[int] = None) -> VectorData: """Fill Redis with random vectors and return a VectorData object for verification.""" vectors = [] names = [] r.delete(key) for i in range(count): vec = generate_random_vector(dim) name = f"{key}:item:{i}" vectors.append(vec) names.append(name) vec_bytes = struct.pack(f'{dim}f', *vec) args = [key] if with_reduce: args.extend(['REDUCE', with_reduce]) args.extend(['FP32', vec_bytes, name]) r.execute_command('VADD', *args) return VectorData(vectors=vectors, names=names) class TestCase: def __init__(self, primary_port=6379, replica_port=6380): self.error_msg = None self.error_details = None self.test_key = f"test:{self.__class__.__name__.lower()}" # Primary Redis instance self.redis = redis.Redis(port=primary_port) # Replica Redis instance self.replica = redis.Redis(port=replica_port) # Replication status self.replication_setup = False # Ports self.primary_port = primary_port self.replica_port = replica_port def setup(self): self.redis.delete(self.test_key) def teardown(self): self.redis.delete(self.test_key) def setup_replication(self) -> bool: """ Setup replication between primary and replica Redis instances. Returns True if replication is successfully established, False otherwise. """ # Configure replica to replicate from primary self.replica.execute_command('REPLICAOF', '127.0.0.1', self.primary_port) # Wait for replication to be established max_attempts = 10 for attempt in range(max_attempts): # Check replication info repl_info = self.replica.info('replication') # Check if replication is established if (repl_info.get('role') == 'slave' and repl_info.get('master_host') == '127.0.0.1' and repl_info.get('master_port') == self.primary_port and repl_info.get('master_link_status') == 'up'): self.replication_setup = True return True # Wait before next attempt time.sleep(0.5) # If we get here, replication wasn't established self.error_msg = "Failed to establish replication between primary and replica" return False def test(self): raise NotImplementedError("Subclasses must implement test method") def run(self): try: self.setup() self.test() return True except AssertionError as e: self.error_msg = str(e) import traceback self.error_details = traceback.format_exc() return False except Exception as e: self.error_msg = f"Unexpected error: {str(e)}" import traceback self.error_details = traceback.format_exc() return False finally: self.teardown() def getname(self): """Each test class should override this to provide its name""" return self.__class__.__name__ def estimated_runtime(self): """"Each test class should override this if it takes a significant amount of time to run. Default is 100ms""" return 0.1 def find_test_classes(primary_port, replica_port): test_classes = [] tests_dir = 'tests' if not os.path.exists(tests_dir): return [] for file in os.listdir(tests_dir): if file.endswith('.py'): module_name = f"tests.{file[:-3]}" try: module = importlib.import_module(module_name) for name, obj in inspect.getmembers(module): if inspect.isclass(obj) and obj.__name__ != 'TestCase' and hasattr(obj, 'test'): # Create test instance with specified ports test_instance = obj() test_instance.redis = redis.Redis(port=primary_port) test_instance.replica = redis.Redis(port=replica_port) test_instance.primary_port = primary_port test_instance.replica_port = replica_port test_classes.append(test_instance) except Exception as e: print(f"Error loading {file}: {e}") return test_classes def check_redis_empty(r, instance_name): """Check if Redis instance is empty""" try: dbsize = r.dbsize() if dbsize > 0: print(colored(f"ERROR: {instance_name} Redis instance is not empty (dbsize: {dbsize}).", "red")) print(colored("Make sure you're not using a production instance and that all data is safe to delete.", "red")) sys.exit(1) except redis.exceptions.ConnectionError: print(colored(f"ERROR: Cannot connect to {instance_name} Redis instance.", "red")) sys.exit(1) def check_replica_running(replica_port): """Check if replica Redis instance is running""" r = redis.Redis(port=replica_port) try: r.ping() return True except redis.exceptions.ConnectionError: print(colored(f"WARNING: Replica Redis instance (port {replica_port}) is not running.", "yellow")) print(colored("Replication tests will fail. Make sure to start the replica instance.", "yellow")) return False def run_tests(): # Parse command line arguments parser = argparse.ArgumentParser(description='Run Redis vector tests.') parser.add_argument('--primary-port', type=int, default=6379, help='Primary Redis instance port (default: 6379)') parser.add_argument('--replica-port', type=int, default=6380, help='Replica Redis instance port (default: 6380)') args = parser.parse_args() print("================================================") print(f"Make sure to have Redis running on localhost") print(f"Primary port: {args.primary_port}") print(f"Replica port: {args.replica_port}") print("with --enable-debug-command yes") print("================================================\n") # Check if Redis instances are empty primary = redis.Redis(port=args.primary_port) replica = redis.Redis(port=args.replica_port) check_redis_empty(primary, "Primary") # Check if replica is running replica_running = check_replica_running(args.replica_port) if replica_running: check_redis_empty(replica, "Replica") tests = find_test_classes(args.primary_port, args.replica_port) if not tests: print("No tests found!") return # Sort tests by estimated runtime tests.sort(key=lambda t: t.estimated_runtime()) passed = 0 total = len(tests) for test in tests: print(f"{test.getname()}: ", end="") sys.stdout.flush() start_time = time.time() success = test.run() duration = time.time() - start_time if success: print(colored("OK", "green"), f"({duration:.2f}s)") passed += 1 else: print(colored("ERR", "red"), f"({duration:.2f}s)") print(f"Error: {test.error_msg}") if test.error_details: print("\nTraceback:") print(test.error_details) print("\n" + "="*50) print(f"\nTest Summary: {passed}/{total} tests passed") if passed == total: print(colored("\nALL TESTS PASSED!", "green")) else: print(colored(f"\n{total-passed} TESTS FAILED!", "red")) if __name__ == "__main__": run_tests() redis-8.0.2/modules/vector-sets/tests/000077500000000000000000000000001501533116600177205ustar00rootroot00000000000000redis-8.0.2/modules/vector-sets/tests/basic_commands.py000066400000000000000000000013761501533116600232430ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct class BasicCommands(TestCase): def getname(self): return "VADD, VDIM, VCARD basic usage" def test(self): # Test VADD vec = generate_random_vector(4) vec_bytes = struct.pack('4f', *vec) result = self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, f'{self.test_key}:item:1') assert result == 1, "VADD should return 1 for first item" # Test VDIM dim = self.redis.execute_command('VDIM', self.test_key) assert dim == 4, f"VDIM should return 4, got {dim}" # Test VCARD card = self.redis.execute_command('VCARD', self.test_key) assert card == 1, f"VCARD should return 1, got {card}" redis-8.0.2/modules/vector-sets/tests/basic_similarity.py000066400000000000000000000031111501533116600236150ustar00rootroot00000000000000from test import TestCase class BasicSimilarity(TestCase): def getname(self): return "VSIM reported distance makes sense with 4D vectors" def test(self): # Add two very similar vectors, one different vec1 = [1, 0, 0, 0] vec2 = [0.99, 0.01, 0, 0] vec3 = [0.1, 1, -1, 0.5] # Add vectors using VALUES format self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], f'{self.test_key}:item:1') self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec2], f'{self.test_key}:item:2') self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec3], f'{self.test_key}:item:3') # 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 assert results_dict[f'{self.test_key}:item:1'] > 0.99, "Self-similarity should be very high" assert results_dict[f'{self.test_key}:item:2'] > 0.99, "Similar vector should have high similarity" assert results_dict[f'{self.test_key}:item:3'] < 0.8, "Not very similar vector should have low similarity" redis-8.0.2/modules/vector-sets/tests/concurrent_vadd_cas_del_vsim.py000066400000000000000000000132441501533116600261660ustar00rootroot00000000000000from test import TestCase, generate_random_vector import threading import time import struct class ThreadingStressTest(TestCase): def getname(self): return "Concurrent VADD/DEL/VSIM operations stress test" def estimated_runtime(self): return 10 # Test runs for 10 seconds def test(self): # Constants - easy to modify if needed NUM_VADD_THREADS = 10 NUM_VSIM_THREADS = 1 NUM_DEL_THREADS = 1 TEST_DURATION = 10 # seconds VECTOR_DIM = 100 DEL_INTERVAL = 1 # seconds # Shared flags and state stop_event = threading.Event() error_list = [] error_lock = threading.Lock() def log_error(thread_name, error): with error_lock: error_list.append(f"{thread_name}: {error}") def vadd_worker(thread_id): """Thread function to perform VADD operations""" thread_name = f"VADD-{thread_id}" try: vector_count = 0 while not stop_event.is_set(): try: # Generate random vector vec = generate_random_vector(VECTOR_DIM) vec_bytes = struct.pack(f'{VECTOR_DIM}f', *vec) # Add vector with CAS option self.redis.execute_command( 'VADD', self.test_key, 'FP32', vec_bytes, f'{self.test_key}:item:{thread_id}:{vector_count}', 'CAS' ) vector_count += 1 # Small sleep to reduce CPU pressure if vector_count % 10 == 0: time.sleep(0.001) except Exception as e: log_error(thread_name, f"Error: {str(e)}") time.sleep(0.1) # Slight backoff on error except Exception as e: log_error(thread_name, f"Thread error: {str(e)}") def del_worker(): """Thread function that deletes the key periodically""" thread_name = "DEL" try: del_count = 0 while not stop_event.is_set(): try: # Sleep first, then delete time.sleep(DEL_INTERVAL) if stop_event.is_set(): break self.redis.delete(self.test_key) del_count += 1 except Exception as e: log_error(thread_name, f"Error: {str(e)}") except Exception as e: log_error(thread_name, f"Thread error: {str(e)}") def vsim_worker(thread_id): """Thread function to perform VSIM operations""" thread_name = f"VSIM-{thread_id}" try: search_count = 0 while not stop_event.is_set(): try: # Generate query vector query_vec = generate_random_vector(VECTOR_DIM) query_str = [str(x) for x in query_vec] # Perform similarity search args = ['VSIM', self.test_key, 'VALUES', VECTOR_DIM] args.extend(query_str) args.extend(['COUNT', 10]) self.redis.execute_command(*args) search_count += 1 # Small sleep to reduce CPU pressure if search_count % 10 == 0: time.sleep(0.005) except Exception as e: # Don't log empty array errors, as they're expected when key doesn't exist if "empty array" not in str(e).lower(): log_error(thread_name, f"Error: {str(e)}") time.sleep(0.1) # Slight backoff on error except Exception as e: log_error(thread_name, f"Thread error: {str(e)}") # Start all threads threads = [] # VADD threads for i in range(NUM_VADD_THREADS): thread = threading.Thread(target=vadd_worker, args=(i,)) thread.start() threads.append(thread) # DEL threads for _ in range(NUM_DEL_THREADS): thread = threading.Thread(target=del_worker) thread.start() threads.append(thread) # VSIM threads for i in range(NUM_VSIM_THREADS): thread = threading.Thread(target=vsim_worker, args=(i,)) thread.start() threads.append(thread) # Let the test run for the specified duration time.sleep(TEST_DURATION) # Signal all threads to stop stop_event.set() # Wait for threads to finish for thread in threads: thread.join(timeout=2.0) # Check if Redis is still responsive try: ping_result = self.redis.ping() assert ping_result, "Redis did not respond to PING after stress test" except Exception as e: assert False, f"Redis connection failed after stress test: {str(e)}" # Report any errors for diagnosis, but don't fail the test unless PING fails if error_list: error_count = len(error_list) print(f"\nEncountered {error_count} errors during stress test.") print("First 5 errors:") for error in error_list[:5]: print(f"- {error}") redis-8.0.2/modules/vector-sets/tests/concurrent_vsim_and_del.py000066400000000000000000000034321501533116600251620ustar00rootroot00000000000000from test import TestCase, fill_redis_with_vectors, generate_random_vector import threading, time class ConcurrentVSIMAndDEL(TestCase): def getname(self): return "Concurrent VSIM and DEL operations" def estimated_runtime(self): return 2 def test(self): # Fill the key with 5000 random vectors dim = 128 count = 5000 fill_redis_with_vectors(self.redis, self.test_key, count, dim) # List to store results from threads thread_results = [] def vsim_thread(): """Thread function to perform VSIM operations until the key is deleted""" while True: query_vec = generate_random_vector(dim) result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', dim, *[str(x) for x in query_vec], 'COUNT', 10) if not result: # Empty array detected, key is deleted thread_results.append(True) break # Start multiple threads to perform VSIM operations threads = [] for _ in range(4): # Start 4 threads t = threading.Thread(target=vsim_thread) t.start() threads.append(t) # Delete the key while threads are still running time.sleep(1) self.redis.delete(self.test_key) # Wait for all threads to finish (they will exit once they detect the key is deleted) for t in threads: t.join() # Verify that all threads detected an empty array or error assert len(thread_results) == len(threads), "Not all threads detected the key deletion" assert all(thread_results), "Some threads did not detect an empty array or error after DEL" redis-8.0.2/modules/vector-sets/tests/debug_digest.py000066400000000000000000000036661501533116600227320ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct class DebugDigestTest(TestCase): def getname(self): return "[regression] DEBUG DIGEST-VALUE with attributes" def test(self): # Generate random vectors vec1 = generate_random_vector(4) vec2 = generate_random_vector(4) vec_bytes1 = struct.pack('4f', *vec1) vec_bytes2 = struct.pack('4f', *vec2) # Add vectors to the key, one with attribute, one without self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes1, f'{self.test_key}:item:1') self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes2, f'{self.test_key}:item:2', 'SETATTR', '{"color":"red"}') # Call DEBUG DIGEST-VALUE on the key try: digest1 = self.redis.execute_command('DEBUG', 'DIGEST-VALUE', self.test_key) assert digest1 is not None, "DEBUG DIGEST-VALUE should return a value" # Change attribute and verify digest changes self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:2', '{"color":"blue"}') digest2 = self.redis.execute_command('DEBUG', 'DIGEST-VALUE', self.test_key) assert digest2 is not None, "DEBUG DIGEST-VALUE should return a value after attribute change" assert digest1 != digest2, "Digest should change when an attribute is modified" # Remove attribute and verify digest changes again self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:2', '') digest3 = self.redis.execute_command('DEBUG', 'DIGEST-VALUE', self.test_key) assert digest3 is not None, "DEBUG DIGEST-VALUE should return a value after attribute removal" assert digest2 != digest3, "Digest should change when an attribute is removed" except Exception as e: raise AssertionError(f"DEBUG DIGEST-VALUE command failed: {str(e)}") redis-8.0.2/modules/vector-sets/tests/deletion.py000066400000000000000000000166101501533116600221010ustar00rootroot00000000000000from test import TestCase, fill_redis_with_vectors, generate_random_vector import random """ A note about this test: It was experimentally tried to modify hnsw.c in order to avoid calling hnsw_reconnect_nodes(). In this case, the test fails very often with EF set to 250, while it hardly fails at all with the same parameters if hnsw_reconnect_nodes() is called. Note that for the nature of the test (it is very strict) it can still fail from time to time, without this signaling any actual bug. """ class VREM(TestCase): def getname(self): return "Deletion and graph state after deletion" def estimated_runtime(self): return 2.0 def format_neighbors_with_scores(self, links_result, old_links=None, items_to_remove=None): """Format neighbors with their similarity scores and status indicators""" if not links_result: return "No neighbors" output = [] for level, neighbors in enumerate(links_result): level_num = len(links_result) - level - 1 output.append(f"Level {level_num}:") # Get neighbors and scores neighbors_with_scores = [] for i in range(0, len(neighbors), 2): neighbor = neighbors[i].decode() if isinstance(neighbors[i], bytes) else neighbors[i] score = float(neighbors[i+1]) if i+1 < len(neighbors) else None status = "" # For old links, mark deleted ones if items_to_remove and neighbor in items_to_remove: status = " [lost]" # For new links, mark newly added ones elif old_links is not None: # Check if this neighbor was in the old links at this level was_present = False if old_links and level < len(old_links): old_neighbors = [n.decode() if isinstance(n, bytes) else n for n in old_links[level]] was_present = neighbor in old_neighbors if not was_present: status = " [gained]" if score is not None: neighbors_with_scores.append(f"{len(neighbors_with_scores)+1}. {neighbor} ({score:.6f}){status}") else: neighbors_with_scores.append(f"{len(neighbors_with_scores)+1}. {neighbor}{status}") output.extend([" " + n for n in neighbors_with_scores]) return "\n".join(output) def test(self): # 1. Fill server with random elements dim = 128 count = 5000 data = fill_redis_with_vectors(self.redis, self.test_key, count, dim) # 2. Do VSIM to get 200 items query_vec = generate_random_vector(dim) results = self.redis.execute_command('VSIM', self.test_key, 'VALUES', dim, *[str(x) for x in query_vec], 'COUNT', 200, 'WITHSCORES') # Convert results to list of (item, score) pairs, sorted by score items = [] for i in range(0, len(results), 2): item = results[i].decode() score = float(results[i+1]) items.append((item, score)) items.sort(key=lambda x: x[1], reverse=True) # Sort by similarity # Store the graph structure for all items before deletion neighbors_before = {} for item, _ in items: links = self.redis.execute_command('VLINKS', self.test_key, item, 'WITHSCORES') if links: # Some items might not have links neighbors_before[item] = links # 3. Remove 100 random items items_to_remove = set(item for item, _ in random.sample(items, 100)) # Keep track of top 10 non-removed items top_remaining = [] for item, score in items: if item not in items_to_remove: top_remaining.append((item, score)) if len(top_remaining) == 10: break # Remove the items for item in items_to_remove: result = self.redis.execute_command('VREM', self.test_key, item) assert result == 1, f"VREM failed to remove {item}" # 4. Do VSIM again with same vector new_results = self.redis.execute_command('VSIM', self.test_key, 'VALUES', dim, *[str(x) for x in query_vec], 'COUNT', 200, 'WITHSCORES', 'EF', 500) # Convert new results to dict of item -> score new_scores = {} for i in range(0, len(new_results), 2): item = new_results[i].decode() score = float(new_results[i+1]) new_scores[item] = score failure = False failed_item = None failed_reason = None # 5. Verify all top 10 non-removed items are still found with similar scores for item, old_score in top_remaining: if item not in new_scores: failure = True failed_item = item failed_reason = "missing" break new_score = new_scores[item] if abs(new_score - old_score) >= 0.01: failure = True failed_item = item failed_reason = f"score changed: {old_score:.6f} -> {new_score:.6f}" break if failure: print("\nTest failed!") print(f"Problem with item: {failed_item} ({failed_reason})") print("\nOriginal neighbors (with similarity scores):") if failed_item in neighbors_before: print(self.format_neighbors_with_scores( neighbors_before[failed_item], items_to_remove=items_to_remove)) else: print("No neighbors found in original graph") print("\nCurrent neighbors (with similarity scores):") current_links = self.redis.execute_command('VLINKS', self.test_key, failed_item, 'WITHSCORES') if current_links: print(self.format_neighbors_with_scores( current_links, old_links=neighbors_before.get(failed_item))) else: print("No neighbors in current graph") print("\nOriginal results (top 20):") for item, score in items[:20]: deleted = "[deleted]" if item in items_to_remove else "" print(f"{item}: {score:.6f} {deleted}") print("\nNew results after removal (top 20):") new_items = [] for i in range(0, len(new_results), 2): item = new_results[i].decode() score = float(new_results[i+1]) new_items.append((item, score)) new_items.sort(key=lambda x: x[1], reverse=True) for item, score in new_items[:20]: print(f"{item}: {score:.6f}") raise AssertionError(f"Test failed: Problem with item {failed_item} ({failed_reason}). *** IMPORTANT *** This test may fail from time to time without indicating that there is a bug. However normally it should pass. The fact is that it's a quite extreme test where we destroy 50% of nodes of top results and still expect perfect recall, with vectors that are very hostile because of the distribution used.") redis-8.0.2/modules/vector-sets/tests/dimension_validation.py000066400000000000000000000064401501533116600244750ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct import redis.exceptions class DimensionValidation(TestCase): def getname(self): return "[regression] Dimension Validation with Projection" def estimated_runtime(self): return 0.5 def test(self): # Test scenario 1: Create a set with projection original_dim = 100 reduced_dim = 50 # Create the initial vector and set with projection vec1 = generate_random_vector(original_dim) vec1_bytes = struct.pack(f'{original_dim}f', *vec1) # Add first vector with projection result = self.redis.execute_command('VADD', self.test_key, 'REDUCE', reduced_dim, 'FP32', vec1_bytes, f'{self.test_key}:item:1') assert result == 1, "First VADD with REDUCE should return 1" # Check VINFO returns the correct projection information info = self.redis.execute_command('VINFO', self.test_key) info_map = {k.decode('utf-8'): v for k, v in zip(info[::2], info[1::2])} assert 'vector-dim' in info_map, "VINFO should contain vector-dim" assert info_map['vector-dim'] == reduced_dim, f"Expected reduced dimension {reduced_dim}, got {info['vector-dim']}" assert 'projection-input-dim' in info_map, "VINFO should contain projection-input-dim" assert info_map['projection-input-dim'] == original_dim, f"Expected original dimension {original_dim}, got {info['projection-input-dim']}" # Test scenario 2: Try adding a mismatched vector - should fail wrong_dim = 80 wrong_vec = generate_random_vector(wrong_dim) wrong_vec_bytes = struct.pack(f'{wrong_dim}f', *wrong_vec) # This should fail with dimension mismatch error try: self.redis.execute_command('VADD', self.test_key, 'REDUCE', reduced_dim, 'FP32', wrong_vec_bytes, f'{self.test_key}:item:2') assert False, "VADD 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, got: {e}" # Test scenario 3: Add a correctly-sized vector vec2 = generate_random_vector(original_dim) vec2_bytes = struct.pack(f'{original_dim}f', *vec2) # This should succeed result = self.redis.execute_command('VADD', self.test_key, 'REDUCE', reduced_dim, 'FP32', vec2_bytes, f'{self.test_key}:item:3') assert result == 1, "VADD with correct dimensions should succeed" # Check VSIM also validates input dimensions wrong_query = generate_random_vector(wrong_dim) try: self.redis.execute_command('VSIM', self.test_key, 'VALUES', wrong_dim, *[str(x) for x in wrong_query], 'COUNT', 10) 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}" redis-8.0.2/modules/vector-sets/tests/evict_empty.py000066400000000000000000000020171501533116600226220ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct class VREM_LastItemDeletesKey(TestCase): def getname(self): return "VREM last item deletes key" def test(self): # Generate a random vector vec = generate_random_vector(4) vec_bytes = struct.pack('4f', *vec) # Add the vector to the key result = self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, f'{self.test_key}:item:1') assert result == 1, "VADD should return 1 for first item" # Verify the key exists exists = self.redis.exists(self.test_key) assert exists == 1, "Key should exist after VADD" # Remove the item result = self.redis.execute_command('VREM', self.test_key, f'{self.test_key}:item:1') assert result == 1, "VREM should return 1 for successful removal" # Verify the key no longer exists exists = self.redis.exists(self.test_key) assert exists == 0, "Key should no longer exist after VREM of last item" redis-8.0.2/modules/vector-sets/tests/filter_expr.py000066400000000000000000000237741501533116600226320ustar00rootroot00000000000000from test import TestCase class VSIMFilterExpressions(TestCase): def getname(self): return "VSIM FILTER expressions basic functionality" def test(self): # Create a small set of vectors with different attributes # Basic vectors for testing - all orthogonal for clear results vec1 = [1, 0, 0, 0] vec2 = [0, 1, 0, 0] vec3 = [0, 0, 1, 0] vec4 = [0, 0, 0, 1] vec5 = [0.5, 0.5, 0, 0] # Add vectors with various attributes self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], f'{self.test_key}:item:1') self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:1', '{"age": 25, "name": "Alice", "active": true, "scores": [85, 90, 95], "city": "New York"}') self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec2], f'{self.test_key}:item:2') self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:2', '{"age": 30, "name": "Bob", "active": false, "scores": [70, 75, 80], "city": "Boston"}') self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec3], f'{self.test_key}:item:3') self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:3', '{"age": 35, "name": "Charlie", "scores": [60, 65, 70], "city": "Seattle"}') self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec4], f'{self.test_key}:item:4') # Item 4 has no attribute at all self.redis.execute_command('VADD', self.test_key, 'VALUES', 4, *[str(x) for x in vec5], f'{self.test_key}:item:5') self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:5', 'invalid json') # Intentionally malformed JSON # Test 1: Basic equality with numbers result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age == 25') assert len(result) == 1, "Expected 1 result for age == 25" assert result[0].decode() == f'{self.test_key}:item:1', "Expected item:1 for age == 25" # Test 2: Greater than result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age > 25') assert len(result) == 2, "Expected 2 results for age > 25" # Test 3: Less than or equal result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age <= 30') assert len(result) == 2, "Expected 2 results for age <= 30" # Test 4: String equality result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.name == "Alice"') assert len(result) == 1, "Expected 1 result for name == Alice" assert result[0].decode() == f'{self.test_key}:item:1', "Expected item:1 for name == Alice" # Test 5: String inequality result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.name != "Alice"') assert len(result) == 2, "Expected 2 results for name != Alice" # Test 6: Boolean value result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.active') assert len(result) == 1, "Expected 1 result for .active being true" # Test 7: Logical AND result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age > 20 and .age < 30') assert len(result) == 1, "Expected 1 result for 20 < age < 30" assert result[0].decode() == f'{self.test_key}:item:1', "Expected item:1 for 20 < age < 30" # Test 8: Logical OR result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age < 30 or .age > 35') assert len(result) == 1, "Expected 1 result for age < 30 or age > 35" # Test 9: Logical NOT result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '!(.age == 25)') assert len(result) == 2, "Expected 2 results for NOT(age == 25)" # Test 10: The "in" operator with array result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age in [25, 35]') assert len(result) == 2, "Expected 2 results for age in [25, 35]" # Test 11: The "in" operator with strings in array result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.name in ["Alice", "David"]') assert len(result) == 1, "Expected 1 result for name in [Alice, David]" # Test 12: Arithmetic operations - addition result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age + 10 > 40') assert len(result) == 1, "Expected 1 result for age + 10 > 40" # Test 13: Arithmetic operations - multiplication result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age * 2 > 60') assert len(result) == 1, "Expected 1 result for age * 2 > 60" # Test 14: Arithmetic operations - division result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age / 5 == 5') assert len(result) == 1, "Expected 1 result for age / 5 == 5" # Test 15: Arithmetic operations - modulo result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age % 2 == 0') assert len(result) == 1, "Expected 1 result for age % 2 == 0" # Test 16: Power operator result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age ** 2 > 900') assert len(result) == 1, "Expected 1 result for age^2 > 900" # Test 17: Missing attribute (should exclude items missing that attribute) result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.missing_field == "value"') assert len(result) == 0, "Expected 0 results for missing_field == value" # Test 18: No attribute set at all result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.any_field') assert f'{self.test_key}:item:4' not in [item.decode() for item in result], "Item with no attribute should be excluded" # Test 19: Malformed JSON result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.any_field') assert f'{self.test_key}:item:5' not in [item.decode() for item in result], "Item with malformed JSON should be excluded" # Test 20: Complex expression combining multiple operators result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '(.age > 20 and .age < 40) and (.city == "Boston" or .city == "New York")') assert len(result) == 2, "Expected 2 results for the complex expression" expected_items = [f'{self.test_key}:item:1', f'{self.test_key}:item:2'] assert set([item.decode() for item in result]) == set(expected_items), "Expected item:1 and item:2 for the complex expression" # Test 21: Parentheses to control operator precedence result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.age > (20 + 10)') assert len(result) == 1, "Expected 1 result for age > (20 + 10)" # Test 22: Array access (arrays evaluate to true) result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4, *[str(x) for x in vec1], 'FILTER', '.scores') assert len(result) == 3, "Expected 3 results for .scores (arrays evaluate to true)" redis-8.0.2/modules/vector-sets/tests/filter_int.py000066400000000000000000000717611501533116600224450ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct import random import math import json import time class VSIMFilterAdvanced(TestCase): def getname(self): return "VSIM FILTER comprehensive functionality testing" def estimated_runtime(self): return 15 # This test might take up to 15 seconds for the large dataset def setup(self): super().setup() self.dim = 32 # Vector dimension self.count = 5000 # Number of vectors for large tests self.small_count = 50 # Number of vectors for small/quick tests # Categories for attributes self.categories = ["electronics", "furniture", "clothing", "books", "food"] self.cities = ["New York", "London", "Tokyo", "Paris", "Berlin", "Sydney", "Toronto", "Singapore"] self.price_ranges = [(10, 50), (50, 200), (200, 1000), (1000, 5000)] self.years = list(range(2000, 2025)) def create_attributes(self, index): """Create realistic attributes for a vector""" category = random.choice(self.categories) city = random.choice(self.cities) min_price, max_price = random.choice(self.price_ranges) price = round(random.uniform(min_price, max_price), 2) year = random.choice(self.years) in_stock = random.random() > 0.3 # 70% chance of being in stock rating = round(random.uniform(1, 5), 1) views = int(random.expovariate(1/1000)) # Exponential distribution for page views tags = random.sample(["popular", "sale", "new", "limited", "exclusive", "clearance"], k=random.randint(0, 3)) # Add some specific patterns for testing # Every 10th item has a specific property combination for testing is_premium = (index % 10 == 0) # Create attributes dictionary attrs = { "id": index, "category": category, "location": city, "price": price, "year": year, "in_stock": in_stock, "rating": rating, "views": views, "tags": tags } if is_premium: attrs["is_premium"] = True attrs["special_features"] = ["premium", "warranty", "support"] # Add sub-categories for more complex filters if category == "electronics": attrs["subcategory"] = random.choice(["phones", "computers", "cameras", "audio"]) elif category == "furniture": attrs["subcategory"] = random.choice(["chairs", "tables", "sofas", "beds"]) elif category == "clothing": attrs["subcategory"] = random.choice(["shirts", "pants", "dresses", "shoes"]) # Add some intentionally missing fields for testing if random.random() > 0.9: # 10% chance of missing price del attrs["price"] # Some items have promotion field if random.random() > 0.7: # 30% chance of having a promotion attrs["promotion"] = random.choice(["discount", "bundle", "gift"]) # Create invalid JSON for a small percentage of vectors if random.random() > 0.98: # 2% chance of having invalid JSON return "{{invalid json}}" return json.dumps(attrs) def create_vectors_with_attributes(self, key, count): """Create vectors and add attributes to them""" vectors = [] names = [] attribute_map = {} # To store attributes for verification # Create vectors for i in range(count): vec = generate_random_vector(self.dim) vectors.append(vec) name = f"{key}:item:{i}" names.append(name) # Add to Redis vec_bytes = struct.pack(f'{self.dim}f', *vec) self.redis.execute_command('VADD', key, 'FP32', vec_bytes, name) # Create and add attributes attrs = self.create_attributes(i) self.redis.execute_command('VSETATTR', key, name, attrs) # Store attributes for later verification try: attribute_map[name] = json.loads(attrs) if '{' in attrs else None except json.JSONDecodeError: attribute_map[name] = None return vectors, names, attribute_map def filter_linear_search(self, vectors, names, query_vector, filter_expr, attribute_map, k=10): """Perform a linear search with filtering for verification""" similarities = [] query_norm = math.sqrt(sum(x*x for x in query_vector)) if query_norm == 0: return [] for i, vec in enumerate(vectors): name = names[i] attributes = attribute_map.get(name) # Skip if doesn't match filter if not self.matches_filter(attributes, filter_expr): continue vec_norm = math.sqrt(sum(x*x for x in vec)) if vec_norm == 0: continue dot_product = sum(a*b for a,b in zip(query_vector, vec)) cosine_sim = dot_product / (query_norm * vec_norm) distance = 1.0 - cosine_sim redis_similarity = 1.0 - (distance/2.0) similarities.append((name, redis_similarity)) similarities.sort(key=lambda x: x[1], reverse=True) return similarities[:k] def matches_filter(self, attributes, filter_expr): """Filter matching for verification - uses Python eval to handle complex expressions""" if attributes is None: return False # No attributes or invalid JSON # Replace JSON path selectors with Python dictionary access py_expr = filter_expr # Handle `.field` notation (replace with attributes['field']) i = 0 while i < len(py_expr): if py_expr[i] == '.' and (i == 0 or not py_expr[i-1].isalnum()): # Find the end of the selector (stops at operators or whitespace) j = i + 1 while j < len(py_expr) and (py_expr[j].isalnum() or py_expr[j] == '_'): j += 1 if j > i + 1: # Found a valid selector field = py_expr[i+1:j] # Use a safe access pattern that returns a default value based on context py_expr = py_expr[:i] + f"attributes.get('{field}')" + py_expr[j:] i = i + len(f"attributes.get('{field}')") else: i += 1 else: i += 1 # Convert not operator if needed py_expr = py_expr.replace('!', ' not ') try: # Custom evaluation that handles exceptions for missing fields # by returning False for the entire expression # Split the expression on logical operators parts = [] for op in [' and ', ' or ']: if op in py_expr: parts = py_expr.split(op) break if not parts: # No logical operators found parts = [py_expr] # Try to evaluate each part - if any part fails, # the whole expression should fail try: result = eval(py_expr, {"attributes": attributes}) return bool(result) except (TypeError, AttributeError): # This typically happens when trying to compare None with # numbers or other types, or when an attribute doesn't exist return False except Exception as e: print(f"Error evaluating filter expression '{filter_expr}' as '{py_expr}': {e}") return False except Exception as e: print(f"Error evaluating filter expression '{filter_expr}' as '{py_expr}': {e}") return False def safe_decode(self,item): return item.decode() if isinstance(item, bytes) else item def calculate_recall(self, redis_results, linear_results, k=10): """Calculate recall (percentage of correct results retrieved)""" redis_set = set(self.safe_decode(item) for item in redis_results) linear_set = set(item[0] for item in linear_results[:k]) if not linear_set: return 1.0 # If no linear results, consider it perfect recall intersection = redis_set.intersection(linear_set) return len(intersection) / len(linear_set) def test_recall_with_filter(self, filter_expr, ef=500, filter_ef=None): """Test recall for a given filter expression""" # Create query vector query_vec = generate_random_vector(self.dim) # First, get ground truth using linear scan linear_results = self.filter_linear_search( self.vectors, self.names, query_vec, filter_expr, self.attribute_map, k=50) # Calculate true selectivity from ground truth true_selectivity = len(linear_results) / len(self.names) if self.names else 0 # Perform Redis search with filter cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim] cmd_args.extend([str(x) for x in query_vec]) cmd_args.extend(['COUNT', 50, 'WITHSCORES', 'EF', ef, 'FILTER', filter_expr]) if filter_ef: cmd_args.extend(['FILTER-EF', filter_ef]) start_time = time.time() redis_results = self.redis.execute_command(*cmd_args) query_time = time.time() - start_time # Convert Redis results to dict redis_items = {} for i in range(0, len(redis_results), 2): key = redis_results[i].decode() if isinstance(redis_results[i], bytes) else redis_results[i] score = float(redis_results[i+1]) redis_items[key] = score # Calculate metrics recall = self.calculate_recall(redis_items.keys(), linear_results) selectivity = len(redis_items) / len(self.names) if redis_items else 0 # Compare against the true selectivity from linear scan assert abs(selectivity - true_selectivity) < 0.1, \ f"Redis selectivity {selectivity:.3f} differs significantly from ground truth {true_selectivity:.3f}" # We expect high recall for standard parameters if ef >= 500 and (filter_ef is None or filter_ef >= 1000): try: assert recall >= 0.7, \ f"Low recall {recall:.2f} for filter '{filter_expr}'" except AssertionError as e: # Get items found in each set redis_items_set = set(redis_items.keys()) linear_items_set = set(item[0] for item in linear_results) # Find items in each set only_in_redis = redis_items_set - linear_items_set only_in_linear = linear_items_set - redis_items_set in_both = redis_items_set & linear_items_set # Build comprehensive debug message debug = f"\nGround Truth: {len(linear_results)} matching items (total vectors: {len(self.vectors)})" debug += f"\nRedis Found: {len(redis_items)} items with FILTER-EF: {filter_ef or 'default'}" debug += f"\nItems in both sets: {len(in_both)} (recall: {recall:.4f})" debug += f"\nItems only in Redis: {len(only_in_redis)}" debug += f"\nItems only in Ground Truth: {len(only_in_linear)}" # Show some example items from each set with their scores if only_in_redis: debug += "\n\nTOP 5 ITEMS ONLY IN REDIS:" sorted_redis = sorted([(k, v) for k, v in redis_items.items()], key=lambda x: x[1], reverse=True) for i, (item, score) in enumerate(sorted_redis[:5]): if item in only_in_redis: debug += f"\n {i+1}. {item} (Score: {score:.4f})" # Show attribute that should match filter attr = self.attribute_map.get(item) if attr: debug += f" - Attrs: {attr.get('category', 'N/A')}, Price: {attr.get('price', 'N/A')}" if only_in_linear: debug += "\n\nTOP 5 ITEMS ONLY IN GROUND TRUTH:" for i, (item, score) in enumerate(linear_results[:5]): if item in only_in_linear: debug += f"\n {i+1}. {item} (Score: {score:.4f})" # Show attribute that should match filter attr = self.attribute_map.get(item) if attr: debug += f" - Attrs: {attr.get('category', 'N/A')}, Price: {attr.get('price', 'N/A')}" # Help identify parsing issues debug += "\n\nPARSING CHECK:" debug += f"\nRedis command: VSIM {self.test_key} VALUES {self.dim} [...] FILTER '{filter_expr}'" # Check for WITHSCORES handling issues if len(redis_results) > 0 and len(redis_results) % 2 == 0: debug += f"\nRedis returned {len(redis_results)} items (looks like item,score pairs)" debug += f"\nFirst few results: {redis_results[:4]}" # Check the filter implementation debug += "\n\nFILTER IMPLEMENTATION CHECK:" debug += f"\nFilter expression: '{filter_expr}'" debug += "\nSample attribute matches from attribute_map:" count_matching = 0 for i, (name, attrs) in enumerate(self.attribute_map.items()): if attrs and self.matches_filter(attrs, filter_expr): count_matching += 1 if i < 3: # Show first 3 matches debug += f"\n - {name}: {attrs}" debug += f"\nTotal items matching filter in attribute_map: {count_matching}" # Check if results array handling could be wrong debug += "\n\nRESULT ARRAYS CHECK:" if len(linear_results) >= 1: debug += f"\nlinear_results[0]: {linear_results[0]}" if isinstance(linear_results[0], tuple) and len(linear_results[0]) == 2: debug += " (correct tuple format: (name, score))" else: debug += " (UNEXPECTED FORMAT!)" # Debug sort order debug += "\n\nSORTING CHECK:" if len(linear_results) >= 2: debug += f"\nGround truth first item score: {linear_results[0][1]}" debug += f"\nGround truth second item score: {linear_results[1][1]}" debug += f"\nCorrectly sorted by similarity? {linear_results[0][1] >= linear_results[1][1]}" # Re-raise with detailed information raise AssertionError(str(e) + debug) return recall, selectivity, query_time, len(redis_items) def test(self): print(f"\nRunning comprehensive VSIM FILTER tests...") # Create a larger dataset for testing print(f"Creating dataset with {self.count} vectors and attributes...") self.vectors, self.names, self.attribute_map = self.create_vectors_with_attributes( self.test_key, self.count) # ==== 1. Recall and Precision Testing ==== print("Testing recall for various filters...") # Test basic filters with different selectivity results = {} results["category"] = self.test_recall_with_filter('.category == "electronics"') results["price_high"] = self.test_recall_with_filter('.price > 1000') results["in_stock"] = self.test_recall_with_filter('.in_stock') results["rating"] = self.test_recall_with_filter('.rating >= 4') results["complex1"] = self.test_recall_with_filter('.category == "electronics" and .price < 500') print("Filter | Recall | Selectivity | Time (ms) | Results") print("----------------------------------------------------") for name, (recall, selectivity, time_ms, count) in results.items(): print(f"{name:7} | {recall:.3f} | {selectivity:.3f} | {time_ms*1000:.1f} | {count}") # ==== 2. Filter Selectivity Performance ==== print("\nTesting filter selectivity performance...") # High selectivity (very few matches) high_sel_recall, _, high_sel_time, _ = self.test_recall_with_filter('.is_premium') # Medium selectivity med_sel_recall, _, med_sel_time, _ = self.test_recall_with_filter('.price > 100 and .price < 1000') # Low selectivity (many matches) low_sel_recall, _, low_sel_time, _ = self.test_recall_with_filter('.year > 2000') print(f"High selectivity recall: {high_sel_recall:.3f}, time: {high_sel_time*1000:.1f}ms") print(f"Med selectivity recall: {med_sel_recall:.3f}, time: {med_sel_time*1000:.1f}ms") print(f"Low selectivity recall: {low_sel_recall:.3f}, time: {low_sel_time*1000:.1f}ms") # ==== 3. FILTER-EF Parameter Testing ==== print("\nTesting FILTER-EF parameter...") # Test with different FILTER-EF values filter_expr = '.category == "electronics" and .price > 200' ef_values = [100, 500, 2000, 5000] print("FILTER-EF | Recall | Time (ms)") print("-----------------------------") for filter_ef in ef_values: recall, _, query_time, _ = self.test_recall_with_filter( filter_expr, ef=500, filter_ef=filter_ef) print(f"{filter_ef:9} | {recall:.3f} | {query_time*1000:.1f}") # Assert that higher FILTER-EF generally gives better recall low_ef_recall, _, _, _ = self.test_recall_with_filter(filter_expr, filter_ef=100) high_ef_recall, _, _, _ = self.test_recall_with_filter(filter_expr, filter_ef=5000) # This might not always be true due to randomness, but generally holds # We use a softer assertion to avoid flaky tests assert high_ef_recall >= low_ef_recall * 0.8, \ f"Higher FILTER-EF should generally give better recall: {high_ef_recall:.3f} vs {low_ef_recall:.3f}" # ==== 4. Complex Filter Expressions ==== print("\nTesting complex filter expressions...") # Test a variety of complex expressions complex_filters = [ '.price > 100 and (.category == "electronics" or .category == "furniture")', '(.rating > 4 and .in_stock) or (.price < 50 and .views > 1000)', '.category in ["electronics", "clothing"] and .price > 200 and .rating >= 3', '(.category == "electronics" and .subcategory == "phones") or (.category == "furniture" and .price > 1000)', '.year > 2010 and !(.price < 100) and .in_stock' ] print("Expression | Results | Time (ms)") print("-----------------------------") for i, expr in enumerate(complex_filters): try: _, _, query_time, result_count = self.test_recall_with_filter(expr) print(f"Complex {i+1} | {result_count:7} | {query_time*1000:.1f}") except Exception as e: print(f"Complex {i+1} | Error: {str(e)}") # ==== 5. Attribute Type Testing ==== print("\nTesting different attribute types...") type_filters = [ ('.price > 500', "Numeric"), ('.category == "books"', "String equality"), ('.in_stock', "Boolean"), ('.tags in ["sale", "new"]', "Array membership"), ('.rating * 2 > 8', "Arithmetic") ] for expr, type_name in type_filters: try: _, _, query_time, result_count = self.test_recall_with_filter(expr) print(f"{type_name:16} | {expr:30} | {result_count:5} results | {query_time*1000:.1f}ms") except Exception as e: print(f"{type_name:16} | {expr:30} | Error: {str(e)}") # ==== 6. Filter + Count Interaction ==== print("\nTesting COUNT parameter with filters...") filter_expr = '.category == "electronics"' counts = [5, 20, 100] for count in counts: query_vec = generate_random_vector(self.dim) cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim] cmd_args.extend([str(x) for x in query_vec]) cmd_args.extend(['COUNT', count, 'WITHSCORES', 'FILTER', filter_expr]) results = self.redis.execute_command(*cmd_args) result_count = len(results) // 2 # Divide by 2 because WITHSCORES returns pairs # We expect result count to be at most the requested count assert result_count <= count, f"Got {result_count} results with COUNT {count}" print(f"COUNT {count:3} | Got {result_count:3} results") # ==== 7. Edge Cases ==== print("\nTesting edge cases...") # Test with no matching items no_match_expr = '.category == "nonexistent_category"' results = self.redis.execute_command('VSIM', self.test_key, 'VALUES', self.dim, *[str(x) for x in generate_random_vector(self.dim)], 'FILTER', no_match_expr) assert len(results) == 0, f"Expected 0 results for non-matching filter, got {len(results)}" print(f"No matching items: {len(results)} results (expected 0)") # Test with invalid filter syntax try: self.redis.execute_command('VSIM', self.test_key, 'VALUES', self.dim, *[str(x) for x in generate_random_vector(self.dim)], 'FILTER', '.category === "books"') # Triple equals is invalid assert False, "Expected error for invalid filter syntax" except: print("Invalid filter syntax correctly raised an error") # Test with extremely long complex expression long_expr = ' and '.join([f'.rating > {i/10}' for i in range(10)]) try: results = self.redis.execute_command('VSIM', self.test_key, 'VALUES', self.dim, *[str(x) for x in generate_random_vector(self.dim)], 'FILTER', long_expr) print(f"Long expression: {len(results)} results") except Exception as e: print(f"Long expression error: {str(e)}") print("\nComprehensive VSIM FILTER tests completed successfully") class VSIMFilterSelectivityTest(TestCase): def getname(self): return "VSIM FILTER selectivity performance benchmark" def estimated_runtime(self): return 8 # This test might take up to 8 seconds def setup(self): super().setup() self.dim = 32 self.count = 10000 self.test_key = f"{self.test_key}:selectivity" # Use a different key def create_vector_with_age_attribute(self, name, age): """Create a vector with a specific age attribute""" vec = generate_random_vector(self.dim) vec_bytes = struct.pack(f'{self.dim}f', *vec) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, name) self.redis.execute_command('VSETATTR', self.test_key, name, json.dumps({"age": age})) def test(self): print("\nRunning VSIM FILTER selectivity benchmark...") # Create a dataset where we control the exact selectivity print(f"Creating controlled dataset with {self.count} vectors...") # Create vectors with age attributes from 1 to 100 for i in range(self.count): age = (i % 100) + 1 # Ages from 1 to 100 name = f"{self.test_key}:item:{i}" self.create_vector_with_age_attribute(name, age) # Create a query vector query_vec = generate_random_vector(self.dim) # Test filters with different selectivities selectivities = [0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.99] results = [] print("\nSelectivity | Filter | Results | Time (ms)") print("--------------------------------------------------") for target_selectivity in selectivities: # Calculate age threshold for desired selectivity # For example, age <= 10 gives 10% selectivity age_threshold = int(target_selectivity * 100) filter_expr = f'.age <= {age_threshold}' # Run query and measure time start_time = time.time() cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim] cmd_args.extend([str(x) for x in query_vec]) cmd_args.extend(['COUNT', 100, 'FILTER', filter_expr]) results = self.redis.execute_command(*cmd_args) query_time = time.time() - start_time actual_selectivity = len(results) / min(100, int(target_selectivity * self.count)) print(f"{target_selectivity:.2f} | {filter_expr:15} | {len(results):7} | {query_time*1000:.1f}") # Add assertion to ensure reasonable performance for different selectivities # For very selective queries (1%), we might need more exploration if target_selectivity <= 0.05: # For very selective queries, ensure we can find some results assert len(results) > 0, f"No results found for {filter_expr}" else: # For less selective queries, performance should be reasonable assert query_time < 1.0, f"Query too slow: {query_time:.3f}s for {filter_expr}" print("\nSelectivity benchmark completed successfully") class VSIMFilterComparisonTest(TestCase): def getname(self): return "VSIM FILTER EF parameter comparison" def estimated_runtime(self): return 8 # This test might take up to 8 seconds def setup(self): super().setup() self.dim = 32 self.count = 5000 self.test_key = f"{self.test_key}:efparams" # Use a different key def create_dataset(self): """Create a dataset with specific attribute patterns for testing FILTER-EF""" vectors = [] names = [] # Create vectors with category and quality score attributes for i in range(self.count): vec = generate_random_vector(self.dim) name = f"{self.test_key}:item:{i}" # Add vector to Redis vec_bytes = struct.pack(f'{self.dim}f', *vec) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, name) # Create attributes - we want a very selective filter # Only 2% of items have category=premium AND quality>90 category = "premium" if random.random() < 0.1 else random.choice(["standard", "economy", "basic"]) quality = random.randint(1, 100) attrs = { "id": i, "category": category, "quality": quality } self.redis.execute_command('VSETATTR', self.test_key, name, json.dumps(attrs)) vectors.append(vec) names.append(name) return vectors, names def test(self): print("\nRunning VSIM FILTER-EF parameter comparison...") # Create dataset vectors, names = self.create_dataset() # Create a selective filter that matches ~2% of items filter_expr = '.category == "premium" and .quality > 90' # Create query vector query_vec = generate_random_vector(self.dim) # Test different FILTER-EF values ef_values = [50, 100, 500, 1000, 5000] results = [] print("\nFILTER-EF | Results | Time (ms) | Notes") print("---------------------------------------") baseline_count = None for ef in ef_values: # Run query and measure time start_time = time.time() cmd_args = ['VSIM', self.test_key, 'VALUES', self.dim] cmd_args.extend([str(x) for x in query_vec]) cmd_args.extend(['COUNT', 100, 'FILTER', filter_expr, 'FILTER-EF', ef]) query_results = self.redis.execute_command(*cmd_args) query_time = time.time() - start_time # Set baseline for comparison if baseline_count is None: baseline_count = len(query_results) recall_rate = len(query_results) / max(1, baseline_count) if baseline_count > 0 else 1.0 notes = "" if ef == 5000: notes = "Baseline" elif recall_rate < 0.5: notes = "Low recall!" print(f"{ef:9} | {len(query_results):7} | {query_time*1000:.1f} | {notes}") results.append((ef, len(query_results), query_time)) # If we have enough results at highest EF, check that recall improves with higher EF if results[-1][1] >= 5: # At least 5 results for highest EF # Extract result counts result_counts = [r[1] for r in results] # The last result (highest EF) should typically find more results than the first (lowest EF) # but we use a soft assertion to avoid flaky tests assert result_counts[-1] >= result_counts[0], \ f"Higher FILTER-EF should find at least as many results: {result_counts[-1]} vs {result_counts[0]}" print("\nFILTER-EF parameter comparison completed successfully") redis-8.0.2/modules/vector-sets/tests/large_scale.py000066400000000000000000000040571501533116600225410ustar00rootroot00000000000000from test import TestCase, fill_redis_with_vectors, generate_random_vector import random class LargeScale(TestCase): def getname(self): return "Large Scale Comparison" def estimated_runtime(self): return 10 def test(self): dim = 300 count = 20000 k = 50 # Fill Redis and get reference data for comparison random.seed(42) # Make test deterministic data = fill_redis_with_vectors(self.redis, self.test_key, count, dim) # Generate query vector query_vec = generate_random_vector(dim) # Get results from Redis with good exploration factor redis_raw = self.redis.execute_command('VSIM', self.test_key, 'VALUES', dim, *[str(x) for x in query_vec], 'COUNT', k, 'WITHSCORES', 'EF', 500) # Convert Redis results to dict redis_results = {} for i in range(0, len(redis_raw), 2): key = redis_raw[i].decode() score = float(redis_raw[i+1]) redis_results[key] = score # Get results from linear scan linear_results = data.find_k_nearest(query_vec, k) linear_items = {name: score for name, score in linear_results} # Compare overlap redis_set = set(redis_results.keys()) linear_set = set(linear_items.keys()) overlap = len(redis_set & linear_set) # If test fails, print comparison for debugging if overlap < k * 0.7: data.print_comparison({'items': redis_results, 'query_vector': query_vec}, k) assert overlap >= k * 0.7, \ f"Expected at least 70% overlap in top {k} results, got {overlap/k*100:.1f}%" # Verify scores for common items for item in redis_set & linear_set: redis_score = redis_results[item] linear_score = linear_items[item] assert abs(redis_score - linear_score) < 0.01, \ f"Score mismatch for {item}: Redis={redis_score:.3f} Linear={linear_score:.3f}" redis-8.0.2/modules/vector-sets/tests/memory_usage.py000066400000000000000000000032621501533116600227710ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct class MemoryUsageTest(TestCase): def getname(self): return "[regression] MEMORY USAGE with attributes" def test(self): # Generate random vectors vec1 = generate_random_vector(4) vec2 = generate_random_vector(4) vec_bytes1 = struct.pack('4f', *vec1) vec_bytes2 = struct.pack('4f', *vec2) # Add vectors to the key, one with attribute, one without self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes1, f'{self.test_key}:item:1') self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes2, f'{self.test_key}:item:2', 'SETATTR', '{"color":"red"}') # Get memory usage for the key try: memory_usage = self.redis.execute_command('MEMORY', 'USAGE', self.test_key) # If we got here without exception, the command worked assert memory_usage > 0, "MEMORY USAGE should return a positive value" # Add more attributes to increase complexity self.redis.execute_command('VSETATTR', self.test_key, f'{self.test_key}:item:1', '{"color":"blue","size":10}') # Check memory usage again new_memory_usage = self.redis.execute_command('MEMORY', 'USAGE', self.test_key) assert new_memory_usage > 0, "MEMORY USAGE should still return a positive value after setting attributes" # Memory usage should be higher after adding attributes assert new_memory_usage > memory_usage, "Memory usage increase after adding attributes" except Exception as e: raise AssertionError(f"MEMORY USAGE command failed: {str(e)}") redis-8.0.2/modules/vector-sets/tests/node_update.py000066400000000000000000000072221501533116600225640ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct import math import random class VectorUpdateAndClusters(TestCase): def getname(self): return "VADD vector update with cluster relocation" def estimated_runtime(self): return 2.0 # Should take around 2 seconds def generate_cluster_vector(self, base_vec, noise=0.1): """Generate a vector that's similar to base_vec with some noise.""" vec = [x + random.gauss(0, noise) for x in base_vec] # Normalize norm = math.sqrt(sum(x*x for x in vec)) return [x/norm for x in vec] def test(self): dim = 128 vectors_per_cluster = 5000 # Create two very different base vectors for our clusters cluster1_base = generate_random_vector(dim) cluster2_base = [-x for x in cluster1_base] # Opposite direction # Add vectors from first cluster for i in range(vectors_per_cluster): vec = self.generate_cluster_vector(cluster1_base) vec_bytes = struct.pack(f'{dim}f', *vec) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, f'{self.test_key}:cluster1:{i}') # Add vectors from second cluster for i in range(vectors_per_cluster): vec = self.generate_cluster_vector(cluster2_base) vec_bytes = struct.pack(f'{dim}f', *vec) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, f'{self.test_key}:cluster2:{i}') # Pick a test vector from cluster1 test_key = f'{self.test_key}:cluster1:0' # Verify it's in cluster1 using VSIM initial_vec = self.generate_cluster_vector(cluster1_base) results = self.redis.execute_command('VSIM', self.test_key, 'VALUES', dim, *[str(x) for x in initial_vec], 'COUNT', 100, 'WITHSCORES') # Count how many cluster1 items are in top results cluster1_count = sum(1 for i in range(0, len(results), 2) if b'cluster1' in results[i]) assert cluster1_count > 80, "Initial clustering check failed" # Now update the test vector to be in cluster2 new_vec = self.generate_cluster_vector(cluster2_base, noise=0.05) vec_bytes = struct.pack(f'{dim}f', *new_vec) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, test_key) # Verify the embedding was actually updated using VEMB emb_result = self.redis.execute_command('VEMB', self.test_key, test_key) updated_vec = [float(x) for x in emb_result] # Verify updated vector matches what we inserted dot_product = sum(a*b for a,b in zip(updated_vec, new_vec)) similarity = dot_product / (math.sqrt(sum(x*x for x in updated_vec)) * math.sqrt(sum(x*x for x in new_vec))) assert similarity > 0.9, "Vector was not properly updated" # Verify it's now in cluster2 using VSIM results = self.redis.execute_command('VSIM', self.test_key, 'VALUES', dim, *[str(x) for x in cluster2_base], 'COUNT', 100, 'WITHSCORES') # Verify our updated vector is among top results found = False for i in range(0, len(results), 2): if results[i].decode() == test_key: found = True similarity = float(results[i+1]) assert similarity > 0.80, f"Updated vector has low similarity: {similarity}" break assert found, "Updated vector not found in cluster2 proximity" redis-8.0.2/modules/vector-sets/tests/persistence.py000066400000000000000000000067141501533116600226260ustar00rootroot00000000000000from test import TestCase, fill_redis_with_vectors, generate_random_vector import random class HNSWPersistence(TestCase): def getname(self): return "HNSW Persistence" def estimated_runtime(self): return 30 def _verify_results(self, key, dim, query_vec, reduced_dim=None): """Run a query and return results dict""" k = 10 args = ['VSIM', key] if reduced_dim: args.extend(['VALUES', dim]) args.extend([str(x) for x in query_vec]) else: args.extend(['VALUES', dim]) args.extend([str(x) for x in query_vec]) args.extend(['COUNT', k, 'WITHSCORES']) results = self.redis.execute_command(*args) results_dict = {} for i in range(0, len(results), 2): key = results[i].decode() score = float(results[i+1]) results_dict[key] = score return results_dict def test(self): # Setup dimensions dim = 128 reduced_dim = 32 count = 5000 random.seed(42) # Create two datasets - one normal and one with dimension reduction normal_data = fill_redis_with_vectors(self.redis, f"{self.test_key}:normal", count, dim) projected_data = fill_redis_with_vectors(self.redis, f"{self.test_key}:projected", count, dim, reduced_dim) # Generate query vectors we'll use before and after reload query_vec_normal = generate_random_vector(dim) query_vec_projected = generate_random_vector(dim) # Get initial results for both sets initial_normal = self._verify_results(f"{self.test_key}:normal", dim, query_vec_normal) initial_projected = self._verify_results(f"{self.test_key}:projected", dim, query_vec_projected, reduced_dim) # Force Redis to save and reload the dataset self.redis.execute_command('DEBUG', 'RELOAD') # Verify results after reload reloaded_normal = self._verify_results(f"{self.test_key}:normal", dim, query_vec_normal) reloaded_projected = self._verify_results(f"{self.test_key}:projected", dim, query_vec_projected, reduced_dim) # Verify normal vectors results assert len(initial_normal) == len(reloaded_normal), \ "Normal vectors: Result count mismatch before/after reload" for key in initial_normal: assert key in reloaded_normal, f"Normal vectors: Missing item after reload: {key}" assert abs(initial_normal[key] - reloaded_normal[key]) < 0.0001, \ f"Normal vectors: Score mismatch for {key}: " + \ f"before={initial_normal[key]:.6f}, after={reloaded_normal[key]:.6f}" # Verify projected vectors results assert len(initial_projected) == len(reloaded_projected), \ "Projected vectors: Result count mismatch before/after reload" for key in initial_projected: assert key in reloaded_projected, \ f"Projected vectors: Missing item after reload: {key}" assert abs(initial_projected[key] - reloaded_projected[key]) < 0.0001, \ f"Projected vectors: Score mismatch for {key}: " + \ f"before={initial_projected[key]:.6f}, after={reloaded_projected[key]:.6f}" redis-8.0.2/modules/vector-sets/tests/reduce.py000066400000000000000000000063231501533116600215450ustar00rootroot00000000000000from test import TestCase, fill_redis_with_vectors, generate_random_vector class Reduce(TestCase): def getname(self): return "Dimension Reduction" def estimated_runtime(self): return 0.2 def test(self): original_dim = 100 reduced_dim = 80 count = 1000 k = 50 # Number of nearest neighbors to check # Fill Redis with vectors using REDUCE and get reference data data = fill_redis_with_vectors(self.redis, self.test_key, count, original_dim, reduced_dim) # Verify dimension is reduced dim = self.redis.execute_command('VDIM', self.test_key) assert dim == reduced_dim, f"Expected dimension {reduced_dim}, got {dim}" # Generate query vector and get nearest neighbors using Redis query_vec = generate_random_vector(original_dim) redis_raw = self.redis.execute_command('VSIM', self.test_key, 'VALUES', original_dim, *[str(x) for x in query_vec], 'COUNT', k, 'WITHSCORES') # Convert Redis results to dict redis_results = {} for i in range(0, len(redis_raw), 2): key = redis_raw[i].decode() score = float(redis_raw[i+1]) redis_results[key] = score # Get results from linear scan with original vectors linear_results = data.find_k_nearest(query_vec, k) linear_items = {name: score for name, score in linear_results} # Compare overlap between reduced and non-reduced results redis_set = set(redis_results.keys()) linear_set = set(linear_items.keys()) overlap = len(redis_set & linear_set) overlap_ratio = overlap / k # With random projection, we expect some loss of accuracy but should # maintain at least some similarity structure. # Note that gaussian distribution is the worse with this test, so # in real world practice, things will be better. min_expected_overlap = 0.1 # At least 10% overlap in top-k assert overlap_ratio >= min_expected_overlap, \ f"Dimension reduction lost too much structure. Only {overlap_ratio*100:.1f}% overlap in top {k}" # For items that appear in both results, scores should be reasonably correlated common_items = redis_set & linear_set for item in common_items: redis_score = redis_results[item] linear_score = linear_items[item] # Allow for some deviation due to dimensionality reduction assert abs(redis_score - linear_score) < 0.2, \ f"Score mismatch too high for {item}: Redis={redis_score:.3f} Linear={linear_score:.3f}" # If test fails, print comparison for debugging if overlap_ratio < min_expected_overlap: print("\nLow overlap in results. Details:") print("\nTop results from linear scan (original vectors):") for name, score in linear_results: print(f"{name}: {score:.3f}") print("\nTop results from Redis (reduced vectors):") for item, score in sorted(redis_results.items(), key=lambda x: x[1], reverse=True): print(f"{item}: {score:.3f}") redis-8.0.2/modules/vector-sets/tests/replication.py000066400000000000000000000077421501533116600226150ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct import random import time class ComprehensiveReplicationTest(TestCase): def getname(self): return "Comprehensive Replication Test with mixed operations" def estimated_runtime(self): # This test will take longer than the default 100ms return 20.0 # 20 seconds estimate def test(self): # Setup replication between primary and replica assert self.setup_replication(), "Failed to setup replication" # Test parameters num_vectors = 5000 vector_dim = 8 delete_probability = 0.1 cas_probability = 0.3 # Keep track of added items for potential deletion added_items = [] # Add vectors and occasionally delete for i in range(num_vectors): # Generate a random vector vec = generate_random_vector(vector_dim) vec_bytes = struct.pack(f'{vector_dim}f', *vec) item_name = f"{self.test_key}:item:{i}" # Decide whether to use CAS or not use_cas = random.random() < cas_probability if use_cas and added_items: # Get an existing item for CAS reference (if available) cas_item = random.choice(added_items) try: # Add with CAS result = self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, item_name, 'CAS') # Only add to our list if actually added (CAS might fail) if result == 1: added_items.append(item_name) except Exception as e: print(f" CAS VADD failed: {e}") else: try: # Add without CAS result = self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, item_name) # Only add to our list if actually added if result == 1: added_items.append(item_name) except Exception as e: print(f" VADD failed: {e}") # Randomly delete items (with 10% probability) if random.random() < delete_probability and added_items: try: # Select a random item to delete item_to_delete = random.choice(added_items) # Delete the item using VREM (not VDEL) self.redis.execute_command('VREM', self.test_key, item_to_delete) # Remove from our list added_items.remove(item_to_delete) except Exception as e: print(f" VREM failed: {e}") # Allow time for replication to complete time.sleep(2.0) # Verify final VCARD matches primary_card = self.redis.execute_command('VCARD', self.test_key) replica_card = self.replica.execute_command('VCARD', self.test_key) assert primary_card == replica_card, f"Final VCARD mismatch: primary={primary_card}, replica={replica_card}" # Verify VDIM matches primary_dim = self.redis.execute_command('VDIM', self.test_key) replica_dim = self.replica.execute_command('VDIM', self.test_key) assert primary_dim == replica_dim, f"VDIM mismatch: primary={primary_dim}, replica={replica_dim}" # Verify digests match using DEBUG DIGEST primary_digest = self.redis.execute_command('DEBUG', 'DIGEST-VALUE', self.test_key) replica_digest = self.replica.execute_command('DEBUG', 'DIGEST-VALUE', self.test_key) assert primary_digest == replica_digest, f"Digest mismatch: primary={primary_digest}, replica={replica_digest}" # Print summary print(f"\n Added and maintained {len(added_items)} vectors with dimension {vector_dim}") print(f" Final vector count: {primary_card}") print(f" Final digest: {primary_digest[0].decode()}") redis-8.0.2/modules/vector-sets/tests/vadd_cas.py000066400000000000000000000072251501533116600220440ustar00rootroot00000000000000from test import TestCase, generate_random_vector import threading import struct import math import time import random from typing import List, Dict class ConcurrentCASTest(TestCase): def getname(self): return "Concurrent VADD with CAS" def estimated_runtime(self): return 1.5 def worker(self, vectors: List[List[float]], start_idx: int, end_idx: int, dim: int, results: Dict[str, bool]): """Worker thread that adds a subset of vectors using VADD CAS""" for i in range(start_idx, end_idx): vec = vectors[i] name = f"{self.test_key}:item:{i}" vec_bytes = struct.pack(f'{dim}f', *vec) # Try to add the vector with CAS try: result = self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, name, 'CAS') results[name] = (result == 1) # Store if it was actually added except Exception as e: results[name] = False print(f"Error adding {name}: {e}") def verify_vector_similarity(self, vec1: List[float], vec2: List[float]) -> float: """Calculate cosine similarity between two vectors""" dot_product = sum(a*b for a,b in zip(vec1, vec2)) norm1 = math.sqrt(sum(x*x for x in vec1)) norm2 = math.sqrt(sum(x*x for x in vec2)) return dot_product / (norm1 * norm2) if norm1 > 0 and norm2 > 0 else 0 def test(self): # Test parameters dim = 128 total_vectors = 5000 num_threads = 8 vectors_per_thread = total_vectors // num_threads # Generate all vectors upfront random.seed(42) # For reproducibility vectors = [generate_random_vector(dim) for _ in range(total_vectors)] # Prepare threads and results dictionary threads = [] results = {} # Will store success/failure for each vector # Launch threads for i in range(num_threads): start_idx = i * vectors_per_thread end_idx = start_idx + vectors_per_thread if i < num_threads-1 else total_vectors thread = threading.Thread(target=self.worker, args=(vectors, start_idx, end_idx, dim, results)) threads.append(thread) thread.start() # Wait for all threads to complete for thread in threads: thread.join() # Verify cardinality card = self.redis.execute_command('VCARD', self.test_key) assert card == total_vectors, \ f"Expected {total_vectors} elements, but found {card}" # Verify each vector num_verified = 0 for i in range(total_vectors): name = f"{self.test_key}:item:{i}" # Verify the item was successfully added assert results[name], f"Vector {name} was not successfully added" # Get the stored vector stored_vec_raw = self.redis.execute_command('VEMB', self.test_key, name) stored_vec = [float(x) for x in stored_vec_raw] # Verify vector dimensions assert len(stored_vec) == dim, \ f"Stored vector dimension mismatch for {name}: {len(stored_vec)} != {dim}" # Calculate similarity with original vector similarity = self.verify_vector_similarity(vectors[i], stored_vec) assert similarity > 0.99, \ f"Low similarity ({similarity}) for {name}" num_verified += 1 # Final verification assert num_verified == total_vectors, \ f"Only verified {num_verified} out of {total_vectors} vectors" redis-8.0.2/modules/vector-sets/tests/vemb.py000066400000000000000000000031621501533116600212250ustar00rootroot00000000000000from test import TestCase import struct import math class VEMB(TestCase): def getname(self): return "VEMB Command" def test(self): dim = 4 # Add same vector in both formats vec = [1, 0, 0, 0] norm = math.sqrt(sum(x*x for x in vec)) vec = [x/norm for x in vec] # Normalize the vector # Add using FP32 vec_bytes = struct.pack(f'{dim}f', *vec) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes, f'{self.test_key}:item:1') # Add using VALUES self.redis.execute_command('VADD', self.test_key, 'VALUES', dim, *[str(x) for x in vec], f'{self.test_key}:item:2') # Get both back with VEMB result1 = self.redis.execute_command('VEMB', self.test_key, f'{self.test_key}:item:1') result2 = self.redis.execute_command('VEMB', self.test_key, f'{self.test_key}:item:2') retrieved_vec1 = [float(x) for x in result1] retrieved_vec2 = [float(x) for x in result2] # Compare both vectors with original (allow for small quantization errors) for i in range(dim): assert abs(vec[i] - retrieved_vec1[i]) < 0.01, \ f"FP32 vector component {i} mismatch: expected {vec[i]}, got {retrieved_vec1[i]}" assert abs(vec[i] - retrieved_vec2[i]) < 0.01, \ f"VALUES vector component {i} mismatch: expected {vec[i]}, got {retrieved_vec2[i]}" # Test non-existent item result = self.redis.execute_command('VEMB', self.test_key, 'nonexistent') assert result is None, "Non-existent item should return nil" redis-8.0.2/modules/vector-sets/tests/vismember.py000066400000000000000000000042311501533116600222630ustar00rootroot00000000000000from test import TestCase, generate_random_vector import struct class BasicVISMEMBER(TestCase): def getname(self): return "VISMEMBER basic functionality" def test(self): # Add multiple vectors to the vector set vec1 = generate_random_vector(4) vec2 = generate_random_vector(4) vec_bytes1 = struct.pack('4f', *vec1) vec_bytes2 = struct.pack('4f', *vec2) # Create item keys item1 = f'{self.test_key}:item:1' item2 = f'{self.test_key}:item:2' nonexistent_item = f'{self.test_key}:item:nonexistent' # Add the vectors self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes1, item1) self.redis.execute_command('VADD', self.test_key, 'FP32', vec_bytes2, item2) # Test VISMEMBER with existing elements result1 = self.redis.execute_command('VISMEMBER', self.test_key, item1) assert result1 == 1, f"VISMEMBER should return 1 for existing item, got {result1}" result2 = self.redis.execute_command('VISMEMBER', self.test_key, item2) assert result2 == 1, f"VISMEMBER should return 1 for existing item, got {result2}" # Test VISMEMBER with non-existent element result3 = self.redis.execute_command('VISMEMBER', self.test_key, nonexistent_item) assert result3 == 0, f"VISMEMBER should return 0 for non-existent item, got {result3}" # Test VISMEMBER with non-existent key nonexistent_key = f'{self.test_key}_nonexistent' result4 = self.redis.execute_command('VISMEMBER', nonexistent_key, item1) assert result4 == 0, f"VISMEMBER should return 0 for non-existent key, got {result4}" # Test VISMEMBER after removing an element self.redis.execute_command('VREM', self.test_key, item1) result5 = self.redis.execute_command('VISMEMBER', self.test_key, item1) assert result5 == 0, f"VISMEMBER should return 0 after element removal, got {result5}" # Verify item2 still exists result6 = self.redis.execute_command('VISMEMBER', self.test_key, item2) assert result6 == 1, f"VISMEMBER should still return 1 for remaining item, got {result6}" redis-8.0.2/modules/vector-sets/tests/vrandmember.py000066400000000000000000000053331501533116600226000ustar00rootroot00000000000000from test import TestCase, generate_random_vector, fill_redis_with_vectors import struct class VRANDMEMBERTest(TestCase): def getname(self): return "VRANDMEMBER basic functionality" def test(self): # Test with empty key result = self.redis.execute_command('VRANDMEMBER', self.test_key) assert result is None, "VRANDMEMBER on non-existent key should return NULL" result = self.redis.execute_command('VRANDMEMBER', self.test_key, 5) assert isinstance(result, list) and len(result) == 0, "VRANDMEMBER with count on non-existent key should return empty array" # Fill with vectors dim = 4 count = 100 data = fill_redis_with_vectors(self.redis, self.test_key, count, dim) # Test single random member result = self.redis.execute_command('VRANDMEMBER', self.test_key) assert result is not None, "VRANDMEMBER should return a random member" assert result.decode() in data.names, "Random member should be in the set" # Test multiple unique members (positive count) positive_count = 10 result = self.redis.execute_command('VRANDMEMBER', self.test_key, positive_count) assert isinstance(result, list), "VRANDMEMBER with positive count should return an array" assert len(result) == positive_count, f"Should return {positive_count} members" # Check for uniqueness decoded_results = [r.decode() for r in result] assert len(decoded_results) == len(set(decoded_results)), "Results should be unique with positive count" for item in decoded_results: assert item in data.names, "All returned items should be in the set" # Test more members than in the set result = self.redis.execute_command('VRANDMEMBER', self.test_key, count + 10) assert len(result) == count, "Should return only the available members when asking for more than exist" # Test with duplicates (negative count) negative_count = -20 result = self.redis.execute_command('VRANDMEMBER', self.test_key, negative_count) assert isinstance(result, list), "VRANDMEMBER with negative count should return an array" assert len(result) == abs(negative_count), f"Should return {abs(negative_count)} members" # Check that all returned elements are valid decoded_results = [r.decode() for r in result] for item in decoded_results: assert item in data.names, "All returned items should be in the set" # Test with count = 0 (edge case) result = self.redis.execute_command('VRANDMEMBER', self.test_key, 0) assert isinstance(result, list) and len(result) == 0, "VRANDMEMBER with count=0 should return empty array" redis-8.0.2/modules/vector-sets/vset.c000066400000000000000000002403611501533116600177110ustar00rootroot00000000000000/* Redis implementation for vector sets. The data structure itself * is implemented in hnsw.c. * * Copyright (c) 2009-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). * Originally authored by: Salvatore Sanfilippo. * * ======================== Understand threading model ========================= * This code implements threaded operarations for two of the commands: * * 1. VSIM, by default. * 2. VADD, if the CAS option is specified. * * Note that even if the second operation, VADD, is a write operation, only * the neighbors collection for the new node is performed in a thread: then, * the actual insert is performed in the reply callback VADD_CASReply(), * which is executed in the main thread. * * Threaded operations need us to protect various operations with mutexes, * even if a certain degree of protection is already provided by the HNSW * library. Here are a few very important things about this implementation * and the way locking is performed. * * 1. All the write operations are performed in the main Redis thread: * this also include VADD_CASReply() callback, that is called by Redis * internals only in the context of the main thread. However the HNSW * library allows background threads in hnsw_search() (VSIM) to modify * nodes metadata to speedup search (to understand if a node was already * visited), but this only happens after acquiring a specific lock * for a given "read slot". * * 2. We use a global lock for each Vector Set object, called "in_use". This * lock is a read-write lock, and is acquired in read mode by all the * threads that perform reads in the background. It is only acquired in * write mode by vectorSetWaitAllBackgroundClients(): the function acquires * the lock and immediately releases it, with the effect of waiting all the * background threads still running from ending their execution. * * Note that no thread can be spawned, since we only call * vectorSetWaitAllBackgroundClients() from the main Redis thread, that * is also the only thread spawning other threads. * * vectorSetWaitAllBackgroundClients() is used in two ways: * A) When we need to delete a vector set because of (DEL) or other * operations destroying the object, we need to wait that all the * background threads working with this object finished their work. * B) When we modify the HNSW nodes bypassing the normal locking * provided by the HNSW library. This only happens when we update * an existing node attribute so far, in VSETATTR and when we call * VADD to update a node with the SETATTR option. * * 3. Often during read operations performed by Redis commands in the * main thread (VCARD, VEMB, VRANDMEMBER, ...) we don't acquire any * lock at all. The commands run in the main Redis thread, we can only * have, at the same time, background reads against the same data * structure. Note that VSIM_thread() and VADD_thread() still modify the * read slot metadata, that is node->visited_epoch[slot], but as long as * our read commands running in the main thread don't need to use * hnsw_search() or other HNSW functions using the visited epochs slots * we are safe. * * 4. There is a race from the moment we create a thread, passing the * vector set object, to the moment the thread can actually lock the * result win the in_use_lock mutex: as the thread starts, in the meanwhile * a DEL/expire could trigger and remove the object. For this reason * we use an atomic counter that protects our object for this small * time in vectorSetWaitAllBackgroundClients(). This prevents removal * of objects that are about to be taken by threads. * * Note that other competing solutions could be used to fix the problem * but have their set of issues, however they are worth documenting here * and evaluating in the future: * * A. Using a conditional variable we could "wait" for the thread to * acquire the lock. However this means waiting before returning * to the event loop, and would make the command execution slower. * B. We could use again an atomic variable, like we did, but this time * as a refcount for the object, with a vsetAcquire() vsetRelease(). * In this case, the command could retain the object in the main thread * before starting the thread, and the thread, after the work is done, * could release it. This way sometimes the object would be freed by * the thread, and it's while now can be safe to do the kind of resource * deallocation that vectorSetReleaseObject() does, given that the * Redis Modules API is not always thread safe this solution may not * be future-proof. However there is to evaluate it better in the * future. * C. We could use the "B" solution but instead of freeing the object * in the thread, in this specific case we could just put it into a * list and defer it for later freeing (for instance in the reply * callback), so that the object is always freed in the main thread. * This would require a list of objects to free. * * However the current solution only disadvantage is the potential busy * loop, but this busy loop in practical terms will almost never do * much: to trigger it, a number of circumnstances must happen: deleting * Vector Set keys while using them, hitting the small window needed to * start the thread and read-lock the mutex. */ #define _DEFAULT_SOURCE #define _USE_MATH_DEFINES #define _POSIX_C_SOURCE 200809L #include "../../src/redismodule.h" #include #include #include #include #include #include #include #include #include #include "hnsw.h" // We inline directly the expression implementation here so that building // the module is trivial. #include "expr.c" static RedisModuleType *VectorSetType; static uint64_t VectorSetTypeNextId = 0; // Default EF value if not specified during creation. #define VSET_DEFAULT_C_EF 200 // Default EF value if not specified during search. #define VSET_DEFAULT_SEARCH_EF 100 // Default num elements returned by VSIM. #define VSET_DEFAULT_COUNT 10 /* ========================== Internal data structure ====================== */ /* Our abstract data type needs a dual representation similar to Redis * sorted set: the proximity graph, and also a element -> graph-node map * that will allow us to perform deletions and other operations that have * as input the element itself. */ struct vsetObject { HNSW *hnsw; // Proximity graph. RedisModuleDict *dict; // Element -> node mapping. float *proj_matrix; // Random projection matrix, NULL if no projection uint32_t proj_input_size; // Input dimension after projection. // Output dimension is implicit in // hnsw->vector_dim. pthread_rwlock_t in_use_lock; // Lock needed to destroy the object safely. uint64_t id; // Unique ID used by threaded VADD to know the // object is still the same. uint64_t numattribs; // Number of nodes associated with an attribute. atomic_int thread_creation_pending; // Number of threads that are currently // pending to lock the object. }; /* Each node has two associated values: the associated string (the item * in the set) and potentially a JSON string, that is, the attributes, used * for hybrid search with the VSIM FILTER option. */ struct vsetNodeVal { RedisModuleString *item; RedisModuleString *attrib; }; /* Count the number of set bits in an integer (population count/Hamming weight). * This is a portable implementation that doesn't rely on compiler * extensions. */ static inline uint32_t bit_count(uint32_t n) { uint32_t count = 0; while (n) { count += n & 1; n >>= 1; } return count; } /* Create a Hadamard-based projection matrix for dimensionality reduction. * Uses {-1, +1} entries with a pattern based on bit operations. * The pattern is matrix[i][j] = (i & j) % 2 == 0 ? 1 : -1 * Matrix is scaled by 1/sqrt(input_dim) for normalization. * Returns NULL on allocation failure. * * Note that compared to other approaches (random gaussian weights), what * we have here is deterministic, it means that our replicas will have * the same set of weights. Also this approach seems to work much better * in practice, and the distances between elements are better guaranteed. * * Note that we still save the projection matrix in the RDB file, because * in the future we may change the weights generation, and we want everything * to be backward compatible. */ float *createProjectionMatrix(uint32_t input_dim, uint32_t output_dim) { float *matrix = RedisModule_Alloc(sizeof(float) * input_dim * output_dim); /* Scale factor to normalize the projection. */ const float scale = 1.0f / sqrt(input_dim); /* Fill the matrix using Hadamard pattern. */ for (uint32_t i = 0; i < output_dim; i++) { for (uint32_t j = 0; j < input_dim; j++) { /* Calculate position in the flattened matrix. */ uint32_t pos = i * input_dim + j; /* Hadamard pattern: use bit operations to determine sign * If the count of 1-bits in the bitwise AND of i and j is even, * the value is 1, otherwise -1. */ int value = (bit_count(i & j) % 2 == 0) ? 1 : -1; /* Store the scaled value. */ matrix[pos] = value * scale; } } return matrix; } /* Apply random projection to input vector. Returns new allocated vector. */ float *applyProjection(const float *input, const float *proj_matrix, uint32_t input_dim, uint32_t output_dim) { float *output = RedisModule_Alloc(sizeof(float) * output_dim); for (uint32_t i = 0; i < output_dim; i++) { const float *row = &proj_matrix[i * input_dim]; float sum = 0.0f; for (uint32_t j = 0; j < input_dim; j++) { sum += row[j] * input[j]; } output[i] = sum; } return output; } /* Create the vector as HNSW+Dictionary combined data structure. */ struct vsetObject *createVectorSetObject(unsigned int dim, uint32_t quant_type, uint32_t hnsw_M) { struct vsetObject *o; o = RedisModule_Alloc(sizeof(*o)); o->id = VectorSetTypeNextId++; o->hnsw = hnsw_new(dim,quant_type,hnsw_M); if (!o->hnsw) { // May fail because of mutex creation. RedisModule_Free(o); return NULL; } o->dict = RedisModule_CreateDict(NULL); o->proj_matrix = NULL; o->proj_input_size = 0; o->numattribs = 0; o->thread_creation_pending = 0; RedisModule_Assert(pthread_rwlock_init(&o->in_use_lock,NULL) == 0); return o; } void vectorSetReleaseNodeValue(void *v) { struct vsetNodeVal *nv = v; RedisModule_FreeString(NULL,nv->item); if (nv->attrib) RedisModule_FreeString(NULL,nv->attrib); RedisModule_Free(nv); } /* Free the vector set object. */ void vectorSetReleaseObject(struct vsetObject *o) { if (!o) return; if (o->hnsw) hnsw_free(o->hnsw,vectorSetReleaseNodeValue); if (o->dict) RedisModule_FreeDict(NULL,o->dict); if (o->proj_matrix) RedisModule_Free(o->proj_matrix); pthread_rwlock_destroy(&o->in_use_lock); RedisModule_Free(o); } /* Wait for all the threads performing operations on this * index to terminate their work (locking for write will * wait for all the other threads). * * if 'for_del' is set to 1, we also wait for all the pending threads * that still didn't acquire the lock to finish their work. This * is useful only if we are going to call this function to delete * the object, and not if we want to just to modify it. */ void vectorSetWaitAllBackgroundClients(struct vsetObject *vset, int for_del) { if (for_del) { // If we are going to destroy the object, after this call, let's // wait for threads that are being created and still didn't had // a chance to acquire the lock. while (vset->thread_creation_pending > 0); } RedisModule_Assert(pthread_rwlock_wrlock(&vset->in_use_lock) == 0); pthread_rwlock_unlock(&vset->in_use_lock); } /* Return a string representing the quantization type name of a vector set. */ const char *vectorSetGetQuantName(struct vsetObject *o) { switch(o->hnsw->quant_type) { case HNSW_QUANT_NONE: return "f32"; case HNSW_QUANT_Q8: return "int8"; case HNSW_QUANT_BIN: return "bin"; default: return "unknown"; } } /* Insert the specified element into the Vector Set. * If update is '1', the existing node will be updated. * * Returns 1 if the element was added, or 0 if the element was already there * and was just updated. */ int vectorSetInsert(struct vsetObject *o, float *vec, int8_t *qvec, float qrange, RedisModuleString *val, RedisModuleString *attrib, int update, int ef) { hnswNode *node = RedisModule_DictGet(o->dict,val,NULL); if (node != NULL) { if (update) { /* Wait for clients in the background: background VSIM * operations touch the nodes attributes we are going * to touch. */ vectorSetWaitAllBackgroundClients(o,0); struct vsetNodeVal *nv = node->value; /* Pass NULL as value-free function. We want to reuse * the old value. */ hnsw_delete_node(o->hnsw, node, NULL); node = hnsw_insert(o->hnsw,vec,qvec,qrange,0,nv,ef); RedisModule_Assert(node != NULL); RedisModule_DictReplace(o->dict,val,node); /* If attrib != NULL, the user wants that in case of an update we * update the attribute as well (otherwise it remains as it was). * Note that the order of operations is conceinved so that it * works in case the old attrib and the new attrib pointer is the * same. */ if (attrib) { // Empty attribute string means: unset the attribute during // the update. size_t attrlen; RedisModule_StringPtrLen(attrib,&attrlen); if (attrlen != 0) { RedisModule_RetainString(NULL,attrib); o->numattribs++; } else { attrib = NULL; } if (nv->attrib) { o->numattribs--; RedisModule_FreeString(NULL,nv->attrib); } nv->attrib = attrib; } } return 0; } struct vsetNodeVal *nv = RedisModule_Alloc(sizeof(*nv)); nv->item = val; nv->attrib = attrib; node = hnsw_insert(o->hnsw,vec,qvec,qrange,0,nv,ef); if (node == NULL) { // XXX Technically in Redis-land we don't have out of memory, as we // crash on OOM. However the HNSW library may fail for error in the // locking libc call. Probably impossible in practical terms. RedisModule_Free(nv); return 0; } if (attrib != NULL) o->numattribs++; RedisModule_DictSet(o->dict,val,node); RedisModule_RetainString(NULL,val); if (attrib) RedisModule_RetainString(NULL,attrib); return 1; } /* Parse vector from FP32 blob or VALUES format, with optional REDUCE. * Format: [REDUCE dim] FP32|VALUES ... * Returns allocated vector and sets dimension in *dim. * If reduce_dim is not NULL, sets it to the requested reduction dimension. * Returns NULL on parsing error. * * The function sets as a reference *consumed_args, so that the caller * knows how many arguments we consumed in order to parse the input * vector. Remaining arguments are often command options. */ float *parseVector(RedisModuleString **argv, int argc, int start_idx, size_t *dim, uint32_t *reduce_dim, int *consumed_args) { int consumed = 0; // Arguments consumed /* Check for REDUCE option first. */ if (reduce_dim) *reduce_dim = 0; if (reduce_dim && argc > start_idx + 2 && !strcasecmp(RedisModule_StringPtrLen(argv[start_idx],NULL),"REDUCE")) { long long rdim; if (RedisModule_StringToLongLong(argv[start_idx+1],&rdim) != REDISMODULE_OK || rdim <= 0) { return NULL; } if (reduce_dim) *reduce_dim = rdim; start_idx += 2; // Skip REDUCE and its argument. consumed += 2; } /* Now parse the vector format as before. */ float *vec = NULL; const char *vec_format = RedisModule_StringPtrLen(argv[start_idx],NULL); if (!strcasecmp(vec_format,"FP32")) { if (argc < start_idx + 2) return NULL; // Need FP32 + vector + value. size_t vec_raw_len; const char *blob = RedisModule_StringPtrLen(argv[start_idx+1],&vec_raw_len); // Must be 4 bytes per component. if (vec_raw_len % 4 || vec_raw_len < 4) return NULL; *dim = vec_raw_len/4; vec = RedisModule_Alloc(vec_raw_len); if (!vec) return NULL; memcpy(vec,blob,vec_raw_len); consumed += 2; } else if (!strcasecmp(vec_format,"VALUES")) { 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; // Check that all the arguments are available. if (argc < start_idx + 2 + vdim) return NULL; *dim = vdim; vec = RedisModule_Alloc(sizeof(float) * vdim); if (!vec) return NULL; for (int j = 0; j < vdim; j++) { double val; if (RedisModule_StringToDouble(argv[start_idx+2+j],&val) != REDISMODULE_OK) { RedisModule_Free(vec); return NULL; } vec[j] = val; } consumed += vdim + 2; } else { return NULL; // Unknown format. } if (consumed_args) *consumed_args = consumed; return vec; } /* ========================== Commands implementation ======================= */ /* VADD thread handling the "CAS" version of the command, that is * performed blocking the client, accumulating here, in the thread, the * set of potential candidates, and later inserting the element in the * key (if it still exists, and if it is still the *same* vector set) * in the Reply callback. */ void *VADD_thread(void *arg) { pthread_detach(pthread_self()); void **targ = (void**)arg; RedisModuleBlockedClient *bc = targ[0]; struct vsetObject *vset = targ[1]; float *vec = targ[3]; int ef = (uint64_t)targ[6]; /* Lock the object and signal that we are no longer pending * the lock acquisition. */ RedisModule_Assert(pthread_rwlock_rdlock(&vset->in_use_lock) == 0); vset->thread_creation_pending--; /* Look for candidates... */ InsertContext *ic = hnsw_prepare_insert(vset->hnsw, vec, NULL, 0, 0, ef); targ[5] = ic; // Pass the context to the reply callback. /* Unblock the client so that our read reply will be invoked. */ pthread_rwlock_unlock(&vset->in_use_lock); RedisModule_BlockedClientMeasureTimeEnd(bc); RedisModule_UnblockClient(bc,targ); // Use targ as privdata. return NULL; } /* Reply callback for CAS variant of VADD. * Note: this is called in the main thread, in the background thread * we just do the read operation of gathering the neighbors. */ int VADD_CASReply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { (void)argc; RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ int retval = REDISMODULE_OK; void **targ = (void**)RedisModule_GetBlockedClientPrivateData(ctx); uint64_t vset_id = (unsigned long) targ[2]; float *vec = targ[3]; RedisModuleString *val = targ[4]; InsertContext *ic = targ[5]; int ef = (uint64_t)targ[6]; RedisModuleString *attrib = targ[7]; RedisModule_Free(targ); /* Open the key: there are no guarantees it still exists, or contains * a vector set, or even the SAME vector set. */ RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], REDISMODULE_READ|REDISMODULE_WRITE); int type = RedisModule_KeyType(key); struct vsetObject *vset = NULL; if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) == VectorSetType) { vset = RedisModule_ModuleTypeGetValue(key); // Same vector set? if (vset->id != vset_id) vset = NULL; /* Also, if the element was already inserted, we just pretend * the other insert won. We don't even start a threaded VADD * if this was an update, since the deletion of the element itself * in order to perform the update would invalidate the CAS state. */ if (vset && RedisModule_DictGet(vset->dict,val,NULL) != NULL) vset = NULL; } if (vset == NULL) { /* If the object does not match the start of the operation, we * just pretend the VADD was performed BEFORE the key was deleted * or replaced. We return success but don't do anything. */ hnsw_free_insert_context(ic); } else { /* Otherwise try to insert the new element with the neighbors * collected in background. If we fail, do it synchronously again * from scratch. */ // First: allocate the dual-ported value for the node. struct vsetNodeVal *nv = RedisModule_Alloc(sizeof(*nv)); nv->item = val; nv->attrib = attrib; /* Then: insert the node in the HNSW data structure. Note that * 'ic' could be NULL in case hnsw_prepare_insert() failed because of * locking failure (likely impossible in practical terms). */ hnswNode *newnode; if (ic == NULL || (newnode = hnsw_try_commit_insert(vset->hnsw, ic, nv)) == NULL) { /* If we are here, the CAS insert failed. We need to insert * again with full locking for neighbors selection and * actual insertion. This time we can't fail: */ newnode = hnsw_insert(vset->hnsw, vec, NULL, 0, 0, nv, ef); RedisModule_Assert(newnode != NULL); } RedisModule_DictSet(vset->dict,val,newnode); val = NULL; // Don't free it later. attrib = NULL; // Don't free it later. RedisModule_ReplicateVerbatim(ctx); } // Whatever happens is a success... :D RedisModule_ReplyWithBool(ctx,1); if (val) RedisModule_FreeString(ctx,val); // Not added? Free it. if (attrib) RedisModule_FreeString(ctx,attrib); // Not added? Free it. RedisModule_Free(vec); return retval; } /* VADD key [REDUCE dim] FP32|VALUES vector value [CAS] [NOQUANT] [BIN] [Q8] * [M count] */ int VADD_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ if (argc < 5) return RedisModule_WrongArity(ctx); /* Parse vector with optional REDUCE */ size_t dim = 0; uint32_t reduce_dim = 0; int consumed_args; int cas = 0; // Threaded check-and-set style insert. long long ef = VSET_DEFAULT_C_EF; // HNSW creation time EF for new nodes. long long hnsw_create_M = HNSW_DEFAULT_M; // HNSW creation default M value. float *vec = parseVector(argv, argc, 2, &dim, &reduce_dim, &consumed_args); RedisModuleString *attrib = NULL; // Attributes if passed via ATTRIB. if (!vec) return RedisModule_ReplyWithError(ctx,"ERR invalid vector specification"); /* Missing element string at the end? */ if (argc-2-consumed_args < 1) { RedisModule_Free(vec); return RedisModule_WrongArity(ctx); } /* Parse options after the element string. */ uint32_t quant_type = HNSW_QUANT_Q8; // Default quantization type. for (int j = 2 + consumed_args + 1; j < argc; j++) { const char *opt = RedisModule_StringPtrLen(argv[j], NULL); if (!strcasecmp(opt, "CAS")) { cas = 1; } else if (!strcasecmp(opt, "EF") && j+1 < argc) { if (RedisModule_StringToLongLong(argv[j+1], &ef) != REDISMODULE_OK || ef <= 0 || ef > 1000000) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid EF"); } j++; // skip argument. } else if (!strcasecmp(opt, "M") && j+1 < argc) { if (RedisModule_StringToLongLong(argv[j+1], &hnsw_create_M) != REDISMODULE_OK || hnsw_create_M < HNSW_MIN_M || hnsw_create_M > HNSW_MAX_M) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid M"); } j++; // skip argument. } else if (!strcasecmp(opt, "SETATTR") && j+1 < argc) { attrib = argv[j+1]; j++; // skip argument. } else if (!strcasecmp(opt, "NOQUANT")) { quant_type = HNSW_QUANT_NONE; } else if (!strcasecmp(opt, "BIN")) { quant_type = HNSW_QUANT_BIN; } else if (!strcasecmp(opt, "Q8")) { quant_type = HNSW_QUANT_Q8; } else { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx,"ERR invalid option after element"); } } /* Drop CAS if this is a replica and we are getting the command from the * replication link: we want to add/delete items in the same order as * the master, while with CAS the timing would be different. * * Also for Lua scripts and MULTI/EXEC, we want to run the command * on the main thread. */ if (RedisModule_GetContextFlags(ctx) & (REDISMODULE_CTX_FLAGS_REPLICATED| REDISMODULE_CTX_FLAGS_LUA| REDISMODULE_CTX_FLAGS_MULTI)) { cas = 0; } /* Open/create key */ RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], REDISMODULE_READ|REDISMODULE_WRITE); int type = RedisModule_KeyType(key); if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != VectorSetType) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); } /* Get the correct value argument based on format and REDUCE */ RedisModuleString *val = argv[2 + consumed_args]; /* Create or get existing vector set */ struct vsetObject *vset; if (type == REDISMODULE_KEYTYPE_EMPTY) { cas = 0; /* Do synchronous insert at creation, otherwise the * key would be left empty until the threaded part * does not return. It's also pointless to try try * doing threaded first element insertion. */ vset = createVectorSetObject(reduce_dim ? reduce_dim : dim, quant_type, hnsw_create_M); if (vset == NULL) { // We can't fail for OOM in Redis, but the mutex initialization // at least theoretically COULD fail. Likely this code path // is not reachable in practical terms. RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR unable to create a Vector Set: system resources issue?"); } /* Initialize projection if requested */ if (reduce_dim) { vset->proj_matrix = createProjectionMatrix(dim, reduce_dim); vset->proj_input_size = dim; /* Project the vector */ float *projected = applyProjection(vec, vset->proj_matrix, dim, reduce_dim); RedisModule_Free(vec); vec = projected; } RedisModule_ModuleTypeSetValue(key,VectorSetType,vset); } else { vset = RedisModule_ModuleTypeGetValue(key); if (vset->hnsw->quant_type != quant_type) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR asked quantization mismatch with existing vector set"); } if (vset->hnsw->M != hnsw_create_M) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR asked M value mismatch with existing vector set"); } if ((vset->proj_matrix == NULL && vset->hnsw->vector_dim != dim) || (vset->proj_matrix && vset->hnsw->vector_dim != reduce_dim)) { RedisModule_Free(vec); return RedisModule_ReplyWithErrorFormat(ctx, "ERR Vector dimension mismatch - got %d but set has %d", (int)dim, (int)vset->hnsw->vector_dim); } /* Check REDUCE compatibility */ if (reduce_dim) { if (!vset->proj_matrix) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR cannot add projection to existing set without projection"); } if (reduce_dim != vset->hnsw->vector_dim) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR projection dimension mismatch with existing set"); } } /* Apply projection if needed */ if (vset->proj_matrix) { /* Ensure input dimension matches the projection matrix's expected input dimension */ if (dim != vset->proj_input_size) { RedisModule_Free(vec); return RedisModule_ReplyWithErrorFormat(ctx, "ERR Input dimension mismatch for projection - got %d but projection expects %d", (int)dim, (int)vset->proj_input_size); } float *projected = applyProjection(vec, vset->proj_matrix, vset->proj_input_size, vset->hnsw->vector_dim); RedisModule_Free(vec); vec = projected; dim = vset->hnsw->vector_dim; } } /* For existing keys don't do CAS updates. For how things work now, the * CAS state would be invalidated by the deletion before adding back. */ if (cas && RedisModule_DictGet(vset->dict,val,NULL) != NULL) cas = 0; /* Here depending on the CAS option we directly insert in a blocking * way, or use a thread to do candidate neighbors selection and only * later, in the reply callback, actually add the element. */ if (cas) { RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,VADD_CASReply,NULL,NULL,0); pthread_t tid; void **targ = RedisModule_Alloc(sizeof(void*)*8); targ[0] = bc; targ[1] = vset; targ[2] = (void*)(unsigned long)vset->id; targ[3] = vec; targ[4] = val; targ[5] = NULL; // Used later for insertion context. targ[6] = (void*)(unsigned long)ef; targ[7] = attrib; RedisModule_RetainString(ctx,val); if (attrib) RedisModule_RetainString(ctx,attrib); RedisModule_BlockedClientMeasureTimeStart(bc); vset->thread_creation_pending++; if (pthread_create(&tid,NULL,VADD_thread,targ) != 0) { vset->thread_creation_pending--; RedisModule_AbortBlock(bc); RedisModule_Free(targ); RedisModule_FreeString(ctx,val); if (attrib) RedisModule_FreeString(ctx,attrib); // Fall back to synchronous insert, see later in the code. } else { return REDISMODULE_OK; } } /* Insert vector synchronously: we reach this place even * if cas was true but thread creation failed. */ int added = vectorSetInsert(vset,vec,NULL,0,val,attrib,1,ef); RedisModule_Free(vec); RedisModule_ReplyWithBool(ctx,added); if (added) RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } /* HNSW callback to filter items according to a predicate function * (our FILTER expression in this case). */ int vectorSetFilterCallback(void *value, void *privdata) { exprstate *expr = privdata; struct vsetNodeVal *nv = value; if (nv->attrib == NULL) return 0; // No attributes? No match. size_t json_len; char *json = (char*)RedisModule_StringPtrLen(nv->attrib,&json_len); return exprRun(expr,json,json_len); } /* Common path for the execution of the VSIM command both threaded and * not threaded. Note that 'ctx' may be normal context of a thread safe * context obtained from a blocked client. The locking that is specific * to the vset object is handled by the caller, however the function * handles the HNSW locking explicitly. */ void VSIM_execute(RedisModuleCtx *ctx, struct vsetObject *vset, float *vec, unsigned long count, float epsilon, unsigned long withscores, unsigned long ef, exprstate *filter_expr, unsigned long filter_ef, int ground_truth) { /* In our scan, we can't just collect 'count' elements as * if count is small we would explore the graph in an insufficient * way to provide enough recall. * * If the user didn't asked for a specific exploration, we use * VSET_DEFAULT_SEARCH_EF as minimum, or we match count if count * is greater than that. Otherwise the minumim will be the specified * EF argument. */ if (ef == 0) ef = VSET_DEFAULT_SEARCH_EF; if (count > ef) ef = count; /* Perform search */ hnswNode **neighbors = RedisModule_Alloc(sizeof(hnswNode*)*ef); float *distances = RedisModule_Alloc(sizeof(float)*ef); int slot = hnsw_acquire_read_slot(vset->hnsw); unsigned int found; if (ground_truth) { found = hnsw_ground_truth_with_filter(vset->hnsw, vec, ef, neighbors, distances, slot, 0, filter_expr ? vectorSetFilterCallback : NULL, filter_expr); } else { if (filter_expr == NULL) { found = hnsw_search(vset->hnsw, vec, ef, neighbors, distances, slot, 0); } else { found = hnsw_search_with_filter(vset->hnsw, vec, ef, neighbors, distances, slot, 0, vectorSetFilterCallback, filter_expr, filter_ef); } } /* Return results */ if (withscores) RedisModule_ReplyWithMap(ctx, REDISMODULE_POSTPONED_LEN); else RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN); long long arraylen = 0; for (unsigned int i = 0; i < found && i < count; i++) { if (distances[i] > epsilon) break; struct vsetNodeVal *nv = neighbors[i]->value; RedisModule_ReplyWithString(ctx, nv->item); arraylen++; if (withscores) { /* The similarity score is provided in a 0-1 range. */ RedisModule_ReplyWithDouble(ctx, 1.0 - distances[i]/2.0); } } hnsw_release_read_slot(vset->hnsw,slot); if (withscores) RedisModule_ReplySetMapLength(ctx, arraylen); else RedisModule_ReplySetArrayLength(ctx, arraylen); RedisModule_Free(vec); RedisModule_Free(neighbors); RedisModule_Free(distances); if (filter_expr) exprFree(filter_expr); } /* VSIM thread handling the blocked client request. */ void *VSIM_thread(void *arg) { pthread_detach(pthread_self()); // Extract arguments. void **targ = (void**)arg; RedisModuleBlockedClient *bc = targ[0]; struct vsetObject *vset = targ[1]; float *vec = targ[2]; unsigned long count = (unsigned long)targ[3]; float epsilon = *((float*)targ[4]); unsigned long withscores = (unsigned long)targ[5]; unsigned long ef = (unsigned long)targ[6]; exprstate *filter_expr = targ[7]; unsigned long filter_ef = (unsigned long)targ[8]; unsigned long ground_truth = (unsigned long)targ[9]; RedisModule_Free(targ[4]); RedisModule_Free(targ); /* Lock the object and signal that we are no longer pending * the lock acquisition. */ RedisModule_Assert(pthread_rwlock_rdlock(&vset->in_use_lock) == 0); vset->thread_creation_pending--; // Accumulate reply in a thread safe context: no contention. RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc); // Run the query. VSIM_execute(ctx, vset, vec, count, epsilon, withscores, ef, filter_expr, filter_ef, ground_truth); pthread_rwlock_unlock(&vset->in_use_lock); // Cleanup. RedisModule_FreeThreadSafeContext(ctx); RedisModule_BlockedClientMeasureTimeEnd(bc); RedisModule_UnblockClient(bc,NULL); return NULL; } /* VSIM key [ELE|FP32|VALUES] [WITHSCORES] [COUNT num] [EPSILON eps] [EF exploration-factor] [FILTER expression] [FILTER-EF exploration-factor] */ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); /* Basic argument check: need at least key and vector specification * method. */ if (argc < 4) return RedisModule_WrongArity(ctx); /* Defaults */ int withscores = 0; long long count = VSET_DEFAULT_COUNT; /* New default value */ long long ef = 0; /* Exploration factor (see HNSW paper) */ double epsilon = 2.0; /* Max cosine distance */ long long ground_truth = 0; /* Linear scan instead of HNSW search? */ int no_thread = 0; /* NOTHREAD option: exec on main thread. */ /* Things computed later. */ long long filter_ef = 0; exprstate *filter_expr = NULL; /* Get key and vector type */ RedisModuleString *key = argv[1]; const char *vectorType = RedisModule_StringPtrLen(argv[2], NULL); /* Get vector set */ RedisModuleKey *keyptr = RedisModule_OpenKey(ctx, key, REDISMODULE_READ); int type = RedisModule_KeyType(keyptr); if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithEmptyArray(ctx); if (RedisModule_ModuleTypeGetType(keyptr) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); struct vsetObject *vset = RedisModule_ModuleTypeGetValue(keyptr); /* Vector parsing stage */ float *vec = NULL; size_t dim = 0; int vector_args = 0; /* Number of args consumed by vector specification */ if (!strcasecmp(vectorType, "ELE")) { /* Get vector from existing element */ RedisModuleString *ele = argv[3]; hnswNode *node = RedisModule_DictGet(vset->dict, ele, NULL); if (!node) { return RedisModule_ReplyWithError(ctx, "ERR element not found in set"); } vec = RedisModule_Alloc(sizeof(float) * vset->hnsw->vector_dim); hnsw_get_node_vector(vset->hnsw,node,vec); dim = vset->hnsw->vector_dim; vector_args = 2; /* ELE + element name */ } else { /* Parse vector. */ int consumed_args; vec = parseVector(argv, argc, 2, &dim, NULL, &consumed_args); if (!vec) { return RedisModule_ReplyWithError(ctx, "ERR invalid vector specification"); } vector_args = consumed_args; /* Apply projection if the set uses it, with the exception * of ELE type, that will already have the right dimension. */ if (vset->proj_matrix && dim != vset->hnsw->vector_dim) { /* Ensure input dimension matches the projection matrix's expected input dimension */ if (dim != vset->proj_input_size) { RedisModule_Free(vec); return RedisModule_ReplyWithErrorFormat(ctx, "ERR Input dimension mismatch for projection - got %d but projection expects %d", (int)dim, (int)vset->proj_input_size); } float *projected = applyProjection(vec, vset->proj_matrix, vset->proj_input_size, vset->hnsw->vector_dim); RedisModule_Free(vec); vec = projected; dim = vset->hnsw->vector_dim; } /* Count consumed arguments */ if (!strcasecmp(vectorType, "FP32")) { vector_args = 2; /* FP32 + vector blob */ } else if (!strcasecmp(vectorType, "VALUES")) { long long vdim; if (RedisModule_StringToLongLong(argv[3], &vdim) != REDISMODULE_OK) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid vector dimension"); } vector_args = 2 + vdim; /* VALUES + dim + values */ } else { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR vector type must be ELE, FP32 or VALUES"); } } /* Check vector dimension matches set */ if (dim != vset->hnsw->vector_dim) { RedisModule_Free(vec); return RedisModule_ReplyWithErrorFormat(ctx, "ERR Vector dimension mismatch - got %d but set has %d", (int)dim, (int)vset->hnsw->vector_dim); } /* Parse optional arguments - start after vector specification */ int j = 2 + vector_args; while (j < argc) { const char *opt = RedisModule_StringPtrLen(argv[j], NULL); if (!strcasecmp(opt, "WITHSCORES")) { withscores = 1; j++; } else if (!strcasecmp(opt, "TRUTH")) { ground_truth = 1; j++; } else if (!strcasecmp(opt, "NOTHREAD")) { no_thread = 1; j++; } else if (!strcasecmp(opt, "COUNT") && j+1 < argc) { if (RedisModule_StringToLongLong(argv[j+1], &count) != REDISMODULE_OK || count <= 0) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid COUNT"); } j += 2; } else if (!strcasecmp(opt, "EPSILON") && j+1 < argc) { if (RedisModule_StringToDouble(argv[j+1], &epsilon) != REDISMODULE_OK || epsilon <= 0) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid EPSILON"); } j += 2; } else if (!strcasecmp(opt, "EF") && j+1 < argc) { if (RedisModule_StringToLongLong(argv[j+1], &ef) != REDISMODULE_OK || ef <= 0) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid EF"); } j += 2; } else if (!strcasecmp(opt, "FILTER-EF") && j+1 < argc) { if (RedisModule_StringToLongLong(argv[j+1], &filter_ef) != REDISMODULE_OK || filter_ef <= 0) { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR invalid FILTER-EF"); } j += 2; } else if (!strcasecmp(opt, "FILTER") && j+1 < argc) { RedisModuleString *exprarg = argv[j+1]; size_t exprlen; char *exprstr = (char*)RedisModule_StringPtrLen(exprarg,&exprlen); int errpos; filter_expr = exprCompile(exprstr,&errpos); if (filter_expr == NULL) { if ((size_t)errpos >= exprlen) errpos = 0; RedisModule_Free(vec); return RedisModule_ReplyWithErrorFormat(ctx, "ERR syntax error in FILTER expression near: %s", exprstr+errpos); } j += 2; } else { RedisModule_Free(vec); return RedisModule_ReplyWithError(ctx, "ERR syntax error in VSIM command"); } } int threaded_request = 1; // Run on a thread, by default. if (filter_ef == 0) filter_ef = count * 100; // Max filter visited nodes. /* Disable threaded for MULTI/EXEC and Lua, or if explicitly * requested by the user via the NOTHREAD option. */ if (no_thread || (RedisModule_GetContextFlags(ctx) & (REDISMODULE_CTX_FLAGS_LUA| REDISMODULE_CTX_FLAGS_MULTI))) { threaded_request = 0; } if (threaded_request) { /* Note: even if we create one thread per request, the underlying * HNSW library has a fixed number of slots for the threads, as it's * defined in HNSW_MAX_THREADS (beware that if you increase it, * every node will use more memory). This means that while this request * is threaded, and will NOT block Redis, it may end waiting for a * free slot if all the HNSW_MAX_THREADS slots are used. */ RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,NULL,NULL,NULL,0); pthread_t tid; void **targ = RedisModule_Alloc(sizeof(void*)*10); targ[0] = bc; targ[1] = vset; targ[2] = vec; targ[3] = (void*)count; targ[4] = RedisModule_Alloc(sizeof(float)); *((float*)targ[4]) = epsilon; targ[5] = (void*)(unsigned long)withscores; targ[6] = (void*)(unsigned long)ef; targ[7] = (void*)filter_expr; targ[8] = (void*)(unsigned long)filter_ef; targ[9] = (void*)(unsigned long)ground_truth; RedisModule_BlockedClientMeasureTimeStart(bc); vset->thread_creation_pending++; if (pthread_create(&tid,NULL,VSIM_thread,targ) != 0) { vset->thread_creation_pending--; RedisModule_AbortBlock(bc); RedisModule_Free(targ[4]); RedisModule_Free(targ); VSIM_execute(ctx, vset, vec, count, epsilon, withscores, ef, filter_expr, filter_ef, ground_truth); } } else { VSIM_execute(ctx, vset, vec, count, epsilon, withscores, ef, filter_expr, filter_ef, ground_truth); } return REDISMODULE_OK; } /* VDIM : return the dimension of vectors in the vector set. */ int VDIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc != 2) return RedisModule_WrongArity(ctx); RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); int type = RedisModule_KeyType(key); if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithError(ctx, "ERR key does not exist"); if (RedisModule_ModuleTypeGetType(key) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key); return RedisModule_ReplyWithLongLong(ctx, vset->hnsw->vector_dim); } /* VCARD : return cardinality (num of elements) of the vector set. */ int VCARD_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc != 2) return RedisModule_WrongArity(ctx); RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); int type = RedisModule_KeyType(key); if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithLongLong(ctx, 0); if (RedisModule_ModuleTypeGetType(key) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key); return RedisModule_ReplyWithLongLong(ctx, vset->hnsw->node_count); } /* VREM key element * Remove an element from a vector set. * Returns 1 if the element was found and removed, 0 if not found. */ int VREM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ if (argc != 3) return RedisModule_WrongArity(ctx); /* Get key and value */ RedisModuleString *key = argv[1]; RedisModuleString *element = argv[2]; /* Open key */ RedisModuleKey *keyptr = RedisModule_OpenKey(ctx, key, REDISMODULE_READ|REDISMODULE_WRITE); int type = RedisModule_KeyType(keyptr); /* Handle non-existing key or wrong type */ if (type == REDISMODULE_KEYTYPE_EMPTY) { return RedisModule_ReplyWithBool(ctx, 0); } if (RedisModule_ModuleTypeGetType(keyptr) != VectorSetType) { return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); } /* Get vector set from key */ struct vsetObject *vset = RedisModule_ModuleTypeGetValue(keyptr); /* Find the node for this element */ hnswNode *node = RedisModule_DictGet(vset->dict, element, NULL); if (!node) { return RedisModule_ReplyWithBool(ctx, 0); } /* Remove from dictionary */ RedisModule_DictDel(vset->dict, element, NULL); /* Remove from HNSW graph using the high-level API that handles * locking and cleanup. We pass RedisModule_FreeString as the value * free function since the strings were retained at insertion time. */ struct vsetNodeVal *nv = node->value; if (nv->attrib != NULL) vset->numattribs--; RedisModule_Assert(hnsw_delete_node(vset->hnsw, node, vectorSetReleaseNodeValue) == 1); /* Destroy empty vector set. */ if (RedisModule_DictSize(vset->dict) == 0) { RedisModule_DeleteKey(keyptr); } /* Reply and propagate the command */ RedisModule_ReplyWithBool(ctx, 1); RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } /* VEMB key element * Returns the embedding vector associated with an element, or NIL if not * found. The vector is returned in the same format it was added, but the * return value will have some lack of precision due to quantization and * normalization of vectors. Also, if items were added using REDUCE, the * reduced vector is returned instead. */ int VEMB_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); int raw_output = 0; // RAW option. if (argc < 3) return RedisModule_WrongArity(ctx); /* Parse arguments. */ for (int j = 3; j < argc; j++) { const char *opt = RedisModule_StringPtrLen(argv[j], NULL); if (!strcasecmp(opt,"raw")) { raw_output = 1; } else { return RedisModule_ReplyWithError(ctx,"ERR invalid option"); } } /* Get key and element. */ RedisModuleString *key = argv[1]; RedisModuleString *element = argv[2]; /* Open key. */ RedisModuleKey *keyptr = RedisModule_OpenKey(ctx, key, REDISMODULE_READ); int type = RedisModule_KeyType(keyptr); /* Handle non-existing key and key of wrong type. */ if (type == REDISMODULE_KEYTYPE_EMPTY) { return RedisModule_ReplyWithNull(ctx); } else if (RedisModule_ModuleTypeGetType(keyptr) != VectorSetType) { return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); } /* Lookup the node about the specified element. */ struct vsetObject *vset = RedisModule_ModuleTypeGetValue(keyptr); hnswNode *node = RedisModule_DictGet(vset->dict, element, NULL); if (!node) { return RedisModule_ReplyWithNull(ctx); } if (raw_output) { int output_qrange = vset->hnsw->quant_type == HNSW_QUANT_Q8; RedisModule_ReplyWithArray(ctx, 3+output_qrange); RedisModule_ReplyWithSimpleString(ctx, vectorSetGetQuantName(vset)); RedisModule_ReplyWithStringBuffer(ctx, node->vector, hnsw_quants_bytes(vset->hnsw)); RedisModule_ReplyWithDouble(ctx, node->l2); if (output_qrange) RedisModule_ReplyWithDouble(ctx, node->quants_range); } else { /* Get the vector associated with the node. */ float *vec = RedisModule_Alloc(sizeof(float) * vset->hnsw->vector_dim); hnsw_get_node_vector(vset->hnsw, node, vec); // May dequantize/denorm. /* Return as array of doubles. */ RedisModule_ReplyWithArray(ctx, vset->hnsw->vector_dim); for (uint32_t i = 0; i < vset->hnsw->vector_dim; i++) RedisModule_ReplyWithDouble(ctx, vec[i]); RedisModule_Free(vec); } return REDISMODULE_OK; } /* VSETATTR key element json * Set or remove the JSON attribute associated with an element. * Setting an empty string removes the attribute. * The command returns one if the attribute was actually updated or * zero if there is no key or element. */ int VSETATTR_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc != 4) return RedisModule_WrongArity(ctx); RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ|REDISMODULE_WRITE); int type = RedisModule_KeyType(key); if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithBool(ctx, 0); if (RedisModule_ModuleTypeGetType(key) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key); hnswNode *node = RedisModule_DictGet(vset->dict, argv[2], NULL); if (!node) return RedisModule_ReplyWithBool(ctx, 0); struct vsetNodeVal *nv = node->value; RedisModuleString *new_attr = argv[3]; /* Background VSIM operations use the node attributes, so * wait for background operations before messing with them. */ vectorSetWaitAllBackgroundClients(vset,0); /* Set or delete the attribute based on the fact it's an empty * string or not. */ size_t attrlen; RedisModule_StringPtrLen(new_attr, &attrlen); if (attrlen == 0) { // If we had an attribute before, decrease the count and free it. if (nv->attrib) { vset->numattribs--; RedisModule_FreeString(NULL, nv->attrib); nv->attrib = NULL; } } else { // If we didn't have an attribute before, increase the count. // Otherwise free the old one. if (nv->attrib) { RedisModule_FreeString(NULL, nv->attrib); } else { vset->numattribs++; } // Set new attribute. RedisModule_RetainString(NULL, new_attr); nv->attrib = new_attr; } RedisModule_ReplyWithBool(ctx, 1); RedisModule_ReplicateVerbatim(ctx); return REDISMODULE_OK; } /* VGETATTR key element * Get the JSON attribute associated with an element. * Returns NIL if the element has no attribute or doesn't exist. */ int VGETATTR_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc != 3) return RedisModule_WrongArity(ctx); RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); int type = RedisModule_KeyType(key); if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithNull(ctx); if (RedisModule_ModuleTypeGetType(key) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key); hnswNode *node = RedisModule_DictGet(vset->dict, argv[2], NULL); if (!node) return RedisModule_ReplyWithNull(ctx); struct vsetNodeVal *nv = node->value; if (!nv->attrib) return RedisModule_ReplyWithNull(ctx); return RedisModule_ReplyWithString(ctx, nv->attrib); } /* ============================== Reflection ================================ */ /* VLINKS key element [WITHSCORES] * Returns the neighbors of an element at each layer in the HNSW graph. * Reply is an array of arrays, where each nested array represents one level * of neighbors, from highest level to level 0. If WITHSCORES is specified, * each neighbor is followed by its distance from the element. */ int VLINKS_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc < 3 || argc > 4) return RedisModule_WrongArity(ctx); RedisModuleString *key = argv[1]; RedisModuleString *element = argv[2]; /* Parse WITHSCORES option. */ int withscores = 0; if (argc == 4) { const char *opt = RedisModule_StringPtrLen(argv[3], NULL); if (strcasecmp(opt, "WITHSCORES") != 0) { return RedisModule_WrongArity(ctx); } withscores = 1; } RedisModuleKey *keyptr = RedisModule_OpenKey(ctx, key, REDISMODULE_READ); int type = RedisModule_KeyType(keyptr); /* Handle non-existing key or wrong type. */ if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithNull(ctx); if (RedisModule_ModuleTypeGetType(keyptr) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); /* Find the node for this element. */ struct vsetObject *vset = RedisModule_ModuleTypeGetValue(keyptr); hnswNode *node = RedisModule_DictGet(vset->dict, element, NULL); if (!node) return RedisModule_ReplyWithNull(ctx); /* Reply with array of arrays, one per level. */ RedisModule_ReplyWithArray(ctx, node->level + 1); /* For each level, from highest to lowest: */ for (int i = node->level; i >= 0; i--) { /* Reply with array of neighbors at this level. */ if (withscores) RedisModule_ReplyWithMap(ctx,node->layers[i].num_links); else RedisModule_ReplyWithArray(ctx,node->layers[i].num_links); /* Add each neighbor's element value to the array. */ for (uint32_t j = 0; j < node->layers[i].num_links; j++) { struct vsetNodeVal *nv = node->layers[i].links[j]->value; RedisModule_ReplyWithString(ctx, nv->item); if (withscores) { float distance = hnsw_distance(vset->hnsw, node, node->layers[i].links[j]); /* Convert distance to similarity score to match * VSIM behavior.*/ float similarity = 1.0 - distance/2.0; RedisModule_ReplyWithDouble(ctx, similarity); } } } return REDISMODULE_OK; } /* VINFO key * Returns information about a vector set, both visible and hidden * features of the HNSW data structure. */ int VINFO_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc != 2) return RedisModule_WrongArity(ctx); RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); int type = RedisModule_KeyType(key); if (type == REDISMODULE_KEYTYPE_EMPTY) return RedisModule_ReplyWithNullArray(ctx); if (RedisModule_ModuleTypeGetType(key) != VectorSetType) return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key); /* Reply with hash */ RedisModule_ReplyWithMap(ctx, 9); /* Quantization type */ RedisModule_ReplyWithSimpleString(ctx, "quant-type"); RedisModule_ReplyWithSimpleString(ctx, vectorSetGetQuantName(vset)); /* HNSW M value */ RedisModule_ReplyWithSimpleString(ctx, "hnsw-m"); RedisModule_ReplyWithLongLong(ctx, vset->hnsw->M); /* Vector dimensionality. */ RedisModule_ReplyWithSimpleString(ctx, "vector-dim"); RedisModule_ReplyWithLongLong(ctx, vset->hnsw->vector_dim); /* Original input dimension before projection. * This is zero for vector sets without a random projection matrix. */ RedisModule_ReplyWithSimpleString(ctx, "projection-input-dim"); RedisModule_ReplyWithLongLong(ctx, vset->proj_input_size); /* Number of elements. */ RedisModule_ReplyWithSimpleString(ctx, "size"); RedisModule_ReplyWithLongLong(ctx, vset->hnsw->node_count); /* Max level of HNSW. */ RedisModule_ReplyWithSimpleString(ctx, "max-level"); RedisModule_ReplyWithLongLong(ctx, vset->hnsw->max_level); /* Number of nodes with attributes. */ RedisModule_ReplyWithSimpleString(ctx, "attributes-count"); RedisModule_ReplyWithLongLong(ctx, vset->numattribs); /* Vector set ID. */ RedisModule_ReplyWithSimpleString(ctx, "vset-uid"); RedisModule_ReplyWithLongLong(ctx, vset->id); /* HNSW max node ID. */ RedisModule_ReplyWithSimpleString(ctx, "hnsw-max-node-uid"); RedisModule_ReplyWithLongLong(ctx, vset->hnsw->last_id); return REDISMODULE_OK; } /* VRANDMEMBER key [count] * Return random members from a vector set. * * Without count: returns a single random member. * With positive count: N unique random members (no duplicates). * With negative count: N random members (with possible duplicates). * * If the key doesn't exist, returns NULL if count is not given, or * an empty array if a count was given. */ int VRANDMEMBER_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ /* Check arguments. */ if (argc != 2 && argc != 3) return RedisModule_WrongArity(ctx); /* Parse optional count argument. */ long long count = 1; /* Default is to return a single element. */ int with_count = (argc == 3); if (with_count) { if (RedisModule_StringToLongLong(argv[2], &count) != REDISMODULE_OK) { return RedisModule_ReplyWithError(ctx, "ERR COUNT value is not an integer"); } /* Count = 0 is a special case, return empty array */ if (count == 0) { return RedisModule_ReplyWithEmptyArray(ctx); } } /* Open key. */ RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ); int type = RedisModule_KeyType(key); /* Handle non-existing key. */ if (type == REDISMODULE_KEYTYPE_EMPTY) { if (!with_count) { return RedisModule_ReplyWithNull(ctx); } else { return RedisModule_ReplyWithEmptyArray(ctx); } } /* Check key type. */ if (RedisModule_ModuleTypeGetType(key) != VectorSetType) { return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); } /* Get vector set from key. */ struct vsetObject *vset = RedisModule_ModuleTypeGetValue(key); uint64_t set_size = vset->hnsw->node_count; /* No elements in the set? */ if (set_size == 0) { if (!with_count) { return RedisModule_ReplyWithNull(ctx); } else { return RedisModule_ReplyWithEmptyArray(ctx); } } /* Case 1: No count specified: return a single element. */ if (!with_count) { hnswNode *random_node = hnsw_random_node(vset->hnsw, 0); if (random_node) { struct vsetNodeVal *nv = random_node->value; return RedisModule_ReplyWithString(ctx, nv->item); } else { return RedisModule_ReplyWithNull(ctx); } } /* Case 2: COUNT option given, return an array of elements. */ int allow_duplicates = (count < 0); long long abs_count = (count < 0) ? -count : count; /* Cap the count to the set size if we are not allowing duplicates. */ if (!allow_duplicates && abs_count > (long long)set_size) abs_count = set_size; /* Prepare reply. */ RedisModule_ReplyWithArray(ctx, abs_count); if (allow_duplicates) { /* Simple case: With duplicates, just pick random nodes * abs_count times. */ for (long long i = 0; i < abs_count; i++) { hnswNode *random_node = hnsw_random_node(vset->hnsw,0); struct vsetNodeVal *nv = random_node->value; RedisModule_ReplyWithString(ctx, nv->item); } } else { /* Case where count is positive: we need unique elements. * But, if the user asked for many elements, selecting so * many (> 20%) random nodes may be too expansive: we just start * from a random element and follow the next link. * * Otherwisem for the <= 20% case, a dictionary is used to * reject duplicates. */ int use_dict = (abs_count <= set_size * 0.2); if (use_dict) { RedisModuleDict *returned = RedisModule_CreateDict(ctx); long long returned_count = 0; while (returned_count < abs_count) { hnswNode *random_node = hnsw_random_node(vset->hnsw, 0); struct vsetNodeVal *nv = random_node->value; /* Check if we've already returned this element. */ if (RedisModule_DictGet(returned, nv->item, NULL) == NULL) { /* Mark as returned and add to results. */ RedisModule_DictSet(returned, nv->item, (void*)1); RedisModule_ReplyWithString(ctx, nv->item); returned_count++; } } RedisModule_FreeDict(ctx, returned); } else { /* For large samples, get a random starting node and walk * the list. * * IMPORTANT: doing so does not really generate random * elements: it's just a linear scan, but we have no choices. * If we generate too many random elements, more and more would * fail the check of being novel (not yet collected in the set * to return) if the % of elements to emit is too large, we would * spend too much CPU. */ hnswNode *start_node = hnsw_random_node(vset->hnsw, 0); hnswNode *current = start_node; long long returned_count = 0; while (returned_count < abs_count) { if (current == NULL) { /* Restart from head if we hit the end. */ current = vset->hnsw->head; } struct vsetNodeVal *nv = current->value; RedisModule_ReplyWithString(ctx, nv->item); returned_count++; current = current->next; } } } return REDISMODULE_OK; } /* VISMEMBER key element * Check if an element exists in a vector set. * Returns 1 if the element exists, 0 if not. */ int VISMEMBER_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); if (argc != 3) return RedisModule_WrongArity(ctx); RedisModuleString *key = argv[1]; RedisModuleString *element = argv[2]; /* Open key. */ RedisModuleKey *keyptr = RedisModule_OpenKey(ctx, key, REDISMODULE_READ); int type = RedisModule_KeyType(keyptr); /* Handle non-existing key or wrong type. */ if (type == REDISMODULE_KEYTYPE_EMPTY) { /* An element of a non existing key does not exist, like * SISMEMBER & similar. */ return RedisModule_ReplyWithBool(ctx, 0); } if (RedisModule_ModuleTypeGetType(keyptr) != VectorSetType) { return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE); } /* Get the object and test membership via the dictionary in constant * time (assuming a member of average size). */ struct vsetObject *vset = RedisModule_ModuleTypeGetValue(keyptr); hnswNode *node = RedisModule_DictGet(vset->dict, element, NULL); return RedisModule_ReplyWithBool(ctx, node != NULL); } /* ============================== vset type methods ========================= */ #define SAVE_FLAG_HAS_PROJMATRIX (1<<0) #define SAVE_FLAG_HAS_ATTRIBS (1<<1) /* Save object to RDB */ void VectorSetRdbSave(RedisModuleIO *rdb, void *value) { struct vsetObject *vset = value; RedisModule_SaveUnsigned(rdb, vset->hnsw->vector_dim); RedisModule_SaveUnsigned(rdb, vset->hnsw->node_count); uint32_t hnsw_config = (vset->hnsw->quant_type & 0xff) | ((vset->hnsw->M & 0xffff) << 8); RedisModule_SaveUnsigned(rdb, hnsw_config); uint32_t save_flags = 0; if (vset->proj_matrix) save_flags |= SAVE_FLAG_HAS_PROJMATRIX; if (vset->numattribs != 0) save_flags |= SAVE_FLAG_HAS_ATTRIBS; RedisModule_SaveUnsigned(rdb, save_flags); /* Save projection matrix if present */ if (vset->proj_matrix) { uint32_t input_dim = vset->proj_input_size; uint32_t output_dim = vset->hnsw->vector_dim; RedisModule_SaveUnsigned(rdb, input_dim); // Output dim is the same as the first value saved // above, so we don't save it. // Save projection matrix as binary blob size_t matrix_size = sizeof(float) * input_dim * output_dim; RedisModule_SaveStringBuffer(rdb, (const char *)vset->proj_matrix, matrix_size); } hnswNode *node = vset->hnsw->head; while(node) { struct vsetNodeVal *nv = node->value; RedisModule_SaveString(rdb, nv->item); if (vset->numattribs) { if (nv->attrib) RedisModule_SaveString(rdb, nv->attrib); else RedisModule_SaveStringBuffer(rdb, "", 0); } hnswSerNode *sn = hnsw_serialize_node(vset->hnsw,node); RedisModule_SaveStringBuffer(rdb, (const char *)sn->vector, sn->vector_size); RedisModule_SaveUnsigned(rdb, sn->params_count); for (uint32_t j = 0; j < sn->params_count; j++) RedisModule_SaveUnsigned(rdb, sn->params[j]); hnsw_free_serialized_node(sn); node = node->next; } } /* Load object from RDB. Recover from recoverable errors (read errors) * by performing cleanup. */ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) { if (encver != 0) return NULL; // Invalid version uint32_t dim = RedisModule_LoadUnsigned(rdb); uint64_t elements = RedisModule_LoadUnsigned(rdb); uint32_t hnsw_config = RedisModule_LoadUnsigned(rdb); if (RedisModule_IsIOError(rdb)) return NULL; uint32_t quant_type = hnsw_config & 0xff; uint32_t hnsw_m = (hnsw_config >> 8) & 0xffff; /* Check that the quantization type is correct. Otherwise * return ASAP signaling the error. */ if (quant_type != HNSW_QUANT_NONE && quant_type != HNSW_QUANT_Q8 && quant_type != HNSW_QUANT_BIN) return NULL; if (hnsw_m == 0) hnsw_m = 16; // Default, useful for RDB files predating // this configuration parameter: it was fixed // to 16. struct vsetObject *vset = createVectorSetObject(dim,quant_type,hnsw_m); RedisModule_Assert(vset != NULL); /* Load projection matrix if present */ uint32_t save_flags = RedisModule_LoadUnsigned(rdb); if (RedisModule_IsIOError(rdb)) goto ioerr; int has_projection = save_flags & SAVE_FLAG_HAS_PROJMATRIX; int has_attribs = save_flags & SAVE_FLAG_HAS_ATTRIBS; if (has_projection) { 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; 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); } while(elements--) { // Load associated string element. RedisModuleString *ele = RedisModule_LoadString(rdb); if (RedisModule_IsIOError(rdb)) goto ioerr; RedisModuleString *attrib = NULL; if (has_attribs) { attrib = RedisModule_LoadString(rdb); if (RedisModule_IsIOError(rdb)) { RedisModule_FreeString(NULL,ele); goto ioerr; } size_t attrlen; RedisModule_StringPtrLen(attrib,&attrlen); if (attrlen == 0) { RedisModule_FreeString(NULL,attrib); attrib = NULL; } } size_t vector_len; void *vector = RedisModule_LoadStringBuffer(rdb, &vector_len); if (RedisModule_IsIOError(rdb)) { RedisModule_FreeString(NULL,ele); if (attrib) RedisModule_FreeString(NULL,attrib); goto ioerr; } uint32_t vector_bytes = hnsw_quants_bytes(vset->hnsw); if (vector_len != vector_bytes) { RedisModule_LogIOError(rdb,"warning", "Mismatching vector dimension"); RedisModule_FreeString(NULL,ele); if (attrib) RedisModule_FreeString(NULL,attrib); RedisModule_Free(vector); goto ioerr; } // Load node parameters back. uint32_t params_count = RedisModule_LoadUnsigned(rdb); if (RedisModule_IsIOError(rdb)) { RedisModule_FreeString(NULL,ele); if (attrib) RedisModule_FreeString(NULL,attrib); RedisModule_Free(vector); goto ioerr; } uint64_t *params = RedisModule_Alloc(params_count*sizeof(uint64_t)); for (uint32_t j = 0; j < params_count; j++) { // Ignore loading errors here: handled at the end of the loop. params[j] = RedisModule_LoadUnsigned(rdb); } if (RedisModule_IsIOError(rdb)) { RedisModule_FreeString(NULL,ele); if (attrib) RedisModule_FreeString(NULL,attrib); RedisModule_Free(vector); RedisModule_Free(params); goto ioerr; } struct vsetNodeVal *nv = RedisModule_Alloc(sizeof(*nv)); nv->item = ele; nv->attrib = attrib; hnswNode *node = hnsw_insert_serialized(vset->hnsw, vector, params, params_count, nv); if (node == NULL) { RedisModule_LogIOError(rdb,"warning", "Vector set node index loading error"); vectorSetReleaseNodeValue(nv); RedisModule_Free(vector); RedisModule_Free(params); goto ioerr; } if (nv->attrib) vset->numattribs++; RedisModule_DictSet(vset->dict,ele,node); RedisModule_Free(vector); RedisModule_Free(params); } if (!hnsw_deserialize_index(vset->hnsw)) goto ioerr; return vset; ioerr: /* We want to recover from I/O errors and free the partially allocated * data structure to support diskless replication. */ vectorSetReleaseObject(vset); return NULL; } /* Calculate memory usage */ size_t VectorSetMemUsage(const void *value) { const struct vsetObject *vset = value; size_t size = sizeof(*vset); /* Account for HNSW index base structure */ size += sizeof(HNSW); /* Account for projection matrix if present */ if (vset->proj_matrix) { /* For the matrix size, we need the input dimension. We can get it * from the first node if the set is not empty. */ uint32_t input_dim = vset->proj_input_size; uint32_t output_dim = vset->hnsw->vector_dim; size += sizeof(float) * input_dim * output_dim; } /* Account for each node's memory usage. */ hnswNode *node = vset->hnsw->head; if (node == NULL) return size; /* Base node structure. */ size += sizeof(*node) * vset->hnsw->node_count; /* Vector storage. */ uint64_t vec_storage = hnsw_quants_bytes(vset->hnsw); size += vec_storage * vset->hnsw->node_count; /* Layers array. We use 1.33 as average nodes layers count. */ uint64_t layers_storage = sizeof(hnswNodeLayer) * vset->hnsw->node_count; layers_storage = layers_storage * 4 / 3; // 1.33 times. size += layers_storage; /* All the nodes have layer 0 links. */ uint64_t level0_links = node->layers[0].max_links; uint64_t other_levels_links = level0_links/2; size += sizeof(hnswNode*) * level0_links * vset->hnsw->node_count; /* Add the 0.33 remaining part, but upper layers have less links. */ size += (sizeof(hnswNode*) * other_levels_links * vset->hnsw->node_count)/3; /* Associated string value and attributres. * Use Redis Module API to get string size, and guess that all the * elements have similar size as the first few. */ size_t items_scanned = 0, items_size = 0; size_t attribs_scanned = 0, attribs_size = 0; int scan_effort = 20; while(scan_effort > 0 && node) { struct vsetNodeVal *nv = node->value; items_size += RedisModule_MallocSizeString(nv->item); items_scanned++; if (nv->attrib) { attribs_size += RedisModule_MallocSizeString(nv->attrib); attribs_scanned++; } scan_effort--; node = node->next; } /* Add the memory usage due to items. */ if (items_scanned) size += items_size / items_scanned * vset->hnsw->node_count; /* Add memory usage due to attributres. */ if (attribs_scanned == 0) { /* We were not lucky enough to find a single attribute in the * first few items? Let's use a fixed arbitrary value. */ attribs_scanned = 1; attribs_size = 64; } size += attribs_size / attribs_scanned * vset->numattribs; /* Account for dictionary overhead - this is an approximation. */ size += RedisModule_DictSize(vset->dict) * (sizeof(void*) * 2); return size; } /* Free the entire data structure */ void VectorSetFree(void *value) { struct vsetObject *vset = value; vectorSetWaitAllBackgroundClients(vset,1); vectorSetReleaseObject(value); } /* Add object digest to the digest context */ void VectorSetDigest(RedisModuleDigest *md, void *value) { struct vsetObject *vset = value; /* Add consistent order-independent hash of all vectors */ hnswNode *node = vset->hnsw->head; /* Hash the vector dimension and number of nodes. */ RedisModule_DigestAddLongLong(md, vset->hnsw->node_count); RedisModule_DigestAddLongLong(md, vset->hnsw->vector_dim); RedisModule_DigestEndSequence(md); while(node) { struct vsetNodeVal *nv = node->value; /* Hash each vector component */ RedisModule_DigestAddStringBuffer(md, node->vector, hnsw_quants_bytes(vset->hnsw)); /* Hash the associated value */ size_t len; const char *str = RedisModule_StringPtrLen(nv->item, &len); RedisModule_DigestAddStringBuffer(md, (char*)str, len); if (nv->attrib) { str = RedisModule_StringPtrLen(nv->attrib, &len); RedisModule_DigestAddStringBuffer(md, (char*)str, len); } node = node->next; RedisModule_DigestEndSequence(md); } } /* This function must be present on each Redis module. It is used in order to * register the commands into the Redis server. */ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argc); if (RedisModule_Init(ctx,"vectorset",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR; RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS|REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD); RedisModuleTypeMethods tm = { .version = REDISMODULE_TYPE_METHOD_VERSION, .rdb_load = VectorSetRdbLoad, .rdb_save = VectorSetRdbSave, .aof_rewrite = NULL, .mem_usage = VectorSetMemUsage, .free = VectorSetFree, .digest = VectorSetDigest }; VectorSetType = RedisModule_CreateDataType(ctx,"vectorset",0,&tm); if (VectorSetType == NULL) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"VADD", VADD_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"VREM", VREM_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"VSIM", VSIM_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VDIM", VDIM_RedisCommand, "readonly fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VCARD", VCARD_RedisCommand, "readonly fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VEMB", VEMB_RedisCommand, "readonly fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VLINKS", VLINKS_RedisCommand, "readonly fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VINFO", VINFO_RedisCommand, "readonly fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VSETATTR", VSETATTR_RedisCommand, "write fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VGETATTR", VGETATTR_RedisCommand, "readonly fast", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VRANDMEMBER", VRANDMEMBER_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "VISMEMBER", VISMEMBER_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR; hnsw_set_allocator(RedisModule_Free, RedisModule_Alloc, RedisModule_Realloc); return REDISMODULE_OK; } int VectorSets_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { return RedisModule_OnLoad(ctx, argv, argc); } redis-8.0.2/modules/vector-sets/w2v.c000066400000000000000000000434721501533116600174520ustar00rootroot00000000000000/* * HNSW (Hierarchical Navigable Small World) Implementation * Based on the paper by Yu. A. Malkov, D. A. Yashunin * * Copyright (c) 2009-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). * Originally authored by: Salvatore Sanfilippo */ #define _DEFAULT_SOURCE #define _USE_MATH_DEFINES #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "hnsw.h" /* Get current time in milliseconds */ uint64_t ms_time(void) { struct timeval tv; gettimeofday(&tv, NULL); return (uint64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); } /* Implementation of the recall test with random vectors. */ void test_recall(HNSW *index, int ef) { const int num_test_vectors = 10000; const int k = 100; // Number of nearest neighbors to find. if (ef < k) ef = k; // Add recall distribution counters (2% bins from 0-100%). int recall_bins[50] = {0}; // Create array to store vectors for mixing. int num_source_vectors = 1000; // Enough, since we mix them. float **source_vectors = malloc(sizeof(float*) * num_source_vectors); if (!source_vectors) { printf("Failed to allocate memory for source vectors\n"); return; } // Allocate memory for each source vector. for (int i = 0; i < num_source_vectors; i++) { source_vectors[i] = malloc(sizeof(float) * 300); if (!source_vectors[i]) { printf("Failed to allocate memory for source vector %d\n", i); // Clean up already allocated vectors. for (int j = 0; j < i; j++) free(source_vectors[j]); free(source_vectors); return; } } /* Populate source vectors from the index, we just scan the * first N items. */ int source_count = 0; hnswNode *current = index->head; while (current && source_count < num_source_vectors) { hnsw_get_node_vector(index, current, source_vectors[source_count]); source_count++; current = current->next; } if (source_count < num_source_vectors) { printf("Warning: Only found %d nodes for source vectors\n", source_count); num_source_vectors = source_count; } // Allocate memory for test vector. float *test_vector = malloc(sizeof(float) * 300); if (!test_vector) { printf("Failed to allocate memory for test vector\n"); for (int i = 0; i < num_source_vectors; i++) { free(source_vectors[i]); } free(source_vectors); return; } // Allocate memory for results. hnswNode **hnsw_results = malloc(sizeof(hnswNode*) * ef); hnswNode **linear_results = malloc(sizeof(hnswNode*) * ef); float *hnsw_distances = malloc(sizeof(float) * ef); float *linear_distances = malloc(sizeof(float) * ef); if (!hnsw_results || !linear_results || !hnsw_distances || !linear_distances) { printf("Failed to allocate memory for results\n"); if (hnsw_results) free(hnsw_results); if (linear_results) free(linear_results); if (hnsw_distances) free(hnsw_distances); if (linear_distances) free(linear_distances); for (int i = 0; i < num_source_vectors; i++) free(source_vectors[i]); free(source_vectors); free(test_vector); return; } // Initialize random seed. srand(time(NULL)); // Perform recall test. printf("\nPerforming recall test with EF=%d on %d random vectors...\n", ef, num_test_vectors); double total_recall = 0.0; for (int t = 0; t < num_test_vectors; t++) { // Create a random vector by mixing 3 existing vectors. float weights[3] = {0.0}; int src_indices[3] = {0}; // Generate random weights. float weight_sum = 0.0; for (int i = 0; i < 3; i++) { weights[i] = (float)rand() / RAND_MAX; weight_sum += weights[i]; src_indices[i] = rand() % num_source_vectors; } // Normalize weights. for (int i = 0; i < 3; i++) weights[i] /= weight_sum; // Mix vectors. memset(test_vector, 0, sizeof(float) * 300); for (int i = 0; i < 3; i++) { for (int j = 0; j < 300; j++) { test_vector[j] += weights[i] * source_vectors[src_indices[i]][j]; } } // Perform HNSW search with the specified EF parameter. int slot = hnsw_acquire_read_slot(index); int hnsw_found = hnsw_search(index, test_vector, ef, hnsw_results, hnsw_distances, slot, 0); // Perform linear search (ground truth). int linear_found = hnsw_ground_truth_with_filter(index, test_vector, ef, linear_results, linear_distances, slot, 0, NULL, NULL); hnsw_release_read_slot(index, slot); // Calculate recall for this query (intersection size / k). if (hnsw_found > k) hnsw_found = k; if (linear_found > k) linear_found = k; int intersection_count = 0; for (int i = 0; i < linear_found; i++) { for (int j = 0; j < hnsw_found; j++) { if (linear_results[i] == hnsw_results[j]) { intersection_count++; break; } } } double recall = (double)intersection_count / linear_found; total_recall += recall; // Add to distribution bins (2% steps) int bin_index = (int)(recall * 50); if (bin_index >= 50) bin_index = 49; // Handle 100% recall case recall_bins[bin_index]++; // Show progress. if ((t+1) % 1000 == 0 || t == num_test_vectors-1) { printf("Processed %d/%d queries, current avg recall: %.2f%%\n", t+1, num_test_vectors, (total_recall / (t+1)) * 100); } } // Calculate and print final average recall. double avg_recall = (total_recall / num_test_vectors) * 100; printf("\nRecall Test Results:\n"); printf("Average recall@%d (EF=%d): %.2f%%\n", k, ef, avg_recall); // Print recall distribution histogram. printf("\nRecall Distribution (2%% bins):\n"); printf("================================\n"); // Find the maximum bin count for scaling. int max_count = 0; for (int i = 0; i < 50; i++) { if (recall_bins[i] > max_count) max_count = recall_bins[i]; } // Scale factor for histogram (max 50 chars wide) const int max_bars = 50; double scale = (max_count > max_bars) ? (double)max_bars / max_count : 1.0; // Print the histogram. for (int i = 0; i < 50; i++) { int bar_len = (int)(recall_bins[i] * scale); printf("%3d%%-%-3d%% | %-6d |", i*2, (i+1)*2, recall_bins[i]); for (int j = 0; j < bar_len; j++) printf("#"); printf("\n"); } // Cleanup. free(hnsw_results); free(linear_results); free(hnsw_distances); free(linear_distances); free(test_vector); for (int i = 0; i < num_source_vectors; i++) free(source_vectors[i]); free(source_vectors); } /* Example usage in main() */ int w2v_single_thread(int m_param, int quantization, uint64_t numele, int massdel, int self_recall, int recall_ef) { /* Create index */ HNSW *index = hnsw_new(300, quantization, m_param); float v[300]; uint16_t wlen; FILE *fp = fopen("word2vec.bin","rb"); if (fp == NULL) { perror("word2vec.bin file missing"); exit(1); } unsigned char header[8]; if (fread(header,8,1,fp) <= 0) { // Skip header perror("Unexpected EOF"); exit(1); } uint64_t id = 0; uint64_t start_time = ms_time(); char *word = NULL; hnswNode *search_node = NULL; while(id < numele) { if (fread(&wlen,2,1,fp) == 0) break; word = malloc(wlen+1); if (fread(word,wlen,1,fp) <= 0) { perror("unexpected EOF"); exit(1); } word[wlen] = 0; if (fread(v,300*sizeof(float),1,fp) <= 0) { perror("unexpected EOF"); exit(1); } // Plain API that acquires a write lock for the whole time. hnswNode *added = hnsw_insert(index, v, NULL, 0, id++, word, 200); if (!strcmp(word,"banana")) search_node = added; if (!(id % 10000)) printf("%llu added\n", (unsigned long long)id); } uint64_t elapsed = ms_time() - start_time; fclose(fp); printf("%llu words added (%llu words/sec), last word: %s\n", (unsigned long long)index->node_count, (unsigned long long)id*1000/elapsed, word); /* Search query */ if (search_node == NULL) search_node = index->head; hnsw_get_node_vector(index,search_node,v); hnswNode *neighbors[10]; float distances[10]; int found, j; start_time = ms_time(); for (j = 0; j < 20000; j++) found = hnsw_search(index, v, 10, neighbors, distances, 0, 0); elapsed = ms_time() - start_time; printf("%d searches performed (%llu searches/sec), nodes found: %d\n", j, (unsigned long long)j*1000/elapsed, found); if (found > 0) { printf("Found %d neighbors:\n", found); for (int i = 0; i < found; i++) { printf("Node ID: %llu, distance: %f, word: %s\n", (unsigned long long)neighbors[i]->id, distances[i], (char*)neighbors[i]->value); } } // Self-recall test (ability to find the node by its own vector). if (self_recall) { hnsw_print_stats(index); hnsw_test_graph_recall(index,200,0); } // Recall test with random vectors. if (recall_ef > 0) { test_recall(index, recall_ef); } uint64_t connected_nodes; int reciprocal_links; hnsw_validate_graph(index, &connected_nodes, &reciprocal_links); if (massdel) { int remove_perc = 95; printf("\nRemoving %d%% of nodes...\n", remove_perc); uint64_t initial_nodes = index->node_count; hnswNode *current = index->head; while (current && index->node_count > initial_nodes*(100-remove_perc)/100) { hnswNode *next = current->next; hnsw_delete_node(index,current,free); current = next; // In order to don't remove only contiguous nodes, from time // skip a node. if (current && !(random() % remove_perc)) current = current->next; } printf("%llu nodes left\n", (unsigned long long)index->node_count); // Test again. hnsw_validate_graph(index, &connected_nodes, &reciprocal_links); hnsw_test_graph_recall(index,200,0); } hnsw_free(index,free); return 0; } struct threadContext { pthread_mutex_t FileAccessMutex; uint64_t numele; _Atomic uint64_t SearchesDone; _Atomic uint64_t id; FILE *fp; HNSW *index; float *search_vector; }; // Note that in practical terms inserting with many concurrent threads // may be *slower* and not faster, because there is a lot of // contention. So this is more a robustness test than anything else. // // The optimistic commit API goal is actually to exploit the ability to // add faster when there are many concurrent reads. void *threaded_insert(void *ctxptr) { struct threadContext *ctx = ctxptr; char *word; float v[300]; uint16_t wlen; while(1) { pthread_mutex_lock(&ctx->FileAccessMutex); if (fread(&wlen,2,1,ctx->fp) == 0) break; pthread_mutex_unlock(&ctx->FileAccessMutex); word = malloc(wlen+1); if (fread(word,wlen,1,ctx->fp) <= 0) { perror("Unexpected EOF"); exit(1); } word[wlen] = 0; if (fread(v,300*sizeof(float),1,ctx->fp) <= 0) { perror("Unexpected EOF"); exit(1); } // Check-and-set API that performs the costly scan for similar // nodes concurrently with other read threads, and finally // applies the check if the graph wasn't modified. InsertContext *ic; uint64_t next_id = ctx->id++; ic = hnsw_prepare_insert(ctx->index, v, NULL, 0, next_id, 200); if (hnsw_try_commit_insert(ctx->index, ic, word) == NULL) { // This time try locking since the start. hnsw_insert(ctx->index, v, NULL, 0, next_id, word, 200); } if (next_id >= ctx->numele) break; if (!((next_id+1) % 10000)) printf("%llu added\n", (unsigned long long)next_id+1); } return NULL; } void *threaded_search(void *ctxptr) { struct threadContext *ctx = ctxptr; /* Search query */ hnswNode *neighbors[10]; float distances[10]; int found = 0; uint64_t last_id = 0; while(ctx->id < 1000000) { int slot = hnsw_acquire_read_slot(ctx->index); found = hnsw_search(ctx->index, ctx->search_vector, 10, neighbors, distances, slot, 0); hnsw_release_read_slot(ctx->index,slot); last_id = ++ctx->id; } if (found > 0 && last_id == 1000000) { printf("Found %d neighbors:\n", found); for (int i = 0; i < found; i++) { printf("Node ID: %llu, distance: %f, word: %s\n", (unsigned long long)neighbors[i]->id, distances[i], (char*)neighbors[i]->value); } } return NULL; } int w2v_multi_thread(int m_param, int numthreads, int quantization, uint64_t numele) { /* Create index */ struct threadContext ctx; ctx.index = hnsw_new(300, quantization, m_param); ctx.fp = fopen("word2vec.bin","rb"); if (ctx.fp == NULL) { perror("word2vec.bin file missing"); exit(1); } unsigned char header[8]; if (fread(header,8,1,ctx.fp) <= 0) { // Skip header perror("Unexpected EOF"); exit(1); } pthread_mutex_init(&ctx.FileAccessMutex,NULL); uint64_t start_time = ms_time(); ctx.id = 0; ctx.numele = numele; pthread_t threads[numthreads]; for (int j = 0; j < numthreads; j++) pthread_create(&threads[j], NULL, threaded_insert, &ctx); // Wait for all the threads to terminate adding items. for (int j = 0; j < numthreads; j++) pthread_join(threads[j],NULL); uint64_t elapsed = ms_time() - start_time; fclose(ctx.fp); // Obtain the last word. hnswNode *node = ctx.index->head; char *word = node->value; // We will search this last inserted word in the next test. // Let's save its embedding. ctx.search_vector = malloc(sizeof(float)*300); hnsw_get_node_vector(ctx.index,node,ctx.search_vector); printf("%llu words added (%llu words/sec), last word: %s\n", (unsigned long long)ctx.index->node_count, (unsigned long long)ctx.id*1000/elapsed, word); /* Search query */ start_time = ms_time(); ctx.id = 0; // We will use this atomic field to stop at N queries done. for (int j = 0; j < numthreads; j++) pthread_create(&threads[j], NULL, threaded_search, &ctx); // Wait for all the threads to terminate searching. for (int j = 0; j < numthreads; j++) pthread_join(threads[j],NULL); elapsed = ms_time() - start_time; printf("%llu searches performed (%llu searches/sec)\n", (unsigned long long)ctx.id, (unsigned long long)ctx.id*1000/elapsed); hnsw_print_stats(ctx.index); uint64_t connected_nodes; int reciprocal_links; hnsw_validate_graph(ctx.index, &connected_nodes, &reciprocal_links); printf("%llu connected nodes. Links all reciprocal: %d\n", (unsigned long long)connected_nodes, reciprocal_links); hnsw_free(ctx.index,free); return 0; } int main(int argc, char **argv) { int quantization = HNSW_QUANT_NONE; int numthreads = 0; uint64_t numele = 20000; int m_param = 0; // Default value (0 means use HNSW_DEFAULT_M) /* This you can enable in single thread mode for testing: */ int massdel = 0; // If true, does the mass deletion test. int self_recall = 0; // If true, does the self-recall test. int recall_ef = 0; // If not 0, does the recall test with this EF value. for (int j = 1; j < argc; j++) { int moreargs = argc-j-1; if (!strcasecmp(argv[j],"--quant")) { quantization = HNSW_QUANT_Q8; } else if (!strcasecmp(argv[j],"--bin")) { quantization = HNSW_QUANT_BIN; } else if (!strcasecmp(argv[j],"--mass-del")) { massdel = 1; } else if (!strcasecmp(argv[j],"--self-recall")) { self_recall = 1; } else if (moreargs >= 1 && !strcasecmp(argv[j],"--recall")) { recall_ef = atoi(argv[j+1]); j++; } else if (moreargs >= 1 && !strcasecmp(argv[j],"--threads")) { numthreads = atoi(argv[j+1]); j++; } else if (moreargs >= 1 && !strcasecmp(argv[j],"--numele")) { numele = strtoll(argv[j+1],NULL,0); j++; if (numele < 1) numele = 1; } else if (moreargs >= 1 && !strcasecmp(argv[j],"--m")) { m_param = atoi(argv[j+1]); j++; } else if (!strcasecmp(argv[j],"--help")) { printf("%s [--quant] [--bin] [--thread ] [--numele ] [--m ] [--mass-del] [--self-recall] [--recall ]\n", argv[0]); exit(0); } else { printf("Unrecognized option or wrong number of arguments: %s\n", argv[j]); exit(1); } } if (quantization == HNSW_QUANT_NONE) { printf("You can enable quantization with --quant\n"); } if (numthreads > 0) { w2v_multi_thread(m_param, numthreads, quantization, numele); } else { printf("Single thread execution. Use --threads 4 for concurrent API\n"); w2v_single_thread(m_param, quantization, numele, massdel, self_recall, recall_ef); } } redis-8.0.2/redis-full.conf000066400000000000000000000316451501533116600155560ustar00rootroot00000000000000include redis.conf loadmodule ./modules/redisbloom/redisbloom.so loadmodule ./modules/redisearch/redisearch.so loadmodule ./modules/redisjson/rejson.so loadmodule ./modules/redistimeseries/redistimeseries.so ############################## QUERY ENGINE CONFIG ############################ # Keep numeric ranges in numeric tree parent nodes of leafs for `x` generations. # numeric, valid range: [0, 2], default: 0 # # search-_numeric-ranges-parents 0 # The number of iterations to run while performing background indexing # before we call usleep(1) (sleep for 1 micro-second) and make sure that we # allow redis to process other commands. # numeric, valid range: [1, UINT32_MAX], default: 100 # # search-bg-index-sleep-gap 100 # The default dialect used in search queries. # numeric, valid range: [1, 4], default: 1 # # search-default-dialect 1 # the fork gc will only start to clean when the number of not cleaned document # will exceed this threshold. # numeric, valid range: [1, LLONG_MAX], default: 100 # # search-fork-gc-clean-threshold 100 # interval (in seconds) in which to retry running the forkgc after failure. # numeric, valid range: [1, LLONG_MAX], default: 5 # # search-fork-gc-retry-interval 5 # interval (in seconds) in which to run the fork gc (relevant only when fork # gc is used). # numeric, valid range: [1, LLONG_MAX], default: 30 # # search-fork-gc-run-interval 30 # the amount of seconds for the fork GC to sleep before exiting. # numeric, valid range: [0, LLONG_MAX], default: 0 # # search-fork-gc-sleep-before-exit 0 # Scan this many documents at a time during every GC iteration. # numeric, valid range: [1, LLONG_MAX], default: 100 # # search-gc-scan-size 100 # Max number of cursors for a given index that can be opened inside of a shard. # numeric, valid range: [0, LLONG_MAX], default: 128 # # search-index-cursor-limit 128 # Maximum number of results from ft.aggregate command. # numeric, valid range: [0, (1ULL << 31)], default: 1ULL << 31 # # search-max-aggregate-results 2147483648 # Maximum prefix expansions to be used in a query. # numeric, valid range: [1, LLONG_MAX], default: 200 # # search-max-prefix-expansions 200 # Maximum runtime document table size (for this process). # numeric, valid range: [1, 100000000], default: 1000000 # # search-max-doctablesize 1000000 # max idle time allowed to be set for cursor, setting it high might cause # high memory consumption. # numeric, valid range: [1, LLONG_MAX], default: 300000 # # search-cursor-max-idle 300000 # Maximum number of results from ft.search command. # numeric, valid range: [0, 1ULL << 31], default: 1000000 # # search-max-search-results 1000000 # Number of worker threads to use for background tasks when the server is # in an operation event. # numeric, valid range: [1, 16], default: 4 # # search-min-operation-workers 4 # Minimum length of term to be considered for phonetic matching. # numeric, valid range: [1, LLONG_MAX], default: 3 # # search-min-phonetic-term-len 3 # the minimum prefix for expansions (`*`). # numeric, valid range: [1, LLONG_MAX], default: 2 # # search-min-prefix 2 # the minimum word length to stem. # numeric, valid range: [2, UINT32_MAX], default: 4 # # search-min-stem-len 4 # Delta used to increase positional offsets between array # slots for multi text values. # Can control the level of separation between phrases in different # array slots (related to the SLOP parameter of ft.search command)" # numeric, valid range: [1, UINT32_MAX], default: 100 # # search-multi-text-slop 100 # Used for setting the buffer limit threshold for vector similarity tiered # HNSW index, so that if we are using WORKERS for indexing, and the # number of vectors waiting in the buffer to be indexed exceeds this limit, # we insert new vectors directly into HNSW. # numeric, valid range: [0, LLONG_MAX], default: 1024 # # search-tiered-hnsw-buffer-limit 1024 # Query timeout. # numeric, valid range: [1, LLONG_MAX], default: 500 # # search-timeout 500 # minimum number of iterators in a union from which the iterator will # will switch to heap-based implementation. # numeric, valid range: [1, LLONG_MAX], default: 20 # switch to heap based implementation. # # search-union-iterator-heap 20 # The maximum memory resize for vector similarity indexes (in bytes). # numeric, valid range: [0, UINT32_MAX], default: 0 # # search-vss-max-resize 0 # Number of worker threads to use for query processing and background tasks. # numeric, valid range: [0, 16], default: 0 # This configuration also affects the number of connections per shard. # # search-workers 0 # The number of high priority tasks to be executed at any given time by the # worker thread pool, before executing low priority tasks. After this number # of high priority tasks are being executed, the worker thread pool will # execute high and low priority tasks alternately. # numeric, valid range: [0, LLONG_MAX], default: 1 # # search-workers-priority-bias-threshold 1 # Load extension scoring/expansion module. Immutable. # string, default: "" # # search-ext-load "" # Path to Chinese dictionary configuration file (for Chinese tokenization). Immutable. # string, default: "" # # search-friso-ini "" # Action to perform when search timeout is exceeded (choose RETURN or FAIL). # enum, valid values: ["return", "fail"], default: "fail" # # search-on-timeout fail # Determine whether some index resources are free on a second thread. # bool, default: yes # # search-_free-resource-on-thread yes # Enable legacy compression of double to float. # bool, default: no # # search-_numeric-compress no # Disable print of time for ft.profile. For testing only. # bool, default: yes # # search-_print-profile-clock yes # Intersection iterator orders the children iterators by their relative estimated # number of results in ascending order, so that if we see first iterators with # a lower count of results we will skip a larger number of results, which # translates into faster iteration. If this flag is set, we use this # optimization in a way where union iterators are being factorize by the number # of their own children, so that we sort by the number of children times the # overall estimated number of results instead. # bool, default: no # # search-_prioritize-intersect-union-children no # Set to run without memory pools. # bool, default: no # # search-no-mem-pools no # Disable garbage collection (for this process). # bool, default: no # # search-no-gc no # Enable commands filter which optimize indexing on partial hash updates. # bool, default: no # # search-partial-indexed-docs no # Disable compression for DocID inverted index. Boost CPU performance. # bool, default: no # # search-raw-docid-encoding no # Number of search threads in the coordinator thread pool. # numeric, valid range: [1, LLONG_MAX], default: 20 # # search-threads 20 # Timeout for topology validation (in milliseconds). After this timeout, # any pending requests will be processed, even if the topology is not fully connected. # numeric, valid range: [0, LLONG_MAX], default: 30000 # # search-topology-validation-timeout 30000 ############################## TIME SERIES CONFIG ############################# # The maximal number of per-shard threads for cross-key queries when using cluster mode # (TS.MRANGE, TS.MREVRANGE, TS.MGET, and TS.QUERYINDEX). # Note: increasing this value may either increase or decrease the performance. # integer, valid range: [1..16], default: 3 # This is a load-time configuration parameter. # # ts-num-threads 3 # Default compaction rules for newly created key with TS.ADD, TS.INCRBY, and TS.DECRBY. # Has no effect on keys created with TS.CREATE. # This default value is applied to each new time series upon its creation. # string, see documentation for rules format, default: no compaction rules # # ts-compaction-policy "" # Default chunk encoding for automatically-created compacted time series. # This default value is applied to each new compacted time series automatically # created when ts-compaction-policy is specified. # valid values: COMPRESSED, UNCOMPRESSED, default: COMPRESSED # # ts-encoding COMPRESSED # Default retention period, in milliseconds. 0 means no expiration. # This default value is applied to each new time series upon its creation. # If ts-compaction-policy is specified - it is overridden for created # compactions as specified in ts-compaction-policy. # integer, valid range: [0 .. LLONG_MAX], default: 0 # # ts-retention-policy 0 # Default policy for handling insertion (TS.ADD and TS.MADD) of multiple # samples with identical timestamps. # This default value is applied to each new time series upon its creation. # string, valid values: BLOCK, FIRST, LAST, MIN, MAX, SUM, default: BLOCK # # ts-duplicate-policy BLOCK # Default initial allocation size, in bytes, for the data part of each new chunk # This default value is applied to each new time series upon its creation. # integer, valid range: [48 .. 1048576]; must be a multiple of 8, default: 4096 # # ts-chunk-size-bytes 4096 # Default values for newly created time series. # Many sensors report data periodically. Often, the difference between the measured # value and the previous measured value is negligible and related to random noise # or to measurement accuracy limitations. In such situations it may be preferable # not to add the new measurement to the time series. # A new sample is considered a duplicate and is ignored if the following conditions are met: # - The time series is not a compaction; # - The time series' DUPLICATE_POLICY IS LAST; # - The sample is added in-order (timestamp >= max_timestamp); # - The difference of the current timestamp from the previous timestamp # (timestamp - max_timestamp) is less than or equal to ts-ignore-max-time-diff # - The absolute value difference of the current value from the value at the previous maximum timestamp # (abs(value - value_at_max_timestamp) is less than or equal to ts-ignore-max-val-diff. # where max_timestamp is the timestamp of the sample with the largest timestamp in the time series, # and value_at_max_timestamp is the value at max_timestamp. # ts-ignore-max-time-diff: integer, valid range: [0 .. LLONG_MAX], default: 0 # ts-ignore-max-val-diff: double, Valid range: [0 .. DBL_MAX], default: 0 # # ts-ignore-max-time-diff 0 # ts-ignore-max-val-diff 0 ########################### BLOOM FILTERS CONFIG ############################## # Defaults values for new Bloom filters created with BF.ADD, BF.MADD, BF.INSERT, and BF.RESERVE # These defaults are applied to each new Bloom filter upon its creation. # Error ratio # The desired probability for false positives. # For a false positive rate of 0.1% (1 in 1000) - the value should be 0.001. # double, Valid range: (0 .. 1), value greater than 0.25 is treated as 0.25, default: 0.01 # # bf-error-rate 0.01 # Initial capacity # The number of entries intended to be added to the filter. # integer, valid range: [1 .. 1GB], default: 100 # # bf-initial-size 100 # Expansion factor # When capacity is reached, an additional sub-filter is created. # The size of the new sub-filter is the size of the last sub-filter multiplied # by expansion. # integer, [0 .. 32768]. 0 is equivalent to NONSCALING. default: 2 # # bf-expansion-factor 2 ########################### CUCKOO FILTERS CONFIG ############################# # Defaults values for new Cuckoo filters created with # CF.ADD, CF.ADDNX, CF.INSERT, CF.INSERTNX, and CF.RESERVE # These defaults are applied to each new Cuckoo filter upon its creation. # Initial capacity # A filter will likely not fill up to 100% of its capacity. # Make sure to reserve extra capacity if you want to avoid expansions. # value is rounded to the next 2^n integer. # integer, valid range: [2*cf-bucket-size .. 1GB], default: 1024 # # cf-initial-size 1024 # Number of items in each bucket # The minimal false positive rate is 2/255 ~ 0.78% when bucket size of 1 is used. # Larger buckets increase the error rate linearly, but improve the fill rate. # integer, valid range: [1 .. 255], default: 2 # # cf-bucket-size 2 # Maximum iterations # Number of attempts to swap items between buckets before declaring filter # as full and creating an additional filter. # A lower value improves performance. A higher value improves fill rate. # integer, Valid range: [1 .. 65535], default: 20 # # cf-max-iterations 20 # Expansion factor # When a new filter is created, its size is the size of the current filter # multiplied by this factor. # integer, Valid range: [0 .. 32768], 0 is equivalent to NONSCALING, default: 1 # # cf-expansion-factor 1 # Maximum expansions # integer, Valid range: [1 .. 65536], default: 32 # # cf-max-expansions 32 ################################## SECURITY ################################### # # The following is a list of command categories and their meanings: # # * search - Query engine related. # * json - Data type: JSON related. # * timeseries - Data type: time series related. # * bloom - Data type: Bloom filter related. # * cuckoo - Data type: cuckoo filter related. # * topk - Data type: top-k related. # * cms - Data type: count-min sketch related. # * tdigest - Data type: t-digest related. redis-8.0.2/redis.conf000066400000000000000000003275611501533116600146230ustar00rootroot00000000000000# Redis configuration file example. # # Note that in order to read the configuration file, Redis must be # started with the file path as first argument: # # ./redis-server /path/to/redis.conf # Note on units: when memory size is needed, it is possible to specify # it in the usual form of 1k 5GB 4M and so forth: # # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes # # units are case insensitive so 1GB 1Gb 1gB are all the same. ################################## INCLUDES ################################### # Include one or more other config files here. This is useful if you # have a standard template that goes to all Redis servers but also need # to customize a few per-server settings. Include files can include # other files, so use this wisely. # # Note that option "include" won't be rewritten by command "CONFIG REWRITE" # from admin or Redis Sentinel. Since Redis always uses the last processed # line as value of a configuration directive, you'd better put includes # at the beginning of this file to avoid overwriting config change at runtime. # # If instead you are interested in using includes to override configuration # options, it is better to use include as the last line. # # Included paths may contain wildcards. All files matching the wildcards will # be included in alphabetical order. # Note that if an include path contains a wildcards but no files match it when # the server is started, the include statement will be ignored and no error will # be emitted. It is safe, therefore, to include wildcard files from empty # directories. # # include /path/to/local.conf # include /path/to/other.conf # include /path/to/fragments/*.conf # ################################## MODULES ##################################### # Load modules at startup. If the server is not able to load modules # it will abort. It is possible to use multiple loadmodule directives. # # loadmodule /path/to/my_module.so # loadmodule /path/to/other_module.so # loadmodule /path/to/args_module.so [arg [arg ...]] ################################## NETWORK ##################################### # By default, if no "bind" configuration directive is specified, Redis listens # for connections from all available network interfaces on the host machine. # It is possible to listen to just one or multiple selected interfaces using # the "bind" configuration directive, followed by one or more IP addresses. # Each address can be prefixed by "-", which means that redis will not fail to # start if the address is not available. Being not available only refers to # addresses that does not correspond to any network interface. Addresses that # are already in use will always fail, and unsupported protocols will always BE # silently skipped. # # Examples: # # bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses # bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6 # bind * -::* # like the default, all available interfaces # # ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the # internet, binding to all the interfaces is dangerous and will expose the # instance to everybody on the internet. So by default we uncomment the # following bind directive, that will force Redis to listen only on the # IPv4 and IPv6 (if available) loopback interface addresses (this means Redis # will only be able to accept client connections from the same host that it is # running on). # # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES # COMMENT OUT THE FOLLOWING LINE. # # You will also need to set a password unless you explicitly disable protected # mode. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bind 127.0.0.1 -::1 # By default, outgoing connections (from replica to master, from Sentinel to # instances, cluster bus, etc.) are not bound to a specific local address. In # most cases, this means the operating system will handle that based on routing # and the interface through which the connection goes out. # # Using bind-source-addr it is possible to configure a specific address to bind # to, which may also affect how the connection gets routed. # # Example: # # bind-source-addr 10.0.0.1 # Protected mode is a layer of security protection, in order to avoid that # Redis instances left open on the internet are accessed and exploited. # # When protected mode is on and the default user has no password, the server # only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address # (::1) or Unix domain sockets. # # By default protected mode is enabled. You should disable it only if # you are sure you want clients from other hosts to connect to Redis # even if no authentication is configured. protected-mode yes # Redis uses default hardened security configuration directives to reduce the # attack surface on innocent users. Therefore, several sensitive configuration # directives are immutable, and some potentially-dangerous commands are blocked. # # Configuration directives that control files that Redis writes to (e.g., 'dir' # and 'dbfilename') and that aren't usually modified during runtime # are protected by making them immutable. # # Commands that can increase the attack surface of Redis and that aren't usually # called by users are blocked by default. # # These can be exposed to either all connections or just local ones by setting # each of the configs listed below to either of these values: # # no - Block for any connection (remain immutable) # yes - Allow for any connection (no protection) # local - Allow only for local connections. Ones originating from the # IPv4 address (127.0.0.1), IPv6 address (::1) or Unix domain sockets. # # enable-protected-configs no # enable-debug-command no # enable-module-command no # Accept connections on the specified port, default is 6379 (IANA #815344). # If port 0 is specified Redis will not listen on a TCP socket. port 6379 # TCP listen() backlog. # # In high requests-per-second environments you need a high backlog in order # to avoid slow clients connection issues. Note that the Linux kernel # will silently truncate it to the value of /proc/sys/net/core/somaxconn so # make sure to raise both the value of somaxconn and tcp_max_syn_backlog # in order to get the desired effect. tcp-backlog 511 # Unix socket. # # Specify the path for the Unix socket that will be used to listen for # incoming connections. There is no default, so Redis will not listen # on a unix socket when not specified. # # unixsocket /run/redis.sock # unixsocketperm 700 # Close the connection after a client is idle for N seconds (0 to disable) timeout 0 # TCP keepalive. # # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence # of communication. This is useful for two reasons: # # 1) Detect dead peers. # 2) Force network equipment in the middle to consider the connection to be # alive. # # On Linux, the specified value (in seconds) is the period used to send ACKs. # Note that to close the connection the double of the time is needed. # On other kernels the period depends on the kernel configuration. # # A reasonable value for this option is 300 seconds, which is the new # Redis default starting with Redis 3.2.1. tcp-keepalive 300 # Apply OS-specific mechanism to mark the listening socket with the specified # ID, to support advanced routing and filtering capabilities. # # On Linux, the ID represents a connection mark. # On FreeBSD, the ID represents a socket cookie ID. # On OpenBSD, the ID represents a route table ID. # # The default value is 0, which implies no marking is required. # socket-mark-id 0 ################################# TLS/SSL ##################################### # By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration # directive can be used to define TLS-listening ports. To enable TLS on the # default port, use: # # port 0 # tls-port 6379 # Configure a X.509 certificate and private key to use for authenticating the # server to connected clients, masters or cluster peers. These files should be # PEM formatted. # # tls-cert-file redis.crt # tls-key-file redis.key # # If the key file is encrypted using a passphrase, it can be included here # as well. # # tls-key-file-pass secret # Normally Redis uses the same certificate for both server functions (accepting # connections) and client functions (replicating from a master, establishing # cluster bus connections, etc.). # # Sometimes certificates are issued with attributes that designate them as # client-only or server-only certificates. In that case it may be desired to use # different certificates for incoming (server) and outgoing (client) # connections. To do that, use the following directives: # # tls-client-cert-file client.crt # tls-client-key-file client.key # # If the key file is encrypted using a passphrase, it can be included here # as well. # # tls-client-key-file-pass secret # Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange, # required by older versions of OpenSSL (<3.0). Newer versions do not require # this configuration and recommend against it. # # tls-dh-params-file redis.dh # Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL # clients and peers. Redis requires an explicit configuration of at least one # of these, and will not implicitly use the system wide configuration. # # tls-ca-cert-file ca.crt # tls-ca-cert-dir /etc/ssl/certs # By default, clients (including replica servers) on a TLS port are required # to authenticate using valid client side certificates. # # If "no" is specified, client certificates are not required and not accepted. # If "optional" is specified, client certificates are accepted and must be # valid if provided, but are not required. # # tls-auth-clients no # tls-auth-clients optional # By default, a Redis replica does not attempt to establish a TLS connection # with its master. # # Use the following directive to enable TLS on replication links. # # tls-replication yes # By default, the Redis Cluster bus uses a plain TCP connection. To enable # TLS for the bus protocol, use the following directive: # # tls-cluster yes # By default, only TLSv1.2 and TLSv1.3 are enabled and it is highly recommended # that older formally deprecated versions are kept disabled to reduce the attack surface. # You can explicitly specify TLS versions to support. # Allowed values are case insensitive and include "TLSv1", "TLSv1.1", "TLSv1.2", # "TLSv1.3" (OpenSSL >= 1.1.1) or any combination. # To enable only TLSv1.2 and TLSv1.3, use: # # tls-protocols "TLSv1.2 TLSv1.3" # Configure allowed ciphers. See the ciphers(1ssl) manpage for more information # about the syntax of this string. # # Note: this configuration applies only to <= TLSv1.2. # # tls-ciphers DEFAULT:!MEDIUM # Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more # information about the syntax of this string, and specifically for TLSv1.3 # ciphersuites. # # tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 # When choosing a cipher, use the server's preference instead of the client # preference. By default, the server follows the client's preference. # # tls-prefer-server-ciphers yes # By default, TLS session caching is enabled to allow faster and less expensive # reconnections by clients that support it. Use the following directive to disable # caching. # # tls-session-caching no # Change the default number of TLS sessions cached. A zero value sets the cache # to unlimited size. The default size is 20480. # # tls-session-cache-size 5000 # Change the default timeout of cached TLS sessions. The default timeout is 300 # seconds. # # tls-session-cache-timeout 60 ################################# GENERAL ##################################### # By default Redis does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. # When Redis is supervised by upstart or systemd, this parameter has no impact. daemonize no # If you run Redis from upstart or systemd, Redis can interact with your # supervision tree. Options: # supervised no - no supervision interaction # supervised upstart - signal upstart by putting Redis into SIGSTOP mode # requires "expect stop" in your upstart job config # supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET # on startup, and updating Redis status on a regular # basis. # supervised auto - detect upstart or systemd method based on # UPSTART_JOB or NOTIFY_SOCKET environment variables # Note: these supervision methods only signal "process is ready." # They do not enable continuous pings back to your supervisor. # # The default is "no". To run under upstart/systemd, you can simply uncomment # the line below: # # supervised auto # If a pid file is specified, Redis writes it where specified at startup # and removes it at exit. # # When the server runs non daemonized, no pid file is created if none is # specified in the configuration. When the server is daemonized, the pid file # is used even if not specified, defaulting to "/var/run/redis.pid". # # Creating a pid file is best effort: if Redis is not able to create it # nothing bad happens, the server will start and run normally. # # Note that on modern Linux systems "/run/redis.pid" is more conforming # and should be used instead. pidfile /var/run/redis_6379.pid # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably) # warning (only very important / critical messages are logged) # nothing (nothing is logged) loglevel notice # Specify the log file name. Also the empty string can be used to force # Redis to log on the standard output. Note that if you use standard # output for logging but daemonize, logs will be sent to /dev/null logfile "" # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. # syslog-enabled no # Specify the syslog identity. # syslog-ident redis # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. # syslog-facility local0 # To disable the built in crash log, which will possibly produce cleaner core # dumps when they are needed, uncomment the following: # # crash-log-enabled no # To disable the fast memory check that's run as part of the crash log, which # will possibly let redis terminate sooner, uncomment the following: # # crash-memcheck-enabled no # Set the number of databases. The default database is DB 0, you can select # a different one on a per-connection basis using SELECT where # dbid is a number between 0 and 'databases'-1 databases 16 # By default Redis shows an ASCII art logo only when started to log to the # standard output and if the standard output is a TTY and syslog logging is # disabled. Basically this means that normally a logo is displayed only in # interactive sessions. # # However it is possible to force the pre-4.0 behavior and always show a # ASCII art logo in startup logs by setting the following option to yes. always-show-logo no # To avoid logging personal identifiable information (PII) into server log file, # uncomment the following: # # hide-user-data-from-log yes # By default, Redis modifies the process title (as seen in 'top' and 'ps') to # provide some runtime information. It is possible to disable this and leave # the process name as executed by setting the following to no. set-proc-title yes # When changing the process title, Redis uses the following template to construct # the modified title. # # Template variables are specified in curly brackets. The following variables are # supported: # # {title} Name of process as executed if parent, or type of child process. # {listen-addr} Bind address or '*' followed by TCP or TLS port listening on, or # Unix socket if only that's available. # {server-mode} Special mode, i.e. "[sentinel]" or "[cluster]". # {port} TCP port listening on, or 0. # {tls-port} TLS port listening on, or 0. # {unixsocket} Unix domain socket listening on, or "". # {config-file} Name of configuration file used. # proc-title-template "{title} {listen-addr} {server-mode}" # Set the local environment which is used for string comparison operations, and # also affect the performance of Lua scripts. Empty String indicates the locale # is derived from the environment variables. locale-collate "" ################################ SNAPSHOTTING ################################ # Save the DB to disk. # # save [ ...] # # Redis will save the DB if the given number of seconds elapsed and it # surpassed the given number of write operations against the DB. # # Snapshotting can be completely disabled with a single empty string argument # as in following example: # # save "" # # Unless specified otherwise, by default Redis will save the DB: # * After 3600 seconds (an hour) if at least 1 change was performed # * After 300 seconds (5 minutes) if at least 100 changes were performed # * After 60 seconds if at least 10000 changes were performed # # You can set these explicitly by uncommenting the following line. # # save 3600 1 300 100 60 10000 # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. # This will make the user aware (in a hard way) that data is not persisting # on disk properly, otherwise chances are that no one will notice and some # disaster will happen. # # If the background saving process will start working again Redis will # automatically allow writes again. # # However if you have setup your proper monitoring of the Redis server # and persistence, you may want to disable this feature so that Redis will # continue to work as usual even if there are problems with disk, # permissions, and so forth. stop-writes-on-bgsave-error yes # Compress string objects using LZF when dump .rdb databases? # By default compression is enabled as it's almost always a win. # If you want to save some CPU in the saving child set it to 'no' but # the dataset will likely be bigger if you have compressible values or keys. rdbcompression yes # Since version 5 of RDB a CRC64 checksum is placed at the end of the file. # This makes the format more resistant to corruption but there is a performance # hit to pay (around 10%) when saving and loading RDB files, so you can disable it # for maximum performances. # # RDB files created with checksum disabled have a checksum of zero that will # tell the loading code to skip the check. rdbchecksum yes # Enables or disables full sanitization checks for ziplist and listpack etc when # loading an RDB or RESTORE payload. This reduces the chances of a assertion or # crash later on while processing commands. # Options: # no - Never perform full sanitization # yes - Always perform full sanitization # clients - Perform full sanitization only for user connections. # Excludes: RDB files, RESTORE commands received from the master # connection, and client connections which have the # skip-sanitize-payload ACL flag. # The default should be 'clients' but since it currently affects cluster # resharding via MIGRATE, it is temporarily set to 'no' by default. # # sanitize-dump-payload no # The filename where to dump the DB dbfilename dump.rdb # Remove RDB files used by replication in instances without persistence # enabled. By default this option is disabled, however there are environments # where for regulations or other security concerns, RDB files persisted on # disk by masters in order to feed replicas, or stored on disk by replicas # in order to load them for the initial synchronization, should be deleted # ASAP. Note that this option ONLY WORKS in instances that have both AOF # and RDB persistence disabled, otherwise is completely ignored. # # An alternative (and sometimes better) way to obtain the same effect is # to use diskless replication on both master and replicas instances. However # in the case of replicas, diskless is not always an option. rdb-del-sync-files no # The working directory. # # The DB will be written inside this directory, with the filename specified # above using the 'dbfilename' configuration directive. # # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. dir ./ ################################# REPLICATION ################################# # Master-Replica replication. Use replicaof to make a Redis instance a copy of # another Redis server. A few things to understand ASAP about Redis replication. # # +------------------+ +---------------+ # | Master | ---> | Replica | # | (receive writes) | | (exact copy) | # +------------------+ +---------------+ # # 1) Redis replication is asynchronous, but you can configure a master to # stop accepting writes if it appears to be not connected with at least # a given number of replicas. # 2) Redis replicas are able to perform a partial resynchronization with the # master if the replication link is lost for a relatively small amount of # time. You may want to configure the replication backlog size (see the next # sections of this file) with a sensible value depending on your needs. # 3) Replication is automatic and does not need user intervention. After a # network partition replicas automatically try to reconnect to masters # and resynchronize with them. # # replicaof # If the master is password protected (using the "requirepass" configuration # directive below) it is possible to tell the replica to authenticate before # starting the replication synchronization process, otherwise the master will # refuse the replica request. # # masterauth # # However this is not enough if you are using Redis ACLs (for Redis version # 6 or greater), and the default user is not capable of running the PSYNC # command and/or other commands needed for replication. In this case it's # better to configure a special user to use with replication, and specify the # masteruser configuration as such: # # masteruser # # When masteruser is specified, the replica will authenticate against its # master using the new AUTH form: AUTH . # When a replica loses its connection with the master, or when the replication # is still in progress, the replica can act in two different ways: # # 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will # still reply to client requests, possibly with out of date data, or the # data set may just be empty if this is the first synchronization. # # 2) If replica-serve-stale-data is set to 'no' the replica will reply with error # "MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'" # to all data access commands, excluding commands such as: # INFO, REPLICAOF, AUTH, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, # UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, # HOST and LATENCY. # replica-serve-stale-data yes # You can configure a replica instance to accept writes or not. Writing against # a replica instance may be useful to store some ephemeral data (because data # written on a replica will be easily deleted after resync with the master) but # may also cause problems if clients are writing to it because of a # misconfiguration. # # Since Redis 2.6 by default replicas are read-only. # # Note: read only replicas are not designed to be exposed to untrusted clients # on the internet. It's just a protection layer against misuse of the instance. # Still a read only replica exports by default all the administrative commands # such as CONFIG, DEBUG, and so forth. To a limited extent you can improve # security of read only replicas using 'rename-command' to shadow all the # administrative / dangerous commands. replica-read-only yes # Replication SYNC strategy: disk or socket. # # New replicas and reconnecting replicas that are not able to continue the # replication process just receiving differences, need to do what is called a # "full synchronization". An RDB file is transmitted from the master to the # replicas. # # The transmission can happen in two different ways: # # 1) Disk-backed: The Redis master creates a new process that writes the RDB # file on disk. Later the file is transferred by the parent # process to the replicas incrementally. # 2) Diskless: The Redis master creates a new process that directly writes the # RDB file to replica sockets, without touching the disk at all. # # With disk-backed replication, while the RDB file is generated, more replicas # can be queued and served with the RDB file as soon as the current child # producing the RDB file finishes its work. With diskless replication instead # once the transfer starts, new replicas arriving will be queued and a new # transfer will start when the current one terminates. # # When diskless replication is used, the master waits a configurable amount of # time (in seconds) before starting the transfer in the hope that multiple # replicas will arrive and the transfer can be parallelized. # # With slow disks and fast (large bandwidth) networks, diskless replication # works better. repl-diskless-sync yes # When diskless replication is enabled, it is possible to configure the delay # the server waits in order to spawn the child that transfers the RDB via socket # to the replicas. # # This is important since once the transfer starts, it is not possible to serve # new replicas arriving, that will be queued for the next RDB transfer, so the # server waits a delay in order to let more replicas arrive. # # The delay is specified in seconds, and by default is 5 seconds. To disable # it entirely just set it to 0 seconds and the transfer will start ASAP. repl-diskless-sync-delay 5 # When diskless replication is enabled with a delay, it is possible to let # the replication start before the maximum delay is reached if the maximum # number of replicas expected have connected. Default of 0 means that the # maximum is not defined and Redis will wait the full delay. repl-diskless-sync-max-replicas 0 # ----------------------------------------------------------------------------- # WARNING: Since in this setup the replica does not immediately store an RDB on # disk, it may cause data loss during failovers. RDB diskless load + Redis # modules not handling I/O reads may cause Redis to abort in case of I/O errors # during the initial synchronization stage with the master. # ----------------------------------------------------------------------------- # # Replica can load the RDB it reads from the replication link directly from the # socket, or store the RDB to a file and read that file after it was completely # received from the master. # # In many cases the disk is slower than the network, and storing and loading # the RDB file may increase replication time (and even increase the master's # Copy on Write memory and replica buffers). # However, when parsing the RDB file directly from the socket, in order to avoid # data loss it's only safe to flush the current dataset when the new dataset is # fully loaded in memory, resulting in higher memory usage. # For this reason we have the following options: # # "disabled" - Don't use diskless load (store the rdb file to the disk first) # "swapdb" - Keep current db contents in RAM while parsing the data directly # from the socket. Replicas in this mode can keep serving current # dataset while replication is in progress, except for cases where # they can't recognize master as having a data set from same # replication history. # Note that this requires sufficient memory, if you don't have it, # you risk an OOM kill. # "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. repl-diskless-load disabled # Master send PINGs to its replicas in a predefined interval. It's possible to # change this interval with the repl-ping-replica-period option. The default # value is 10 seconds. # # repl-ping-replica-period 10 # The following option sets the replication timeout for: # # 1) Bulk transfer I/O during SYNC, from the point of view of replica. # 2) Master timeout from the point of view of replicas (data, pings). # 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). # # It is important to make sure that this value is greater than the value # specified for repl-ping-replica-period otherwise a timeout will be detected # every time there is low traffic between the master and the replica. The default # value is 60 seconds. # # repl-timeout 60 # Disable TCP_NODELAY on the replica socket after SYNC? # # If you select "yes" Redis will use a smaller number of TCP packets and # less bandwidth to send data to replicas. But this can add a delay for # the data to appear on the replica side, up to 40 milliseconds with # Linux kernels using a default configuration. # # If you select "no" the delay for data to appear on the replica side will # be reduced but more bandwidth will be used for replication. # # By default we optimize for low latency, but in very high traffic conditions # or when the master and replicas are many hops away, turning this to "yes" may # be a good idea. repl-disable-tcp-nodelay no # Set the replication backlog size. The backlog is a buffer that accumulates # replica data when replicas are disconnected for some time, so that when a # replica wants to reconnect again, often a full resync is not needed, but a # partial resync is enough, just passing the portion of data the replica # missed while disconnected. # # The bigger the replication backlog, the longer the replica can endure the # disconnect and later be able to perform a partial resynchronization. # # The backlog is only allocated if there is at least one replica connected. # # repl-backlog-size 1mb # After a master has no connected replicas for some time, the backlog will be # freed. The following option configures the amount of seconds that need to # elapse, starting from the time the last replica disconnected, for the backlog # buffer to be freed. # # Note that replicas never free the backlog for timeout, since they may be # promoted to masters later, and should be able to correctly "partially # resynchronize" with other replicas: hence they should always accumulate backlog. # # A value of 0 means to never release the backlog. # # repl-backlog-ttl 3600 # During a fullsync, the master may decide to send both the RDB file and the # replication stream to the replica in parallel. This approach shifts the # responsibility of buffering the replication stream to the replica during the # fullsync process. The replica accumulates the replication stream data until # the RDB file is fully loaded. Once the RDB delivery is completed and # successfully loaded, the replica begins processing and applying the # accumulated replication data to the db. The configuration below controls how # much replication data the replica can accumulate during a fullsync. # # When the replica reaches this limit, it will stop accumulating further data. # At this point, additional data accumulation may occur on the master side # depending on the 'client-output-buffer-limit ' config of master. # # A value of 0 means replica inherits hard limit of # 'client-output-buffer-limit ' config to limit accumulation size. # # replica-full-sync-buffer-limit 0 # The replica priority is an integer number published by Redis in the INFO # output. It is used by Redis Sentinel in order to select a replica to promote # into a master if the master is no longer working correctly. # # A replica with a low priority number is considered better for promotion, so # for instance if there are three replicas with priority 10, 100, 25 Sentinel # will pick the one with priority 10, that is the lowest. # # However a special priority of 0 marks the replica as not able to perform the # role of master, so a replica with priority of 0 will never be selected by # Redis Sentinel for promotion. # # By default the priority is 100. replica-priority 100 # The propagation error behavior controls how Redis will behave when it is # unable to handle a command being processed in the replication stream from a master # or processed while reading from an AOF file. Errors that occur during propagation # are unexpected, and can cause data inconsistency. However, there are edge cases # in earlier versions of Redis where it was possible for the server to replicate or persist # commands that would fail on future versions. For this reason the default behavior # is to ignore such errors and continue processing commands. # # If an application wants to ensure there is no data divergence, this configuration # should be set to 'panic' instead. The value can also be set to 'panic-on-replicas' # to only panic when a replica encounters an error on the replication stream. One of # these two panic values will become the default value in the future once there are # sufficient safety mechanisms in place to prevent false positive crashes. # # propagation-error-behavior ignore # Replica ignore disk write errors controls the behavior of a replica when it is # unable to persist a write command received from its master to disk. By default, # this configuration is set to 'no' and will crash the replica in this condition. # It is not recommended to change this default, however in order to be compatible # with older versions of Redis this config can be toggled to 'yes' which will just # log a warning and execute the write command it got from the master. # # replica-ignore-disk-write-errors no # ----------------------------------------------------------------------------- # By default, Redis Sentinel includes all replicas in its reports. A replica # can be excluded from Redis Sentinel's announcements. An unannounced replica # will be ignored by the 'sentinel replicas ' command and won't be # exposed to Redis Sentinel's clients. # # This option does not change the behavior of replica-priority. Even with # replica-announced set to 'no', the replica can be promoted to master. To # prevent this behavior, set replica-priority to 0. # # replica-announced yes # It is possible for a master to stop accepting writes if there are less than # N replicas connected, having a lag less or equal than M seconds. # # The N replicas need to be in "online" state. # # The lag in seconds, that must be <= the specified value, is calculated from # the last ping received from the replica, that is usually sent every second. # # This option does not GUARANTEE that N replicas will accept the write, but # will limit the window of exposure for lost writes in case not enough replicas # are available, to the specified number of seconds. # # For example to require at least 3 replicas with a lag <= 10 seconds use: # # min-replicas-to-write 3 # min-replicas-max-lag 10 # # Setting one or the other to 0 disables the feature. # # By default min-replicas-to-write is set to 0 (feature disabled) and # min-replicas-max-lag is set to 10. # A Redis master is able to list the address and port of the attached # replicas in different ways. For example the "INFO replication" section # offers this information, which is used, among other tools, by # Redis Sentinel in order to discover replica instances. # Another place where this info is available is in the output of the # "ROLE" command of a master. # # The listed IP address and port normally reported by a replica is # obtained in the following way: # # IP: The address is auto detected by checking the peer address # of the socket used by the replica to connect with the master. # # Port: The port is communicated by the replica during the replication # handshake, and is normally the port that the replica is using to # listen for connections. # # However when port forwarding or Network Address Translation (NAT) is # used, the replica may actually be reachable via different IP and port # pairs. The following two options can be used by a replica in order to # report to its master a specific set of IP and port, so that both INFO # and ROLE will report those values. # # There is no need to use both the options if you need to override just # the port or the IP address. # # replica-announce-ip 5.5.5.5 # replica-announce-port 1234 ############################### KEYS TRACKING ################################# # Redis implements server assisted support for client side caching of values. # This is implemented using an invalidation table that remembers, using # a radix key indexed by key name, what clients have which keys. In turn # this is used in order to send invalidation messages to clients. Please # check this page to understand more about the feature: # # https://redis.io/docs/latest/develop/use/client-side-caching/ # # When tracking is enabled for a client, all the read only queries are assumed # to be cached: this will force Redis to store information in the invalidation # table. When keys are modified, such information is flushed away, and # invalidation messages are sent to the clients. However if the workload is # heavily dominated by reads, Redis could use more and more memory in order # to track the keys fetched by many clients. # # For this reason it is possible to configure a maximum fill value for the # invalidation table. By default it is set to 1M of keys, and once this limit # is reached, Redis will start to evict keys in the invalidation table # even if they were not modified, just to reclaim memory: this will in turn # force the clients to invalidate the cached values. Basically the table # maximum size is a trade off between the memory you want to spend server # side to track information about who cached what, and the ability of clients # to retain cached objects in memory. # # If you set the value to 0, it means there are no limits, and Redis will # retain as many keys as needed in the invalidation table. # In the "stats" INFO section, you can find information about the number of # keys in the invalidation table at every given moment. # # Note: when key tracking is used in broadcasting mode, no memory is used # in the server side so this setting is useless. # # tracking-table-max-keys 1000000 ################################## SECURITY ################################### # Warning: since Redis is pretty fast, an outside user can try up to # 1 million passwords per second against a modern box. This means that you # should use very strong passwords, otherwise they will be very easy to break. # Note that because the password is really a shared secret between the client # and the server, and should not be memorized by any human, the password # can be easily a long string from /dev/urandom or whatever, so by using a # long and unguessable password no brute force attack will be possible. # Redis ACL users are defined in the following format: # # user ... acl rules ... # # For example: # # user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 # # The special username "default" is used for new connections. If this user # has the "nopass" rule, then new connections will be immediately authenticated # as the "default" user without the need of any password provided via the # AUTH command. Otherwise if the "default" user is not flagged with "nopass" # the connections will start in not authenticated state, and will require # AUTH (or the HELLO command AUTH option) in order to be authenticated and # start to work. # # The ACL rules that describe what a user can do are the following: # # on Enable the user: it is possible to authenticate as this user. # off Disable the user: it's no longer possible to authenticate # with this user, however the already authenticated connections # will still work. # skip-sanitize-payload RESTORE dump-payload sanitization is skipped. # sanitize-payload RESTORE dump-payload is sanitized (default). # + Allow the execution of that command. # May be used with `|` for allowing subcommands (e.g "+config|get") # - Disallow the execution of that command. # May be used with `|` for blocking subcommands (e.g "-config|set") # +@ Allow the execution of all the commands in such category # with valid categories are like @admin, @set, @sortedset, ... # and so forth, see the full list in the server.c file where # the Redis command table is described and defined. # The special category @all means all the commands, but currently # present in the server, and that will be loaded in the future # via modules. # +|first-arg Allow a specific first argument of an otherwise # disabled command. It is only supported on commands with # no sub-commands, and is not allowed as negative form # like -SELECT|1, only additive starting with "+". This # feature is deprecated and may be removed in the future. # allcommands Alias for +@all. Note that it implies the ability to execute # all the future commands loaded via the modules system. # nocommands Alias for -@all. # ~ Add a pattern of keys that can be mentioned as part of # commands. For instance ~* allows all the keys. The pattern # is a glob-style pattern like the one of KEYS. # It is possible to specify multiple patterns. # %R~ Add key read pattern that specifies which keys can be read # from. # %W~ Add key write pattern that specifies which keys can be # written to. # allkeys Alias for ~* # resetkeys Flush the list of allowed keys patterns. # & Add a glob-style pattern of Pub/Sub channels that can be # accessed by the user. It is possible to specify multiple channel # patterns. # allchannels Alias for &* # resetchannels Flush the list of allowed channel patterns. # > Add this password to the list of valid password for the user. # For example >mypass will add "mypass" to the list. # This directive clears the "nopass" flag (see later). # < Remove this password from the list of valid passwords. # nopass All the set passwords of the user are removed, and the user # is flagged as requiring no password: it means that every # password will work against this user. If this directive is # used for the default user, every new connection will be # immediately authenticated with the default user without # any explicit AUTH command required. Note that the "resetpass" # directive will clear this condition. # resetpass Flush the list of allowed passwords. Moreover removes the # "nopass" status. After "resetpass" the user has no associated # passwords and there is no way to authenticate without adding # some password (or setting it as "nopass" later). # reset Performs the following actions: resetpass, resetkeys, resetchannels, # allchannels (if acl-pubsub-default is set), off, clearselectors, -@all. # The user returns to the same state it has immediately after its creation. # () Create a new selector with the options specified within the # parentheses and attach it to the user. Each option should be # space separated. The first character must be ( and the last # character must be ). # clearselectors Remove all of the currently attached selectors. # Note this does not change the "root" user permissions, # which are the permissions directly applied onto the # user (outside the parentheses). # # ACL rules can be specified in any order: for instance you can start with # passwords, then flags, or key patterns. However note that the additive # and subtractive rules will CHANGE MEANING depending on the ordering. # For instance see the following example: # # user alice on +@all -DEBUG ~* >somepassword # # This will allow "alice" to use all the commands with the exception of the # DEBUG command, since +@all added all the commands to the set of the commands # alice can use, and later DEBUG was removed. However if we invert the order # of two ACL rules the result will be different: # # user alice on -DEBUG +@all ~* >somepassword # # Now DEBUG was removed when alice had yet no commands in the set of allowed # commands, later all the commands are added, so the user will be able to # execute everything. # # Basically ACL rules are processed left-to-right. # # The following is a list of command categories and their meanings: # * keyspace - Writing or reading from keys, databases, or their metadata # in a type agnostic way. Includes DEL, RESTORE, DUMP, RENAME, EXISTS, DBSIZE, # KEYS, EXPIRE, TTL, FLUSHALL, etc. Commands that may modify the keyspace, # key or metadata will also have `write` category. Commands that only read # the keyspace, key or metadata will have the `read` category. # * read - Reading from keys (values or metadata). Note that commands that don't # interact with keys, will not have either `read` or `write`. # * write - Writing to keys (values or metadata) # * admin - Administrative commands. Normal applications will never need to use # these. Includes REPLICAOF, CONFIG, DEBUG, SAVE, MONITOR, ACL, SHUTDOWN, etc. # * dangerous - Potentially dangerous (each should be considered with care for # various reasons). This includes FLUSHALL, MIGRATE, RESTORE, SORT, KEYS, # CLIENT, DEBUG, INFO, CONFIG, SAVE, REPLICAOF, etc. # * connection - Commands affecting the connection or other connections. # This includes AUTH, SELECT, COMMAND, CLIENT, ECHO, PING, etc. # * blocking - Potentially blocking the connection until released by another # command. # * fast - Fast O(1) commands. May loop on the number of arguments, but not the # number of elements in the key. # * slow - All commands that are not Fast. # * pubsub - PUBLISH / SUBSCRIBE related # * transaction - WATCH / MULTI / EXEC related commands. # * scripting - Scripting related. # * set - Data type: sets related. # * sortedset - Data type: zsets related. # * list - Data type: lists related. # * hash - Data type: hashes related. # * string - Data type: strings related. # * bitmap - Data type: bitmaps related. # * hyperloglog - Data type: hyperloglog related. # * geo - Data type: geo related. # * stream - Data type: streams related. # # For more information about ACL configuration please refer to # the Redis web site at https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/ # ACL LOG # # The ACL Log tracks failed commands and authentication events associated # with ACLs. The ACL Log is useful to troubleshoot failed commands blocked # by ACLs. The ACL Log is stored in memory. You can reclaim memory with # ACL LOG RESET. Define the maximum entry length of the ACL Log below. acllog-max-len 128 # Using an external ACL file # # Instead of configuring users here in this file, it is possible to use # a stand-alone file just listing users. The two methods cannot be mixed: # if you configure users here and at the same time you activate the external # ACL file, the server will refuse to start. # # The format of the external ACL user file is exactly the same as the # format that is used inside redis.conf to describe users. # # aclfile /etc/redis/users.acl # IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility # layer on top of the new ACL system. The option effect will be just setting # the password for the default user. Clients will still authenticate using # AUTH as usually, or more explicitly with AUTH default # if they follow the new protocol: both will work. # # The requirepass is not compatible with aclfile option and the ACL LOAD # command, these will cause requirepass to be ignored. # # requirepass foobared # New users are initialized with restrictive permissions by default, via the # equivalent of this ACL rule 'off resetkeys -@all'. Starting with Redis 6.2, it # is possible to manage access to Pub/Sub channels with ACL rules as well. The # default Pub/Sub channels permission if new users is controlled by the # acl-pubsub-default configuration directive, which accepts one of these values: # # allchannels: grants access to all Pub/Sub channels # resetchannels: revokes access to all Pub/Sub channels # # From Redis 7.0, acl-pubsub-default defaults to 'resetchannels' permission. # # acl-pubsub-default resetchannels # Command renaming (DEPRECATED). # # ------------------------------------------------------------------------ # WARNING: avoid using this option if possible. Instead use ACLs to remove # commands from the default user, and put them only in some admin user you # create for administrative purposes. # ------------------------------------------------------------------------ # # It is possible to change the name of dangerous commands in a shared # environment. For instance the CONFIG command may be renamed into something # hard to guess so that it will still be available for internal-use tools # but not available for general clients. # # Example: # # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 # # It is also possible to completely kill a command by renaming it into # an empty string: # # rename-command CONFIG "" # # Please note that changing the name of commands that are logged into the # AOF file or transmitted to replicas may cause problems. ################################### CLIENTS #################################### # Set the max number of connected clients at the same time. By default # this limit is set to 10000 clients, however if the Redis server is not # able to configure the process file limit to allow for the specified limit # the max number of allowed clients is set to the current file limit # minus 32 (as Redis reserves a few file descriptors for internal uses). # # Once the limit is reached Redis will close all the new connections sending # an error 'max number of clients reached'. # # IMPORTANT: When Redis Cluster is used, the max number of connections is also # shared with the cluster bus: every node in the cluster will use two # connections, one incoming and another outgoing. It is important to size the # limit accordingly in case of very large clusters. # # maxclients 10000 ############################## MEMORY MANAGEMENT ################################ # Set a memory usage limit to the specified amount of bytes. # When the memory limit is reached Redis will try to remove keys # according to the eviction policy selected (see maxmemory-policy). # # If Redis can't remove keys according to the policy, or if the policy is # set to 'noeviction', Redis will start to reply with errors to commands # that would use more memory, like SET, LPUSH, and so on, and will continue # to reply to read-only commands like GET. # # This option is usually useful when using Redis as an LRU or LFU cache, or to # set a hard memory limit for an instance (using the 'noeviction' policy). # # WARNING: If you have replicas attached to an instance with maxmemory on, # the size of the output buffers needed to feed the replicas are subtracted # from the used memory count, so that network problems / resyncs will # not trigger a loop where keys are evicted, and in turn the output # buffer of replicas is full with DELs of keys evicted triggering the deletion # of more keys, and so forth until the database is completely emptied. # # In short... if you have replicas attached it is suggested that you set a lower # limit for maxmemory so that there is some free RAM on the system for replica # output buffers (but this is not needed if the policy is 'noeviction'). # # maxmemory # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory # is reached. You can select one from the following behaviors: # # volatile-lru -> Evict using approximated LRU, only keys with an expire set. # 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-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) # noeviction -> Don't evict anything, just return an error on write operations. # # LRU means Least Recently Used # LFU means Least Frequently Used # # Both LRU, LFU and volatile-ttl are implemented using approximated # randomized algorithms. # # 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 # modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE, # SORT (due to the STORE argument), and EXEC (if the transaction includes any # command that requires memory). # # The default is: # # maxmemory-policy noeviction # LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated # algorithms (in order to save memory), so you can tune it for speed or # accuracy. By default Redis will check five keys and pick the one that was # used least recently, you can change the sample size using the following # configuration directive. # # The default of 5 produces good enough results. 10 Approximates very closely # true LRU but costs more CPU. 3 is faster but not very accurate. The maximum # value that can be set is 64. # # maxmemory-samples 5 # Eviction processing is designed to function well with the default setting. # If there is an unusually large amount of write traffic, this value may need to # be increased. Decreasing this value may reduce latency at the risk of # eviction processing effectiveness # 0 = minimum latency, 10 = default, 100 = process without regard to latency # # maxmemory-eviction-tenacity 10 # Starting from Redis 5, by default a replica will ignore its maxmemory setting # (unless it is promoted to master after a failover or manually). It means # that the eviction of keys will be just handled by the master, sending the # DEL commands to the replica as keys evict in the master side. # # This behavior ensures that masters and replicas stay consistent, and is usually # what you want, however if your replica is writable, or you want the replica # to have a different memory setting, and you are sure all the writes performed # to the replica are idempotent, then you may change this default (but be sure # to understand what you are doing). # # Note that since the replica by default does not evict, it may end using more # memory than the one set via maxmemory (there are certain buffers that may # be larger on the replica, or data structures may sometimes take more memory # and so forth). So make sure you monitor your replicas and make sure they # have enough memory to never hit a real out-of-memory condition before the # master hits the configured maxmemory setting. # # replica-ignore-maxmemory yes # Redis reclaims expired keys in two ways: upon access when those keys are # found to be expired, and also in background, in what is called the # "active expire key". The key space is slowly and interactively scanned # looking for expired keys to reclaim, so that it is possible to free memory # of keys that are expired and will never be accessed again in a short time. # # The default effort of the expire cycle will try to avoid having more than # ten percent of expired keys still in memory, and will try to avoid consuming # more than 25% of total memory and to add latency to the system. However # it is possible to increase the expire "effort" that is normally set to # "1", to a greater value, up to the value "10". At its maximum value the # system will use more CPU, longer cycles (and technically may introduce # more latency), and will tolerate less already expired keys still present # in the system. It's a tradeoff between memory, CPU and latency. # # active-expire-effort 1 ############################# LAZY FREEING #################################### # Redis has two primitives to delete keys. One is called DEL and is a blocking # deletion of the object. It means that the server stops processing new commands # in order to reclaim all the memory associated with an object in a synchronous # way. If the key deleted is associated with a small object, the time needed # in order to execute the DEL command is very small and comparable to most other # O(1) or O(log_N) commands in Redis. However if the key is associated with an # aggregated value containing millions of elements, the server can block for # a long time (even seconds) in order to complete the operation. # # For the above reasons Redis also offers non blocking deletion primitives # such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and # FLUSHDB commands, in order to reclaim memory in background. Those commands # are executed in constant time. Another thread will incrementally free the # object in the background as fast as possible. # # DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. # It's up to the design of the application to understand when it is a good # idea to use one or the other. However the Redis server sometimes has to # delete keys or flush the whole database as a side effect of other operations. # Specifically Redis deletes objects independently of a user call in the # following scenarios: # # 1) On eviction, because of the maxmemory and maxmemory policy configurations, # in order to make room for new data, without going over the specified # memory limit. # 2) Because of expire: when a key with an associated time to live (see the # EXPIRE command) must be deleted from memory. # 3) Because of a side effect of a command that stores data on a key that may # already exist. For example the RENAME command may delete the old key # content when it is replaced with another one. Similarly SUNIONSTORE # or SORT with STORE option may delete existing keys. The SET command # itself removes any old content of the specified key in order to replace # it with the specified string. # 4) During replication, when a replica performs a full resynchronization with # its master, the content of the whole database is removed in order to # load the RDB file just transferred. # # In all the above cases the default is to delete objects in a blocking way, # like if DEL was called. However you can configure each case specifically # in order to instead release memory in a non-blocking way like if UNLINK # was called, using the following configuration directives. lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no # It is also possible, for the case when to replace the user code DEL calls # with UNLINK calls is not easy, to modify the default behavior of the DEL # command to act exactly like UNLINK, using the following configuration # directive: lazyfree-lazy-user-del no # FLUSHDB, FLUSHALL, SCRIPT FLUSH and FUNCTION FLUSH support both asynchronous and synchronous # deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the # commands. When neither flag is passed, this directive will be used to determine # if the data should be deleted asynchronously. lazyfree-lazy-user-flush no ################################ THREADED I/O ################################# # Redis is mostly single threaded, however there are certain threaded # operations such as UNLINK, slow I/O accesses and other things that are # performed on side threads. # # Now it is also possible to handle Redis clients socket reads and writes # in different I/O threads. Since especially writing is so slow, normally # Redis users use pipelining in order to speed up the Redis performances per # core, and spawn multiple instances in order to scale more. Using I/O # threads it is possible to easily speedup several times Redis without resorting # to pipelining nor sharding of the instance. # # By default threading is disabled, we suggest enabling it only in machines # that have at least 4 or more cores, leaving at least one spare core. # We also recommend using threaded I/O only if you actually have performance # problems, with Redis instances being able to use a quite big percentage of # CPU time, otherwise there is no point in using this feature. # # So for instance if you have a four cores boxes, try to use 3 I/O # threads, if you have a 8 cores, try to use 7 threads. In order to # enable I/O threads use the following configuration directive: # # io-threads 4 # # Setting io-threads to 1 will just use the main thread as usual. # When I/O threads are enabled, we not only use threads for writes, that # is to thread the write(2) syscall and transfer the client buffers to the # socket, but also use threads for reads and protocol parsing. # # NOTE: If you want to test the Redis speedup using redis-benchmark, make # sure you also run the benchmark itself in threaded mode, using the # --threads option to match the number of Redis threads, otherwise you'll not # be able to notice the improvements. ############################ KERNEL OOM CONTROL ############################## # On Linux, it is possible to hint the kernel OOM killer on what processes # should be killed first when out of memory. # # Enabling this feature makes Redis actively control the oom_score_adj value # for all its processes, depending on their role. The default scores will # attempt to have background child processes killed before all others, and # replicas killed before masters. # # Redis supports these options: # # no: Don't make changes to oom-score-adj (default). # yes: Alias to "relative" see below. # absolute: Values in oom-score-adj-values are written as is to the kernel. # relative: Values are used relative to the initial value of oom_score_adj when # the server starts and are then clamped to a range of -1000 to 1000. # Because typically the initial value is 0, they will often match the # absolute values. oom-score-adj no # When oom-score-adj is used, this directive controls the specific values used # for master, replica and background child processes. Values range -2000 to # 2000 (higher means more likely to be killed). # # Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) # can freely increase their value, but not decrease it below its initial # settings. This means that setting oom-score-adj to "relative" and setting the # oom-score-adj-values to positive values will always succeed. oom-score-adj-values 0 200 800 #################### KERNEL transparent hugepage CONTROL ###################### # Usually the kernel Transparent Huge Pages control is set to "madvise" or # "never" by default (/sys/kernel/mm/transparent_hugepage/enabled), in which # case this config has no effect. On systems in which it is set to "always", # redis will attempt to disable it specifically for the redis process in order # to avoid latency problems specifically with fork(2) and CoW. # If for some reason you prefer to keep it enabled, you can set this config to # "no" and the kernel global to "always". disable-thp yes ############################## APPEND ONLY MODE ############################### # By default Redis asynchronously dumps the dataset on disk. This mode is # good enough in many applications, but an issue with the Redis process or # a power outage may result into a few minutes of writes lost (depending on # the configured save points). # # The Append Only File is an alternative persistence mode that provides # much better durability. For instance using the default data fsync policy # (see later in the config file) Redis can lose just one second of writes in a # dramatic event like a server power outage, or a single write if something # wrong with the Redis process itself happens, but the operating system is # still running correctly. # # AOF and RDB persistence can be enabled at the same time without problems. # If the AOF is enabled on startup Redis will load the AOF, that is the file # with the better durability guarantees. # # Note that changing this value in a config file of an existing database and # restarting the server can lead to data loss. A conversion needs to be done # by setting it via CONFIG command on a live server first. # # Please check https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/ for more information. appendonly no # The base name of the append only file. # # Redis 7 and newer use a set of append-only files to persist the dataset # and changes applied to it. There are two basic types of files in use: # # - Base files, which are a snapshot representing the complete state of the # dataset at the time the file was created. Base files can be either in # the form of RDB (binary serialized) or AOF (textual commands). # - Incremental files, which contain additional commands that were applied # to the dataset following the previous file. # # In addition, manifest files are used to track the files and the order in # which they were created and should be applied. # # Append-only file names are created by Redis following a specific pattern. # The file name's prefix is based on the 'appendfilename' configuration # parameter, followed by additional information about the sequence and type. # # For example, if appendfilename is set to appendonly.aof, the following file # names could be derived: # # - appendonly.aof.1.base.rdb as a base file. # - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files. # - appendonly.aof.manifest as a manifest file. appendfilename "appendonly.aof" # For convenience, Redis stores all persistent append-only files in a dedicated # directory. The name of the directory is determined by the appenddirname # configuration parameter. appenddirname "appendonlydir" # The fsync() call tells the Operating System to actually write data on disk # instead of waiting for more data in the output buffer. Some OS will really flush # data on disk, some other OS will just try to do it ASAP. # # Redis supports three different modes: # # no: don't fsync, just let the OS flush the data when it wants. Faster. # always: fsync after every write to the append only log. Slow, Safest. # everysec: fsync only one time every second. Compromise. # # The default is "everysec", as that's usually the right compromise between # speed and data safety. It's up to you to understand if you can relax this to # "no" that will let the operating system flush the output buffer when # it wants, for better performances (but if you can live with the idea of # some data loss consider the default persistence mode that's snapshotting), # or on the contrary, use "always" that's very slow but a bit safer than # everysec. # # More details please check the following article: # http://antirez.com/post/redis-persistence-demystified.html # # If unsure, use "everysec". # appendfsync always appendfsync everysec # appendfsync no # When the AOF fsync policy is set to always or everysec, and a background # saving process (a background save or AOF log background rewriting) is # performing a lot of I/O against the disk, in some Linux configurations # Redis may block too long on the fsync() call. Note that there is no fix for # this currently, as even performing fsync in a different thread will block # our synchronous write(2) call. # # In order to mitigate this problem it's possible to use the following option # that will prevent fsync() from being called in the main process while a # BGSAVE or BGREWRITEAOF is in progress. # # This means that while another child is saving, the durability of Redis is # the same as "appendfsync no". In practical terms, this means that it is # possible to lose up to 30 seconds of log in the worst scenario (with the # default Linux settings). # # If you have latency problems turn this to "yes". Otherwise leave it as # "no" that is the safest pick from the point of view of durability. no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. # Redis is able to automatically rewrite the log file implicitly calling # BGREWRITEAOF when the AOF log size grows by the specified percentage. # # This is how it works: Redis remembers the size of the AOF file after the # latest rewrite (if no rewrite has happened since the restart, the size of # the AOF at startup is used). # # This base size is compared to the current size. If the current size is # bigger than the specified percentage, the rewrite is triggered. Also # you need to specify a minimal size for the AOF file to be rewritten, this # is useful to avoid rewriting the AOF file even if the percentage increase # is reached but it is still pretty small. # # Specify a percentage of zero in order to disable the automatic AOF # rewrite feature. auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # An AOF file may be found to be truncated at the end during the Redis # startup process, when the AOF data gets loaded back into memory. # This may happen when the system where Redis is running # crashes, especially when an ext4 filesystem is mounted without the # data=ordered option (however this can't happen when Redis itself # crashes or aborts but the operating system still works correctly). # # Redis can either exit with an error when this happens, or load as much # data as possible (the default now) and start if the AOF file is found # to be truncated at the end. The following option controls this behavior. # # If aof-load-truncated is set to yes, a truncated AOF file is loaded and # the Redis server starts emitting a log to inform the user of the event. # Otherwise if the option is set to no, the server aborts with an error # and refuses to start. When the option is set to no, the user requires # to fix the AOF file using the "redis-check-aof" utility before to restart # the server. # # Note that if the AOF file will be found to be corrupted in the middle # the server will still exit with an error. This option only applies when # Redis will try to read more data from the AOF file but not enough bytes # will be found. aof-load-truncated yes # Redis can create append-only base files in either RDB or AOF formats. Using # the RDB format is always faster and more efficient, and disabling it is only # supported for backward compatibility purposes. aof-use-rdb-preamble yes # Redis supports recording timestamp annotations in the AOF to support restoring # the data from a specific point-in-time. However, using this capability changes # the AOF format in a way that may not be compatible with existing AOF parsers. aof-timestamp-enabled no ################################ SHUTDOWN ##################################### # Maximum time to wait for replicas when shutting down, in seconds. # # During shut down, a grace period allows any lagging replicas to catch up with # the latest replication offset before the master exists. This period can # prevent data loss, especially for deployments without configured disk backups. # # The 'shutdown-timeout' value is the grace period's duration in seconds. It is # only applicable when the instance has replicas. To disable the feature, set # the value to 0. # # shutdown-timeout 10 # When Redis receives a SIGINT or SIGTERM, shutdown is initiated and by default # an RDB snapshot is written to disk in a blocking operation if save points are configured. # The options used on signaled shutdown can include the following values: # default: Saves RDB snapshot only if save points are configured. # Waits for lagging replicas to catch up. # save: Forces a DB saving operation even if no save points are configured. # nosave: Prevents DB saving operation even if one or more save points are configured. # now: Skips waiting for lagging replicas. # force: Ignores any errors that would normally prevent the server from exiting. # # Any combination of values is allowed as long as "save" and "nosave" are not set simultaneously. # Example: "nosave force now" # # shutdown-on-sigint default # shutdown-on-sigterm default ################ NON-DETERMINISTIC LONG BLOCKING COMMANDS ##################### # Maximum time in milliseconds for EVAL scripts, functions and in some cases # modules' commands before Redis can start processing or rejecting other clients. # # If the maximum execution time is reached Redis will start to reply to most # commands with a BUSY error. # # In this state Redis will only allow a handful of commands to be executed. # For instance, SCRIPT KILL, FUNCTION KILL, SHUTDOWN NOSAVE and possibly some # module specific 'allow-busy' commands. # # SCRIPT KILL and FUNCTION KILL will only be able to stop a script that did not # yet call any write commands, so SHUTDOWN NOSAVE may be the only way to stop # the server in the case a write command was already issued by the script when # the user doesn't want to wait for the natural termination of the script. # # The default is 5 seconds. It is possible to set it to 0 or a negative value # to disable this mechanism (uninterrupted execution). Note that in the past # this config had a different name, which is now an alias, so both of these do # the same: # lua-time-limit 5000 # busy-reply-threshold 5000 ################################ REDIS CLUSTER ############################### # Normal Redis instances can't be part of a Redis Cluster; only nodes that are # started as cluster nodes can. In order to start a Redis instance as a # cluster node enable the cluster support uncommenting the following: # # cluster-enabled yes # Every cluster node has a cluster configuration file. This file is not # intended to be edited by hand. It is created and updated by Redis nodes. # Every Redis Cluster node requires a different cluster configuration file. # Make sure that instances running in the same system do not have # overlapping cluster configuration file names. # # cluster-config-file nodes-6379.conf # Cluster node timeout is the amount of milliseconds a node must be unreachable # for it to be considered in failure state. # Most other internal time limits are a multiple of the node timeout. # # cluster-node-timeout 15000 # The cluster port is the port that the cluster bus will listen for inbound connections on. When set # to the default value, 0, it will be bound to the command port + 10000. Setting this value requires # you to specify the cluster bus port when executing cluster meet. # cluster-port 0 # A replica of a failing master will avoid to start a failover if its data # looks too old. # # There is no simple way for a replica to actually have an exact measure of # its "data age", so the following two checks are performed: # # 1) If there are multiple replicas able to failover, they exchange messages # in order to try to give an advantage to the replica with the best # replication offset (more data from the master processed). # Replicas will try to get their rank by offset, and apply to the start # of the failover a delay proportional to their rank. # # 2) Every single replica computes the time of the last interaction with # its master. This can be the last ping or command received (if the master # is still in the "connected" state), or the time that elapsed since the # disconnection with the master (if the replication link is currently down). # If the last interaction is too old, the replica will not try to failover # at all. # # The point "2" can be tuned by user. Specifically a replica will not perform # the failover if, since the last interaction with the master, the time # elapsed is greater than: # # (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period # # So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor # is 10, and assuming a default repl-ping-replica-period of 10 seconds, the # replica will not try to failover if it was not able to talk with the master # for longer than 310 seconds. # # A large cluster-replica-validity-factor may allow replicas with too old data to failover # a master, while a too small value may prevent the cluster from being able to # elect a replica at all. # # For maximum availability, it is possible to set the cluster-replica-validity-factor # to a value of 0, which means, that replicas will always try to failover the # master regardless of the last time they interacted with the master. # (However they'll always try to apply a delay proportional to their # offset rank). # # Zero is the only value able to guarantee that when all the partitions heal # the cluster will always be able to continue. # # cluster-replica-validity-factor 10 # Cluster replicas are able to migrate to orphaned masters, that are masters # that are left without working replicas. This improves the cluster ability # to resist to failures as otherwise an orphaned master can't be failed over # in case of failure if it has no working replicas. # # Replicas migrate to orphaned masters only if there are still at least a # given number of other working replicas for their old master. This number # is the "migration barrier". A migration barrier of 1 means that a replica # will migrate only if there is at least 1 other working replica for its master # and so forth. It usually reflects the number of replicas you want for every # master in your cluster. # # Default is 1 (replicas migrate only if their masters remain with at least # one replica). To disable migration just set it to a very large value or # set cluster-allow-replica-migration to 'no'. # A value of 0 can be set but is useful only for debugging and dangerous # in production. # # cluster-migration-barrier 1 # Turning off this option allows to use less automatic cluster configuration. # It both disables migration to orphaned masters and migration from masters # that became empty. # # Default is 'yes' (allow automatic migrations). # # cluster-allow-replica-migration yes # By default Redis Cluster nodes stop accepting queries if they detect there # is at least a hash slot uncovered (no available node is serving it). # This way if the cluster is partially down (for example a range of hash slots # are no longer covered) all the cluster becomes, eventually, unavailable. # It automatically returns available as soon as all the slots are covered again. # # However sometimes you want the subset of the cluster which is working, # to continue to accept queries for the part of the key space that is still # covered. In order to do so, just set the cluster-require-full-coverage # option to no. # # cluster-require-full-coverage yes # This option, when set to yes, prevents replicas from trying to failover its # master during master failures. However the replica can still perform a # manual failover, if forced to do so. # # This is useful in different scenarios, especially in the case of multiple # data center operations, where we want one side to never be promoted if not # in the case of a total DC failure. # # cluster-replica-no-failover no # This option, when set to yes, allows nodes to serve read traffic while the # cluster is in a down state, as long as it believes it owns the slots. # # This is useful for two cases. The first case is for when an application # doesn't require consistency of data during node failures or network partitions. # One example of this is a cache, where as long as the node has the data it # should be able to serve it. # # The second use case is for configurations that don't meet the recommended # three shards but want to enable cluster mode and scale later. A # master outage in a 1 or 2 shard configuration causes a read/write outage to the # entire cluster without this option set, with it set there is only a write outage. # Without a quorum of masters, slot ownership will not change automatically. # # cluster-allow-reads-when-down no # This option, when set to yes, allows nodes to serve pubsub shard traffic while # the cluster is in a down state, as long as it believes it owns the slots. # # This is useful if the application would like to use the pubsub feature even when # the cluster global stable state is not OK. If the application wants to make sure only # one shard is serving a given channel, this feature should be kept as yes. # # cluster-allow-pubsubshard-when-down yes # Cluster link send buffer limit is the limit on the memory usage of an individual # cluster bus link's send buffer in bytes. Cluster links would be freed if they exceed # this limit. This is to primarily prevent send buffers from growing unbounded on links # toward slow peers (E.g. PubSub messages being piled up). # This limit is disabled by default. Enable this limit when 'mem_cluster_links' INFO field # and/or 'send-buffer-allocated' entries in the 'CLUSTER LINKS` command output continuously increase. # Minimum limit of 1gb is recommended so that cluster link buffer can fit in at least a single # PubSub message by default. (client-query-buffer-limit default value is 1gb) # # cluster-link-sendbuf-limit 0 # Clusters can configure their announced hostname using this config. This is a common use case for # applications that need to use TLS Server Name Indication (SNI) or dealing with DNS based # routing. By default this value is only shown as additional metadata in the CLUSTER SLOTS # command, but can be changed using 'cluster-preferred-endpoint-type' config. This value is # communicated along the clusterbus to all nodes, setting it to an empty string will remove # the hostname and also propagate the removal. # # cluster-announce-hostname "" # Clusters can configure an optional nodename to be used in addition to the node ID for # debugging and admin information. This name is broadcasted between nodes, so will be used # in addition to the node ID when reporting cross node events such as node failures. # cluster-announce-human-nodename "" # Clusters can advertise how clients should connect to them using either their IP address, # a user defined hostname, or by declaring they have no endpoint. Which endpoint is # shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type # config with values 'ip', 'hostname', or 'unknown-endpoint'. This value controls how # the endpoint returned for MOVED/ASKING requests as well as the first field of CLUSTER SLOTS. # If the preferred endpoint type is set to hostname, but no announced hostname is set, a '?' # will be returned instead. # # When a cluster advertises itself as having an unknown endpoint, it's indicating that # the server doesn't know how clients can reach the cluster. This can happen in certain # networking situations where there are multiple possible routes to the node, and the # server doesn't know which one the client took. In this case, the server is expecting # the client to reach out on the same endpoint it used for making the last request, but use # the port provided in the response. # # cluster-preferred-endpoint-type ip # This configuration defines the sampling ratio (0-100) for checking command # compatibility in cluster mode. When a command is executed, it is sampled at # the specified ratio to determine if it complies with Redis cluster constraints, # such as cross-slot restrictions. # # - A value of 0 means no commands are sampled for compatibility checks. # - A value of 100 means all commands are checked. # - Intermediate values (e.g., 10) mean that approximately 10% of the commands # are randomly selected for compatibility verification. # # Higher sampling ratios may introduce additional performance overhead, especially # under high QPS. The default value is 0 (no sampling). # # cluster-compatibility-sample-ratio 0 # In order to setup your cluster make sure to read the documentation # available at https://redis.io web site. ########################## CLUSTER DOCKER/NAT support ######################## # In certain deployments, Redis Cluster nodes address discovery fails, because # addresses are NAT-ted or because ports are forwarded (the typical case is # Docker and other containers). # # In order to make Redis Cluster working in such environments, a static # configuration where each node knows its public address is needed. The # following four options are used for this scope, and are: # # * cluster-announce-ip # * cluster-announce-port # * cluster-announce-tls-port # * cluster-announce-bus-port # # Each instructs the node about its address, client ports (for connections # without and with TLS) and cluster message bus port. The information is then # published in the header of the bus packets so that other nodes will be able to # correctly map the address of the node publishing the information. # # If tls-cluster is set to yes and cluster-announce-tls-port is omitted or set # to zero, then cluster-announce-port refers to the TLS port. Note also that # cluster-announce-tls-port has no effect if tls-cluster is set to no. # # If the above options are not used, the normal Redis Cluster auto-detection # will be used instead. # # Note that when remapped, the bus port may not be at the fixed offset of # clients port + 10000, so you can specify any port and bus-port depending # on how they get remapped. If the bus-port is not set, a fixed offset of # 10000 will be used as usual. # # Example: # # cluster-announce-ip 10.1.1.5 # cluster-announce-tls-port 6379 # cluster-announce-port 0 # cluster-announce-bus-port 6380 ################################## SLOW LOG ################################### # The Redis Slow Log is a system to log queries that exceeded a specified # execution time. The execution time does not include the I/O operations # like talking with the client, sending the reply and so forth, # but just the time needed to actually execute the command (this is the only # stage of command execution where the thread is blocked and can not serve # other requests in the meantime). # # You can configure the slow log with two parameters: one tells Redis # what is the execution time, in microseconds, to exceed in order for the # command to get logged, and the other parameter is the length of the # slow log. When a new command is logged the oldest one is removed from the # queue of logged commands. # The following time is expressed in microseconds, so 1000000 is equivalent # to one second. Note that a negative number disables the slow log, while # a value of zero forces the logging of every command. slowlog-log-slower-than 10000 # There is no limit to this length. Just be aware that it will consume memory. # You can reclaim memory used by the slow log with SLOWLOG RESET. slowlog-max-len 128 ################################ LATENCY MONITOR ############################## # The Redis latency monitoring subsystem samples different operations # at runtime in order to collect data related to possible sources of # latency of a Redis instance. # # Via the LATENCY command this information is available to the user that can # print graphs and obtain reports. # # The system only logs operations that were performed in a time equal or # greater than the amount of milliseconds specified via the # latency-monitor-threshold configuration directive. When its value is set # to zero, the latency monitor is turned off. # # By default latency monitoring is disabled since it is mostly not needed # if you don't have latency issues, and collecting data has a performance # impact, that while very small, can be measured under big load. Latency # monitoring can easily be enabled at runtime using the command # "CONFIG SET latency-monitor-threshold " if needed. latency-monitor-threshold 0 ################################ LATENCY TRACKING ############################## # The Redis extended latency monitoring tracks the per command latencies and enables # exporting the percentile distribution via the INFO latencystats command, # and cumulative latency distributions (histograms) via the LATENCY command. # # By default, the extended latency monitoring is enabled since the overhead # of keeping track of the command latency is very small. # latency-tracking yes # By default the exported latency percentiles via the INFO latencystats command # are the p50, p99, and p999. # latency-tracking-info-percentiles 50 99 99.9 ############################# EVENT NOTIFICATION ############################## # Redis can notify Pub/Sub clients about events happening in the key space. # This feature is documented at https://redis.io/docs/latest/develop/use/keyspace-notifications/ # # For instance if keyspace events notification is enabled, and a client # performs a DEL operation on key "foo" stored in the Database 0, two # messages will be published via Pub/Sub: # # PUBLISH __keyspace@0__:foo del # PUBLISH __keyevent@0__:del foo # # It is possible to select the events that Redis will notify among a set # of classes. Every class is identified by a single character: # # K Keyspace events, published with __keyspace@__ prefix. # E Keyevent events, published with __keyevent@__ prefix. # g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... # $ String commands # l List commands # s Set commands # h Hash commands # z Sorted set commands # x Expired events (events generated every time a key expires) # 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 # d Module key type events # m Key-miss events (Note: It is not included in the 'A' class) # A Alias for g$lshzxetd, so that the "AKE" string means all the events # (Except key-miss events which are excluded from 'A' due to their # unique nature). # # The "notify-keyspace-events" takes as argument a string that is composed # of zero or multiple characters. The empty string means that notifications # are disabled. # # Example: to enable list and generic events, from the point of view of the # event name, use: # # notify-keyspace-events Elg # # Example 2: to get the stream of the expired keys subscribing to channel # name __keyevent@0__:expired use: # # notify-keyspace-events Ex # # By default all notifications are disabled because most users don't need # this feature and the feature has some overhead. Note that if you don't # specify at least one of K or E, no events will be delivered. notify-keyspace-events "" ############################### ADVANCED CONFIG ############################### # Hashes are encoded using a memory efficient data structure when they have a # small number of entries, and the biggest entry does not exceed a given # threshold. These thresholds can be configured using the following directives. hash-max-listpack-entries 512 hash-max-listpack-value 64 # Lists are also encoded in a special way to save a lot of space. # The number of entries allowed per internal list node can be specified # as a fixed maximum size or a maximum number of elements. # For a fixed maximum size, use -5 through -1, meaning: # -5: max size: 64 Kb <-- not recommended for normal workloads # -4: max size: 32 Kb <-- not recommended # -3: max size: 16 Kb <-- probably not recommended # -2: max size: 8 Kb <-- good # -1: max size: 4 Kb <-- good # Positive numbers mean store up to _exactly_ that number of elements # per list node. # The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), # but if your use case is unique, adjust the settings as necessary. list-max-listpack-size -2 # Lists may also be compressed. # Compress depth is the number of quicklist ziplist nodes from *each* side of # the list to *exclude* from compression. The head and tail of the list # are always uncompressed for fast push/pop operations. Settings are: # 0: disable all list compression # 1: depth 1 means "don't start compressing until after 1 node into the list, # going from either the head or tail" # So: [head]->node->node->...->node->[tail] # [head], [tail] will always be uncompressed; inner nodes will compress. # 2: [head]->[next]->node->node->...->node->[prev]->[tail] # 2 here means: don't compress head or head->next or tail->prev or tail, # but compress all nodes between them. # 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] # etc. list-compress-depth 0 # Sets have a special encoding when a set is composed # of just strings that happen to be integers in radix 10 in the range # of 64 bit signed integers. # The following configuration setting sets the limit in the size of the # set in order to use this special memory saving encoding. set-max-intset-entries 512 # Sets containing non-integer values are also encoded using a memory efficient # data structure when they have a small number of entries, and the biggest entry # does not exceed a given threshold. These thresholds can be configured using # the following directives. set-max-listpack-entries 128 set-max-listpack-value 64 # Similarly to hashes and lists, sorted sets are also specially encoded in # order to save a lot of space. This encoding is only used when the length and # elements of a sorted set are below the following limits: zset-max-listpack-entries 128 zset-max-listpack-value 64 # HyperLogLog sparse representation bytes limit. The limit includes the # 16 bytes header. When a HyperLogLog using the sparse representation crosses # this limit, it is converted into the dense representation. # # A value greater than 16000 is totally useless, since at that point the # dense representation is more memory efficient. # # The suggested value is ~ 3000 in order to have the benefits of # the space efficient encoding without slowing down too much PFADD, # which is O(N) with the sparse encoding. The value can be raised to # ~ 10000 when CPU is not a concern, but space is, and the data set is # composed of many HyperLogLogs with cardinality in the 0 - 15000 range. hll-sparse-max-bytes 3000 # Streams macro node max size / items. The stream data structure is a radix # tree of big nodes that encode multiple items inside. Using this configuration # it is possible to configure how big a single node can be in bytes, and the # maximum number of items it may contain before switching to a new node when # appending new stream entries. If any of the following settings are set to # zero, the limit is ignored, so for instance it is possible to set just a # max entries limit by setting max-bytes to 0 and max-entries to the desired # value. stream-node-max-bytes 4096 stream-node-max-entries 100 # 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) # performs a lazy rehashing: the more operation you run into a hash table # that is rehashing, the more rehashing "steps" are performed, so if the # server is idle the rehashing is never complete and some more memory is used # by the hash table. # # The default is to use this millisecond 10 times every second in order to # actively rehash the main dictionaries, freeing memory when possible. # # If unsure: # use "activerehashing no" if you have hard latency requirements and it is # not a good thing in your environment that Redis can reply from time to time # to queries with 2 milliseconds delay. # # use "activerehashing yes" if you don't have such hard requirements but # want to free memory asap when possible. activerehashing yes # The client output buffer limits can be used to force disconnection of clients # that are not reading data from the server fast enough for some reason (a # common reason is that a Pub/Sub client can't consume messages as fast as the # publisher can produce them). # # The limit can be set differently for the three different classes of clients: # # normal -> normal clients including MONITOR clients # replica -> replica clients # pubsub -> clients subscribed to at least one pubsub channel or pattern # # The syntax of every client-output-buffer-limit directive is the following: # # client-output-buffer-limit # # A client is immediately disconnected once the hard limit is reached, or if # the soft limit is reached and remains reached for the specified number of # seconds (continuously). # So for instance if the hard limit is 32 megabytes and the soft limit is # 16 megabytes / 10 seconds, the client will get disconnected immediately # if the size of the output buffers reach 32 megabytes, but will also get # disconnected if the client reaches 16 megabytes and continuously overcomes # the limit for 10 seconds. # # By default normal clients are not limited because they don't receive data # without asking (in a push way), but just after a request, so only # asynchronous clients may create a scenario where data is requested faster # than it can read. # # Instead there is a default limit for pubsub and replica clients, since # subscribers and replicas receive data in a push fashion. # # Note that it doesn't make sense to set the replica clients output buffer # limit lower than the repl-backlog-size config (partial sync will succeed # and then replica will get disconnected). # Such a configuration is ignored (the size of repl-backlog-size will be used). # This doesn't have memory consumption implications since the replica client # will share the backlog buffers memory. # # Both the hard or the soft limit can be disabled by setting them to zero. client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 # Client query buffers accumulate new commands. They are limited to a fixed # amount by default in order to avoid that a protocol desynchronization (for # instance due to a bug in the client) will lead to unbound memory usage in # the query buffer. However you can configure it here if you have very special # needs, such as a command with huge argument, or huge multi/exec requests or alike. # # client-query-buffer-limit 1gb # In some scenarios client connections can hog up memory leading to OOM # errors or data eviction. To avoid this we can cap the accumulated memory # used by all client connections (all pubsub and normal clients). Once we # reach that limit connections will be dropped by the server freeing up # memory. The server will attempt to drop the connections using the most # memory first. We call this mechanism "client eviction". # # Client eviction is configured using the maxmemory-clients setting as follows: # 0 - client eviction is disabled (default) # # A memory value can be used for the client eviction threshold, # for example: # maxmemory-clients 1g # # A percentage value (between 1% and 100%) means the client eviction threshold # is based on a percentage of the maxmemory setting. For example to set client # eviction at 5% of maxmemory: # maxmemory-clients 5% # In the Redis protocol, bulk requests, that are, elements representing single # strings, are normally limited to 512 mb. However you can change this limit # here, but must be 1mb or greater # # proto-max-bulk-len 512mb # Redis calls an internal function to perform many background tasks, like # closing connections of clients in timeout, purging expired keys that are # never requested, and so forth. # # Not all tasks are performed with the same frequency, but Redis checks for # tasks to perform according to the specified "hz" value. # # By default "hz" is set to 10. Raising the value will use more CPU when # Redis is idle, but at the same time will make Redis more responsive when # there are many keys expiring at the same time, and timeouts may be # handled with more precision. # # The range is between 1 and 500, however a value over 100 is usually not # a good idea. Most users should use the default of 10 and raise this up to # 100 only in environments where very low latency is required. hz 10 # Normally it is useful to have an HZ value which is proportional to the # number of clients connected. This is useful in order, for instance, to # avoid too many clients are processed for each background task invocation # in order to avoid latency spikes. # # Since the default HZ value by default is conservatively set to 10, Redis # offers, and enables by default, the ability to use an adaptive HZ value # which will temporarily raise when there are many connected clients. # # When dynamic HZ is enabled, the actual configured HZ will be used # as a baseline, but multiples of the configured HZ value will be actually # used as needed once more clients are connected. In this way an idle # instance will use very little CPU time while a busy instance will be # more responsive. dynamic-hz yes # When a child rewrites the AOF file, if the following option is enabled # the file will be fsync-ed every 4 MB of data generated. This is useful # in order to commit the file to the disk more incrementally and avoid # big latency spikes. aof-rewrite-incremental-fsync yes # When redis saves RDB file, if the following option is enabled # the file will be fsync-ed every 4 MB of data generated. This is useful # in order to commit the file to the disk more incrementally and avoid # big latency spikes. rdb-save-incremental-fsync yes # Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good # idea to start with the default settings and only change them after investigating # how to improve the performances and how the keys LFU change over time, which # is possible to inspect via the OBJECT FREQ command. # # There are two tunable parameters in the Redis LFU implementation: the # counter logarithm factor and the counter decay time. It is important to # understand what the two parameters mean before changing them. # # The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis # uses a probabilistic increment with logarithmic behavior. Given the value # of the old counter, when a key is accessed, the counter is incremented in # this way: # # 1. A random number R between 0 and 1 is extracted. # 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). # 3. The counter is incremented only if R < P. # # The default lfu-log-factor is 10. This is a table of how the frequency # counter changes with a different number of accesses with different # logarithmic factors: # # +--------+------------+------------+------------+------------+------------+ # | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | # +--------+------------+------------+------------+------------+------------+ # | 0 | 104 | 255 | 255 | 255 | 255 | # +--------+------------+------------+------------+------------+------------+ # | 1 | 18 | 49 | 255 | 255 | 255 | # +--------+------------+------------+------------+------------+------------+ # | 10 | 10 | 18 | 142 | 255 | 255 | # +--------+------------+------------+------------+------------+------------+ # | 100 | 8 | 11 | 49 | 143 | 255 | # +--------+------------+------------+------------+------------+------------+ # # NOTE: The above table was obtained by running the following commands: # # redis-benchmark -n 1000000 incr foo # redis-cli object freq foo # # NOTE 2: The counter initial value is 5 in order to give new objects a chance # to accumulate hits. # # The counter decay time is the time, in minutes, that must elapse in order # for the key counter to be decremented. # # The default value for the lfu-decay-time is 1. A special value of 0 means we # will never decay the counter. # # lfu-log-factor 10 # lfu-decay-time 1 # The maximum number of new client connections accepted per event-loop cycle. This configuration # is set independently for TLS connections. # # By default, up to 10 new connection will be accepted per event-loop cycle for normal connections # and up to 1 new connection per event-loop cycle for TLS connections. # # Adjusting this to a larger number can slightly improve efficiency for new connections # at the risk of causing timeouts for regular commands on established connections. It is # not advised to change this without ensuring that all clients have limited connection # pools and exponential backoff in the case of command/connection timeouts. # # If your application is establishing a large number of new connections per second you should # also consider tuning the value of tcp-backlog, which allows the kernel to buffer more # pending connections before dropping or rejecting connections. # # max-new-connections-per-cycle 10 # max-new-tls-connections-per-cycle 1 ########################### ACTIVE DEFRAGMENTATION ####################### # # What is active defragmentation? # ------------------------------- # # Active (online) defragmentation allows a Redis server to compact the # spaces left between small allocations and deallocations of data in memory, # thus allowing to reclaim back memory. # # Fragmentation is a natural process that happens with every allocator (but # less so with Jemalloc, fortunately) and certain workloads. Normally a server # restart is needed in order to lower the fragmentation, or at least to flush # away all the data and create it again. However thanks to this feature # implemented by Oran Agra for Redis 4.0 this process can happen at runtime # in a "hot" way, while the server is running. # # Basically when the fragmentation is over a certain level (see the # configuration options below) Redis will start to create new copies of the # values in contiguous memory regions by exploiting certain specific Jemalloc # features (in order to understand if an allocation is causing fragmentation # and to allocate it in a better place), and at the same time, will release the # old copies of the data. This process, repeated incrementally for all the keys # will cause the fragmentation to drop back to normal values. # # Important things to understand: # # 1. This feature is disabled by default, and only works if you compiled Redis # to use the copy of Jemalloc we ship with the source code of Redis. # This is the default with Linux builds. # # 2. You never need to enable this feature if you don't have fragmentation # issues. # # 3. Once you experience fragmentation, you can enable this feature when # needed with the command "CONFIG SET activedefrag yes". # # The configuration parameters are able to fine tune the behavior of the # defragmentation process. If you are not sure about what they mean it is # a good idea to leave the defaults untouched. # Active defragmentation is disabled by default # activedefrag no # Minimum amount of fragmentation waste to start active defrag # active-defrag-ignore-bytes 100mb # Minimum percentage of fragmentation to start active defrag # active-defrag-threshold-lower 10 # Maximum percentage of fragmentation at which we use maximum effort # active-defrag-threshold-upper 100 # Minimal effort for defrag in CPU percentage, to be used when the lower # threshold is reached # active-defrag-cycle-min 1 # Maximal effort for defrag in CPU percentage, to be used when the upper # threshold is reached # active-defrag-cycle-max 25 # Maximum number of set/hash/zset/list fields that will be processed from # the main dictionary scan # active-defrag-max-scan-fields 1000 # Jemalloc background thread for purging will be enabled by default jemalloc-bg-thread yes # It is possible to pin different threads and processes of Redis to specific # CPUs in your system, in order to maximize the performances of the server. # This is useful both in order to pin different Redis threads in different # CPUs, but also in order to make sure that multiple Redis instances running # in the same host will be pinned to different CPUs. # # Normally you can do this using the "taskset" command, however it is also # possible to this via Redis configuration directly, both in Linux and FreeBSD. # # You can pin the server/IO threads, bio threads, aof rewrite child process, and # the bgsave child process. The syntax to specify the cpu list is the same as # the taskset command: # # Set redis server/io threads to cpu affinity 0,2,4,6: # server-cpulist 0-7:2 # # Set bio threads to cpu affinity 1,3: # bio-cpulist 1,3 # # Set aof rewrite child process to cpu affinity 8,9,10,11: # aof-rewrite-cpulist 8-11 # # Set bgsave child process to cpu affinity 1,10,11 # bgsave-cpulist 1,10-11 # In some cases redis will emit warnings and even refuse to start if it detects # that the system is in bad state, it is possible to suppress these warnings # by setting the following config which takes a space delimited list of warnings # to suppress # # ignore-warnings ARM64-COW-BUG redis-8.0.2/runtest000077500000000000000000000004271501533116600142650ustar00rootroot00000000000000#!/bin/sh TCL_VERSIONS="8.5 8.6 8.7" TCLSH="" for VERSION in $TCL_VERSIONS; do TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL done if [ -z $TCLSH ] then echo "You need tcl 8.5 or newer in order to run the Redis test" exit 1 fi $TCLSH tests/test_helper.tcl "${@}" redis-8.0.2/runtest-cluster000077500000000000000000000004331501533116600157410ustar00rootroot00000000000000#!/bin/sh TCL_VERSIONS="8.5 8.6 8.7" TCLSH="" for VERSION in $TCL_VERSIONS; do TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL done if [ -z $TCLSH ] then echo "You need tcl 8.5 or newer in order to run the Redis Cluster test" exit 1 fi $TCLSH tests/cluster/run.tcl $* redis-8.0.2/runtest-moduleapi000077500000000000000000000034651501533116600162470ustar00rootroot00000000000000#!/bin/sh TCL_VERSIONS="8.5 8.6 8.7" TCLSH="" [ -z "$MAKE" ] && MAKE=make for VERSION in $TCL_VERSIONS; do TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL done if [ -z $TCLSH ] then echo "You need tcl 8.5 or newer in order to run the Redis ModuleApi test" exit 1 fi $MAKE -C tests/modules && \ $TCLSH tests/test_helper.tcl \ --single unit/moduleapi/commandfilter \ --single unit/moduleapi/basics \ --single unit/moduleapi/fork \ --single unit/moduleapi/testrdb \ --single unit/moduleapi/infotest \ --single unit/moduleapi/moduleconfigs \ --single unit/moduleapi/infra \ --single unit/moduleapi/propagate \ --single unit/moduleapi/hooks \ --single unit/moduleapi/misc \ --single unit/moduleapi/blockonkeys \ --single unit/moduleapi/blockonbackground \ --single unit/moduleapi/scan \ --single unit/moduleapi/datatype \ --single unit/moduleapi/auth \ --single unit/moduleapi/keyspace_events \ --single unit/moduleapi/blockedclient \ --single unit/moduleapi/getkeys \ --single unit/moduleapi/test_lazyfree \ --single unit/moduleapi/defrag \ --single unit/moduleapi/keyspecs \ --single unit/moduleapi/hash \ --single unit/moduleapi/zset \ --single unit/moduleapi/list \ --single unit/moduleapi/stream \ --single unit/moduleapi/mallocsize \ --single unit/moduleapi/datatype2 \ --single unit/moduleapi/cluster \ --single unit/moduleapi/aclcheck \ --single unit/moduleapi/subcommands \ --single unit/moduleapi/reply \ --single unit/moduleapi/cmdintrospection \ --single unit/moduleapi/eventloop \ --single unit/moduleapi/timer \ --single unit/moduleapi/publish \ --single unit/moduleapi/usercall \ --single unit/moduleapi/postnotifications \ --single unit/moduleapi/async_rm_call \ --single unit/moduleapi/moduleauth \ --single unit/moduleapi/rdbloadsave \ --single unit/moduleapi/crash \ --single unit/moduleapi/internalsecret \ "${@}" redis-8.0.2/runtest-sentinel000077500000000000000000000004351501533116600161030ustar00rootroot00000000000000#!/bin/sh TCL_VERSIONS="8.5 8.6 8.7" TCLSH="" for VERSION in $TCL_VERSIONS; do TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL done if [ -z $TCLSH ] then echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test" exit 1 fi $TCLSH tests/sentinel/run.tcl $* redis-8.0.2/sentinel.conf000066400000000000000000000347631501533116600153350ustar00rootroot00000000000000# Example sentinel.conf # By default protected mode is disabled in sentinel mode. Sentinel is reachable # from interfaces different than localhost. Make sure the sentinel instance is # protected from the outside world via firewalling or other means. protected-mode no # port # The port that this sentinel instance will run on port 26379 # By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis-sentinel.pid when # daemonized. daemonize no # When running daemonized, Redis Sentinel writes a pid file in # /var/run/redis-sentinel.pid by default. You can specify a custom pid file # location here. pidfile /var/run/redis-sentinel.pid # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably) # warning (only very important / critical messages are logged) # nothing (nothing is logged) loglevel notice # Specify the log file name. Also the empty string can be used to force # Sentinel to log on the standard output. Note that if you use standard # output for logging but daemonize, logs will be sent to /dev/null logfile "" # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. # syslog-enabled no # Specify the syslog identity. # syslog-ident sentinel # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. # syslog-facility local0 # sentinel announce-ip # sentinel announce-port # # The above two configuration directives are useful in environments where, # because of NAT, Sentinel is reachable from outside via a non-local address. # # When announce-ip is provided, the Sentinel will claim the specified IP address # in HELLO messages used to gossip its presence, instead of auto-detecting the # local address as it usually does. # # Similarly when announce-port is provided and is valid and non-zero, Sentinel # will announce the specified TCP port. # # The two options don't need to be used together, if only announce-ip is # provided, the Sentinel will announce the specified IP and the server port # as specified by the "port" option. If only announce-port is provided, the # Sentinel will announce the auto-detected local IP and the specified port. # # Example: # # sentinel announce-ip 1.2.3.4 # dir # Every long running process should have a well-defined working directory. # For Redis Sentinel to chdir to /tmp at startup is the simplest thing # for the process to don't interfere with administrative tasks such as # unmounting filesystems. dir /tmp # sentinel monitor # # Tells Sentinel to monitor this master, and to consider it in O_DOWN # (Objectively Down) state only if at least sentinels agree. # # Note that whatever is the ODOWN quorum, a Sentinel will require to # be elected by the majority of the known Sentinels in order to # start a failover, so no failover can be performed in minority. # # Replicas are auto-discovered, so you don't need to specify replicas in # any way. Sentinel itself will rewrite this configuration file adding # the replicas using additional configuration options. # Also note that the configuration file is rewritten when a # replica is promoted to master. # # Note: master name should not include special characters or spaces. # The valid charset is A-z 0-9 and the three characters ".-_". sentinel monitor mymaster 127.0.0.1 6379 2 # sentinel auth-pass # # Set the password to use to authenticate with the master and replicas. # Useful if there is a password set in the Redis instances to monitor. # # Note that the master password is also used for replicas, so it is not # possible to set a different password in masters and replicas instances # if you want to be able to monitor these instances with Sentinel. # # However you can have Redis instances without the authentication enabled # mixed with Redis instances requiring the authentication (as long as the # password set is the same for all the instances requiring the password) as # the AUTH command will have no effect in Redis instances with authentication # switched off. # # Example: # # sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # sentinel auth-user # # This is useful in order to authenticate to instances having ACL capabilities, # that is, running Redis 6.0 or greater. When just auth-pass is provided the # Sentinel instance will authenticate to Redis using the old "AUTH " # method. When also an username is provided, it will use "AUTH ". # In the Redis servers side, the ACL to provide just minimal access to # Sentinel instances, should be configured along the following lines: # # user sentinel-user >somepassword +client +subscribe +publish \ # +ping +info +multi +slaveof +config +client +exec on # sentinel down-after-milliseconds # # Number of milliseconds the master (or any attached replica or sentinel) should # be unreachable (as in, not acceptable reply to PING, continuously, for the # specified period) in order to consider it in S_DOWN state (Subjectively # Down). # # Default is 30 seconds. sentinel down-after-milliseconds mymaster 30000 # IMPORTANT NOTE: starting with Redis 6.2 ACL capability is supported for # Sentinel mode, please refer to the Redis website https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/ # for more details. # Sentinel's ACL users are defined in the following format: # # user ... acl rules ... # # For example: # # user worker +@admin +@connection ~* on >ffa9203c493aa99 # # For more information about ACL configuration please refer to the Redis # website at https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/ and redis server configuration # template redis.conf. # ACL LOG # # The ACL Log tracks failed commands and authentication events associated # with ACLs. The ACL Log is useful to troubleshoot failed commands blocked # by ACLs. The ACL Log is stored in memory. You can reclaim memory with # ACL LOG RESET. Define the maximum entry length of the ACL Log below. acllog-max-len 128 # Using an external ACL file # # Instead of configuring users here in this file, it is possible to use # a stand-alone file just listing users. The two methods cannot be mixed: # if you configure users here and at the same time you activate the external # ACL file, the server will refuse to start. # # The format of the external ACL user file is exactly the same as the # format that is used inside redis.conf to describe users. # # aclfile /etc/redis/sentinel-users.acl # requirepass # # You can configure Sentinel itself to require a password, however when doing # so Sentinel will try to authenticate with the same password to all the # other Sentinels. So you need to configure all your Sentinels in a given # group with the same "requirepass" password. Check the following documentation # for more info: https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/ # # IMPORTANT NOTE: starting with Redis 6.2 "requirepass" is a compatibility # layer on top of the ACL system. The option effect will be just setting # the password for the default user. Clients will still authenticate using # AUTH as usually, or more explicitly with AUTH default # if they follow the new protocol: both will work. # # New config files are advised to use separate authentication control for # incoming connections (via ACL), and for outgoing connections (via # sentinel-user and sentinel-pass) # # The requirepass is not compatible with aclfile option and the ACL LOAD # command, these will cause requirepass to be ignored. # sentinel sentinel-user # # You can configure Sentinel to authenticate with other Sentinels with specific # user name. # sentinel sentinel-pass # # The password for Sentinel to authenticate with other Sentinels. If sentinel-user # is not configured, Sentinel will use 'default' user with sentinel-pass to authenticate. # sentinel parallel-syncs # # How many replicas we can reconfigure to point to the new replica simultaneously # during the failover. Use a low number if you use the replicas to serve query # to avoid that all the replicas will be unreachable at about the same # time while performing the synchronization with the master. sentinel parallel-syncs mymaster 1 # sentinel failover-timeout # # Specifies the failover timeout in milliseconds. It is used in many ways: # # - The time needed to re-start a failover after a previous failover was # already tried against the same master by a given Sentinel, is two # times the failover timeout. # # - The time needed for a replica replicating to a wrong master according # to a Sentinel current configuration, to be forced to replicate # with the right master, is exactly the failover timeout (counting since # the moment a Sentinel detected the misconfiguration). # # - The time needed to cancel a failover that is already in progress but # did not produced any configuration change (SLAVEOF NO ONE yet not # acknowledged by the promoted replica). # # - The maximum time a failover in progress waits for all the replicas to be # reconfigured as replicas of the new master. However even after this time # the replicas will be reconfigured by the Sentinels anyway, but not with # the exact parallel-syncs progression as specified. # # Default is 3 minutes. sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION # # sentinel notification-script and sentinel reconfig-script are used in order # to configure scripts that are called to notify the system administrator # or to reconfigure clients after a failover. The scripts are executed # with the following rules for error handling: # # If script exits with "1" the execution is retried later (up to a maximum # number of times currently set to 10). # # If script exits with "2" (or an higher value) the script execution is # not retried. # # If script terminates because it receives a signal the behavior is the same # as exit code 1. # # A script has a maximum running time of 60 seconds. After this limit is # reached the script is terminated with a SIGKILL and the execution retried. # NOTIFICATION SCRIPT # # sentinel notification-script # # Call the specified notification script for any sentinel event that is # generated in the WARNING level (for instance -sdown, -odown, and so forth). # This script should notify the system administrator via email, SMS, or any # other messaging system, that there is something wrong with the monitored # Redis systems. # # The script is called with just two arguments: the first is the event type # and the second the event description. # # The script must exist and be executable in order for sentinel to start if # this option is provided. # # Example: # # sentinel notification-script mymaster /var/redis/notify.sh # CLIENTS RECONFIGURATION SCRIPT # # sentinel client-reconfig-script # # When the master changed because of a failover a script can be called in # order to perform application-specific tasks to notify the clients that the # configuration has changed and the master is at a different address. # # The following arguments are passed to the script: # # # # is currently always "start" # is either "leader" or "observer" # # The arguments from-ip, from-port, to-ip, to-port are used to communicate # the old address of the master and the new address of the elected replica # (now a master). # # This script should be resistant to multiple invocations. # # Example: # # sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # SECURITY # # By default SENTINEL SET will not be able to change the notification-script # and client-reconfig-script at runtime. This avoids a trivial security issue # where clients can set the script to anything and trigger a failover in order # to get the program executed. sentinel deny-scripts-reconfig yes # REDIS COMMANDS RENAMING (DEPRECATED) # # WARNING: avoid using this option if possible, instead use ACLs. # # Sometimes the Redis server has certain commands, that are needed for Sentinel # to work correctly, renamed to unguessable strings. This is often the case # of CONFIG and SLAVEOF in the context of providers that provide Redis as # a service, and don't want the customers to reconfigure the instances outside # of the administration console. # # In such case it is possible to tell Sentinel to use different command names # instead of the normal ones. For example if the master "mymaster", and the # associated replicas, have "CONFIG" all renamed to "GUESSME", I could use: # # SENTINEL rename-command mymaster CONFIG GUESSME # # After such configuration is set, every time Sentinel would use CONFIG it will # use GUESSME instead. Note that there is no actual need to respect the command # case, so writing "config guessme" is the same in the example above. # # SENTINEL SET can also be used in order to perform this configuration at runtime. # # In order to set a command back to its original name (undo the renaming), it # is possible to just rename a command to itself: # # SENTINEL rename-command mymaster CONFIG CONFIG # HOSTNAMES SUPPORT # # Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR # to specify an IP address. Also, it requires the Redis replica-announce-ip # keyword to specify only IP addresses. # # You may enable hostnames support by enabling resolve-hostnames. Note # that you must make sure your DNS is configured properly and that DNS # resolution does not introduce very long delays. # SENTINEL resolve-hostnames no # When resolve-hostnames is enabled, Sentinel still uses IP addresses # when exposing instances to users, configuration files, etc. If you want # to retain the hostnames when announced, enable announce-hostnames below. # SENTINEL announce-hostnames no # When master_reboot_down_after_period is set to 0, Sentinel does not fail over # when receiving a -LOADING response from a master. This was the only supported # behavior before version 7.0. # # Otherwise, Sentinel will use this value as the time (in ms) it is willing to # accept a -LOADING response after a master has been rebooted, before failing # over. SENTINEL master-reboot-down-after-period mymaster 0 redis-8.0.2/src/000077500000000000000000000000001501533116600134175ustar00rootroot00000000000000redis-8.0.2/src/.gitignore000066400000000000000000000000521501533116600154040ustar00rootroot00000000000000*.gcda *.gcno *.gcov redis.info lcov-html redis-8.0.2/src/Makefile000066400000000000000000000424451501533116600150700ustar00rootroot00000000000000# Redis Makefile # Copyright (c) 2011-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). # # The Makefile composes the final FINAL_CFLAGS and FINAL_LDFLAGS using # what is needed for Redis plus the standard CFLAGS and LDFLAGS passed. # However when building the dependencies (Jemalloc, Lua, Hiredis, ...) # CFLAGS and LDFLAGS are propagated to the dependencies, so to pass # flags only to be used when compiling / linking Redis itself REDIS_CFLAGS # and REDIS_LDFLAGS are used instead (this is the case of 'make gcov'). # # Dependencies are stored in the Makefile.dep file. To rebuild this file # Just use 'make dep', but this is only needed by developers. release_hdr := $(shell sh -c './mkreleasehdr.sh') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1')) # Optimization flags. To override, the OPTIMIZATION variable can be passed, but # some automatic defaults are added to it. To specify optimization flags # explicitly without any defaults added, pass the OPT variable instead. OPTIMIZATION?=-O3 ifeq ($(OPTIMIZATION),-O3) ifeq (clang,$(CLANG)) OPTIMIZATION+=-flto else OPTIMIZATION+=-flto=auto endif endif ifneq ($(OPTIMIZATION),-O0) OPTIMIZATION+=-fno-omit-frame-pointer endif DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float NODEPS:=clean distclean # Default settings STD=-pedantic -DREDIS_STATIC='' # Use -Wno-c11-extensions on clang, either where explicitly used or on # platforms we can assume it's being used. ifeq (clang,$(CLANG)) STD+=-Wno-c11-extensions else ifneq (,$(findstring FreeBSD,$(uname_S))) STD+=-Wno-c11-extensions endif endif WARN=-Wall -W -Wno-missing-field-initializers -Werror=deprecated-declarations -Wstrict-prototypes OPT=$(OPTIMIZATION) SKIP_VEC_SETS?=no # Detect if the compiler supports C11 _Atomic. # NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions. NUMBER_SIGN_CHAR := \# C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include " > foo.c; \ $(CC) -std=gnu11 -c foo.c -o foo.o > /dev/null 2>&1; \ if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c') ifeq ($(C11_ATOMIC),yes) STD+=-std=gnu11 else SKIP_VEC_SETS=yes STD+=-std=c99 endif PREFIX?=/usr/local INSTALL_BIN=$(PREFIX)/bin INSTALL=install PKG_CONFIG?=pkg-config ifndef PYTHON PYTHON := $(shell which python3 || which python) endif # Default allocator defaults to Jemalloc on Linux and libc otherwise MALLOC=libc ifeq ($(uname_S),Linux) MALLOC=jemalloc endif # To get ARM stack traces if Redis crashes we need a special C flag. ifneq (,$(filter aarch64 armv%,$(uname_M))) CFLAGS+=-funwind-tables endif # Backwards compatibility for selecting an allocator ifeq ($(USE_TCMALLOC),yes) MALLOC=tcmalloc endif ifeq ($(USE_TCMALLOC_MINIMAL),yes) MALLOC=tcmalloc_minimal endif ifeq ($(USE_JEMALLOC),yes) MALLOC=jemalloc endif ifeq ($(USE_JEMALLOC),no) MALLOC=libc endif ifdef SANITIZER ifeq ($(SANITIZER),address) MALLOC=libc CFLAGS+=-fsanitize=address -fno-sanitize-recover=all -fno-omit-frame-pointer LDFLAGS+=-fsanitize=address else ifeq ($(SANITIZER),undefined) MALLOC=libc 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 $(error "unknown sanitizer=${SANITIZER}") endif endif endif endif # Override default settings if possible -include .make-settings FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) FINAL_LDFLAGS=$(LDFLAGS) $(OPT) $(REDIS_LDFLAGS) $(DEBUG) FINAL_LIBS=-lm -lstdc++ DEBUG=-g -ggdb # Linux ARM32 needs -latomic at linking time ifneq (,$(findstring armv,$(uname_M))) FINAL_LIBS+=-latomic endif ifeq ($(uname_S),SunOS) # SunOS ifeq ($(findstring -m32,$(FINAL_CFLAGS)),) CFLAGS+=-m64 endif ifeq ($(findstring -m32,$(FINAL_LDFLAGS)),) LDFLAGS+=-m64 endif DEBUG=-g DEBUG_FLAGS=-g export CFLAGS LDFLAGS DEBUG DEBUG_FLAGS INSTALL=cp -pf FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6 FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt ifeq ($(USE_BACKTRACE),yes) FINAL_CFLAGS+= -DUSE_BACKTRACE endif else ifeq ($(uname_S),Darwin) # Darwin FINAL_LIBS+= -ldl # Homebrew's OpenSSL is not linked to /usr/local to avoid # conflicts with the system's LibreSSL installation so it # must be referenced explicitly during build. ifeq ($(uname_M),arm64) # Homebrew arm64 uses /opt/homebrew as HOMEBREW_PREFIX OPENSSL_PREFIX?=/opt/homebrew/opt/openssl else # Homebrew x86/ppc uses /usr/local as HOMEBREW_PREFIX OPENSSL_PREFIX?=/usr/local/opt/openssl endif else ifeq ($(uname_S),AIX) # AIX FINAL_LDFLAGS+= -Wl,-bexpall FINAL_LIBS+=-ldl -pthread -lcrypt -lbsd else ifeq ($(uname_S),OpenBSD) # OpenBSD FINAL_LIBS+= -lpthread ifeq ($(USE_BACKTRACE),yes) FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include FINAL_LDFLAGS+= -L/usr/local/lib FINAL_LIBS+= -lexecinfo endif else ifeq ($(uname_S),NetBSD) # NetBSD FINAL_LIBS+= -lpthread ifeq ($(USE_BACKTRACE),yes) FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/pkg/include FINAL_LDFLAGS+= -L/usr/pkg/lib FINAL_LIBS+= -lexecinfo endif else ifeq ($(uname_S),FreeBSD) # FreeBSD FINAL_LIBS+= -lpthread -lexecinfo else ifeq ($(uname_S),DragonFly) # DragonFly FINAL_LIBS+= -lpthread -lexecinfo else ifeq ($(uname_S),OpenBSD) # OpenBSD FINAL_LIBS+= -lpthread -lexecinfo else ifeq ($(uname_S),NetBSD) # NetBSD FINAL_LIBS+= -lpthread -lexecinfo else ifeq ($(uname_S),Haiku) # Haiku FINAL_CFLAGS+= -DBSD_SOURCE FINAL_LDFLAGS+= -lbsd -lnetwork FINAL_LIBS+= -lpthread else # All the other OSes (notably Linux) FINAL_LDFLAGS+= -rdynamic FINAL_LIBS+=-ldl -pthread -lrt endif endif endif endif endif endif endif endif endif endif ifdef OPENSSL_PREFIX OPENSSL_CFLAGS=-I$(OPENSSL_PREFIX)/include OPENSSL_LDFLAGS=-L$(OPENSSL_PREFIX)/lib # Also export OPENSSL_PREFIX so it ends up in deps sub-Makefiles export 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 # Determine systemd support and/or build preference (defaulting to auto-detection) BUILD_WITH_SYSTEMD=no LIBSYSTEMD_LIBS=-lsystemd # If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to # auto-detect libsystemd's presence and link accordingly. ifneq ($(USE_SYSTEMD),no) LIBSYSTEMD_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libsystemd && echo $$?) # If libsystemd cannot be detected, continue building without support for it # (unless a later check tells us otherwise) ifeq ($(LIBSYSTEMD_PKGCONFIG),0) BUILD_WITH_SYSTEMD=yes LIBSYSTEMD_LIBS=$(shell $(PKG_CONFIG) --libs libsystemd) endif endif # If 'USE_SYSTEMD' is set to "yes" use pkg-config if available or fall back to # default -lsystemd. ifeq ($(USE_SYSTEMD),yes) BUILD_WITH_SYSTEMD=yes endif ifeq ($(BUILD_WITH_SYSTEMD),yes) FINAL_LIBS+=$(LIBSYSTEMD_LIBS) FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD endif ifeq ($(MALLOC),tcmalloc) FINAL_CFLAGS+= -DUSE_TCMALLOC FINAL_LIBS+= -ltcmalloc endif ifeq ($(MALLOC),tcmalloc_minimal) FINAL_CFLAGS+= -DUSE_TCMALLOC FINAL_LIBS+= -ltcmalloc_minimal endif ifeq ($(MALLOC),jemalloc) DEPENDENCY_TARGETS+= jemalloc FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS) endif # LIBSSL & LIBCRYPTO LIBSSL_LIBS= LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?) ifeq ($(LIBSSL_PKGCONFIG),0) LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl) else LIBSSL_LIBS=-lssl endif LIBCRYPTO_LIBS= LIBCRYPTO_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libcrypto && echo $$?) ifeq ($(LIBCRYPTO_PKGCONFIG),0) LIBCRYPTO_LIBS=$(shell $(PKG_CONFIG) --libs libcrypto) else LIBCRYPTO_LIBS=-lcrypto endif BUILD_NO:=0 BUILD_YES:=1 BUILD_MODULE:=2 ifeq ($(BUILD_TLS),yes) FINAL_CFLAGS+=-DUSE_OPENSSL=$(BUILD_YES) $(OPENSSL_CFLAGS) -DBUILD_TLS_MODULE=$(BUILD_NO) FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS) FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS) endif TLS_MODULE= TLS_MODULE_NAME:=redis-tls$(PROG_SUFFIX).so TLS_MODULE_CFLAGS:=$(FINAL_CFLAGS) ifeq ($(BUILD_TLS),module) FINAL_CFLAGS+=-DUSE_OPENSSL=$(BUILD_MODULE) $(OPENSSL_CFLAGS) TLS_CLIENT_LIBS = ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS) TLS_MODULE=$(TLS_MODULE_NAME) TLS_MODULE_CFLAGS+=-DUSE_OPENSSL=$(BUILD_MODULE) $(OPENSSL_CFLAGS) -DBUILD_TLS_MODULE=$(BUILD_MODULE) endif ifneq ($(SKIP_VEC_SETS),yes) vpath %.c ../modules/vector-sets REDIS_VEC_SETS_OBJ=hnsw.o vset.o FINAL_CFLAGS+=-DINCLUDE_VEC_SETS=1 endif ifndef V define MAKE_INSTALL @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$(1)$(ENDCOLOR) 1>&2 @$(INSTALL) $(1) $(2) endef else define MAKE_INSTALL $(INSTALL) $(1) $(2) endef endif REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL) CCCOLOR="\033[34m" LINKCOLOR="\033[34;1m" SRCCOLOR="\033[33m" BINCOLOR="\033[37;1m" MAKECOLOR="\033[32;1m" ENDCOLOR="\033[0m" ifndef V QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_GEN = @printf ' %b %b\n' $(CCCOLOR)GEN$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; endif ifneq (, $(findstring LOG_REQ_RES, $(REDIS_CFLAGS))) COMMANDS_DEF_FILENAME=commands_with_reply_schema GEN_COMMANDS_FLAGS=--with-reply-schema else COMMANDS_DEF_FILENAME=commands GEN_COMMANDS_FLAGS= endif REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX) REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX) REDIS_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o kvstore.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_legacy.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_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) REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o redisassert.o release.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX) REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX) ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(REDIS_SERVER_OBJ) $(REDIS_VEC_SETS_OBJ) $(REDIS_CLI_OBJ) $(REDIS_BENCHMARK_OBJ))) all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) $(TLS_MODULE) @echo "" @echo "Hint: It's a good idea to run 'make test' ;)" @echo "" Makefile.dep: -$(REDIS_CC) -MM $(ALL_SOURCES) > Makefile.dep 2> /dev/null || true ifeq (0, $(words $(findstring $(MAKECMDGOALS), $(NODEPS)))) -include Makefile.dep endif .PHONY: all persist-settings: distclean echo STD=$(STD) >> .make-settings echo WARN=$(WARN) >> .make-settings echo OPT=$(OPT) >> .make-settings echo MALLOC=$(MALLOC) >> .make-settings echo BUILD_TLS=$(BUILD_TLS) >> .make-settings echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings echo CFLAGS=$(CFLAGS) >> .make-settings echo LDFLAGS=$(LDFLAGS) >> .make-settings echo REDIS_CFLAGS=$(REDIS_CFLAGS) >> .make-settings 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)) .PHONY: persist-settings # Prerequisites target .make-prerequisites: @touch $@ # Clean everything, persist settings and build dependencies if anything changed ifneq ($(strip $(PREV_FINAL_CFLAGS)), $(strip $(FINAL_CFLAGS))) .make-prerequisites: persist-settings endif ifneq ($(strip $(PREV_FINAL_LDFLAGS)), $(strip $(FINAL_LDFLAGS))) .make-prerequisites: persist-settings 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 $(FINAL_LIBS) # redis-sentinel $(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) # redis-check-rdb $(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME) # redis-check-aof $(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) # redis-tls.so $(TLS_MODULE_NAME): $(REDIS_SERVER_NAME) $(QUIET_CC)$(CC) -o $@ tls.c -shared -fPIC $(TLS_MODULE_CFLAGS) $(TLS_CLIENT_LIBS) # redis-cli $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o ../deps/hdr_histogram/libhdrhistogram.a $(FINAL_LIBS) $(TLS_CLIENT_LIBS) # redis-benchmark $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/libhdrhistogram.a $(FINAL_LIBS) $(TLS_CLIENT_LIBS) DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_VEC_SETS_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d) -include $(DEP) # Because the jemalloc.h header is generated as a part of the jemalloc build, # building it should complete before building any other object. Instead of # depending on a single artifact, build all dependencies first. %.o: %.c .make-prerequisites $(REDIS_CC) -MMD -o $@ -c $< # The following files are checked in and don't normally need to be rebuilt. They # are built only if python is available and their prereqs are modified. ifneq (,$(PYTHON)) $(COMMANDS_DEF_FILENAME).def: commands/*.json ../utils/generate-command-code.py $(QUIET_GEN)$(PYTHON) ../utils/generate-command-code.py $(GEN_COMMANDS_FLAGS) fmtargs.h: ../utils/generate-fmtargs.py $(QUITE_GEN)sed '/Everything below this line/,$$d' $@ > $@.tmp $(QUITE_GEN)$(PYTHON) ../utils/generate-fmtargs.py >> $@.tmp $(QUITE_GEN)mv $@.tmp $@ endif commands.c: $(COMMANDS_DEF_FILENAME).def clean: rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep *.so rm -f $(DEP) .PHONY: clean distclean: clean -(cd ../deps && $(MAKE) distclean) -(cd modules && $(MAKE) clean) -(cd ../tests/modules && $(MAKE) clean) -(rm -f .make-*) .PHONY: distclean test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) @(cd ..; ./runtest) test-modules: $(REDIS_SERVER_NAME) @(cd ..; ./runtest-moduleapi) test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) @(cd ..; ./runtest-sentinel) test-cluster: $(REDIS_SERVER_NAME) $(REDIS_CLI_NAME) @(cd ..; ./runtest-cluster) check: test lcov: @lcov --version $(MAKE) gcov @(set -e; cd ..; ./runtest) @geninfo -o redis.info . @genhtml --legend -o lcov-html redis.info .PHONY: lcov bench: $(REDIS_BENCHMARK_NAME) ./$(REDIS_BENCHMARK_NAME) 32bit: @echo "" @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386" @echo "" $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" SKIP_VEC_SETS="yes" gcov: $(MAKE) REDIS_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_LDFLAGS="-fprofile-arcs -ftest-coverage" noopt: $(MAKE) OPTIMIZATION="-O0" valgrind: $(MAKE) OPTIMIZATION="-O0" MALLOC="libc" helgrind: $(MAKE) OPTIMIZATION="-O0" MALLOC="libc" CFLAGS="-D__ATOMIC_VAR_FORCE_SYNC_MACROS" REDIS_CFLAGS="-I/usr/local/include" REDIS_LDFLAGS="-L/usr/local/lib" install: all @mkdir -p $(INSTALL_BIN) $(call MAKE_INSTALL,$(REDIS_SERVER_NAME),$(INSTALL_BIN)) $(call MAKE_INSTALL,$(REDIS_BENCHMARK_NAME),$(INSTALL_BIN)) $(call MAKE_INSTALL,$(REDIS_CLI_NAME),$(INSTALL_BIN)) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME) uninstall: rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)} redis-8.0.2/src/acl.c000066400000000000000000003745471501533116600143460ustar00rootroot00000000000000/* * Copyright (c) 2018-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 "server.h" #include "cluster.h" #include "sha256.h" #include #include /* ============================================================================= * Global state for ACLs * ==========================================================================*/ rax *Users; /* Table mapping usernames to user structures. */ user *DefaultUser; /* Global reference to the default user. Every new connection is associated to it, if no AUTH or HELLO is used to authenticate with a different user. */ list *UsersToLoad; /* This is a list of users found in the configuration file that we'll need to load in the final stage of Redis initialization, after all the modules are already loaded. Every list element is a NULL terminated array of SDS pointers: the first is the user name, all the remaining pointers are ACL rules in the same format as ACLSetUser(). */ list *ACLLog; /* Our security log, the user is able to inspect that using the ACL LOG command .*/ long long ACLLogEntryCount = 0; /* Number of ACL log entries created */ static rax *commandId = NULL; /* Command name to id mapping */ static unsigned long nextid = 0; /* Next command id that has not been assigned */ #define ACL_MAX_CATEGORIES 64 /* Maximum number of command categories */ struct ACLCategoryItem { char *name; uint64_t flag; } ACLDefaultCommandCategories[] = { /* See redis.conf for details on each category. */ {"keyspace", ACL_CATEGORY_KEYSPACE}, {"read", ACL_CATEGORY_READ}, {"write", ACL_CATEGORY_WRITE}, {"set", ACL_CATEGORY_SET}, {"sortedset", ACL_CATEGORY_SORTEDSET}, {"list", ACL_CATEGORY_LIST}, {"hash", ACL_CATEGORY_HASH}, {"string", ACL_CATEGORY_STRING}, {"bitmap", ACL_CATEGORY_BITMAP}, {"hyperloglog", ACL_CATEGORY_HYPERLOGLOG}, {"geo", ACL_CATEGORY_GEO}, {"stream", ACL_CATEGORY_STREAM}, {"pubsub", ACL_CATEGORY_PUBSUB}, {"admin", ACL_CATEGORY_ADMIN}, {"fast", ACL_CATEGORY_FAST}, {"slow", ACL_CATEGORY_SLOW}, {"blocking", ACL_CATEGORY_BLOCKING}, {"dangerous", ACL_CATEGORY_DANGEROUS}, {"connection", ACL_CATEGORY_CONNECTION}, {"transaction", ACL_CATEGORY_TRANSACTION}, {"scripting", ACL_CATEGORY_SCRIPTING}, {NULL,0} /* Terminator. */ }; static struct ACLCategoryItem *ACLCommandCategories = NULL; static size_t nextCommandCategory = 0; /* Index of the next command category to be added */ /* Implements the ability to add to the list of ACL categories at runtime. Since each ACL category * also requires a bit in the acl_categories flag, there is a limit to the number that can be added. * The new ACL categories occupy the remaining bits of acl_categories flag, other than the bits * occupied by the default ACL command categories. * * The optional `flag` argument allows the assignment of the `acl_categories` flag bit to the ACL category. * When adding a new category, except for the default ACL command categories, this arguments should be `0` * to allow the function to assign the next available `acl_categories` flag bit to the new ACL category. * * returns 1 -> Added, 0 -> Failed (out of space) * * This function is present here to gain access to the ACLCommandCategories array and add a new ACL category. */ int ACLAddCommandCategory(const char *name, uint64_t flag) { if (nextCommandCategory >= ACL_MAX_CATEGORIES) return 0; ACLCommandCategories[nextCommandCategory].name = zstrdup(name); ACLCommandCategories[nextCommandCategory].flag = flag != 0 ? flag : (1ULL<>4)]; hex[j*2+1] = cset[(hash[j]&0xF)]; } return sdsnewlen(hex,HASH_PASSWORD_LEN); } /* Given a hash and the hash length, returns C_OK if it is a valid password * hash, or C_ERR otherwise. */ int ACLCheckPasswordHash(unsigned char *hash, int hashlen) { if (hashlen != HASH_PASSWORD_LEN) { return C_ERR; } /* Password hashes can only be characters that represent * hexadecimal values, which are numbers and lowercase * characters 'a' through 'f'. */ for(int i = 0; i < HASH_PASSWORD_LEN; i++) { char c = hash[i]; if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) { return C_ERR; } } return C_OK; } /* ============================================================================= * Low level ACL API * ==========================================================================*/ /* Return 1 if the specified string contains spaces or null characters. * We do this for usernames and key patterns for simpler rewriting of * ACL rules, presentation on ACL list, and to avoid subtle security bugs * that may arise from parsing the rules in presence of escapes. * The function returns 0 if the string has no spaces. */ int ACLStringHasSpaces(const char *s, size_t len) { for (size_t i = 0; i < len; i++) { if (isspace(s[i]) || s[i] == 0) return 1; } return 0; } /* Given the category name the command returns the corresponding flag, or * zero if there is no match. */ uint64_t ACLGetCommandCategoryFlagByName(const char *name) { for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { if (!strcasecmp(name,ACLCommandCategories[j].name)) { return ACLCommandCategories[j].flag; } } return 0; /* No match. */ } /* Method for searching for a user within a list of user definitions. The * list contains an array of user arguments, and we are only * searching the first argument, the username, for a match. */ int ACLListMatchLoadedUser(void *definition, void *user) { sds *user_definition = definition; return sdscmp(user_definition[0], user) == 0; } /* Method for passwords/pattern comparison used for the user->passwords list * so that we can search for items with listSearchKey(). */ int ACLListMatchSds(void *a, void *b) { return sdscmp(a,b) == 0; } /* Method to free list elements from ACL users password/patterns lists. */ void ACLListFreeSds(void *item) { sdsfreegeneric(item); } /* Method to duplicate list elements from ACL users password/patterns lists. */ void *ACLListDupSds(void *item) { return sdsdup(item); } /* Structure used for handling key patterns with different key * based permissions. */ typedef struct { int flags; /* The ACL key permission types for this key pattern */ sds pattern; /* The pattern to match keys against */ } keyPattern; /* Create a new key pattern. */ keyPattern *ACLKeyPatternCreate(sds pattern, int flags) { keyPattern *new = (keyPattern *) zmalloc(sizeof(keyPattern)); new->pattern = pattern; new->flags = flags; return new; } /* Free a key pattern and internal structures. */ void ACLKeyPatternFree(keyPattern *pattern) { sdsfree(pattern->pattern); zfree(pattern); } /* Method for passwords/pattern comparison used for the user->passwords list * so that we can search for items with listSearchKey(). */ int ACLListMatchKeyPattern(void *a, void *b) { return sdscmp(((keyPattern *) a)->pattern,((keyPattern *) b)->pattern) == 0; } /* Method to free list elements from ACL users password/patterns lists. */ void ACLListFreeKeyPattern(void *item) { ACLKeyPatternFree(item); } /* Method to duplicate list elements from ACL users password/patterns lists. */ void *ACLListDupKeyPattern(void *item) { keyPattern *old = (keyPattern *) item; return ACLKeyPatternCreate(sdsdup(old->pattern), old->flags); } /* Append the string representation of a key pattern onto the * provided base string. */ sds sdsCatPatternString(sds base, keyPattern *pat) { if (pat->flags == ACL_ALL_PERMISSION) { base = sdscatlen(base,"~",1); } else if (pat->flags == ACL_READ_PERMISSION) { base = sdscatlen(base,"%R~",3); } else if (pat->flags == ACL_WRITE_PERMISSION) { base = sdscatlen(base,"%W~",3); } else { serverPanic("Invalid key pattern flag detected"); } return sdscatsds(base, pat->pattern); } /* Create an empty selector with the provided set of initial * flags. The selector will be default have no permissions. */ aclSelector *ACLCreateSelector(int flags) { aclSelector *selector = zmalloc(sizeof(aclSelector)); selector->flags = flags | server.acl_pubsub_default; selector->patterns = listCreate(); selector->channels = listCreate(); selector->allowed_firstargs = NULL; selector->command_rules = sdsempty(); listSetMatchMethod(selector->patterns,ACLListMatchKeyPattern); listSetFreeMethod(selector->patterns,ACLListFreeKeyPattern); listSetDupMethod(selector->patterns,ACLListDupKeyPattern); listSetMatchMethod(selector->channels,ACLListMatchSds); listSetFreeMethod(selector->channels,ACLListFreeSds); listSetDupMethod(selector->channels,ACLListDupSds); memset(selector->allowed_commands,0,sizeof(selector->allowed_commands)); return selector; } /* Cleanup the provided selector, including all interior structures. */ void ACLFreeSelector(aclSelector *selector) { listRelease(selector->patterns); listRelease(selector->channels); sdsfree(selector->command_rules); ACLResetFirstArgs(selector); zfree(selector); } /* Create an exact copy of the provided selector. */ aclSelector *ACLCopySelector(aclSelector *src) { aclSelector *dst = zmalloc(sizeof(aclSelector)); dst->flags = src->flags; dst->patterns = listDup(src->patterns); dst->channels = listDup(src->channels); dst->command_rules = sdsdup(src->command_rules); memcpy(dst->allowed_commands,src->allowed_commands, sizeof(dst->allowed_commands)); dst->allowed_firstargs = NULL; /* Copy the allowed first-args array of array of SDS strings. */ if (src->allowed_firstargs) { for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) { if (!(src->allowed_firstargs[j])) continue; for (int i = 0; src->allowed_firstargs[j][i]; i++) { ACLAddAllowedFirstArg(dst, j, src->allowed_firstargs[j][i]); } } } return dst; } /* List method for freeing a selector */ void ACLListFreeSelector(void *a) { ACLFreeSelector((aclSelector *) a); } /* List method for duplicating a selector */ void *ACLListDuplicateSelector(void *src) { return ACLCopySelector((aclSelector *)src); } /* All users have an implicit root selector which * provides backwards compatibility to the old ACLs- * permissions. */ aclSelector *ACLUserGetRootSelector(user *u) { serverAssert(listLength(u->selectors)); aclSelector *s = (aclSelector *) listNodeValue(listFirst(u->selectors)); serverAssert(s->flags & SELECTOR_FLAG_ROOT); return s; } /* Create a new user with the specified name, store it in the list * of users (the Users global radix tree), and returns a reference to * the structure representing the user. * * If the user with such name already exists NULL is returned. */ user *ACLCreateUser(const char *name, size_t namelen) { if (raxFind(Users,(unsigned char*)name,namelen,NULL)) return NULL; user *u = zmalloc(sizeof(*u)); u->name = sdsnewlen(name,namelen); u->flags = USER_FLAG_DISABLED; u->flags |= USER_FLAG_SANITIZE_PAYLOAD; u->passwords = listCreate(); u->acl_string = NULL; listSetMatchMethod(u->passwords,ACLListMatchSds); listSetFreeMethod(u->passwords,ACLListFreeSds); listSetDupMethod(u->passwords,ACLListDupSds); u->selectors = listCreate(); listSetFreeMethod(u->selectors,ACLListFreeSelector); listSetDupMethod(u->selectors,ACLListDuplicateSelector); /* Add the initial root selector */ aclSelector *s = ACLCreateSelector(SELECTOR_FLAG_ROOT); listAddNodeHead(u->selectors, s); raxInsert(Users,(unsigned char*)name,namelen,u,NULL); return u; } /* This function should be called when we need an unlinked "fake" user * we can use in order to validate ACL rules or for other similar reasons. * The user will not get linked to the Users radix tree. The returned * user should be released with ACLFreeUser() as usually. */ user *ACLCreateUnlinkedUser(void) { char username[64]; for (int j = 0; ; j++) { snprintf(username,sizeof(username),"__fakeuser:%d__",j); user *fakeuser = ACLCreateUser(username,strlen(username)); if (fakeuser == NULL) continue; int retval = raxRemove(Users,(unsigned char*) username, strlen(username),NULL); serverAssert(retval != 0); return fakeuser; } } /* Release the memory used by the user structure. Note that this function * will not remove the user from the Users global radix tree. */ void ACLFreeUser(user *u) { sdsfree(u->name); if (u->acl_string) { decrRefCount(u->acl_string); u->acl_string = NULL; } listRelease(u->passwords); listRelease(u->selectors); zfree(u); } /* Generic version of ACLFreeUser. */ void ACLFreeUserGeneric(void *u) { ACLFreeUser((user *)u); } /* When a user is deleted we need to cycle the active * connections in order to kill all the pending ones that * are authenticated with such user. */ void ACLFreeUserAndKillClients(user *u) { listIter li; listNode *ln; listRewind(server.clients,&li); while ((ln = listNext(&li)) != NULL) { client *c = listNodeValue(ln); if (c->user == u) { /* We'll free the connection asynchronously, so * in theory to set a different user is not needed. * However if there are bugs in Redis, soon or later * this may result in some security hole: it's much * more defensive to set the default user and put * it in non authenticated mode. */ deauthenticateAndCloseClient(c); } } ACLFreeUser(u); } /* Copy the user ACL rules from the source user 'src' to the destination * user 'dst' so that at the end of the process they'll have exactly the * same rules (but the names will continue to be the original ones). */ void ACLCopyUser(user *dst, user *src) { listRelease(dst->passwords); listRelease(dst->selectors); dst->passwords = listDup(src->passwords); dst->selectors = listDup(src->selectors); dst->flags = src->flags; if (dst->acl_string) { decrRefCount(dst->acl_string); } dst->acl_string = src->acl_string; if (dst->acl_string) { /* if src is NULL, we set it to NULL, if not, need to increment reference count */ incrRefCount(dst->acl_string); } } /* Given a command ID, this function set by reference 'word' and 'bit' * so that user->allowed_commands[word] will address the right word * where the corresponding bit for the provided ID is stored, and * so that user->allowed_commands[word]&bit will identify that specific * bit. The function returns C_ERR in case the specified ID overflows * the bitmap in the user representation. */ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) { if (id >= USER_COMMAND_BITS_COUNT) return C_ERR; *word = id / sizeof(uint64_t) / 8; *bit = 1ULL << (id % (sizeof(uint64_t) * 8)); return C_OK; } /* Check if the specified command bit is set for the specified user. * The function returns 1 is the bit is set or 0 if it is not. * Note that this function does not check the ALLCOMMANDS flag of the user * but just the lowlevel bitmask. * * If the bit overflows the user internal representation, zero is returned * in order to disallow the execution of the command in such edge case. */ int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) { uint64_t word, bit; if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return 0; return (selector->allowed_commands[word] & bit) != 0; } /* When +@all or allcommands is given, we set a reserved bit as well that we * can later test, to see if the user has the right to execute "future commands", * that is, commands loaded later via modules. */ int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) { return ACLGetSelectorCommandBit(selector,USER_COMMAND_BITS_COUNT-1); } /* Set the specified command bit for the specified user to 'value' (0 or 1). * If the bit overflows the user internal representation, no operation * is performed. As a side effect of calling this function with a value of * zero, the user flag ALLCOMMANDS is cleared since it is no longer possible * to skip the command bit explicit test. */ void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value) { uint64_t word, bit; if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return; if (value) { selector->allowed_commands[word] |= bit; } else { selector->allowed_commands[word] &= ~bit; selector->flags &= ~SELECTOR_FLAG_ALLCOMMANDS; } } /* Remove a rule from the retained command rules. Always match rules * verbatim, but also remove subcommand rules if we are adding or removing the * entire command. */ void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) { size_t new_len = sdslen(new_rule); char *existing_rule = selector->command_rules; /* Loop over the existing rules, trying to find a rule that "matches" * the new rule. If we find a match, then remove the command from the string by * copying the later rules over it. */ while(existing_rule[0]) { /* The first character of the rule is +/-, which we don't need to compare. */ char *copy_position = existing_rule; existing_rule += 1; /* Assume a trailing space after a command is part of the command, like '+get ', so trim it * as well if the command is removed. */ char *rule_end = strchr(existing_rule, ' '); if (!rule_end) { /* This is the last rule, so move it to the end of the string. */ rule_end = existing_rule + strlen(existing_rule); /* This approach can leave a trailing space if the last rule is removed, * but only if it's not the first rule, so handle that case. */ if (copy_position != selector->command_rules) copy_position -= 1; } char *copy_end = rule_end; if (*copy_end == ' ') copy_end++; /* Exact match or the rule we are comparing is a subcommand denoted by '|' */ size_t existing_len = rule_end - existing_rule; if (!memcmp(existing_rule, new_rule, min(existing_len, new_len))) { if ((existing_len == new_len) || (existing_len > new_len && (existing_rule[new_len]) == '|')) { /* Copy the remaining rules starting at the next rule to replace the rule to be * deleted, including the terminating NULL character. */ memmove(copy_position, copy_end, strlen(copy_end) + 1); existing_rule = copy_position; continue; } } existing_rule = copy_end; } /* There is now extra padding at the end of the rules, so clean that up. */ sdsupdatelen(selector->command_rules); } /* This function is resopnsible for updating the command_rules struct so that relative ordering of * commands and categories is maintained and can be reproduced without loss. */ void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) { sds new_rule = sdsnew(rule); sdstolower(new_rule); ACLSelectorRemoveCommandRule(selector, new_rule); if (sdslen(selector->command_rules)) selector->command_rules = sdscat(selector->command_rules, " "); selector->command_rules = sdscatfmt(selector->command_rules, allow ? "+%S" : "-%S", new_rule); sdsfree(new_rule); } /* This function is used to allow/block a specific command. * Allowing/blocking a container command also applies for its subcommands */ void ACLChangeSelectorPerm(aclSelector *selector, struct redisCommand *cmd, int allow) { unsigned long id = cmd->id; ACLSetSelectorCommandBit(selector,id,allow); ACLResetFirstArgsForCommand(selector,id); if (cmd->subcommands_dict) { dictEntry *de; dictIterator *di = dictGetSafeIterator(cmd->subcommands_dict); while((de = dictNext(di)) != NULL) { struct redisCommand *sub = (struct redisCommand *)dictGetVal(de); ACLSetSelectorCommandBit(selector,sub->id,allow); } dictReleaseIterator(di); } } /* This is like ACLSetSelectorCommandBit(), but instead of setting the specified * ID, it will check all the commands in the category specified as argument, * and will set all the bits corresponding to such commands to the specified * value. Since the category passed by the user may be non existing, the * function returns C_ERR if the category was not found, or C_OK if it was * found and the operation was performed. */ void ACLSetSelectorCommandBitsForCategory(dict *commands, aclSelector *selector, uint64_t cflag, int value) { dictIterator *di = dictGetIterator(commands); dictEntry *de; while ((de = dictNext(di)) != NULL) { struct redisCommand *cmd = dictGetVal(de); if (cmd->acl_categories & cflag) { ACLChangeSelectorPerm(selector,cmd,value); } if (cmd->subcommands_dict) { ACLSetSelectorCommandBitsForCategory(cmd->subcommands_dict, selector, cflag, value); } } dictReleaseIterator(di); } /* This function is responsible for recomputing the command bits for all selectors of the existing users. * It uses the 'command_rules', a string representation of the ordered categories and commands, * to recompute the command bits. */ void ACLRecomputeCommandBitsFromCommandRulesAllUsers(void) { raxIterator ri; raxStart(&ri,Users); raxSeek(&ri,"^",NULL,0); while(raxNext(&ri)) { user *u = ri.data; listIter li; listNode *ln; listRewind(u->selectors,&li); while((ln = listNext(&li))) { aclSelector *selector = (aclSelector *) listNodeValue(ln); int argc = 0; sds *argv = sdssplitargs(selector->command_rules, &argc); serverAssert(argv != NULL); /* Checking selector's permissions for all commands to start with a clean state. */ if (ACLSelectorCanExecuteFutureCommands(selector)) { int res = ACLSetSelector(selector,"+@all",-1); serverAssert(res == C_OK); } else { int res = ACLSetSelector(selector,"-@all",-1); serverAssert(res == C_OK); } /* Apply all of the commands and categories to this selector. */ for(int i = 0; i < argc; i++) { int res = ACLSetSelector(selector, argv[i], sdslen(argv[i])); serverAssert(res == C_OK); } sdsfreesplitres(argv, argc); } } raxStop(&ri); } int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allow) { uint64_t cflag = ACLGetCommandCategoryFlagByName(category + 1); if (!cflag) return C_ERR; ACLUpdateCommandRules(selector, category, allow); /* Set the actual command bits on the selector. */ ACLSetSelectorCommandBitsForCategory(server.orig_commands, selector, cflag, allow); return C_OK; } void ACLCountCategoryBitsForCommands(dict *commands, aclSelector *selector, unsigned long *on, unsigned long *off, uint64_t cflag) { dictIterator *di = dictGetIterator(commands); dictEntry *de; while ((de = dictNext(di)) != NULL) { struct redisCommand *cmd = dictGetVal(de); if (cmd->acl_categories & cflag) { if (ACLGetSelectorCommandBit(selector,cmd->id)) (*on)++; else (*off)++; } if (cmd->subcommands_dict) { ACLCountCategoryBitsForCommands(cmd->subcommands_dict, selector, on, off, cflag); } } dictReleaseIterator(di); } /* Return the number of commands allowed (on) and denied (off) for the user 'u' * in the subset of commands flagged with the specified category name. * If the category name is not valid, C_ERR is returned, otherwise C_OK is * returned and on and off are populated by reference. */ int ACLCountCategoryBitsForSelector(aclSelector *selector, unsigned long *on, unsigned long *off, const char *category) { uint64_t cflag = ACLGetCommandCategoryFlagByName(category); if (!cflag) return C_ERR; *on = *off = 0; ACLCountCategoryBitsForCommands(server.orig_commands, selector, on, off, cflag); return C_OK; } /* This function returns an SDS string representing the specified selector ACL * rules related to command execution, in the same format you could set them * back using ACL SETUSER. The function will return just the set of rules needed * to recreate the user commands bitmap, without including other user flags such * as on/off, passwords and so forth. The returned string always starts with * the +@all or -@all rule, depending on the user bitmap, and is followed, if * needed, by the other rules needed to narrow or extend what the user can do. */ sds ACLDescribeSelectorCommandRules(aclSelector *selector) { sds rules = sdsempty(); /* We use this fake selector as a "sanity" check to make sure the rules * we generate have the same bitmap as those on the current selector. */ aclSelector *fake_selector = ACLCreateSelector(0); /* Here we want to understand if we should start with +@all or -@all. * Note that when starting with +@all and subtracting, the user * will be able to execute future commands, while -@all and adding will just * allow the user the run the selected commands and/or categories. * How do we test for that? We use the trick of a reserved command ID bit * that is set only by +@all (and its alias "allcommands"). */ if (ACLSelectorCanExecuteFutureCommands(selector)) { rules = sdscat(rules,"+@all "); ACLSetSelector(fake_selector,"+@all",-1); } else { rules = sdscat(rules,"-@all "); ACLSetSelector(fake_selector,"-@all",-1); } /* Apply all of the commands and categories to the fake selector. */ int argc = 0; sds *argv = sdssplitargs(selector->command_rules, &argc); serverAssert(argv != NULL); for(int i = 0; i < argc; i++) { int res = ACLSetSelector(fake_selector, argv[i], -1); serverAssert(res == C_OK); } if (sdslen(selector->command_rules)) { rules = sdscatfmt(rules, "%S ", selector->command_rules); } sdsfreesplitres(argv, argc); /* Trim the final useless space. */ sdsrange(rules,0,-2); /* This is technically not needed, but we want to verify that now the * predicted bitmap is exactly the same as the user bitmap, and abort * otherwise, because aborting is better than a security risk in this * code path. */ if (memcmp(fake_selector->allowed_commands, selector->allowed_commands, sizeof(selector->allowed_commands)) != 0) { serverLog(LL_WARNING, "CRITICAL ERROR: User ACLs don't match final bitmap: '%s'", rules); serverPanic("No bitmap match in ACLDescribeSelectorCommandRules()"); } ACLFreeSelector(fake_selector); return rules; } sds ACLDescribeSelector(aclSelector *selector) { listIter li; listNode *ln; sds res = sdsempty(); /* Key patterns. */ if (selector->flags & SELECTOR_FLAG_ALLKEYS) { res = sdscatlen(res,"~* ",3); } else { listRewind(selector->patterns,&li); while((ln = listNext(&li))) { keyPattern *thispat = (keyPattern *)listNodeValue(ln); res = sdsCatPatternString(res, thispat); res = sdscatlen(res," ",1); } } /* Pub/sub channel patterns. */ if (selector->flags & SELECTOR_FLAG_ALLCHANNELS) { res = sdscatlen(res,"&* ",3); } else { res = sdscatlen(res,"resetchannels ",14); listRewind(selector->channels,&li); while((ln = listNext(&li))) { sds thispat = listNodeValue(ln); res = sdscatlen(res,"&",1); res = sdscatsds(res,thispat); res = sdscatlen(res," ",1); } } /* Command rules. */ sds rules = ACLDescribeSelectorCommandRules(selector); res = sdscatsds(res,rules); sdsfree(rules); return res; } /* This is similar to ACLDescribeSelectorCommandRules(), however instead of * describing just the user command rules, everything is described: user * flags, keys, passwords and finally the command rules obtained via * the ACLDescribeSelectorCommandRules() function. This is the function we call * when we want to rewrite the configuration files describing ACLs and * in order to show users with ACL LIST. */ robj *ACLDescribeUser(user *u) { if (u->acl_string) { incrRefCount(u->acl_string); return u->acl_string; } sds res = sdsempty(); /* Flags. */ for (int j = 0; ACLUserFlags[j].flag; j++) { if (u->flags & ACLUserFlags[j].flag) { res = sdscat(res,ACLUserFlags[j].name); res = sdscatlen(res," ",1); } } /* Passwords. */ listIter li; listNode *ln; listRewind(u->passwords,&li); while((ln = listNext(&li))) { sds thispass = listNodeValue(ln); res = sdscatlen(res,"#",1); res = sdscatsds(res,thispass); res = sdscatlen(res," ",1); } /* Selectors (Commands and keys) */ listRewind(u->selectors,&li); while((ln = listNext(&li))) { aclSelector *selector = (aclSelector *) listNodeValue(ln); sds default_perm = ACLDescribeSelector(selector); if (selector->flags & SELECTOR_FLAG_ROOT) { res = sdscatfmt(res, "%s", default_perm); } else { res = sdscatfmt(res, " (%s)", default_perm); } sdsfree(default_perm); } u->acl_string = createObject(OBJ_STRING, res); /* because we are returning it, have to increase count */ incrRefCount(u->acl_string); return u->acl_string; } /* Get a command from the original command table, that is not affected * by the command renaming operations: we base all the ACL work from that * table, so that ACLs are valid regardless of command renaming. */ struct redisCommand *ACLLookupCommand(const char *name) { struct redisCommand *cmd; sds sdsname = sdsnew(name); cmd = lookupCommandBySdsLogic(server.orig_commands,sdsname); sdsfree(sdsname); return cmd; } /* Flush the array of allowed first-args for the specified user * and command ID. */ void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) { if (selector->allowed_firstargs && selector->allowed_firstargs[id]) { for (int i = 0; selector->allowed_firstargs[id][i]; i++) sdsfree(selector->allowed_firstargs[id][i]); zfree(selector->allowed_firstargs[id]); selector->allowed_firstargs[id] = NULL; } } /* Flush the entire table of first-args. This is useful on +@all, -@all * or similar to return back to the minimal memory usage (and checks to do) * for the user. */ void ACLResetFirstArgs(aclSelector *selector) { if (selector->allowed_firstargs == NULL) return; for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) { if (selector->allowed_firstargs[j]) { for (int i = 0; selector->allowed_firstargs[j][i]; i++) sdsfree(selector->allowed_firstargs[j][i]); zfree(selector->allowed_firstargs[j]); } } zfree(selector->allowed_firstargs); selector->allowed_firstargs = NULL; } /* Add a first-arg to the list of subcommands for the user 'u' and * the command id specified. */ void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) { /* If this is the first first-arg to be configured for * this user, we have to allocate the first-args array. */ if (selector->allowed_firstargs == NULL) { selector->allowed_firstargs = zcalloc(USER_COMMAND_BITS_COUNT * sizeof(sds*)); } /* We also need to enlarge the allocation pointing to the * null terminated SDS array, to make space for this one. * To start check the current size, and while we are here * make sure the first-arg is not already specified inside. */ long items = 0; if (selector->allowed_firstargs[id]) { while(selector->allowed_firstargs[id][items]) { /* If it's already here do not add it again. */ if (!strcasecmp(selector->allowed_firstargs[id][items],sub)) return; items++; } } /* Now we can make space for the new item (and the null term). */ items += 2; selector->allowed_firstargs[id] = zrealloc(selector->allowed_firstargs[id], sizeof(sds)*items); selector->allowed_firstargs[id][items-2] = sdsnew(sub); selector->allowed_firstargs[id][items-1] = NULL; } /* Create an ACL selector from the given ACL operations, which should be * a list of space separate ACL operations that starts and ends * with parentheses. * * If any of the operations are invalid, NULL will be returned instead * and errno will be set corresponding to the interior error. */ aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) { serverAssert(opset[0] == '(' && opset[opsetlen - 1] == ')'); aclSelector *s = ACLCreateSelector(0); int argc = 0; sds trimmed = sdsnewlen(opset + 1, opsetlen - 2); sds *argv = sdssplitargs(trimmed, &argc); for (int i = 0; i < argc; i++) { if (ACLSetSelector(s, argv[i], sdslen(argv[i])) == C_ERR) { ACLFreeSelector(s); s = NULL; goto cleanup; } } cleanup: sdsfreesplitres(argv, argc); sdsfree(trimmed); return s; } /* Set a selector's properties with the provided 'op'. * * + Allow the execution of that command. * May be used with `|` for allowing subcommands (e.g "+config|get") * - Disallow the execution of that command. * May be used with `|` for blocking subcommands (e.g "-config|set") * +@ Allow the execution of all the commands in such category * with valid categories are like @admin, @set, @sortedset, ... * and so forth, see the full list in the server.c file where * the Redis command table is described and defined. * The special category @all means all the commands, but currently * present in the server, and that will be loaded in the future * via modules. * +|first-arg Allow a specific first argument of an otherwise * disabled command. Note that this form is not * allowed as negative like -SELECT|1, but * only additive starting with "+". * allcommands Alias for +@all. Note that it implies the ability to execute * all the future commands loaded via the modules system. * nocommands Alias for -@all. * ~ Add a pattern of keys that can be mentioned as part of * commands. For instance ~* allows all the keys. The pattern * is a glob-style pattern like the one of KEYS. * It is possible to specify multiple patterns. * %R~ Add key read pattern that specifies which keys can be read * from. * %W~ Add key write pattern that specifies which keys can be * written to. * allkeys Alias for ~* * resetkeys Flush the list of allowed keys patterns. * & Add a pattern of channels that can be mentioned as part of * Pub/Sub commands. For instance &* allows all the channels. The * pattern is a glob-style pattern like the one of PSUBSCRIBE. * It is possible to specify multiple patterns. * allchannels Alias for &* * resetchannels Flush the list of allowed channel patterns. */ int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) { if (!strcasecmp(op,"allkeys") || !strcasecmp(op,"~*")) { selector->flags |= SELECTOR_FLAG_ALLKEYS; listEmpty(selector->patterns); } else if (!strcasecmp(op,"resetkeys")) { selector->flags &= ~SELECTOR_FLAG_ALLKEYS; listEmpty(selector->patterns); } else if (!strcasecmp(op,"allchannels") || !strcasecmp(op,"&*")) { selector->flags |= SELECTOR_FLAG_ALLCHANNELS; listEmpty(selector->channels); } else if (!strcasecmp(op,"resetchannels")) { selector->flags &= ~SELECTOR_FLAG_ALLCHANNELS; listEmpty(selector->channels); } else if (!strcasecmp(op,"allcommands") || !strcasecmp(op,"+@all")) { memset(selector->allowed_commands,255,sizeof(selector->allowed_commands)); selector->flags |= SELECTOR_FLAG_ALLCOMMANDS; sdsclear(selector->command_rules); ACLResetFirstArgs(selector); } else if (!strcasecmp(op,"nocommands") || !strcasecmp(op,"-@all")) { memset(selector->allowed_commands,0,sizeof(selector->allowed_commands)); selector->flags &= ~SELECTOR_FLAG_ALLCOMMANDS; sdsclear(selector->command_rules); ACLResetFirstArgs(selector); } else if (op[0] == '~' || op[0] == '%') { if (selector->flags & SELECTOR_FLAG_ALLKEYS) { errno = EEXIST; return C_ERR; } int flags = 0; size_t offset = 1; if (op[0] == '%') { int perm_ok = 1; for (; offset < oplen; offset++) { if (toupper(op[offset]) == 'R' && !(flags & ACL_READ_PERMISSION)) { flags |= ACL_READ_PERMISSION; } else if (toupper(op[offset]) == 'W' && !(flags & ACL_WRITE_PERMISSION)) { flags |= ACL_WRITE_PERMISSION; } else if (op[offset] == '~') { offset++; break; } else { perm_ok = 0; break; } } if (!flags || !perm_ok) { errno = EINVAL; return C_ERR; } } else { flags = ACL_ALL_PERMISSION; } if (ACLStringHasSpaces(op+offset,oplen-offset)) { errno = EINVAL; return C_ERR; } keyPattern *newpat = ACLKeyPatternCreate(sdsnewlen(op+offset,oplen-offset), flags); listNode *ln = listSearchKey(selector->patterns,newpat); /* Avoid re-adding the same key pattern multiple times. */ if (ln == NULL) { listAddNodeTail(selector->patterns,newpat); } else { ((keyPattern *)listNodeValue(ln))->flags |= flags; ACLKeyPatternFree(newpat); } selector->flags &= ~SELECTOR_FLAG_ALLKEYS; } else if (op[0] == '&') { if (selector->flags & SELECTOR_FLAG_ALLCHANNELS) { errno = EISDIR; return C_ERR; } if (ACLStringHasSpaces(op+1,oplen-1)) { errno = EINVAL; return C_ERR; } sds newpat = sdsnewlen(op+1,oplen-1); listNode *ln = listSearchKey(selector->channels,newpat); /* Avoid re-adding the same channel pattern multiple times. */ if (ln == NULL) listAddNodeTail(selector->channels,newpat); else sdsfree(newpat); selector->flags &= ~SELECTOR_FLAG_ALLCHANNELS; } else if (op[0] == '+' && op[1] != '@') { if (strrchr(op,'|') == NULL) { struct redisCommand *cmd = ACLLookupCommand(op+1); if (cmd == NULL) { errno = ENOENT; return C_ERR; } ACLChangeSelectorPerm(selector,cmd,1); ACLUpdateCommandRules(selector,cmd->fullname,1); } else { /* Split the command and subcommand parts. */ char *copy = zstrdup(op+1); char *sub = strrchr(copy,'|'); sub[0] = '\0'; sub++; struct redisCommand *cmd = ACLLookupCommand(copy); /* Check if the command exists. We can't check the * first-arg to see if it is valid. */ if (cmd == NULL) { zfree(copy); errno = ENOENT; return C_ERR; } /* We do not support allowing first-arg of a subcommand */ if (cmd->parent) { zfree(copy); errno = ECHILD; return C_ERR; } /* The subcommand cannot be empty, so things like DEBUG| * are syntax errors of course. */ if (strlen(sub) == 0) { zfree(copy); errno = EINVAL; return C_ERR; } if (cmd->subcommands_dict) { /* If user is trying to allow a valid subcommand we can just add its unique ID */ cmd = ACLLookupCommand(op+1); if (cmd == NULL) { zfree(copy); errno = ENOENT; return C_ERR; } ACLChangeSelectorPerm(selector,cmd,1); } else { /* If user is trying to use the ACL mech to block SELECT except SELECT 0 or * block DEBUG except DEBUG OBJECT (DEBUG subcommands are not considered * subcommands for now) we use the allowed_firstargs mechanism. */ /* 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); ACLAddAllowedFirstArg(selector,cmd->id,sub); } ACLUpdateCommandRules(selector,op+1,1); zfree(copy); } } else if (op[0] == '-' && op[1] != '@') { struct redisCommand *cmd = ACLLookupCommand(op+1); if (cmd == NULL) { errno = ENOENT; return C_ERR; } ACLChangeSelectorPerm(selector,cmd,0); ACLUpdateCommandRules(selector,cmd->fullname,0); } else if ((op[0] == '+' || op[0] == '-') && op[1] == '@') { int bitval = op[0] == '+' ? 1 : 0; if (ACLSetSelectorCategory(selector,op+1,bitval) == C_ERR) { errno = ENOENT; return C_ERR; } } else { errno = EINVAL; return C_ERR; } return C_OK; } /* Set user properties according to the string "op". The following * is a description of what different strings will do: * * on Enable the user: it is possible to authenticate as this user. * off Disable the user: it's no longer possible to authenticate * with this user, however the already authenticated connections * will still work. * skip-sanitize-payload RESTORE dump-payload sanitization is skipped. * sanitize-payload RESTORE dump-payload is sanitized (default). * > Add this password to the list of valid password for the user. * For example >mypass will add "mypass" to the list. * This directive clears the "nopass" flag (see later). * # Add this password hash to the list of valid hashes for * the user. This is useful if you have previously computed * the hash, and don't want to store it in plaintext. * This directive clears the "nopass" flag (see later). * < Remove this password from the list of valid passwords. * ! Remove this hashed password from the list of valid passwords. * This is useful when you want to remove a password just by * hash without knowing its plaintext version at all. * nopass All the set passwords of the user are removed, and the user * is flagged as requiring no password: it means that every * password will work against this user. If this directive is * used for the default user, every new connection will be * immediately authenticated with the default user without * any explicit AUTH command required. Note that the "resetpass" * directive will clear this condition. * resetpass Flush the list of allowed passwords. Moreover removes the * "nopass" status. After "resetpass" the user has no associated * passwords and there is no way to authenticate without adding * some password (or setting it as "nopass" later). * reset Performs the following actions: resetpass, resetkeys, resetchannels, * allchannels (if acl-pubsub-default is set), off, clearselectors, -@all. * The user returns to the same state it has immediately after its creation. * () Create a new selector with the options specified within the * parentheses and attach it to the user. Each option should be * space separated. The first character must be ( and the last * character must be ). * clearselectors Remove all of the currently attached selectors. * Note this does not change the "root" user permissions, * which are the permissions directly applied onto the * user (outside the parentheses). * * Selector options can also be specified by this function, in which case * they update the root selector for the user. * * The 'op' string must be null terminated. The 'oplen' argument should * specify the length of the 'op' string in case the caller requires to pass * binary data (for instance the >password form may use a binary password). * Otherwise the field can be set to -1 and the function will use strlen() * to determine the length. * * The function returns C_OK if the action to perform was understood because * the 'op' string made sense. Otherwise C_ERR is returned if the operation * is unknown or has some syntax error. * * When an error is returned, errno is set to the following values: * * EINVAL: The specified opcode is not understood or the key/channel pattern is * invalid (contains non allowed characters). * ENOENT: The command name or command category provided with + or - is not * known. * EEXIST: You are adding a key pattern after "*" was already added. This is * almost surely an error on the user side. * EISDIR: You are adding a channel pattern after "*" was already added. This is * almost surely an error on the user side. * ENODEV: The password you are trying to remove from the user does not exist. * EBADMSG: The hash you are trying to add is not a valid hash. * ECHILD: Attempt to allow a specific first argument of a subcommand */ int ACLSetUser(user *u, const char *op, ssize_t oplen) { /* as we are changing the ACL, the old generated string is now invalid */ if (u->acl_string) { decrRefCount(u->acl_string); u->acl_string = NULL; } if (oplen == -1) oplen = strlen(op); if (oplen == 0) return C_OK; /* Empty string is a no-operation. */ if (!strcasecmp(op,"on")) { u->flags |= USER_FLAG_ENABLED; u->flags &= ~USER_FLAG_DISABLED; } else if (!strcasecmp(op,"off")) { u->flags |= USER_FLAG_DISABLED; u->flags &= ~USER_FLAG_ENABLED; } else if (!strcasecmp(op,"skip-sanitize-payload")) { u->flags |= USER_FLAG_SANITIZE_PAYLOAD_SKIP; u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD; } else if (!strcasecmp(op,"sanitize-payload")) { u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD_SKIP; u->flags |= USER_FLAG_SANITIZE_PAYLOAD; } else if (!strcasecmp(op,"nopass")) { u->flags |= USER_FLAG_NOPASS; listEmpty(u->passwords); } else if (!strcasecmp(op,"resetpass")) { u->flags &= ~USER_FLAG_NOPASS; listEmpty(u->passwords); } else if (op[0] == '>' || op[0] == '#') { sds newpass; if (op[0] == '>') { newpass = ACLHashPassword((unsigned char*)op+1,oplen-1); } else { if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) { errno = EBADMSG; return C_ERR; } newpass = sdsnewlen(op+1,oplen-1); } listNode *ln = listSearchKey(u->passwords,newpass); /* Avoid re-adding the same password multiple times. */ if (ln == NULL) listAddNodeTail(u->passwords,newpass); else sdsfree(newpass); u->flags &= ~USER_FLAG_NOPASS; } else if (op[0] == '<' || op[0] == '!') { sds delpass; if (op[0] == '<') { delpass = ACLHashPassword((unsigned char*)op+1,oplen-1); } else { if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) { errno = EBADMSG; return C_ERR; } delpass = sdsnewlen(op+1,oplen-1); } listNode *ln = listSearchKey(u->passwords,delpass); sdsfree(delpass); if (ln) { listDelNode(u->passwords,ln); } else { errno = ENODEV; return C_ERR; } } else if (op[0] == '(' && op[oplen - 1] == ')') { aclSelector *selector = aclCreateSelectorFromOpSet(op, oplen); if (!selector) { /* No errorno set, propagate it from interior error. */ return C_ERR; } listAddNodeTail(u->selectors, selector); return C_OK; } else if (!strcasecmp(op,"clearselectors")) { listIter li; listNode *ln; listRewind(u->selectors,&li); /* There has to be a root selector */ serverAssert(listNext(&li)); while((ln = listNext(&li))) { listDelNode(u->selectors, ln); } return C_OK; } else if (!strcasecmp(op,"reset")) { serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK); serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK); serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK); if (server.acl_pubsub_default & SELECTOR_FLAG_ALLCHANNELS) serverAssert(ACLSetUser(u,"allchannels",-1) == C_OK); serverAssert(ACLSetUser(u,"off",-1) == C_OK); serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK); serverAssert(ACLSetUser(u,"clearselectors",-1) == C_OK); serverAssert(ACLSetUser(u,"-@all",-1) == C_OK); } else { aclSelector *selector = ACLUserGetRootSelector(u); if (ACLSetSelector(selector, op, oplen) == C_ERR) { return C_ERR; } } return C_OK; } /* Return a description of the error that occurred in ACLSetUser() according to * the errno value set by the function on error. */ const char *ACLSetUserStringError(void) { const char *errmsg = "Wrong format"; if (errno == ENOENT) errmsg = "Unknown command or category name in ACL"; else if (errno == EINVAL) errmsg = "Syntax error"; else if (errno == EEXIST) errmsg = "Adding a pattern after the * pattern (or the " "'allkeys' flag) is not valid and does not have any " "effect. Try 'resetkeys' to start with an empty " "list of patterns"; else if (errno == EISDIR) errmsg = "Adding a pattern after the * pattern (or the " "'allchannels' flag) is not valid and does not have any " "effect. Try 'resetchannels' to start with an empty " "list of channels"; else if (errno == ENODEV) errmsg = "The password you are trying to remove from the user does " "not exist"; else if (errno == EBADMSG) errmsg = "The password hash must be exactly 64 characters and contain " "only lowercase hexadecimal characters"; else if (errno == EALREADY) errmsg = "Duplicate user found. A user can only be defined once in " "config files"; else if (errno == ECHILD) errmsg = "Allowing first-arg of a subcommand is not supported"; return errmsg; } /* Create the default user, this has special permissions. */ user *ACLCreateDefaultUser(void) { user *new = ACLCreateUser("default",7); ACLSetUser(new,"+@all",-1); ACLSetUser(new,"~*",-1); ACLSetUser(new,"&*",-1); ACLSetUser(new,"on",-1); ACLSetUser(new,"nopass",-1); return new; } /* Initialization of the ACL subsystem. */ void ACLInit(void) { Users = raxNew(); UsersToLoad = listCreate(); ACLInitCommandCategories(); listSetMatchMethod(UsersToLoad, ACLListMatchLoadedUser); ACLLog = listCreate(); DefaultUser = ACLCreateDefaultUser(); } /* Check the username and password pair and return C_OK if they are valid, * otherwise C_ERR is returned and errno is set to: * * EINVAL: if the username-password do not match. * ENOENT: if the specified user does not exist at all. */ int ACLCheckUserCredentials(robj *username, robj *password) { user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr)); if (u == NULL) { errno = ENOENT; return C_ERR; } /* Disabled users can't login. */ if (u->flags & USER_FLAG_DISABLED) { errno = EINVAL; return C_ERR; } /* If the user is configured to don't require any password, we * are already fine here. */ if (u->flags & USER_FLAG_NOPASS) return C_OK; /* Check all the user passwords for at least one to match. */ listIter li; listNode *ln; listRewind(u->passwords,&li); sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr)); while((ln = listNext(&li))) { sds thispass = listNodeValue(ln); if (!time_independent_strcmp(hashed, thispass, HASH_PASSWORD_LEN)) { sdsfree(hashed); return C_OK; } } sdsfree(hashed); /* If we reached this point, no password matched. */ errno = EINVAL; return C_ERR; } /* If `err` is provided, this is added as an error reply to the client. * Otherwise, the standard Auth error is added as a reply. */ void addAuthErrReply(client *c, robj *err) { if (clientHasPendingReplies(c)) return; if (!err) { addReplyError(c, "-WRONGPASS invalid username-password pair or user is disabled."); return; } addReplyError(c, err->ptr); } /* This is like ACLCheckUserCredentials(), however if the user/pass * are correct, the connection is put in authenticated state and the * connection user reference is populated. * * The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */ int checkPasswordBasedAuth(client *c, robj *username, robj *password) { if (ACLCheckUserCredentials(username,password) == C_OK) { c->authenticated = 1; c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr)); moduleNotifyUserChanged(c); return AUTH_OK; } else { addACLLogEntry(c,ACL_DENIED_AUTH,(c->flags & CLIENT_MULTI) ? ACL_LOG_CTX_MULTI : ACL_LOG_CTX_TOPLEVEL,0,username->ptr,NULL); return AUTH_ERR; } } /* Attempt authenticating the user - first through module based authentication, * and then, if needed, with normal password based authentication. * Returns one of the following codes: * AUTH_OK - Indicates that authentication succeeded. * AUTH_ERR - Indicates that authentication failed. * AUTH_BLOCKED - Indicates module authentication is in progress through a blocking implementation. */ int ACLAuthenticateUser(client *c, robj *username, robj *password, robj **err) { int result = checkModuleAuthentication(c, username, password, err); /* If authentication was not handled by any Module, attempt normal password based auth. */ if (result == AUTH_NOT_HANDLED) { result = checkPasswordBasedAuth(c, username, password); } return result; } /* For ACL purposes, every user has a bitmap with the commands that such * user is allowed to execute. In order to populate the bitmap, every command * should have an assigned ID (that is used to index the bitmap). This function * creates such an ID: it uses sequential IDs, reusing the same ID for the same * command name, so that a command retains the same ID in case of modules that * are unloaded and later reloaded. * * The function does not take ownership of the 'cmdname' SDS string. * */ unsigned long ACLGetCommandID(sds cmdname) { sds lowername = sdsdup(cmdname); sdstolower(lowername); if (commandId == NULL) commandId = raxNew(); void *id; if (raxFind(commandId,(unsigned char*)lowername,sdslen(lowername),&id)) { sdsfree(lowername); return (unsigned long)id; } raxInsert(commandId,(unsigned char*)lowername,strlen(lowername), (void*)nextid,NULL); sdsfree(lowername); unsigned long thisid = nextid; nextid++; /* We never assign the last bit in the user commands bitmap structure, * this way we can later check if this bit is set, understanding if the * current ACL for the user was created starting with a +@all to add all * the possible commands and just subtracting other single commands or * categories, or if, instead, the ACL was created just adding commands * and command categories from scratch, not allowing future commands by * default (loaded via modules). This is useful when rewriting the ACLs * with ACL SAVE. */ if (nextid == USER_COMMAND_BITS_COUNT-1) nextid++; return thisid; } /* Clear command id table and reset nextid to 0. */ void ACLClearCommandID(void) { if (commandId) raxFree(commandId); commandId = NULL; nextid = 0; } /* Return an username by its name, or NULL if the user does not exist. */ user *ACLGetUserByName(const char *name, size_t namelen) { void *myuser = NULL; raxFind(Users,(unsigned char*)name,namelen,&myuser); return myuser; } /* ============================================================================= * ACL permission checks * ==========================================================================*/ /* Check if the key can be accessed by the selector. * * If the selector can access the key, ACL_OK is returned, otherwise * ACL_DENIED_KEY is returned. */ static int ACLSelectorCheckKey(aclSelector *selector, const char *key, int keylen, int keyspec_flags) { /* The selector can access any key */ if (selector->flags & SELECTOR_FLAG_ALLKEYS) return ACL_OK; listIter li; listNode *ln; listRewind(selector->patterns,&li); int key_flags = 0; if (keyspec_flags & CMD_KEY_ACCESS) key_flags |= ACL_READ_PERMISSION; if (keyspec_flags & CMD_KEY_INSERT) key_flags |= ACL_WRITE_PERMISSION; if (keyspec_flags & CMD_KEY_DELETE) key_flags |= ACL_WRITE_PERMISSION; if (keyspec_flags & CMD_KEY_UPDATE) key_flags |= ACL_WRITE_PERMISSION; /* Is given key represent a prefix of a set of keys */ int prefix = keyspec_flags & CMD_KEY_PREFIX; /* Test this key against every pattern. */ while((ln = listNext(&li))) { keyPattern *pattern = listNodeValue(ln); if ((pattern->flags & key_flags) != key_flags) continue; size_t plen = sdslen(pattern->pattern); if (prefix) { if (prefixmatch(pattern->pattern,plen,key,keylen,0)) return ACL_OK; } else { if (stringmatchlen(pattern->pattern, plen, key, keylen, 0)) return ACL_OK; } } return ACL_DENIED_KEY; } /* Checks if the provided selector selector has access specified in flags * to all keys in the keyspace. For example, CMD_KEY_READ access requires either * '%R~*', '~*', or allkeys to be granted to the selector. Returns 1 if all * the access flags are satisfied with this selector or 0 otherwise. */ static int ACLSelectorHasUnrestrictedKeyAccess(aclSelector *selector, int flags) { /* The selector can access any key */ if (selector->flags & SELECTOR_FLAG_ALLKEYS) return 1; listIter li; listNode *ln; listRewind(selector->patterns,&li); int access_flags = 0; if (flags & CMD_KEY_ACCESS) access_flags |= ACL_READ_PERMISSION; if (flags & CMD_KEY_INSERT) access_flags |= ACL_WRITE_PERMISSION; if (flags & CMD_KEY_DELETE) access_flags |= ACL_WRITE_PERMISSION; if (flags & CMD_KEY_UPDATE) access_flags |= ACL_WRITE_PERMISSION; /* Test this key against every pattern. */ while((ln = listNext(&li))) { keyPattern *pattern = listNodeValue(ln); if ((pattern->flags & access_flags) != access_flags) continue; if (!strcmp(pattern->pattern,"*")) { return 1; } } return 0; } /* Checks a channel against a provided list of channels. The is_pattern * argument should only be used when subscribing (not when publishing) * and controls whether the input channel is evaluated as a channel pattern * (like in PSUBSCRIBE) or a plain channel name (like in SUBSCRIBE). * * Note that a plain channel name like in PUBLISH or SUBSCRIBE can be * matched against ACL channel patterns, but the pattern provided in PSUBSCRIBE * can only be matched as a literal against an ACL pattern (using plain string compare). */ static int ACLCheckChannelAgainstList(list *reference, const char *channel, int channellen, int is_pattern) { listIter li; listNode *ln; listRewind(reference, &li); while((ln = listNext(&li))) { sds pattern = listNodeValue(ln); size_t plen = sdslen(pattern); /* Channel patterns are matched literally against the channels in * the list. Regular channels perform pattern matching. */ if ((is_pattern && !strcmp(pattern,channel)) || (!is_pattern && stringmatchlen(pattern,plen,channel,channellen,0))) { return ACL_OK; } } return ACL_DENIED_CHANNEL; } /* To prevent duplicate calls to getKeysResult, a cache is maintained * in between calls to the various selectors. */ typedef struct { int keys_init; getKeysResult keys; } aclKeyResultCache; void initACLKeyResultCache(aclKeyResultCache *cache) { cache->keys_init = 0; } void cleanupACLKeyResultCache(aclKeyResultCache *cache) { if (cache->keys_init) getKeysFreeResult(&(cache->keys)); } /* Check if the command is ready to be executed according to the * ACLs associated with the specified selector. * * If the selector can execute the command ACL_OK is returned, otherwise * ACL_DENIED_CMD, ACL_DENIED_KEY, or ACL_DENIED_CHANNEL is returned: the first in case the * command cannot be executed because the selector is not allowed to run such * command, the second and third if the command is denied because the selector is trying * to access a key or channel that are not among the specified patterns. */ static int ACLSelectorCheckCmd(aclSelector *selector, struct redisCommand *cmd, robj **argv, int argc, int *keyidxptr, aclKeyResultCache *cache) { uint64_t id = cmd->id; int ret; if (!(selector->flags & SELECTOR_FLAG_ALLCOMMANDS) && !(cmd->flags & CMD_NO_AUTH)) { /* If the bit is not set we have to check further, in case the * command is allowed just with that specific first argument. */ if (ACLGetSelectorCommandBit(selector,id) == 0) { /* Check if the first argument matches. */ if (argc < 2 || selector->allowed_firstargs == NULL || selector->allowed_firstargs[id] == NULL) { return ACL_DENIED_CMD; } long subid = 0; while (1) { if (selector->allowed_firstargs[id][subid] == NULL) return ACL_DENIED_CMD; int idx = cmd->parent ? 2 : 1; if (!strcasecmp(argv[idx]->ptr,selector->allowed_firstargs[id][subid])) break; /* First argument match found. Stop here. */ subid++; } } } /* Check if the user can execute commands explicitly touching the keys * mentioned in the command arguments. */ if (!(selector->flags & SELECTOR_FLAG_ALLKEYS) && doesCommandHaveKeys(cmd)) { if (!(cache->keys_init)) { cache->keys = (getKeysResult) GETKEYS_RESULT_INIT; getKeysFromCommandWithSpecs(cmd, argv, argc, GET_KEYSPEC_DEFAULT, &(cache->keys)); cache->keys_init = 1; } getKeysResult *result = &(cache->keys); keyReference *resultidx = result->keys; for (int j = 0; j < result->numkeys; j++) { int idx = resultidx[j].pos; ret = ACLSelectorCheckKey(selector, argv[idx]->ptr, sdslen(argv[idx]->ptr), resultidx[j].flags); if (ret != ACL_OK) { if (keyidxptr) *keyidxptr = resultidx[j].pos; return ret; } } } /* Check if the user can execute commands explicitly touching the channels * mentioned in the command arguments */ const int channel_flags = CMD_CHANNEL_PUBLISH | CMD_CHANNEL_SUBSCRIBE; if (!(selector->flags & SELECTOR_FLAG_ALLCHANNELS) && doesCommandHaveChannelsWithFlags(cmd, channel_flags)) { getKeysResult channels = (getKeysResult) GETKEYS_RESULT_INIT; getChannelsFromCommand(cmd, argv, argc, &channels); keyReference *channelref = channels.keys; for (int j = 0; j < channels.numkeys; j++) { int idx = channelref[j].pos; if (!(channelref[j].flags & channel_flags)) continue; int is_pattern = channelref[j].flags & CMD_CHANNEL_PATTERN; int ret = ACLCheckChannelAgainstList(selector->channels, argv[idx]->ptr, sdslen(argv[idx]->ptr), is_pattern); if (ret != ACL_OK) { if (keyidxptr) *keyidxptr = channelref[j].pos; getKeysFreeResult(&channels); return ret; } } getKeysFreeResult(&channels); } return ACL_OK; } /* Check if the key can be accessed by the client according to * the ACLs associated with the specified user according to the * keyspec access flags. * * If the user can access the key, ACL_OK is returned, otherwise * ACL_DENIED_KEY is returned. */ int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags) { listIter li; listNode *ln; /* If there is no associated user, the connection can run anything. */ if (u == NULL) return ACL_OK; /* Check all of the selectors */ listRewind(u->selectors,&li); while((ln = listNext(&li))) { aclSelector *s = (aclSelector *) listNodeValue(ln); if (ACLSelectorCheckKey(s, key, keylen, flags) == ACL_OK) { return ACL_OK; } } return ACL_DENIED_KEY; } /* Checks if the user can execute the given command with the added restriction * it must also have the access specified in flags to any key in the key space. * For example, CMD_KEY_READ access requires either '%R~*', '~*', or allkeys to be * granted in addition to the access required by the command. Returns 1 * if the user has access or 0 otherwise. */ int ACLUserCheckCmdWithUnrestrictedKeyAccess(user *u, struct redisCommand *cmd, robj **argv, int argc, int flags) { listIter li; listNode *ln; int local_idxptr; /* If there is no associated user, the connection can run anything. */ if (u == NULL) return 1; /* For multiple selectors, we cache the key result in between selector * calls to prevent duplicate lookups. */ aclKeyResultCache cache; initACLKeyResultCache(&cache); /* Check each selector sequentially */ listRewind(u->selectors,&li); while((ln = listNext(&li))) { aclSelector *s = (aclSelector *) listNodeValue(ln); int acl_retval = ACLSelectorCheckCmd(s, cmd, argv, argc, &local_idxptr, &cache); if (acl_retval == ACL_OK && ACLSelectorHasUnrestrictedKeyAccess(s, flags)) { cleanupACLKeyResultCache(&cache); return 1; } } cleanupACLKeyResultCache(&cache); return 0; } /* Check if the channel can be accessed by the client according to * the ACLs associated with the specified user. * * If the user can access the key, ACL_OK is returned, otherwise * ACL_DENIED_CHANNEL is returned. */ int ACLUserCheckChannelPerm(user *u, sds channel, int is_pattern) { listIter li; listNode *ln; /* If there is no associated user, the connection can run anything. */ if (u == NULL) return ACL_OK; /* Check all of the selectors */ listRewind(u->selectors,&li); while((ln = listNext(&li))) { aclSelector *s = (aclSelector *) listNodeValue(ln); /* The selector can run any keys */ if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return ACL_OK; /* Otherwise, loop over the selectors list and check each channel */ if (ACLCheckChannelAgainstList(s->channels, channel, sdslen(channel), is_pattern) == ACL_OK) { return ACL_OK; } } return ACL_DENIED_CHANNEL; } /* Lower level API that checks if a specified user is able to execute a given command. * * If the command fails an ACL check, idxptr will be to set to the first argv entry that * causes the failure, either 0 if the command itself fails or the idx of the key/channel * that causes the failure */ int ACLCheckAllUserCommandPerm(user *u, struct redisCommand *cmd, robj **argv, int argc, int *idxptr) { listIter li; listNode *ln; /* If there is no associated user, the connection can run anything. */ if (u == NULL) 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. */ int relevant_error = ACL_DENIED_CMD; int local_idxptr = 0, last_idx = 0; /* For multiple selectors, we cache the key result in between selector * calls to prevent duplicate lookups. */ aclKeyResultCache cache; initACLKeyResultCache(&cache); /* Check each selector sequentially */ listRewind(u->selectors,&li); while((ln = listNext(&li))) { aclSelector *s = (aclSelector *) listNodeValue(ln); int acl_retval = ACLSelectorCheckCmd(s, cmd, argv, argc, &local_idxptr, &cache); if (acl_retval == ACL_OK) { cleanupACLKeyResultCache(&cache); return ACL_OK; } if (acl_retval > relevant_error || (acl_retval == relevant_error && local_idxptr > last_idx)) { relevant_error = acl_retval; last_idx = local_idxptr; } } *idxptr = last_idx; cleanupACLKeyResultCache(&cache); return relevant_error; } /* High level API for checking if a client can execute the queued up command */ int ACLCheckAllPerm(client *c, int *idxptr) { return ACLCheckAllUserCommandPerm(c->user, c->cmd, c->argv, c->argc, idxptr); } /* If 'new' can access all channels 'original' could then return NULL; Otherwise return a list of channels that the new user can access */ list *getUpcomingChannelList(user *new, user *original) { listIter li, lpi; listNode *ln, *lpn; /* Optimization: we check if any selector has all channel permissions. */ listRewind(new->selectors,&li); while((ln = listNext(&li))) { aclSelector *s = (aclSelector *) listNodeValue(ln); if (s->flags & SELECTOR_FLAG_ALLCHANNELS) return NULL; } /* Next, check if the new list of channels * is a strict superset of the original. This is done by * created an "upcoming" list of all channels that are in * the new user and checking each of the existing channels * against it. */ list *upcoming = listCreate(); listRewind(new->selectors,&li); while((ln = listNext(&li))) { aclSelector *s = (aclSelector *) listNodeValue(ln); listRewind(s->channels, &lpi); while((lpn = listNext(&lpi))) { listAddNodeTail(upcoming, listNodeValue(lpn)); } } int match = 1; listRewind(original->selectors,&li); while((ln = listNext(&li)) && match) { aclSelector *s = (aclSelector *) listNodeValue(ln); /* If any of the original selectors has the all-channels permission, but * the new ones don't (this is checked earlier in this function), then the * new list is not a strict superset of the original. */ if (s->flags & SELECTOR_FLAG_ALLCHANNELS) { match = 0; break; } listRewind(s->channels, &lpi); while((lpn = listNext(&lpi)) && match) { if (!listSearchKey(upcoming, listNodeValue(lpn))) { match = 0; break; } } } if (match) { /* All channels were matched, no need to kill clients. */ listRelease(upcoming); return NULL; } return upcoming; } /* Check if the client should be killed because it is subscribed to channels that were * permitted in the past, are not in the `upcoming` channel list. */ int ACLShouldKillPubsubClient(client *c, list *upcoming) { robj *o; int kill = 0; if (getClientType(c) == CLIENT_TYPE_PUBSUB) { /* Check for pattern violations. */ dictIterator *di = dictGetIterator(c->pubsub_patterns); dictEntry *de; while (!kill && ((de = dictNext(di)) != NULL)) { o = dictGetKey(de); int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1); kill = (res == ACL_DENIED_CHANNEL); } dictReleaseIterator(di); /* Check for channel violations. */ if (!kill) { /* Check for global channels violation. */ di = dictGetIterator(c->pubsub_channels); while (!kill && ((de = dictNext(di)) != NULL)) { o = dictGetKey(de); int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0); kill = (res == ACL_DENIED_CHANNEL); } dictReleaseIterator(di); } if (!kill) { /* Check for shard channels violation. */ di = dictGetIterator(c->pubsubshard_channels); while (!kill && ((de = dictNext(di)) != NULL)) { o = dictGetKey(de); int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0); kill = (res == ACL_DENIED_CHANNEL); } dictReleaseIterator(di); } if (kill) { return 1; } } return 0; } /* Check if the user's existing pub/sub clients violate the ACL pub/sub * permissions specified via the upcoming argument, and kill them if so. */ void ACLKillPubsubClientsIfNeeded(user *new, user *original) { /* Do nothing if there are no subscribers. */ if (pubsubTotalSubscriptions() == 0) return; list *channels = getUpcomingChannelList(new, original); /* If the new user's pubsub permissions are a strict superset of the original, return early. */ if (!channels) return; listIter li; listNode *ln; /* Permissions have changed, so we need to iterate through all * the clients and disconnect those that are no longer valid. * Scan all connected clients to find the user's pub/subs. */ listRewind(server.clients,&li); while ((ln = listNext(&li)) != NULL) { client *c = listNodeValue(ln); if (c->user != original) continue; if (ACLShouldKillPubsubClient(c, channels)) deauthenticateAndCloseClient(c); } listRelease(channels); } /* ============================================================================= * ACL loading / saving functions * ==========================================================================*/ /* Selector definitions should be sent as a single argument, however * we will be lenient and try to find selector definitions spread * across multiple arguments since it makes for a simpler user experience * for ACL SETUSER as well as when loading from conf files. * * This function takes in an array of ACL operators, excluding the username, * and merges selector operations that are spread across multiple arguments. The return * value is a new SDS array, with length set to the passed in merged_argc. Arguments * that are untouched are still duplicated. If there is an unmatched parenthesis, NULL * is returned and invalid_idx is set to the argument with the start of the opening * parenthesis. */ sds *ACLMergeSelectorArguments(sds *argv, int argc, int *merged_argc, int *invalid_idx) { *merged_argc = 0; int open_bracket_start = -1; sds *acl_args = (sds *) zmalloc(sizeof(sds) * argc); sds selector = NULL; for (int j = 0; j < argc; j++) { char *op = argv[j]; if (open_bracket_start == -1 && (op[0] == '(' && op[sdslen(op) - 1] != ')')) { selector = sdsdup(argv[j]); open_bracket_start = j; continue; } if (open_bracket_start != -1) { selector = sdscatfmt(selector, " %s", op); if (op[sdslen(op) - 1] == ')') { open_bracket_start = -1; acl_args[*merged_argc] = selector; (*merged_argc)++; } continue; } acl_args[*merged_argc] = sdsdup(argv[j]); (*merged_argc)++; } if (open_bracket_start != -1) { for (int i = 0; i < *merged_argc; i++) sdsfree(acl_args[i]); zfree(acl_args); sdsfree(selector); if (invalid_idx) *invalid_idx = open_bracket_start; return NULL; } return acl_args; } /* takes an acl string already split on spaces and adds it to the given user * if the user object is NULL, will create a user with the given username * * Returns an error as an sds string if the ACL string is not parsable */ sds ACLStringSetUser(user *u, sds username, sds *argv, int argc) { serverAssert(u != NULL || username != NULL); sds error = NULL; int merged_argc = 0, invalid_idx = 0; sds *acl_args = ACLMergeSelectorArguments(argv, argc, &merged_argc, &invalid_idx); if (!acl_args) { return sdscatfmt(sdsempty(), "Unmatched parenthesis in acl selector starting " "at '%s'.", (char *) argv[invalid_idx]); } /* Create a temporary user to validate and stage all changes against * before applying to an existing user or creating a new user. If all * arguments are valid the user parameters will all be applied together. * If there are any errors then none of the changes will be applied. */ user *tempu = ACLCreateUnlinkedUser(); if (u) { ACLCopyUser(tempu, u); } for (int j = 0; j < merged_argc; j++) { if (ACLSetUser(tempu,acl_args[j],(ssize_t) sdslen(acl_args[j])) != C_OK) { const char *errmsg = ACLSetUserStringError(); error = sdscatfmt(sdsempty(), "Error in ACL SETUSER modifier '%s': %s", (char*)acl_args[j], errmsg); goto cleanup; } } /* Existing pub/sub clients authenticated with the user may need to be * disconnected if (some of) their channel permissions were revoked. */ if (u) { ACLKillPubsubClientsIfNeeded(tempu, u); } /* Overwrite the user with the temporary user we modified above. */ if (!u) { u = ACLCreateUser(username,sdslen(username)); } serverAssert(u != NULL); ACLCopyUser(u, tempu); cleanup: ACLFreeUser(tempu); for (int i = 0; i < merged_argc; i++) { sdsfree(acl_args[i]); } zfree(acl_args); return error; } /* Given an argument vector describing a user in the form: * * user ... ACL rules and flags ... * * this function validates, and if the syntax is valid, appends * the user definition to a list for later loading. * * The rules are tested for validity and if there obvious syntax errors * the function returns C_ERR and does nothing, otherwise C_OK is returned * and the user is appended to the list. * * Note that this function cannot stop in case of commands that are not found * and, in that case, the error will be emitted later, because certain * commands may be defined later once modules are loaded. * * When an error is detected and C_ERR is returned, the function populates * by reference (if not set to NULL) the argc_err argument with the index * of the argv vector that caused the error. */ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) { if (argc < 2 || strcasecmp(argv[0],"user")) { if (argc_err) *argc_err = 0; return C_ERR; } if (listSearchKey(UsersToLoad, argv[1])) { if (argc_err) *argc_err = 1; errno = EALREADY; return C_ERR; } /* Merged selectors before trying to process */ int merged_argc; sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, argc_err); if (!acl_args) { return C_ERR; } /* Try to apply the user rules in a fake user to see if they * are actually valid. */ user *fakeuser = ACLCreateUnlinkedUser(); for (int j = 0; j < merged_argc; j++) { if (ACLSetUser(fakeuser,acl_args[j],sdslen(acl_args[j])) == C_ERR) { if (errno != ENOENT) { ACLFreeUser(fakeuser); if (argc_err) *argc_err = j; for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); zfree(acl_args); return C_ERR; } } } /* Rules look valid, let's append the user to the list. */ sds *copy = zmalloc(sizeof(sds)*(merged_argc + 2)); copy[0] = sdsdup(argv[1]); for (int j = 0; j < merged_argc; j++) copy[j+1] = sdsdup(acl_args[j]); copy[merged_argc + 1] = NULL; listAddNodeTail(UsersToLoad,copy); ACLFreeUser(fakeuser); for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); zfree(acl_args); return C_OK; } /* This function will load the configured users appended to the server * configuration via ACLAppendUserForLoading(). On loading errors it will * log an error and return C_ERR, otherwise C_OK will be returned. */ int ACLLoadConfiguredUsers(void) { listIter li; listNode *ln; listRewind(UsersToLoad,&li); while ((ln = listNext(&li)) != NULL) { sds *aclrules = listNodeValue(ln); sds username = aclrules[0]; if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) { serverLog(LL_WARNING,"Spaces not allowed in ACL usernames"); return C_ERR; } user *u = ACLCreateUser(username,sdslen(username)); if (!u) { /* Only valid duplicate user is the default one. */ serverAssert(!strcmp(username, "default")); u = ACLGetUserByName("default",7); ACLSetUser(u,"reset",-1); } /* Load every rule defined for this user. */ for (int j = 1; aclrules[j]; j++) { if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) { const char *errmsg = ACLSetUserStringError(); serverLog(LL_WARNING,"Error loading ACL rule '%s' for " "the user named '%s': %s", aclrules[j],aclrules[0],errmsg); return C_ERR; } } /* Having a disabled user in the configuration may be an error, * warn about it without returning any error to the caller. */ if (u->flags & USER_FLAG_DISABLED) { 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]); } } 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: * * user ... 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. * * 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 * reason, so the function will attempt to validate the rules before loading * each user. For every line that will be found broken the function will * collect an error message. * * IMPORTANT: If there is at least a single error, nothing will be loaded * and the rules will remain exactly as they were. * * At the end of the process, if no errors were found in the whole file then * NULL is returned. Otherwise an SDS string describing in a single line * a description of all the issues found is returned. */ sds ACLLoadFromFile(const char *filename) { FILE *fp; char buf[1024]; /* Open the ACL file. */ if ((fp = fopen(filename,"r")) == NULL) { sds errors = sdscatprintf(sdsempty(), "Error loading ACLs, opening file '%s': %s", filename, strerror(errno)); return errors; } /* Load the whole file as a single string in memory. */ sds acls = sdsempty(); while(fgets(buf,sizeof(buf),fp) != NULL) acls = sdscat(acls,buf); fclose(fp); /* Split the file into lines and attempt to load each line. */ int totlines; sds *lines, errors = sdsempty(); lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines); sdsfree(acls); /* We do all the loading in a fresh instance of the Users radix tree, * so if there are errors loading the ACL file we can rollback to the * old version. */ rax *old_users = Users; Users = raxNew(); /* Load each line of the file. */ for (int i = 0; i < totlines; i++) { sds *argv; int argc; int linenum = i+1; lines[i] = sdstrim(lines[i]," \t\r\n"); /* Skip blank lines */ if (lines[i][0] == '\0') continue; /* Split into arguments */ argv = sdssplitlen(lines[i],sdslen(lines[i])," ",1,&argc); if (argv == NULL) { errors = sdscatprintf(errors, "%s:%d: unbalanced quotes in acl line. ", server.acl_filename, linenum); continue; } /* Skip this line if the resulting command vector is empty. */ if (argc == 0) { sdsfreesplitres(argv,argc); continue; } /* The line should start with the "user" keyword. */ if (strcmp(argv[0],"user") || argc < 2) { errors = sdscatprintf(errors, "%s:%d should start with user keyword followed " "by the username. ", server.acl_filename, linenum); sdsfreesplitres(argv,argc); continue; } /* Spaces are not allowed in usernames. */ if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) { errors = sdscatprintf(errors, "'%s:%d: username '%s' contains invalid characters. ", server.acl_filename, linenum, argv[1]); sdsfreesplitres(argv,argc); continue; } user *u = ACLCreateUser(argv[1],sdslen(argv[1])); /* If the user already exists we assume it's an error and abort. */ if (!u) { errors = sdscatprintf(errors,"WARNING: Duplicate user '%s' found on line %d. ", argv[1], linenum); sdsfreesplitres(argv,argc); continue; } /* Finally process the options and validate they can * be cleanly applied to the user. If any option fails * to apply, the other values won't be applied since * all the pending changes will get dropped. */ int merged_argc; sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, NULL); if (!acl_args) { errors = sdscatprintf(errors, "%s:%d: Unmatched parenthesis in selector definition.", server.acl_filename, linenum); } int syntax_error = 0; for (int j = 0; j < merged_argc; j++) { acl_args[j] = sdstrim(acl_args[j],"\t\r\n"); if (ACLSetUser(u,acl_args[j],sdslen(acl_args[j])) != C_OK) { const char *errmsg = ACLSetUserStringError(); if (errno == ENOENT) { /* For missing commands, we print out more information since * it shouldn't contain any sensitive information. */ errors = sdscatprintf(errors, "%s:%d: Error in applying operation '%s': %s. ", server.acl_filename, linenum, acl_args[j], errmsg); } else if (syntax_error == 0) { /* For all other errors, only print out the first error encountered * since it might affect future operations. */ errors = sdscatprintf(errors, "%s:%d: %s. ", server.acl_filename, linenum, errmsg); syntax_error = 1; } } } for (int i = 0; i < merged_argc; i++) sdsfree(acl_args[i]); zfree(acl_args); /* Apply the rule to the new users set only if so far there * are no errors, otherwise it's useless since we are going * to discard the new users set anyway. */ if (sdslen(errors) != 0) { sdsfreesplitres(argv,argc); continue; } sdsfreesplitres(argv,argc); } sdsfreesplitres(lines,totlines); /* Check if we found errors and react accordingly. */ if (sdslen(errors) == 0) { /* The default user pointer is referenced in different places: instead * of replacing such occurrences it is much simpler to copy the new * default user configuration in the old one. */ user *new_default = ACLGetUserByName("default",7); if (!new_default) { new_default = ACLCreateDefaultUser(); } ACLCopyUser(DefaultUser,new_default); ACLFreeUser(new_default); raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL); raxRemove(old_users,(unsigned char*)"default",7,NULL); /* If there are some subscribers, we need to check if we need to drop some clients. */ rax *user_channels = NULL; if (pubsubTotalSubscriptions() > 0) { user_channels = raxNew(); } listIter li; listNode *ln; listRewind(server.clients,&li); while ((ln = listNext(&li)) != NULL) { client *c = listNodeValue(ln); /* a MASTER client can do everything (and user = NULL) so we can skip it */ if (c->flags & CLIENT_MASTER) continue; user *original = c->user; list *channels = NULL; user *new = ACLGetUserByName(c->user->name, sdslen(c->user->name)); if (new && user_channels) { if (!raxFind(user_channels, (unsigned char*)(new->name), sdslen(new->name), (void**)&channels)) { channels = getUpcomingChannelList(new, original); raxInsert(user_channels, (unsigned char*)(new->name), sdslen(new->name), channels, NULL); } } /* When the new channel list is NULL, it means the new user's channel list is a superset of the old user's list. */ if (!new || (channels && ACLShouldKillPubsubClient(c, channels))) { deauthenticateAndCloseClient(c); continue; } c->user = new; } if (user_channels) raxFreeWithCallback(user_channels, listReleaseGeneric); raxFreeWithCallback(old_users, ACLFreeUserGeneric); sdsfree(errors); return NULL; } else { raxFreeWithCallback(Users, ACLFreeUserGeneric); Users = old_users; errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed"); return errors; } } /* Generate a copy of the ACLs currently in memory in the specified filename. * Returns C_OK on success or C_ERR if there was an error during the I/O. * When C_ERR is returned a log is produced with hints about the issue. */ int ACLSaveToFile(const char *filename) { sds acl = sdsempty(); int fd = -1; sds tmpfilename = NULL; int retval = C_ERR; /* Let's generate an SDS string containing the new version of the * ACL file. */ raxIterator ri; raxStart(&ri,Users); raxSeek(&ri,"^",NULL,0); while(raxNext(&ri)) { user *u = ri.data; /* Return information in the configuration file format. */ sds user = sdsnew("user "); user = sdscatsds(user,u->name); user = sdscatlen(user," ",1); robj *descr = ACLDescribeUser(u); user = sdscatsds(user,descr->ptr); decrRefCount(descr); acl = sdscatsds(acl,user); acl = sdscatlen(acl,"\n",1); sdsfree(user); } raxStop(&ri); /* Create a temp file with the new content. */ tmpfilename = sdsnew(filename); tmpfilename = sdscatfmt(tmpfilename,".tmp-%i-%I", (int) getpid(),commandTimeSnapshot()); if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) { serverLog(LL_WARNING,"Opening temp ACL file for ACL SAVE: %s", strerror(errno)); goto cleanup; } /* Write it. */ size_t offset = 0; while (offset < sdslen(acl)) { ssize_t written_bytes = write(fd,acl + offset,sdslen(acl) - offset); if (written_bytes <= 0) { if (errno == EINTR) continue; serverLog(LL_WARNING,"Writing ACL file for ACL SAVE: %s", strerror(errno)); goto cleanup; } offset += written_bytes; } if (redis_fsync(fd) == -1) { serverLog(LL_WARNING,"Syncing ACL file for ACL SAVE: %s", strerror(errno)); goto cleanup; } close(fd); fd = -1; /* Let's replace the new file with the old one. */ if (rename(tmpfilename,filename) == -1) { serverLog(LL_WARNING,"Renaming ACL file for ACL SAVE: %s", strerror(errno)); goto cleanup; } if (fsyncFileDir(filename) == -1) { serverLog(LL_WARNING,"Syncing ACL directory for ACL SAVE: %s", strerror(errno)); goto cleanup; } sdsfree(tmpfilename); tmpfilename = NULL; retval = C_OK; /* If we reached this point, everything is fine. */ cleanup: if (fd != -1) close(fd); if (tmpfilename) unlink(tmpfilename); sdsfree(tmpfilename); sdsfree(acl); return retval; } /* This function is called once the server is already running, modules are * loaded, and we are ready to start, in order to load the ACLs either from * the pending list of users defined in redis.conf, or from the ACL file. * The function will just exit with an error if the user is trying to mix * both the loading methods. */ void ACLLoadUsersAtStartup(void) { if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) { serverLog(LL_WARNING, "Configuring Redis with users defined in redis.conf and at " "the same setting an ACL file path is invalid. This setup " "is very likely to lead to configuration errors and security " "holes, please define either an ACL file or declare users " "directly in your redis.conf, but not both."); exit(1); } if (ACLLoadConfiguredUsers() == C_ERR) { serverLog(LL_WARNING, "Critical error while loading ACLs. Exiting."); exit(1); } if (server.acl_filename[0] != '\0') { sds errors = ACLLoadFromFile(server.acl_filename); if (errors) { serverLog(LL_WARNING, "Aborting Redis startup because of ACL errors: %s", errors); sdsfree(errors); exit(1); } } } /* ============================================================================= * ACL log * ==========================================================================*/ #define ACL_LOG_GROUPING_MAX_TIME_DELTA 60000 /* This structure defines an entry inside the ACL log. */ typedef struct ACLLogEntry { uint64_t count; /* Number of times this happened recently. */ int reason; /* Reason for denying the command. ACL_DENIED_*. */ int context; /* Toplevel, Lua or MULTI/EXEC? ACL_LOG_CTX_*. */ sds object; /* The key name or command name. */ sds username; /* User the client is authenticated with. */ mstime_t ctime; /* Milliseconds time of last update to this entry. */ sds cinfo; /* Client info (last client if updated). */ long long entry_id; /* The pair (entry_id, timestamp_created) is a unique identifier of this entry * in case the node dies and is restarted, it can detect that if it's a new series. */ mstime_t timestamp_created; /* UNIX time in milliseconds at the time of this entry's creation. */ } ACLLogEntry; /* This function will check if ACL entries 'a' and 'b' are similar enough * that we should actually update the existing entry in our ACL log instead * of creating a new one. */ int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) { if (a->reason != b->reason) return 0; if (a->context != b->context) return 0; mstime_t delta = a->ctime - b->ctime; if (delta < 0) delta = -delta; if (delta > ACL_LOG_GROUPING_MAX_TIME_DELTA) return 0; if (sdscmp(a->object,b->object) != 0) return 0; if (sdscmp(a->username,b->username) != 0) return 0; return 1; } /* Release an ACL log entry. */ void ACLFreeLogEntry(void *leptr) { ACLLogEntry *le = leptr; sdsfree(le->object); sdsfree(le->username); sdsfree(le->cinfo); zfree(le); } /* Update the relevant counter by the reason */ void ACLUpdateInfoMetrics(int reason){ if (reason == ACL_DENIED_AUTH) { server.acl_info.user_auth_failures++; } else if (reason == ACL_DENIED_CMD) { server.acl_info.invalid_cmd_accesses++; } else if (reason == ACL_DENIED_KEY) { server.acl_info.invalid_key_accesses++; } else if (reason == ACL_DENIED_CHANNEL) { server.acl_info.invalid_channel_accesses++; } else { serverPanic("Unknown ACL_DENIED encoding"); } } static void trimACLLogEntriesToMaxLen(void) { while(listLength(ACLLog) > server.acllog_max_len) { listNode *ln = listLast(ACLLog); ACLLogEntry *le = listNodeValue(ln); ACLFreeLogEntry(le); listDelNode(ACLLog,ln); } } /* Adds a new entry in the ACL log, making sure to delete the old entry * if we reach the maximum length allowed for the log. This function attempts * to find similar entries in the current log in order to bump the counter of * the log entry instead of creating many entries for very similar ACL * rules issues. * * The argpos argument is used when the reason is ACL_DENIED_KEY or * ACL_DENIED_CHANNEL, since it allows the function to log the key or channel * name that caused the problem. * * The last 2 arguments are a manual override to be used, instead of any of the automatic * ones which depend on the client and reason arguments (use NULL for default). * * If `object` is not NULL, this functions takes over it. */ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username, sds object) { /* Update ACL info metrics */ ACLUpdateInfoMetrics(reason); if (server.acllog_max_len == 0) { trimACLLogEntriesToMaxLen(); return; } /* Create a new entry. */ struct ACLLogEntry *le = zmalloc(sizeof(*le)); le->count = 1; le->reason = reason; le->username = sdsdup(username ? username : c->user->name); le->ctime = commandTimeSnapshot(); le->entry_id = ACLLogEntryCount; le->timestamp_created = le->ctime; if (object) { le->object = object; } else { switch(reason) { case ACL_DENIED_CMD: le->object = sdsdup(c->cmd->fullname); break; case ACL_DENIED_KEY: le->object = sdsdup(c->argv[argpos]->ptr); break; case ACL_DENIED_CHANNEL: le->object = sdsdup(c->argv[argpos]->ptr); break; case ACL_DENIED_AUTH: le->object = sdsdup(c->argv[0]->ptr); break; default: le->object = sdsempty(); } } /* if we have a real client from the network, use it (could be missing on module timers) */ client *realclient = server.current_client? server.current_client : c; le->cinfo = catClientInfoString(sdsempty(),realclient); le->context = context; /* Try to match this entry with past ones, to see if we can just * update an existing entry instead of creating a new one. */ long toscan = 10; /* Do a limited work trying to find duplicated. */ listIter li; listNode *ln; listRewind(ACLLog,&li); ACLLogEntry *match = NULL; while (toscan-- && (ln = listNext(&li)) != NULL) { ACLLogEntry *current = listNodeValue(ln); if (ACLLogMatchEntry(current,le)) { match = current; listDelNode(ACLLog,ln); listAddNodeHead(ACLLog,current); break; } } /* If there is a match update the entry, otherwise add it as a * new one. */ if (match) { /* We update a few fields of the existing entry and bump the * counter of events for this entry. */ sdsfree(match->cinfo); match->cinfo = le->cinfo; match->ctime = le->ctime; match->count++; /* Release the old entry. */ le->cinfo = NULL; ACLFreeLogEntry(le); } else { /* Add it to our list of entries. We'll have to trim the list * to its maximum size. */ ACLLogEntryCount++; /* Incrementing the entry_id count to make each record in the log unique. */ listAddNodeHead(ACLLog, le); trimACLLogEntriesToMaxLen(); } } sds getAclErrorMessage(int acl_res, user *user, struct redisCommand *cmd, sds errored_val, int verbose) { switch (acl_res) { case ACL_DENIED_CMD: return sdscatfmt(sdsempty(), "User %S has no permissions to run " "the '%S' command", user->name, cmd->fullname); case ACL_DENIED_KEY: if (verbose) { return sdscatfmt(sdsempty(), "User %S has no permissions to access " "the '%S' key", user->name, errored_val); } else { return sdsnew("No permissions to access a key"); } case ACL_DENIED_CHANNEL: if (verbose) { return sdscatfmt(sdsempty(), "User %S has no permissions to access " "the '%S' channel", user->name, errored_val); } else { return sdsnew("No permissions to access a channel"); } } serverPanic("Reached deadcode on getAclErrorMessage"); } /* ============================================================================= * ACL related commands * ==========================================================================*/ /* ACL CAT category */ void aclCatWithFlags(client *c, dict *commands, uint64_t cflag, int *arraylen) { dictEntry *de; dictIterator *di = dictGetIterator(commands); while ((de = dictNext(di)) != NULL) { struct redisCommand *cmd = dictGetVal(de); if (cmd->acl_categories & cflag) { addReplyBulkCBuffer(c, cmd->fullname, sdslen(cmd->fullname)); (*arraylen)++; } if (cmd->subcommands_dict) { aclCatWithFlags(c, cmd->subcommands_dict, cflag, arraylen); } } dictReleaseIterator(di); } /* Add the formatted response from a single selector to the ACL GETUSER * response. This function returns the number of fields added. * * Setting verbose to 1 means that the full qualifier for key and channel * permissions are shown. */ int aclAddReplySelectorDescription(client *c, aclSelector *s) { listIter li; listNode *ln; /* Commands */ addReplyBulkCString(c,"commands"); sds cmddescr = ACLDescribeSelectorCommandRules(s); addReplyBulkSds(c,cmddescr); /* Key patterns */ addReplyBulkCString(c,"keys"); if (s->flags & SELECTOR_FLAG_ALLKEYS) { addReplyBulkCBuffer(c,"~*",2); } else { sds dsl = sdsempty(); listRewind(s->patterns,&li); while((ln = listNext(&li))) { keyPattern *thispat = (keyPattern *) listNodeValue(ln); if (ln != listFirst(s->patterns)) dsl = sdscat(dsl, " "); dsl = sdsCatPatternString(dsl, thispat); } addReplyBulkSds(c, dsl); } /* Pub/sub patterns */ addReplyBulkCString(c,"channels"); if (s->flags & SELECTOR_FLAG_ALLCHANNELS) { addReplyBulkCBuffer(c,"&*",2); } else { sds dsl = sdsempty(); listRewind(s->channels,&li); while((ln = listNext(&li))) { sds thispat = listNodeValue(ln); if (ln != listFirst(s->channels)) dsl = sdscat(dsl, " "); dsl = sdscatfmt(dsl, "&%S", thispat); } addReplyBulkSds(c, dsl); } return 3; } /* ACL -- show and modify the configuration of ACL users. * ACL HELP * ACL LOAD * ACL SAVE * ACL LIST * ACL USERS * ACL CAT [] * ACL SETUSER ... acl rules ... * ACL DELUSER [...] * ACL GETUSER * ACL GENPASS [] * ACL WHOAMI * ACL LOG [ | RESET] */ void aclCommand(client *c) { char *sub = c->argv[1]->ptr; if (!strcasecmp(sub,"setuser") && c->argc >= 3) { /* Initially redact all of the arguments to not leak any information * about the user. */ for (int j = 2; j < c->argc; j++) { redactClientCommandArgument(c, j); } sds username = c->argv[2]->ptr; /* Check username validity. */ if (ACLStringHasSpaces(username,sdslen(username))) { addReplyError(c, "Usernames can't contain spaces or null characters"); return; } user *u = ACLGetUserByName(username,sdslen(username)); sds *temp_argv = zmalloc(c->argc * sizeof(sds)); for (int i = 3; i < c->argc; i++) temp_argv[i-3] = c->argv[i]->ptr; sds error = ACLStringSetUser(u, username, temp_argv, c->argc - 3); zfree(temp_argv); if (error == NULL) { addReply(c,shared.ok); } else { addReplyErrorSdsSafe(c, error); } return; } else if (!strcasecmp(sub,"deluser") && c->argc >= 3) { /* Initially redact all the arguments to not leak any information * about the users. */ for (int j = 2; j < c->argc; j++) redactClientCommandArgument(c, j); int deleted = 0; for (int j = 2; j < c->argc; j++) { sds username = c->argv[j]->ptr; if (!strcmp(username,"default")) { addReplyError(c,"The 'default' user cannot be removed"); return; } } for (int j = 2; j < c->argc; j++) { sds username = c->argv[j]->ptr; user *u; if (raxRemove(Users,(unsigned char*)username, sdslen(username), (void**)&u)) { ACLFreeUserAndKillClients(u); deleted++; } } addReplyLongLong(c,deleted); } else if (!strcasecmp(sub,"getuser") && c->argc == 3) { /* Redact the username to not leak any information about the user. */ redactClientCommandArgument(c, 2); user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr)); if (u == NULL) { addReplyNull(c); return; } void *ufields = addReplyDeferredLen(c); int fields = 3; /* Flags */ addReplyBulkCString(c,"flags"); void *deflen = addReplyDeferredLen(c); int numflags = 0; for (int j = 0; ACLUserFlags[j].flag; j++) { if (u->flags & ACLUserFlags[j].flag) { addReplyBulkCString(c,ACLUserFlags[j].name); numflags++; } } setDeferredSetLen(c,deflen,numflags); /* Passwords */ addReplyBulkCString(c,"passwords"); addReplyArrayLen(c,listLength(u->passwords)); listIter li; listNode *ln; listRewind(u->passwords,&li); while((ln = listNext(&li))) { sds thispass = listNodeValue(ln); addReplyBulkCBuffer(c,thispass,sdslen(thispass)); } /* Include the root selector at the top level for backwards compatibility */ fields += aclAddReplySelectorDescription(c, ACLUserGetRootSelector(u)); /* Describe all of the selectors on this user, including duplicating the root selector */ addReplyBulkCString(c,"selectors"); addReplyArrayLen(c, listLength(u->selectors) - 1); listRewind(u->selectors,&li); serverAssert(listNext(&li)); while((ln = listNext(&li))) { void *slen = addReplyDeferredLen(c); int sfields = aclAddReplySelectorDescription(c, (aclSelector *)listNodeValue(ln)); setDeferredMapLen(c, slen, sfields); } setDeferredMapLen(c, ufields, fields); } else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) && c->argc == 2) { int justnames = !strcasecmp(sub,"users"); addReplyArrayLen(c,raxSize(Users)); raxIterator ri; raxStart(&ri,Users); raxSeek(&ri,"^",NULL,0); while(raxNext(&ri)) { user *u = ri.data; if (justnames) { addReplyBulkCBuffer(c,u->name,sdslen(u->name)); } else { /* Return information in the configuration file format. */ sds config = sdsnew("user "); config = sdscatsds(config,u->name); config = sdscatlen(config," ",1); robj *descr = ACLDescribeUser(u); config = sdscatsds(config,descr->ptr); decrRefCount(descr); addReplyBulkSds(c,config); } } raxStop(&ri); } else if (!strcasecmp(sub,"whoami") && c->argc == 2) { if (c->user != NULL) { addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name)); } else { addReplyNull(c); } } else if (server.acl_filename[0] == '\0' && (!strcasecmp(sub,"load") || !strcasecmp(sub,"save"))) { addReplyError(c,"This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration."); return; } else if (!strcasecmp(sub,"load") && c->argc == 2) { sds errors = ACLLoadFromFile(server.acl_filename); if (errors == NULL) { addReply(c,shared.ok); } else { addReplyError(c,errors); sdsfree(errors); } } else if (!strcasecmp(sub,"save") && c->argc == 2) { if (ACLSaveToFile(server.acl_filename) == C_OK) { addReply(c,shared.ok); } else { addReplyError(c,"There was an error trying to save the ACLs. " "Please check the server logs for more " "information"); } } else if (!strcasecmp(sub,"cat") && c->argc == 2) { void *dl = addReplyDeferredLen(c); int j; for (j = 0; ACLCommandCategories[j].flag != 0; j++) addReplyBulkCString(c,ACLCommandCategories[j].name); setDeferredArrayLen(c,dl,j); } else if (!strcasecmp(sub,"cat") && c->argc == 3) { uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr); if (cflag == 0) { addReplyErrorFormat(c, "Unknown category '%.128s'", (char*)c->argv[2]->ptr); return; } int arraylen = 0; void *dl = addReplyDeferredLen(c); aclCatWithFlags(c, server.orig_commands, cflag, &arraylen); setDeferredArrayLen(c,dl,arraylen); } else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) { #define GENPASS_MAX_BITS 4096 char pass[GENPASS_MAX_BITS/8*2]; /* Hex representation. */ long bits = 256; /* By default generate 256 bits passwords. */ if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL) != C_OK) return; if (bits <= 0 || bits > GENPASS_MAX_BITS) { addReplyErrorFormat(c, "ACL GENPASS argument must be the number of " "bits for the output password, a positive number " "up to %d",GENPASS_MAX_BITS); return; } long chars = (bits+3)/4; /* Round to number of characters to emit. */ getRandomHexChars(pass,chars); addReplyBulkCBuffer(c,pass,chars); } else if (!strcasecmp(sub,"log") && (c->argc == 2 || c->argc ==3)) { long count = 10; /* Number of entries to emit by default. */ /* Parse the only argument that LOG may have: it could be either * the number of entries the user wants to display, or alternatively * the "RESET" command in order to flush the old entries. */ if (c->argc == 3) { if (!strcasecmp(c->argv[2]->ptr,"reset")) { listSetFreeMethod(ACLLog,ACLFreeLogEntry); listEmpty(ACLLog); listSetFreeMethod(ACLLog,NULL); addReply(c,shared.ok); return; } else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK) { return; } if (count < 0) count = 0; } /* Fix the count according to the number of entries we got. */ if ((size_t)count > listLength(ACLLog)) count = listLength(ACLLog); addReplyArrayLen(c,count); listIter li; listNode *ln; listRewind(ACLLog,&li); mstime_t now = commandTimeSnapshot(); while (count-- && (ln = listNext(&li)) != NULL) { ACLLogEntry *le = listNodeValue(ln); addReplyMapLen(c,10); addReplyBulkCString(c,"count"); addReplyLongLong(c,le->count); addReplyBulkCString(c,"reason"); char *reasonstr; switch(le->reason) { case ACL_DENIED_CMD: reasonstr="command"; break; case ACL_DENIED_KEY: reasonstr="key"; break; case ACL_DENIED_CHANNEL: reasonstr="channel"; break; case ACL_DENIED_AUTH: reasonstr="auth"; break; default: reasonstr="unknown"; } addReplyBulkCString(c,reasonstr); addReplyBulkCString(c,"context"); char *ctxstr; switch(le->context) { case ACL_LOG_CTX_TOPLEVEL: ctxstr="toplevel"; break; case ACL_LOG_CTX_MULTI: ctxstr="multi"; break; case ACL_LOG_CTX_LUA: ctxstr="lua"; break; case ACL_LOG_CTX_MODULE: ctxstr="module"; break; default: ctxstr="unknown"; } addReplyBulkCString(c,ctxstr); addReplyBulkCString(c,"object"); addReplyBulkCBuffer(c,le->object,sdslen(le->object)); addReplyBulkCString(c,"username"); addReplyBulkCBuffer(c,le->username,sdslen(le->username)); addReplyBulkCString(c,"age-seconds"); double age = (double)(now - le->ctime)/1000; addReplyDouble(c,age); addReplyBulkCString(c,"client-info"); addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo)); addReplyBulkCString(c, "entry-id"); addReplyLongLong(c, le->entry_id); addReplyBulkCString(c, "timestamp-created"); addReplyLongLong(c, le->timestamp_created); addReplyBulkCString(c, "timestamp-last-updated"); addReplyLongLong(c, le->ctime); } } else if (!strcasecmp(sub,"dryrun") && c->argc >= 4) { struct redisCommand *cmd; user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr)); if (u == NULL) { addReplyErrorFormat(c, "User '%s' not found", (char *)c->argv[2]->ptr); return; } if ((cmd = lookupCommand(c->argv + 3, c->argc - 3)) == NULL) { addReplyErrorFormat(c, "Command '%s' not found", (char *)c->argv[3]->ptr); return; } if ((cmd->arity > 0 && cmd->arity != c->argc-3) || (c->argc-3 < -cmd->arity)) { addReplyErrorFormat(c,"wrong number of arguments for '%s' command", cmd->fullname); return; } int idx; int result = ACLCheckAllUserCommandPerm(u, cmd, c->argv + 3, c->argc - 3, &idx); if (result != ACL_OK) { sds err = getAclErrorMessage(result, u, cmd, c->argv[idx+3]->ptr, 1); addReplyBulkSds(c, err); return; } addReply(c,shared.ok); } else if (c->argc == 2 && !strcasecmp(sub,"help")) { const char *help[] = { "CAT []", " List all commands that belong to , or all command categories", " when no category is specified.", "DELUSER [ ...]", " Delete a list of users.", "DRYRUN [ ...]", " Returns whether the user can execute the given command without executing the command.", "GETUSER ", " Get the user's details.", "GENPASS []", " Generate a secure 256-bit user password. The optional `bits` argument can", " be used to specify a different size.", "LIST", " Show users details in config file format.", "LOAD", " Reload users from the ACL file.", "LOG [ | RESET]", " Show the ACL log entries.", "SAVE", " Save the current config to the ACL file.", "SETUSER [ ...]", " Create or modify a user with the specified attributes.", "USERS", " List all the registered usernames.", "WHOAMI", " Return the current connection username.", NULL }; addReplyHelp(c,help); } else { addReplySubcommandSyntaxError(c); } } void addReplyCommandCategories(client *c, struct redisCommand *cmd) { int flagcount = 0; void *flaglen = addReplyDeferredLen(c); for (int j = 0; ACLCommandCategories[j].flag != 0; j++) { if (cmd->acl_categories & ACLCommandCategories[j].flag) { addReplyStatusFormat(c, "@%s", ACLCommandCategories[j].name); flagcount++; } } setDeferredSetLen(c, flaglen, flagcount); } /* When successful, initiates an internal connection, that is able to execute * internal commands (see CMD_INTERNAL). */ static void internalAuth(client *c) { if (server.cluster == NULL) { addReplyError(c, "Cannot authenticate as an internal connection on non-cluster instances"); return; } sds password = c->argv[2]->ptr; /* Get internal secret. */ size_t len = -1; const char *internal_secret = clusterGetSecret(&len); if (sdslen(password) != len) { addReplyError(c, "-WRONGPASS invalid internal password"); return; } if (!time_independent_strcmp((char *)internal_secret, (char *)password, len)) { c->flags |= CLIENT_INTERNAL; /* No further authentication is needed. */ c->authenticated = 1; /* Set the user to the unrestricted user, if it is not already set (default). */ if (c->user != NULL) { c->user = NULL; moduleNotifyUserChanged(c); } addReply(c, shared.ok); } else { addReplyError(c, "-WRONGPASS invalid internal password"); } } /* AUTH * AUTH (Redis >= 6.0 form) * * When the user is omitted it means that we are trying to authenticate * against the default user. */ void authCommand(client *c) { /* Only two or three argument forms are allowed. */ if (c->argc > 3) { addReplyErrorObject(c,shared.syntaxerr); return; } /* Always redact the second argument */ redactClientCommandArgument(c, 1); /* Handle the two different forms here. The form with two arguments * will just use "default" as username. */ robj *username, *password; if (c->argc == 2) { /* Mimic the old behavior of giving an error for the two argument * form if no password is configured. */ if (DefaultUser->flags & USER_FLAG_NOPASS) { addReplyError(c,"AUTH called without any password " "configured for the default user. Are you sure " "your configuration is correct?"); return; } username = shared.default_username; password = c->argv[1]; } else { username = c->argv[1]; password = c->argv[2]; redactClientCommandArgument(c, 2); /* Handle internal authentication commands. * Note: No user-defined ACL user can have this username (no spaces * allowed), thus no conflicts with ACL possible. */ if (!strcmp(username->ptr, "internal connection")) { internalAuth(c); return; } } robj *err = NULL; int result = ACLAuthenticateUser(c, username, password, &err); if (result == AUTH_OK) { addReply(c, shared.ok); } else if (result == AUTH_ERR) { addAuthErrReply(c, err); } if (err) decrRefCount(err); } /* Set the password for the "default" ACL user. This implements supports for * requirepass config, so passing in NULL will set the user to be nopass. */ void ACLUpdateDefaultUserPassword(sds password) { ACLSetUser(DefaultUser,"resetpass",-1); if (password) { sds aclop = sdscatlen(sdsnew(">"), password, sdslen(password)); ACLSetUser(DefaultUser,aclop,sdslen(aclop)); sdsfree(aclop); } else { ACLSetUser(DefaultUser,"nopass",-1); } } redis-8.0.2/src/adlist.c000066400000000000000000000243771501533116600150600ustar00rootroot00000000000000/* adlist.c - A generic doubly linked list implementation * * Copyright (c) 2006-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 #include "adlist.h" #include "zmalloc.h" /* Create a new list. The created list can be freed with * listRelease(), but private value of every node need to be freed * by the user before to call listRelease(), or by setting a free method using * listSetFreeMethod. * * On error, NULL is returned. Otherwise the pointer to the new list. */ list *listCreate(void) { struct list *list; if ((list = zmalloc(sizeof(*list))) == NULL) return NULL; list->head = list->tail = NULL; list->len = 0; list->dup = NULL; list->free = NULL; list->match = NULL; return list; } /* Remove all the elements from the list without destroying the list itself. */ void listEmpty(list *list) { unsigned long len; listNode *current, *next; current = list->head; len = list->len; while(len--) { next = current->next; if (list->free) list->free(current->value); zfree(current); current = next; } list->head = list->tail = NULL; list->len = 0; } /* Free the whole list. * * This function can't fail. */ void listRelease(list *list) { if (!list) return; listEmpty(list); zfree(list); } /* Generic version of listRelease. */ void listReleaseGeneric(void *list) { listRelease((struct list*)list); } /* Add a new node to the list, to head, containing the specified 'value' * pointer as value. * * On error, NULL is returned and no operation is performed (i.e. the * list remains unaltered). * On success the 'list' pointer you pass to the function is returned. */ list *listAddNodeHead(list *list, void *value) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; node->value = value; listLinkNodeHead(list, node); return list; } /* * Add a node that has already been allocated to the head of list */ void listLinkNodeHead(list* list, listNode *node) { if (list->len == 0) { list->head = list->tail = node; node->prev = node->next = NULL; } else { node->prev = NULL; node->next = list->head; list->head->prev = node; list->head = node; } list->len++; } /* Add a new node to the list, to tail, containing the specified 'value' * pointer as value. * * On error, NULL is returned and no operation is performed (i.e. the * list remains unaltered). * On success the 'list' pointer you pass to the function is returned. */ list *listAddNodeTail(list *list, void *value) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; node->value = value; listLinkNodeTail(list, node); return list; } /* * Add a node that has already been allocated to the tail of list */ void listLinkNodeTail(list *list, listNode *node) { if (list->len == 0) { list->head = list->tail = node; node->prev = node->next = NULL; } else { node->prev = list->tail; node->next = NULL; list->tail->next = node; list->tail = node; } list->len++; } list *listInsertNode(list *list, listNode *old_node, void *value, int after) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) return NULL; node->value = value; if (after) { node->prev = old_node; node->next = old_node->next; if (list->tail == old_node) { list->tail = node; } } else { node->next = old_node; node->prev = old_node->prev; if (list->head == old_node) { list->head = node; } } if (node->prev != NULL) { node->prev->next = node; } if (node->next != NULL) { node->next->prev = node; } list->len++; return list; } /* Remove the specified node from the specified list. * The node is freed. If free callback is provided the value is freed as well. * * This function can't fail. */ void listDelNode(list *list, listNode *node) { listUnlinkNode(list, node); if (list->free) list->free(node->value); zfree(node); } /* * Remove the specified node from the list without freeing it. */ void listUnlinkNode(list *list, listNode *node) { if (node->prev) node->prev->next = node->next; else list->head = node->next; if (node->next) node->next->prev = node->prev; else list->tail = node->prev; node->next = NULL; node->prev = NULL; list->len--; } /* Returns a list iterator 'iter'. After the initialization every * call to listNext() will return the next element of the list. * * This function can't fail. */ listIter *listGetIterator(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 */ void listRewind(list *list, listIter *li) { li->next = list->head; li->direction = AL_START_HEAD; } void listRewindTail(list *list, listIter *li) { li->next = list->tail; li->direction = AL_START_TAIL; } /* Return the next element of an iterator. * It's valid to remove the currently returned element using * listDelNode(), but not to remove other elements. * * The function returns a pointer to the next element of the list, * or NULL if there are no more elements, so the classical usage * pattern is: * * iter = listGetIterator(list,); * while ((node = listNext(iter)) != NULL) { * doSomethingWith(listNodeValue(node)); * } * * */ listNode *listNext(listIter *iter) { listNode *current = iter->next; if (current != NULL) { if (iter->direction == AL_START_HEAD) iter->next = current->next; else iter->next = current->prev; } return current; } /* Duplicate the whole list. On out of memory NULL is returned. * On success a copy of the original list is returned. * * The 'Dup' method set with listSetDupMethod() function is used * to copy the node value. Otherwise the same pointer value of * the original node is used as value of the copied node. * * The original list both on success or error is never modified. */ list *listDup(list *orig) { list *copy; listIter iter; listNode *node; if ((copy = listCreate()) == NULL) return NULL; copy->dup = orig->dup; copy->free = orig->free; copy->match = orig->match; listRewind(orig, &iter); while((node = listNext(&iter)) != NULL) { void *value; if (copy->dup) { value = copy->dup(node->value); if (value == NULL) { listRelease(copy); return NULL; } } else { value = node->value; } if (listAddNodeTail(copy, value) == NULL) { /* Free value if dup succeed but listAddNodeTail failed. */ if (copy->free) copy->free(value); listRelease(copy); return NULL; } } return copy; } /* Search the list for a node matching a given key. * The match is performed using the 'match' method * set with listSetMatchMethod(). If no 'match' method * is set, the 'value' pointer of every node is directly * compared with the 'key' pointer. * * On success the first matching node pointer is returned * (search starts from head). If no matching node exists * NULL is returned. */ listNode *listSearchKey(list *list, void *key) { listIter iter; listNode *node; listRewind(list, &iter); while((node = listNext(&iter)) != NULL) { if (list->match) { if (list->match(node->value, key)) { return node; } } else { if (key == node->value) { return node; } } } return NULL; } /* Return the element at the specified zero-based index * where 0 is the head, 1 is the element next to head * and so on. Negative integers are used in order to count * from the tail, -1 is the last element, -2 the penultimate * and so on. If the index is out of range NULL is returned. */ listNode *listIndex(list *list, long index) { listNode *n; if (index < 0) { index = (-index)-1; n = list->tail; while(index-- && n) n = n->prev; } else { n = list->head; while(index-- && n) n = n->next; } return n; } /* Rotate the list removing the tail node and inserting it to the head. */ void listRotateTailToHead(list *list) { if (listLength(list) <= 1) return; /* Detach current tail */ listNode *tail = list->tail; list->tail = tail->prev; list->tail->next = NULL; /* Move it as head */ list->head->prev = tail; tail->prev = NULL; tail->next = list->head; list->head = tail; } /* Rotate the list removing the head node and inserting it to the tail. */ void listRotateHeadToTail(list *list) { if (listLength(list) <= 1) return; listNode *head = list->head; /* Detach current head */ list->head = head->next; list->head->prev = NULL; /* Move it as tail */ list->tail->next = head; head->next = NULL; head->prev = list->tail; list->tail = head; } /* Add all the elements of the list 'o' at the end of the * list 'l'. The list 'other' remains empty but otherwise valid. */ void listJoin(list *l, list *o) { if (o->len == 0) return; o->head->prev = l->tail; if (l->tail) l->tail->next = o->head; else l->head = o->head; l->tail = o->tail; l->len += o->len; /* Setup other as an empty list. */ o->head = o->tail = NULL; o->len = 0; } /* Initializes the node's value and sets its pointers * so that it is initially not a member of any list. */ void listInitNode(listNode *node, void *value) { node->prev = NULL; node->next = NULL; node->value = value; } redis-8.0.2/src/adlist.h000066400000000000000000000046461501533116600150620ustar00rootroot00000000000000/* adlist.h - A generic doubly linked list implementation * * Copyright (c) 2006-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). */ #ifndef __ADLIST_H__ #define __ADLIST_H__ /* Node, List, and Iterator are the only data structures used currently. */ typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; } listNode; typedef struct listIter { listNode *next; int direction; } listIter; typedef struct list { listNode *head; listNode *tail; void *(*dup)(void *ptr); void (*free)(void *ptr); int (*match)(void *ptr, void *key); unsigned long len; } list; /* Functions implemented as macros */ #define listLength(l) ((l)->len) #define listFirst(l) ((l)->head) #define listLast(l) ((l)->tail) #define listPrevNode(n) ((n)->prev) #define listNextNode(n) ((n)->next) #define listNodeValue(n) ((n)->value) #define listSetDupMethod(l,m) ((l)->dup = (m)) #define listSetFreeMethod(l,m) ((l)->free = (m)) #define listSetMatchMethod(l,m) ((l)->match = (m)) #define listGetDupMethod(l) ((l)->dup) #define listGetFreeMethod(l) ((l)->free) #define listGetMatchMethod(l) ((l)->match) /* Prototypes */ list *listCreate(void); void listRelease(list *list); void listReleaseGeneric(void *list); void listEmpty(list *list); 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); listNode *listNext(listIter *iter); void listReleaseIterator(listIter *iter); list *listDup(list *orig); listNode *listSearchKey(list *list, void *key); listNode *listIndex(list *list, long index); void listRewind(list *list, listIter *li); void listRewindTail(list *list, listIter *li); void listRotateTailToHead(list *list); void listRotateHeadToTail(list *list); void listJoin(list *l, list *o); void listInitNode(listNode *node, void *value); void listLinkNodeHead(list *list, listNode *node); void listLinkNodeTail(list *list, listNode *node); void listUnlinkNode(list *list, listNode *node); /* Directions for iterators */ #define AL_START_HEAD 0 #define AL_START_TAIL 1 #endif /* __ADLIST_H__ */ redis-8.0.2/src/ae.c000066400000000000000000000420251501533116600141530ustar00rootroot00000000000000/* A simple event-driven programming library. Originally I wrote this code * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated * it in form of a library for easy reuse. * * Copyright (c) 2006-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 "ae.h" #include "anet.h" #include "redisassert.h" #include #include #include #include #include #include #include #include #include #include "zmalloc.h" #include "config.h" /* Include the best multiplexing layer supported by this system. * The following should be ordered by performances, descending. */ #ifdef HAVE_EVPORT #include "ae_evport.c" #else #ifdef HAVE_EPOLL #include "ae_epoll.c" #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" #else #include "ae_select.c" #endif #endif #endif #define INITIAL_EVENT 1024 aeEventLoop *aeCreateEventLoop(int setsize) { aeEventLoop *eventLoop; int i; monotonicInit(); /* just in case the calling app didn't initialize */ if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err; eventLoop->nevents = setsize < INITIAL_EVENT ? setsize : INITIAL_EVENT; eventLoop->events = zmalloc(sizeof(aeFileEvent)*eventLoop->nevents); eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*eventLoop->nevents); if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; eventLoop->setsize = setsize; eventLoop->timeEventHead = NULL; eventLoop->timeEventNextId = 0; eventLoop->stop = 0; eventLoop->maxfd = -1; eventLoop->beforesleep = NULL; eventLoop->aftersleep = NULL; eventLoop->flags = 0; memset(eventLoop->privdata, 0, sizeof(eventLoop->privdata)); if (aeApiCreate(eventLoop) == -1) goto err; /* Events with mask == AE_NONE are not set. So let's initialize the * vector with it. */ for (i = 0; i < eventLoop->nevents; i++) eventLoop->events[i].mask = AE_NONE; return eventLoop; err: if (eventLoop) { zfree(eventLoop->events); zfree(eventLoop->fired); zfree(eventLoop); } return NULL; } /* Return the current set size. */ int aeGetSetSize(aeEventLoop *eventLoop) { return eventLoop->setsize; } /* * Tell the event processing to change the wait timeout as soon as possible. * * Note: it just means you turn on/off the global AE_DONT_WAIT. */ void aeSetDontWait(aeEventLoop *eventLoop, int noWait) { if (noWait) eventLoop->flags |= AE_DONT_WAIT; else eventLoop->flags &= ~AE_DONT_WAIT; } /* Resize the maximum set size of the event loop. * If the requested set size is smaller than the current set size, but * there is already a file descriptor in use that is >= the requested * set size minus one, AE_ERR is returned and the operation is not * performed at all. * * Otherwise AE_OK is returned and the operation is successful. */ int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) { if (setsize == eventLoop->setsize) return AE_OK; if (eventLoop->maxfd >= setsize) return AE_ERR; if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR; eventLoop->setsize = setsize; /* If the current allocated space is larger than the requested size, * we need to shrink it to the requested size. */ if (setsize < eventLoop->nevents) { eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize); eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize); eventLoop->nevents = setsize; } return AE_OK; } void aeDeleteEventLoop(aeEventLoop *eventLoop) { aeApiFree(eventLoop); zfree(eventLoop->events); zfree(eventLoop->fired); /* Free the time events list. */ aeTimeEvent *next_te, *te = eventLoop->timeEventHead; while (te) { next_te = te->next; if (te->finalizerProc) te->finalizerProc(eventLoop, te->clientData); zfree(te); te = next_te; } zfree(eventLoop); } void aeStop(aeEventLoop *eventLoop) { eventLoop->stop = 1; } int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData) { if (fd >= eventLoop->setsize) { errno = ERANGE; return AE_ERR; } /* Resize the events and fired arrays if the file * descriptor exceeds the current number of events. */ if (unlikely(fd >= eventLoop->nevents)) { int newnevents = eventLoop->nevents; newnevents = (newnevents * 2 > fd + 1) ? newnevents * 2 : fd + 1; newnevents = (newnevents > eventLoop->setsize) ? eventLoop->setsize : newnevents; eventLoop->events = zrealloc(eventLoop->events, sizeof(aeFileEvent) * newnevents); eventLoop->fired = zrealloc(eventLoop->fired, sizeof(aeFiredEvent) * newnevents); /* Initialize new slots with an AE_NONE mask */ for (int i = eventLoop->nevents; i < newnevents; i++) eventLoop->events[i].mask = AE_NONE; eventLoop->nevents = newnevents; } aeFileEvent *fe = &eventLoop->events[fd]; if (aeApiAddEvent(eventLoop, fd, mask) == -1) return AE_ERR; fe->mask |= mask; if (mask & AE_READABLE) fe->rfileProc = proc; if (mask & AE_WRITABLE) fe->wfileProc = proc; fe->clientData = clientData; if (fd > eventLoop->maxfd) eventLoop->maxfd = fd; return AE_OK; } void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) { if (fd >= eventLoop->setsize) return; aeFileEvent *fe = &eventLoop->events[fd]; if (fe->mask == AE_NONE) return; /* We want to always remove AE_BARRIER if set when AE_WRITABLE * is removed. */ if (mask & AE_WRITABLE) mask |= AE_BARRIER; aeApiDelEvent(eventLoop, fd, mask); fe->mask = fe->mask & (~mask); if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { /* Update the max fd */ int j; for (j = eventLoop->maxfd-1; j >= 0; j--) if (eventLoop->events[j].mask != AE_NONE) break; eventLoop->maxfd = j; } } void *aeGetFileClientData(aeEventLoop *eventLoop, int fd) { if (fd >= eventLoop->setsize) return NULL; aeFileEvent *fe = &eventLoop->events[fd]; if (fe->mask == AE_NONE) return NULL; return fe->clientData; } int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { if (fd >= eventLoop->setsize) return 0; aeFileEvent *fe = &eventLoop->events[fd]; return fe->mask; } long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, aeTimeProc *proc, void *clientData, aeEventFinalizerProc *finalizerProc) { long long id = eventLoop->timeEventNextId++; aeTimeEvent *te; te = zmalloc(sizeof(*te)); if (te == NULL) return AE_ERR; te->id = id; te->when = getMonotonicUs() + milliseconds * 1000; te->timeProc = proc; te->finalizerProc = finalizerProc; te->clientData = clientData; te->prev = NULL; te->next = eventLoop->timeEventHead; te->refcount = 0; if (te->next) te->next->prev = te; eventLoop->timeEventHead = te; return id; } int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) { aeTimeEvent *te = eventLoop->timeEventHead; while(te) { if (te->id == id) { te->id = AE_DELETED_EVENT_ID; return AE_OK; } te = te->next; } return AE_ERR; /* NO event with the specified ID found */ } /* How many microseconds until the first timer should fire. * If there are no timers, -1 is returned. * * Note that's O(N) since time events are unsorted. * Possible optimizations (not needed by Redis so far, but...): * 1) Insert the event in order, so that the nearest is just the head. * Much better but still insertion or deletion of timers is O(N). * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). */ static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) { aeTimeEvent *te = eventLoop->timeEventHead; if (te == NULL) return -1; aeTimeEvent *earliest = NULL; while (te) { if ((!earliest || te->when < earliest->when) && te->id != AE_DELETED_EVENT_ID) earliest = te; te = te->next; } monotime now = getMonotonicUs(); return (now >= earliest->when) ? 0 : earliest->when - now; } /* Process time events */ static int processTimeEvents(aeEventLoop *eventLoop) { int processed = 0; aeTimeEvent *te; long long maxId; te = eventLoop->timeEventHead; maxId = eventLoop->timeEventNextId-1; monotime now = getMonotonicUs(); while(te) { long long id; /* Remove events scheduled for deletion. */ if (te->id == AE_DELETED_EVENT_ID) { aeTimeEvent *next = te->next; /* If a reference exists for this timer event, * don't free it. This is currently incremented * for recursive timerProc calls */ if (te->refcount) { te = next; continue; } if (te->prev) te->prev->next = te->next; else eventLoop->timeEventHead = te->next; if (te->next) te->next->prev = te->prev; if (te->finalizerProc) { te->finalizerProc(eventLoop, te->clientData); now = getMonotonicUs(); } zfree(te); te = next; continue; } /* Make sure we don't process time events created by time events in * this iteration. Note that this check is currently useless: we always * add new timers on the head, however if we change the implementation * detail, this check may be useful again: we keep it here for future * defense. */ if (te->id > maxId) { te = te->next; continue; } if (te->when <= now) { int retval; id = te->id; te->refcount++; retval = te->timeProc(eventLoop, id, te->clientData); te->refcount--; processed++; now = getMonotonicUs(); if (retval != AE_NOMORE) { te->when = now + (monotime)retval * 1000; } else { te->id = AE_DELETED_EVENT_ID; } } te = te->next; } return processed; } /* Process every pending file event, then every pending time event * (that may be registered by file event callbacks just processed). * Without special flags the function sleeps until some file event * fires, or when the next time event occurs (if any). * * If flags is 0, the function does nothing and returns. * if flags has AE_ALL_EVENTS set, all the kind of events are processed. * if flags has AE_FILE_EVENTS set, file events are processed. * if flags has AE_TIME_EVENTS set, time events are processed. * if flags has AE_DONT_WAIT set, the function returns ASAP once all * the events that can be handled without a wait are processed. * if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called. * if flags has AE_CALL_BEFORE_SLEEP set, the beforesleep callback is called. * * The function returns the number of events processed. */ int aeProcessEvents(aeEventLoop *eventLoop, int flags) { int processed = 0, numevents; /* Nothing to do? return ASAP */ if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; /* Note that we want to call aeApiPoll() even if there are no * file events to process as long as we want to process time * events, in order to sleep until the next time event is ready * to fire. */ if (eventLoop->maxfd != -1 || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { int j; struct timeval tv, *tvp = NULL; /* NULL means infinite wait. */ int64_t usUntilTimer; if (eventLoop->beforesleep != NULL && (flags & AE_CALL_BEFORE_SLEEP)) eventLoop->beforesleep(eventLoop); /* The eventLoop->flags may be changed inside beforesleep. * So we should check it after beforesleep be called. At the same time, * the parameter flags always should have the highest priority. * That is to say, once the parameter flag is set to AE_DONT_WAIT, * no matter what value eventLoop->flags is set to, we should ignore it. */ if ((flags & AE_DONT_WAIT) || (eventLoop->flags & AE_DONT_WAIT)) { tv.tv_sec = tv.tv_usec = 0; tvp = &tv; } else if (flags & AE_TIME_EVENTS) { usUntilTimer = usUntilEarliestTimer(eventLoop); if (usUntilTimer >= 0) { tv.tv_sec = usUntilTimer / 1000000; tv.tv_usec = usUntilTimer % 1000000; tvp = &tv; } } /* Call the multiplexing API, will return only on timeout or when * some event fires. */ numevents = aeApiPoll(eventLoop, tvp); /* Don't process file events if not requested. */ if (!(flags & AE_FILE_EVENTS)) { numevents = 0; } /* After sleep callback. */ if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP) eventLoop->aftersleep(eventLoop); for (j = 0; j < numevents; j++) { int fd = eventLoop->fired[j].fd; aeFileEvent *fe = &eventLoop->events[fd]; int mask = eventLoop->fired[j].mask; int fired = 0; /* Number of events fired for current fd. */ /* Normally we execute the readable event first, and the writable * event later. This is useful as sometimes we may be able * to serve the reply of a query immediately after processing the * query. * * However if AE_BARRIER is set in the mask, our application is * asking us to do the reverse: never fire the writable event * after the readable. In such a case, we invert the calls. * This is useful when, for instance, we want to do things * in the beforeSleep() hook, like fsyncing a file to disk, * before replying to a client. */ int invert = fe->mask & AE_BARRIER; /* Note the "fe->mask & mask & ..." code: maybe an already * processed event removed an element that fired and we still * didn't processed, so we check if the event is still valid. * * Fire the readable event if the call sequence is not * inverted. */ if (!invert && fe->mask & mask & AE_READABLE) { fe->rfileProc(eventLoop,fd,fe->clientData,mask); fired++; fe = &eventLoop->events[fd]; /* Refresh in case of resize. */ } /* Fire the writable event. */ if (fe->mask & mask & AE_WRITABLE) { if (!fired || fe->wfileProc != fe->rfileProc) { fe->wfileProc(eventLoop,fd,fe->clientData,mask); fired++; } } /* If we have to invert the call, fire the readable event now * after the writable one. */ if (invert) { fe = &eventLoop->events[fd]; /* Refresh in case of resize. */ if ((fe->mask & mask & AE_READABLE) && (!fired || fe->wfileProc != fe->rfileProc)) { fe->rfileProc(eventLoop,fd,fe->clientData,mask); fired++; } } processed++; } } /* Check time events */ if (flags & AE_TIME_EVENTS) processed += processTimeEvents(eventLoop); return processed; /* return the number of processed file/time events */ } /* Wait for milliseconds until the given file descriptor becomes * writable/readable/exception */ int aeWait(int fd, int mask, long long milliseconds) { struct pollfd pfd; int retmask = 0, retval; memset(&pfd, 0, sizeof(pfd)); pfd.fd = fd; if (mask & AE_READABLE) pfd.events |= POLLIN; if (mask & AE_WRITABLE) pfd.events |= POLLOUT; if ((retval = poll(&pfd, 1, milliseconds))== 1) { if (pfd.revents & POLLIN) retmask |= AE_READABLE; if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; return retmask; } else { return retval; } } void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { aeProcessEvents(eventLoop, AE_ALL_EVENTS| AE_CALL_BEFORE_SLEEP| AE_CALL_AFTER_SLEEP); } } char *aeGetApiName(void) { return aeApiName(); } void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { eventLoop->beforesleep = beforesleep; } void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep) { eventLoop->aftersleep = aftersleep; } redis-8.0.2/src/ae.h000066400000000000000000000102531501533116600141560ustar00rootroot00000000000000/* A simple event-driven programming library. Originally I wrote this code * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated * it in form of a library for easy reuse. * * Copyright (c) 2006-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). */ #ifndef __AE_H__ #define __AE_H__ #include "monotonic.h" #define AE_OK 0 #define AE_ERR -1 #define AE_NONE 0 /* No events registered. */ #define AE_READABLE 1 /* Fire when descriptor is readable. */ #define AE_WRITABLE 2 /* Fire when descriptor is writable. */ #define AE_BARRIER 4 /* With WRITABLE, never fire the event if the READABLE event already fired in the same event loop iteration. Useful when you want to persist things to disk before sending replies, and want to do that in a group fashion. */ #define AE_FILE_EVENTS (1<<0) #define AE_TIME_EVENTS (1<<1) #define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS) #define AE_DONT_WAIT (1<<2) #define AE_CALL_BEFORE_SLEEP (1<<3) #define AE_CALL_AFTER_SLEEP (1<<4) #define AE_NOMORE -1 #define AE_DELETED_EVENT_ID -1 /* Macros */ #define AE_NOTUSED(V) ((void) V) struct aeEventLoop; /* Types and data structures */ typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); /* File event structure */ typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */ aeFileProc *rfileProc; aeFileProc *wfileProc; void *clientData; } aeFileEvent; /* Time event structure */ typedef struct aeTimeEvent { long long id; /* time event identifier. */ monotime when; aeTimeProc *timeProc; aeEventFinalizerProc *finalizerProc; void *clientData; struct aeTimeEvent *prev; struct aeTimeEvent *next; int refcount; /* refcount to prevent timer events from being * freed in recursive time event calls. */ } aeTimeEvent; /* A fired event */ typedef struct aeFiredEvent { int fd; int mask; } aeFiredEvent; /* State of an event based program */ typedef struct aeEventLoop { int maxfd; /* highest file descriptor currently registered */ int setsize; /* max number of file descriptors tracked */ long long timeEventNextId; int nevents; /* Size of Registered events */ aeFileEvent *events; /* Registered events */ aeFiredEvent *fired; /* Fired events */ aeTimeEvent *timeEventHead; int stop; void *apidata; /* This is used for polling API specific data */ aeBeforeSleepProc *beforesleep; aeBeforeSleepProc *aftersleep; int flags; void *privdata[2]; } aeEventLoop; /* Prototypes */ aeEventLoop *aeCreateEventLoop(int setsize); void aeDeleteEventLoop(aeEventLoop *eventLoop); void aeStop(aeEventLoop *eventLoop); int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData); void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); int aeGetFileEvents(aeEventLoop *eventLoop, int fd); void *aeGetFileClientData(aeEventLoop *eventLoop, int fd); long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, aeTimeProc *proc, void *clientData, aeEventFinalizerProc *finalizerProc); int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); int aeProcessEvents(aeEventLoop *eventLoop, int flags); int aeWait(int fd, int mask, long long milliseconds); void aeMain(aeEventLoop *eventLoop); char *aeGetApiName(void); void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep); int aeGetSetSize(aeEventLoop *eventLoop); int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); void aeSetDontWait(aeEventLoop *eventLoop, int noWait); #endif redis-8.0.2/src/ae_epoll.c000066400000000000000000000071461501533116600153530ustar00rootroot00000000000000/* Linux epoll(2) based ae.c module * * Copyright (c) 2009-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 typedef struct aeApiState { int epfd; struct epoll_event *events; } aeApiState; static int aeApiCreate(aeEventLoop *eventLoop) { aeApiState *state = zmalloc(sizeof(aeApiState)); if (!state) return -1; state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize); if (!state->events) { zfree(state); return -1; } state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */ if (state->epfd == -1) { zfree(state->events); zfree(state); return -1; } anetCloexec(state->epfd); eventLoop->apidata = state; return 0; } static int aeApiResize(aeEventLoop *eventLoop, int setsize) { aeApiState *state = eventLoop->apidata; state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize); return 0; } static void aeApiFree(aeEventLoop *eventLoop) { aeApiState *state = eventLoop->apidata; close(state->epfd); zfree(state->events); zfree(state); } static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; struct epoll_event ee = {0}; /* avoid valgrind warning */ /* If the fd was already monitored for some event, we need a MOD * operation. Otherwise we need an ADD operation. */ int op = eventLoop->events[fd].mask == AE_NONE ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; ee.events = 0; mask |= eventLoop->events[fd].mask; /* Merge old events */ if (mask & AE_READABLE) ee.events |= EPOLLIN; if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; ee.data.fd = fd; if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1; return 0; } static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) { aeApiState *state = eventLoop->apidata; struct epoll_event ee = {0}; /* avoid valgrind warning */ int mask = eventLoop->events[fd].mask & (~delmask); ee.events = 0; if (mask & AE_READABLE) ee.events |= EPOLLIN; if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; ee.data.fd = fd; if (mask != AE_NONE) { epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee); } else { /* Note, Kernel < 2.6.9 requires a non null event pointer even for * EPOLL_CTL_DEL. */ epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee); } } static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { aeApiState *state = eventLoop->apidata; int retval, numevents = 0; retval = epoll_wait(state->epfd,state->events,eventLoop->setsize, tvp ? (tvp->tv_sec*1000 + (tvp->tv_usec + 999)/1000) : -1); if (retval > 0) { int j; numevents = retval; for (j = 0; j < numevents; j++) { int mask = 0; struct epoll_event *e = state->events+j; if (e->events & EPOLLIN) mask |= AE_READABLE; if (e->events & EPOLLOUT) mask |= AE_WRITABLE; if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE; if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE; eventLoop->fired[j].fd = e->data.fd; eventLoop->fired[j].mask = mask; } } else if (retval == -1 && errno != EINTR) { panic("aeApiPoll: epoll_wait, %s", strerror(errno)); } return numevents; } static char *aeApiName(void) { return "epoll"; } redis-8.0.2/src/ae_evport.c000066400000000000000000000253721501533116600155600ustar00rootroot00000000000000/* ae.c module for illumos event ports. * * Copyright (c) 2012, Joyent, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include #include #include #include #include #include static int evport_debug = 0; /* * This file implements the ae API using event ports, present on Solaris-based * systems since Solaris 10. Using the event port interface, we associate file * descriptors with the port. Each association also includes the set of poll(2) * events that the consumer is interested in (e.g., POLLIN and POLLOUT). * * There's one tricky piece to this implementation: when we return events via * aeApiPoll, the corresponding file descriptors become dissociated from the * port. This is necessary because poll events are level-triggered, so if the * fd didn't become dissociated, it would immediately fire another event since * the underlying state hasn't changed yet. We must re-associate the file * descriptor, but only after we know that our caller has actually read from it. * The ae API does not tell us exactly when that happens, but we do know that * it must happen by the time aeApiPoll is called again. Our solution is to * keep track of the last fds returned by aeApiPoll and re-associate them next * time aeApiPoll is invoked. * * To summarize, in this module, each fd association is EITHER (a) represented * only via the in-kernel association OR (b) represented by pending_fds and * pending_masks. (b) is only true for the last fds we returned from aeApiPoll, * and only until we enter aeApiPoll again (at which point we restore the * in-kernel association). */ #define MAX_EVENT_BATCHSZ 512 typedef struct aeApiState { int portfd; /* event port */ uint_t npending; /* # of pending fds */ int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */ int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */ } aeApiState; static int aeApiCreate(aeEventLoop *eventLoop) { int i; aeApiState *state = zmalloc(sizeof(aeApiState)); if (!state) return -1; state->portfd = port_create(); if (state->portfd == -1) { zfree(state); return -1; } anetCloexec(state->portfd); state->npending = 0; for (i = 0; i < MAX_EVENT_BATCHSZ; i++) { state->pending_fds[i] = -1; state->pending_masks[i] = AE_NONE; } eventLoop->apidata = state; return 0; } static int aeApiResize(aeEventLoop *eventLoop, int setsize) { (void) eventLoop; (void) setsize; /* Nothing to resize here. */ return 0; } static void aeApiFree(aeEventLoop *eventLoop) { aeApiState *state = eventLoop->apidata; close(state->portfd); zfree(state); } static int aeApiLookupPending(aeApiState *state, int fd) { uint_t i; for (i = 0; i < state->npending; i++) { if (state->pending_fds[i] == fd) return (i); } return (-1); } /* * Helper function to invoke port_associate for the given fd and mask. */ static int aeApiAssociate(const char *where, int portfd, int fd, int mask) { int events = 0; int rv, err; if (mask & AE_READABLE) events |= POLLIN; if (mask & AE_WRITABLE) events |= POLLOUT; if (evport_debug) fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events); rv = port_associate(portfd, PORT_SOURCE_FD, fd, events, (void *)(uintptr_t)mask); err = errno; if (evport_debug) fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err)); if (rv == -1) { fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err)); if (err == EAGAIN) fprintf(stderr, "aeApiAssociate: event port limit exceeded."); } return rv; } static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; int fullmask, pfd; if (evport_debug) fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask); /* * Since port_associate's "events" argument replaces any existing events, we * must be sure to include whatever events are already associated when * we call port_associate() again. */ fullmask = mask | eventLoop->events[fd].mask; pfd = aeApiLookupPending(state, fd); if (pfd != -1) { /* * This fd was recently returned from aeApiPoll. It should be safe to * assume that the consumer has processed that poll event, but we play * it safer by simply updating pending_mask. The fd will be * re-associated as usual when aeApiPoll is called again. */ if (evport_debug) fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd); state->pending_masks[pfd] |= fullmask; return 0; } return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask)); } static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; int fullmask, pfd; if (evport_debug) fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask); pfd = aeApiLookupPending(state, fd); if (pfd != -1) { if (evport_debug) fprintf(stderr, "deleting event from pending fd %d\n", fd); /* * This fd was just returned from aeApiPoll, so it's not currently * associated with the port. All we need to do is update * pending_mask appropriately. */ state->pending_masks[pfd] &= ~mask; if (state->pending_masks[pfd] == AE_NONE) state->pending_fds[pfd] = -1; return; } /* * The fd is currently associated with the port. Like with the add case * above, we must look at the full mask for the file descriptor before * updating that association. We don't have a good way of knowing what the * events are without looking into the eventLoop state directly. We rely on * the fact that our caller has already updated the mask in the eventLoop. */ fullmask = eventLoop->events[fd].mask; if (fullmask == AE_NONE) { /* * We're removing *all* events, so use port_dissociate to remove the * association completely. Failure here indicates a bug. */ if (evport_debug) fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd); if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) { perror("aeApiDelEvent: port_dissociate"); abort(); /* will not return */ } } else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd, fullmask) != 0) { /* * ENOMEM is a potentially transient condition, but the kernel won't * generally return it unless things are really bad. EAGAIN indicates * we've reached a resource limit, for which it doesn't make sense to * retry (counter-intuitively). All other errors indicate a bug. In any * of these cases, the best we can do is to abort. */ abort(); /* will not return */ } } static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { aeApiState *state = eventLoop->apidata; struct timespec timeout, *tsp; uint_t mask, i; uint_t nevents; port_event_t event[MAX_EVENT_BATCHSZ]; /* * If we've returned fd events before, we must re-associate them with the * port now, before calling port_get(). See the block comment at the top of * this file for an explanation of why. */ for (i = 0; i < state->npending; i++) { if (state->pending_fds[i] == -1) /* This fd has since been deleted. */ continue; if (aeApiAssociate("aeApiPoll", state->portfd, state->pending_fds[i], state->pending_masks[i]) != 0) { /* See aeApiDelEvent for why this case is fatal. */ abort(); } state->pending_masks[i] = AE_NONE; state->pending_fds[i] = -1; } state->npending = 0; if (tvp != NULL) { timeout.tv_sec = tvp->tv_sec; timeout.tv_nsec = tvp->tv_usec * 1000; tsp = &timeout; } else { tsp = NULL; } /* * port_getn can return with errno == ETIME having returned some events (!). * So if we get ETIME, we check nevents, too. */ nevents = 1; if (port_getn(state->portfd, event, MAX_EVENT_BATCHSZ, &nevents, tsp) == -1 && (errno != ETIME || nevents == 0)) { if (errno == ETIME || errno == EINTR) return 0; /* Any other error indicates a bug. */ panic("aeApiPoll: port_getn, %s", strerror(errno)); } state->npending = nevents; for (i = 0; i < nevents; i++) { mask = 0; if (event[i].portev_events & POLLIN) mask |= AE_READABLE; if (event[i].portev_events & POLLOUT) mask |= AE_WRITABLE; eventLoop->fired[i].fd = event[i].portev_object; eventLoop->fired[i].mask = mask; if (evport_debug) fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n", (int)event[i].portev_object, mask); state->pending_fds[i] = event[i].portev_object; state->pending_masks[i] = (uintptr_t)event[i].portev_user; } return nevents; } static char *aeApiName(void) { return "evport"; } redis-8.0.2/src/ae_kqueue.c000066400000000000000000000152211501533116600155300ustar00rootroot00000000000000/* Kqueue(2)-based ae.c module * * Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include #include #include typedef struct aeApiState { int kqfd; struct kevent *events; /* Events mask for merge read and write event. * To reduce memory consumption, we use 2 bits to store the mask * of an event, so that 1 byte will store the mask of 4 events. */ char *eventsMask; } aeApiState; #define EVENT_MASK_MALLOC_SIZE(sz) (((sz) + 3) / 4) #define EVENT_MASK_OFFSET(fd) ((fd) % 4 * 2) #define EVENT_MASK_ENCODE(fd, mask) (((mask) & 0x3) << EVENT_MASK_OFFSET(fd)) static inline int getEventMask(const char *eventsMask, int fd) { return (eventsMask[fd/4] >> EVENT_MASK_OFFSET(fd)) & 0x3; } static inline void addEventMask(char *eventsMask, int fd, int mask) { eventsMask[fd/4] |= EVENT_MASK_ENCODE(fd, mask); } static inline void resetEventMask(char *eventsMask, int fd) { eventsMask[fd/4] &= ~EVENT_MASK_ENCODE(fd, 0x3); } static int aeApiCreate(aeEventLoop *eventLoop) { aeApiState *state = zmalloc(sizeof(aeApiState)); if (!state) return -1; state->events = zmalloc(sizeof(struct kevent)*eventLoop->setsize); if (!state->events) { zfree(state); return -1; } state->kqfd = kqueue(); if (state->kqfd == -1) { zfree(state->events); zfree(state); return -1; } anetCloexec(state->kqfd); state->eventsMask = zmalloc(EVENT_MASK_MALLOC_SIZE(eventLoop->setsize)); memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(eventLoop->setsize)); eventLoop->apidata = state; return 0; } static int aeApiResize(aeEventLoop *eventLoop, int setsize) { aeApiState *state = eventLoop->apidata; state->events = zrealloc(state->events, sizeof(struct kevent)*setsize); state->eventsMask = zrealloc(state->eventsMask, EVENT_MASK_MALLOC_SIZE(setsize)); memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(setsize)); return 0; } static void aeApiFree(aeEventLoop *eventLoop) { aeApiState *state = eventLoop->apidata; close(state->kqfd); zfree(state->events); zfree(state->eventsMask); zfree(state); } static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; struct kevent ke; 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; } static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; struct kevent ke; 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); } } static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { aeApiState *state = eventLoop->apidata; int retval, numevents = 0; if (tvp != NULL) { struct timespec timeout; timeout.tv_sec = tvp->tv_sec; timeout.tv_nsec = tvp->tv_usec * 1000; retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, &timeout); } else { retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, NULL); } if (retval > 0) { int j; /* Normally we execute the read event first and then the write event. * When the barrier is set, we will do it reverse. * * However, under kqueue, read and write events would be separate * events, which would make it impossible to control the order of * reads and writes. So we store the event's mask we've got and merge * the same fd events later. */ for (j = 0; j < retval; j++) { struct kevent *e = state->events+j; int fd = e->ident; int mask = 0; if (e->filter == EVFILT_READ) mask = AE_READABLE; else if (e->filter == EVFILT_WRITE) mask = AE_WRITABLE; addEventMask(state->eventsMask, fd, mask); } /* Re-traversal to merge read and write events, and set the fd's mask to * 0 so that events are not added again when the fd is encountered again. */ numevents = 0; for (j = 0; j < retval; j++) { struct kevent *e = state->events+j; int fd = e->ident; int mask = getEventMask(state->eventsMask, fd); if (mask) { eventLoop->fired[numevents].fd = fd; eventLoop->fired[numevents].mask = mask; resetEventMask(state->eventsMask, fd); numevents++; } } } else if (retval == -1 && errno != EINTR) { panic("aeApiPoll: kevent, %s", strerror(errno)); } return numevents; } static char *aeApiName(void) { return "kqueue"; } redis-8.0.2/src/ae_select.c000066400000000000000000000051051501533116600155100ustar00rootroot00000000000000/* Select()-based ae.c module. * * Copyright (c) 2009-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 #include typedef struct aeApiState { fd_set rfds, wfds; /* We need to have a copy of the fd sets as it's not safe to reuse * FD sets after select(). */ fd_set _rfds, _wfds; } aeApiState; static int aeApiCreate(aeEventLoop *eventLoop) { aeApiState *state = zmalloc(sizeof(aeApiState)); if (!state) return -1; FD_ZERO(&state->rfds); FD_ZERO(&state->wfds); eventLoop->apidata = state; return 0; } static int aeApiResize(aeEventLoop *eventLoop, int setsize) { AE_NOTUSED(eventLoop); /* Just ensure we have enough room in the fd_set type. */ if (setsize >= FD_SETSIZE) return -1; return 0; } static void aeApiFree(aeEventLoop *eventLoop) { zfree(eventLoop->apidata); } static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; if (mask & AE_READABLE) FD_SET(fd,&state->rfds); if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds); return 0; } static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { aeApiState *state = eventLoop->apidata; if (mask & AE_READABLE) FD_CLR(fd,&state->rfds); if (mask & AE_WRITABLE) FD_CLR(fd,&state->wfds); } static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { aeApiState *state = eventLoop->apidata; int retval, j, numevents = 0; memcpy(&state->_rfds,&state->rfds,sizeof(fd_set)); memcpy(&state->_wfds,&state->wfds,sizeof(fd_set)); retval = select(eventLoop->maxfd+1, &state->_rfds,&state->_wfds,NULL,tvp); if (retval > 0) { for (j = 0; j <= eventLoop->maxfd; j++) { int mask = 0; aeFileEvent *fe = &eventLoop->events[j]; if (fe->mask == AE_NONE) continue; if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds)) mask |= AE_READABLE; if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds)) mask |= AE_WRITABLE; eventLoop->fired[numevents].fd = j; eventLoop->fired[numevents].mask = mask; numevents++; } } else if (retval == -1 && errno != EINTR) { panic("aeApiPoll: select, %s", strerror(errno)); } return numevents; } static char *aeApiName(void) { return "select"; } redis-8.0.2/src/anet.c000066400000000000000000000632001501533116600145130ustar00rootroot00000000000000/* anet.c -- Basic TCP socket stuff made a bit less boring * * Copyright (c) 2006-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 "fmacros.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "anet.h" #include "config.h" #include "util.h" #define UNUSED(x) (void)(x) static void anetSetError(char *err, const char *fmt, ...) { va_list ap; if (!err) return; va_start(ap, fmt); vsnprintf(err, ANET_ERR_LEN, fmt, ap); va_end(ap); } int anetGetError(int fd) { int sockerr = 0; socklen_t errlen = sizeof(sockerr); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1) sockerr = errno; return sockerr; } int anetSetBlock(char *err, int fd, int non_block) { int flags; /* Set the socket blocking (if non_block is zero) or non-blocking. * Note that fcntl(2) for F_GETFL and F_SETFL can't be * interrupted by a signal. */ if ((flags = fcntl(fd, F_GETFL)) == -1) { anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno)); return ANET_ERR; } /* Check if this flag has been set or unset, if so, * then there is no need to call fcntl to set/unset it again. */ if (!!(flags & O_NONBLOCK) == !!non_block) return ANET_OK; if (non_block) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) { anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } int anetNonBlock(char *err, int fd) { return anetSetBlock(err,fd,1); } int anetBlock(char *err, int fd) { return anetSetBlock(err,fd,0); } /* Enable the FD_CLOEXEC on the given fd to avoid fd leaks. * This function should be invoked for fd's on specific places * where fork + execve system calls are called. */ int anetCloexec(int fd) { int r; int flags; do { r = fcntl(fd, F_GETFD); } while (r == -1 && errno == EINTR); if (r == -1 || (r & FD_CLOEXEC)) return r; flags = r | FD_CLOEXEC; do { r = fcntl(fd, F_SETFD, flags); } while (r == -1 && errno == EINTR); return r; } /* Enable TCP keep-alive mechanism to detect dead peers, * TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT will be set accordingly. */ int anetKeepAlive(char *err, int fd, int interval) { int enabled = 1; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled))) { anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno)); return ANET_ERR; } int idle; int intvl; int cnt; /* There are platforms that are expected to support the full mechanism of TCP keep-alive, * we want the compiler to emit warnings of unused variables if the preprocessor directives * somehow fail, and other than those platforms, just omit these warnings if they happen. */ #if !(defined(_AIX) || defined(__APPLE__) || defined(__DragonFly__) || \ defined(__FreeBSD__) || defined(__illumos__) || defined(__linux__) || \ defined(__NetBSD__) || defined(__sun)) UNUSED(interval); UNUSED(idle); UNUSED(intvl); UNUSED(cnt); #endif #ifdef __sun /* The implementation of TCP keep-alive on Solaris/SmartOS is a bit unusual * compared to other Unix-like systems. * Thus, we need to specialize it on Solaris. * * There are two keep-alive mechanisms on Solaris: * - By default, the first keep-alive probe is sent out after a TCP connection is idle for two hours. * If the peer does not respond to the probe within eight minutes, the TCP connection is aborted. * You can alter the interval for sending out the first probe using the socket option TCP_KEEPALIVE_THRESHOLD * in milliseconds or TCP_KEEPIDLE in seconds. * The system default is controlled by the TCP ndd parameter tcp_keepalive_interval. The minimum value is ten seconds. * The maximum is ten days, while the default is two hours. If you receive no response to the probe, * you can use the TCP_KEEPALIVE_ABORT_THRESHOLD socket option to change the time threshold for aborting a TCP connection. * The option value is an unsigned integer in milliseconds. The value zero indicates that TCP should never time out and * abort the connection when probing. The system default is controlled by the TCP ndd parameter tcp_keepalive_abort_interval. * The default is eight minutes. * * - The second implementation is activated if socket option TCP_KEEPINTVL and/or TCP_KEEPCNT are set. * The time between each consequent probes is set by TCP_KEEPINTVL in seconds. * The minimum value is ten seconds. The maximum is ten days, while the default is two hours. * The TCP connection will be aborted after certain amount of probes, which is set by TCP_KEEPCNT, without receiving response. */ idle = interval; if (idle < 10) idle = 10; // kernel expects at least 10 seconds if (idle > 10*24*60*60) idle = 10*24*60*60; // kernel expects at most 10 days /* `TCP_KEEPIDLE`, `TCP_KEEPINTVL`, and `TCP_KEEPCNT` were not available on Solaris * until version 11.4, but let's take a chance here. */ #if defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT) if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle))) { anetSetError(err, "setsockopt TCP_KEEPIDLE: %s\n", strerror(errno)); return ANET_ERR; } intvl = idle/3; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl))) { anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno)); return ANET_ERR; } cnt = 3; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt))) { anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno)); return ANET_ERR; } #else /* Fall back to the first implementation of tcp-alive mechanism for older Solaris, * simulate the tcp-alive mechanism on other platforms via `TCP_KEEPALIVE_THRESHOLD` + `TCP_KEEPALIVE_ABORT_THRESHOLD`. */ idle *= 1000; // kernel expects milliseconds if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, &idle, sizeof(idle))) { anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno)); return ANET_ERR; } /* 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; 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; } #endif return ANET_OK; #endif #ifdef TCP_KEEPIDLE /* Default settings are more or less garbage, with the keepalive time * set to 7200 by default on Linux and other Unix-like systems. * Modify settings to make the feature actually useful. */ /* Send first probe after interval. */ idle = interval; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle))) { anetSetError(err, "setsockopt TCP_KEEPIDLE: %s\n", strerror(errno)); return ANET_ERR; } #elif defined(TCP_KEEPALIVE) /* Darwin/macOS uses TCP_KEEPALIVE in place of TCP_KEEPIDLE. */ idle = interval; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &idle, sizeof(idle))) { anetSetError(err, "setsockopt TCP_KEEPALIVE: %s\n", strerror(errno)); return ANET_ERR; } #endif #ifdef TCP_KEEPINTVL /* Send next probes after the specified interval. Note that we set the * delay as interval / 3, as we send three probes before detecting * an error (see the next setsockopt call). */ intvl = interval/3; if (intvl == 0) intvl = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl))) { anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno)); return ANET_ERR; } #endif #ifdef TCP_KEEPCNT /* Consider the socket in error state after three we send three ACK * probes without getting a reply. */ cnt = 3; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt))) { anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno)); return ANET_ERR; } #endif return ANET_OK; } static int anetSetTcpNoDelay(char *err, int fd, int val) { if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) { anetSetError(err, "setsockopt TCP_NODELAY: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } int anetEnableTcpNoDelay(char *err, int fd) { return anetSetTcpNoDelay(err, fd, 1); } int anetDisableTcpNoDelay(char *err, int fd) { return anetSetTcpNoDelay(err, fd, 0); } /* Set the socket send timeout (SO_SNDTIMEO socket option) to the specified * number of milliseconds, or disable it if the 'ms' argument is zero. */ int anetSendTimeout(char *err, int fd, long long ms) { struct timeval tv; tv.tv_sec = ms/1000; tv.tv_usec = (ms%1000)*1000; if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { anetSetError(err, "setsockopt SO_SNDTIMEO: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } /* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified * number of milliseconds, or disable it if the 'ms' argument is zero. */ int anetRecvTimeout(char *err, int fd, long long ms) { struct timeval tv; tv.tv_sec = ms/1000; tv.tv_usec = (ms%1000)*1000; if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } /* Resolve the hostname "host" and set the string representation of the * IP address into the buffer pointed by "ipbuf". * * If flags is set to ANET_IP_ONLY the function only resolves hostnames * that are actually already IPv4 or IPv6 addresses. This turns the function * into a validating / normalizing function. * * If the flag ANET_PREFER_IPV4 is set, IPv4 is preferred over IPv6. * If the flag ANET_PREFER_IPV6 is set, IPv6 is preferred over IPv4. * */ int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags) { struct addrinfo hints, *info; int rv; memset(&hints,0,sizeof(hints)); if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; if (flags & ANET_PREFER_IPV4 && !(flags & ANET_PREFER_IPV6)) { hints.ai_family = AF_INET; } else if (flags & ANET_PREFER_IPV6 && !(flags & ANET_PREFER_IPV4)) { hints.ai_family = AF_INET6; } hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */ rv = getaddrinfo(host, NULL, &hints, &info); if (rv != 0 && hints.ai_family != AF_UNSPEC) { /* Try the other IP version. */ hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET; rv = getaddrinfo(host, NULL, &hints, &info); } if (rv != 0) { anetSetError(err, "%s", gai_strerror(rv)); return ANET_ERR; } if (info->ai_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)info->ai_addr; inet_ntop(AF_INET, &(sa->sin_addr), ipbuf, ipbuf_len); } else { struct sockaddr_in6 *sa = (struct sockaddr_in6 *)info->ai_addr; inet_ntop(AF_INET6, &(sa->sin6_addr), ipbuf, ipbuf_len); } freeaddrinfo(info); return ANET_OK; } static int anetSetReuseAddr(char *err, int fd) { int yes = 1; /* Make sure connection-intensive things like the redis benchmark * will be able to close/open sockets a zillion of times */ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } static int anetCreateSocket(char *err, int domain) { int s; if ((s = socket(domain, SOCK_STREAM, 0)) == -1) { anetSetError(err, "creating socket: %s", strerror(errno)); return ANET_ERR; } /* Make sure connection-intensive things like the redis benchmark * will be able to close/open sockets a zillion of times */ if (anetSetReuseAddr(err,s) == ANET_ERR) { close(s); return ANET_ERR; } return s; } #define ANET_CONNECT_NONE 0 #define ANET_CONNECT_NONBLOCK 1 #define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */ static int anetTcpGenericConnect(char *err, const char *addr, int port, const char *source_addr, int flags) { int s = ANET_ERR, rv; char portstr[6]; /* strlen("65535") + 1; */ struct addrinfo hints, *servinfo, *bservinfo, *p, *b; snprintf(portstr,sizeof(portstr),"%d",port); memset(&hints,0,sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(addr,portstr,&hints,&servinfo)) != 0) { anetSetError(err, "%s", gai_strerror(rv)); return ANET_ERR; } for (p = servinfo; p != NULL; p = p->ai_next) { /* Try to create the socket and to connect it. * If we fail in the socket() call, or on connect(), we retry with * the next entry in servinfo. */ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) continue; if (anetSetReuseAddr(err,s) == ANET_ERR) goto error; if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK) goto error; if (source_addr) { int bound = 0; /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) { anetSetError(err, "%s", gai_strerror(rv)); goto error; } for (b = bservinfo; b != NULL; b = b->ai_next) { if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { bound = 1; break; } } freeaddrinfo(bservinfo); if (!bound) { anetSetError(err, "bind: %s", strerror(errno)); goto error; } } if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { /* If the socket is non-blocking, it is ok for connect() to * return an EINPROGRESS error here. */ if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) goto end; close(s); s = ANET_ERR; continue; } /* If we ended an iteration of the for loop without errors, we * have a connected socket. Let's return to the caller. */ goto end; } if (p == NULL) anetSetError(err, "creating socket: %s", strerror(errno)); error: if (s != ANET_ERR) { close(s); s = ANET_ERR; } end: freeaddrinfo(servinfo); /* Handle best effort binding: if a binding address was used, but it is * not possible to create a socket, try again without a binding address. */ if (s == ANET_ERR && source_addr && (flags & ANET_CONNECT_BE_BINDING)) { return anetTcpGenericConnect(err,addr,port,NULL,flags); } else { return s; } } int anetTcpNonBlockConnect(char *err, const char *addr, int port) { return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK); } int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr) { return anetTcpGenericConnect(err,addr,port,source_addr, ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING); } int anetUnixGenericConnect(char *err, const char *path, int flags) { int s; struct sockaddr_un sa; if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR) return ANET_ERR; sa.sun_family = AF_LOCAL; redis_strlcpy(sa.sun_path,path,sizeof(sa.sun_path)); if (flags & ANET_CONNECT_NONBLOCK) { if (anetNonBlock(err,s) != ANET_OK) { close(s); return ANET_ERR; } } if (connect(s,(struct sockaddr*)&sa,sizeof(sa)) == -1) { if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) return s; anetSetError(err, "connect: %s", strerror(errno)); close(s); return ANET_ERR; } return s; } static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog, mode_t perm) { if (bind(s,sa,len) == -1) { anetSetError(err, "bind: %s", strerror(errno)); close(s); return ANET_ERR; } if (sa->sa_family == AF_LOCAL && perm) chmod(((struct sockaddr_un *) sa)->sun_path, perm); if (listen(s, backlog) == -1) { anetSetError(err, "listen: %s", strerror(errno)); close(s); return ANET_ERR; } return ANET_OK; } static int anetV6Only(char *err, int s) { int yes = 1; if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) { anetSetError(err, "setsockopt: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; } static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog) { int s = -1, rv; char _port[6]; /* strlen("65535") */ struct addrinfo hints, *servinfo, *p; snprintf(_port,6,"%d",port); memset(&hints,0,sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */ if (bindaddr && !strcmp("*", bindaddr)) bindaddr = NULL; if (af == AF_INET6 && bindaddr && !strcmp("::*", bindaddr)) bindaddr = NULL; if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) { anetSetError(err, "%s", gai_strerror(rv)); return ANET_ERR; } for (p = servinfo; p != NULL; p = p->ai_next) { if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) continue; if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error; if (anetSetReuseAddr(err,s) == ANET_ERR) goto error; if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog,0) == ANET_ERR) s = ANET_ERR; goto end; } if (p == NULL) { anetSetError(err, "unable to bind socket, errno: %d", errno); goto error; } error: if (s != -1) close(s); s = ANET_ERR; end: freeaddrinfo(servinfo); return s; } int anetTcpServer(char *err, int port, char *bindaddr, int backlog) { return _anetTcpServer(err, port, bindaddr, AF_INET, backlog); } int anetTcp6Server(char *err, int port, char *bindaddr, int backlog) { return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog); } int anetUnixServer(char *err, char *path, mode_t perm, int backlog) { int s; struct sockaddr_un sa; if (strlen(path) > sizeof(sa.sun_path)-1) { anetSetError(err,"unix socket path too long (%zu), must be under %zu", strlen(path), sizeof(sa.sun_path)); return ANET_ERR; } if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR) return ANET_ERR; memset(&sa,0,sizeof(sa)); sa.sun_family = AF_LOCAL; redis_strlcpy(sa.sun_path,path,sizeof(sa.sun_path)); if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog,perm) == ANET_ERR) return ANET_ERR; return s; } /* Accept a connection and also make sure the socket is non-blocking, and CLOEXEC. * returns the new socket FD, or -1 on error. */ static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) { int fd; do { /* Use the accept4() call on linux to simultaneously accept and * set a socket as non-blocking. */ #ifdef HAVE_ACCEPT4 fd = accept4(s, sa, len, SOCK_NONBLOCK | SOCK_CLOEXEC); #else fd = accept(s,sa,len); #endif } while(fd == -1 && errno == EINTR); if (fd == -1) { anetSetError(err, "accept: %s", strerror(errno)); return ANET_ERR; } #ifndef HAVE_ACCEPT4 if (anetCloexec(fd) == -1) { anetSetError(err, "anetCloexec: %s", strerror(errno)); close(fd); return ANET_ERR; } if (anetNonBlock(err, fd) != ANET_OK) { close(fd); return ANET_ERR; } #endif return fd; } /* Accept a connection and also make sure the socket is non-blocking, and CLOEXEC. * returns the new socket FD, or -1 on error. */ int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port) { int fd; struct sockaddr_storage sa; socklen_t salen = sizeof(sa); if ((fd = anetGenericAccept(err,serversock,(struct sockaddr*)&sa,&salen)) == ANET_ERR) return ANET_ERR; if (sa.ss_family == AF_INET) { struct sockaddr_in *s = (struct sockaddr_in *)&sa; if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len); if (port) *port = ntohs(s->sin_port); } else { struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa; if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len); if (port) *port = ntohs(s->sin6_port); } return fd; } /* Accept a connection and also make sure the socket is non-blocking, and CLOEXEC. * returns the new socket FD, or -1 on error. */ int anetUnixAccept(char *err, int s) { int fd; struct sockaddr_un sa; socklen_t salen = sizeof(sa); if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR) return ANET_ERR; return fd; } int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int remote) { struct sockaddr_storage sa; socklen_t salen = sizeof(sa); if (remote) { if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error; } else { if (getsockname(fd, (struct sockaddr *)&sa, &salen) == -1) goto error; } if (sa.ss_family == AF_INET) { struct sockaddr_in *s = (struct sockaddr_in *)&sa; if (ip) { if (inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len) == NULL) goto error; } if (port) *port = ntohs(s->sin_port); } else if (sa.ss_family == AF_INET6) { struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa; if (ip) { if (inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len) == NULL) goto error; } if (port) *port = ntohs(s->sin6_port); } else if (sa.ss_family == AF_UNIX) { if (ip) { int res = snprintf(ip, ip_len, "/unixsocket"); if (res < 0 || (unsigned int) res >= ip_len) goto error; } if (port) *port = 0; } else { goto error; } return 0; error: if (ip) { if (ip_len >= 2) { ip[0] = '?'; ip[1] = '\0'; } else if (ip_len == 1) { ip[0] = '\0'; } } if (port) *port = 0; return -1; } /* Create a pipe buffer with given flags for read end and write end. * Note that it supports the file flags defined by pipe2() and fcntl(F_SETFL), * 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__) /* 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); if (pipe2(fds, pipe_flags)) { /* Fail on real failures, and fallback to simple pipe if pipe2 is unsupported. */ if (errno != ENOSYS && errno != EINVAL) return -1; pipe_flags = 0; } else { /* If the flags on both ends are identical, no need to do anything else. */ if ((O_CLOEXEC | read_flags) == (O_CLOEXEC | write_flags)) return 0; /* Clear the flags which have already been set using pipe2. */ read_flags &= ~pipe_flags; write_flags &= ~pipe_flags; } #endif /* When we reach here with pipe_flags of 0, it means pipe2 failed (or was not attempted), * so we try to use pipe. Otherwise, we skip and proceed to set specific flags below. */ if (pipe_flags == 0 && pipe(fds)) return -1; /* File descriptor flags. * Currently, only one such flag is defined: FD_CLOEXEC, the close-on-exec flag. */ if (read_flags & O_CLOEXEC) if (fcntl(fds[0], F_SETFD, FD_CLOEXEC)) goto error; if (write_flags & O_CLOEXEC) if (fcntl(fds[1], F_SETFD, FD_CLOEXEC)) goto error; /* File status flags after clearing the file descriptor flag O_CLOEXEC. */ read_flags &= ~O_CLOEXEC; if (read_flags) if (fcntl(fds[0], F_SETFL, read_flags)) goto error; write_flags &= ~O_CLOEXEC; if (write_flags) if (fcntl(fds[1], F_SETFL, write_flags)) goto error; return 0; error: close(fds[0]); close(fds[1]); return -1; } int anetSetSockMarkId(char *err, int fd, uint32_t id) { #ifdef HAVE_SOCKOPTMARKID if (setsockopt(fd, SOL_SOCKET, SOCKOPTMARKID, (void *)&id, sizeof(id)) == -1) { anetSetError(err, "setsockopt: %s", strerror(errno)); return ANET_ERR; } return ANET_OK; #else UNUSED(fd); UNUSED(id); anetSetError(err,"anetSetSockMarkid unsupported on this platform"); return ANET_OK; #endif } int anetIsFifo(char *filepath) { struct stat sb; if (stat(filepath, &sb) == -1) return 0; return S_ISFIFO(sb.st_mode); } redis-8.0.2/src/anet.h000066400000000000000000000036671501533116600145330ustar00rootroot00000000000000/* anet.c -- Basic TCP socket stuff made a bit less boring * * Copyright (c) 2006-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). */ #ifndef ANET_H #define ANET_H #include #define ANET_OK 0 #define ANET_ERR -1 #define ANET_ERR_LEN 256 /* Flags used with certain functions. */ #define ANET_NONE 0 #define ANET_IP_ONLY (1<<0) #define ANET_PREFER_IPV4 (1<<1) #define ANET_PREFER_IPV6 (1<<2) #if defined(__sun) || defined(_AIX) #define AF_LOCAL AF_UNIX #endif #ifdef _AIX #undef ip_len #endif int anetTcpNonBlockConnect(char *err, const char *addr, int port); int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags); int anetTcpServer(char *err, int port, char *bindaddr, int backlog); int anetTcp6Server(char *err, int port, char *bindaddr, int backlog); int anetUnixServer(char *err, char *path, mode_t perm, int backlog); int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port); int anetUnixAccept(char *err, int serversock); int anetNonBlock(char *err, int fd); int anetBlock(char *err, int fd); int anetCloexec(int fd); int anetEnableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd); int anetSendTimeout(char *err, int fd, long long ms); int anetRecvTimeout(char *err, int fd, long long ms); int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int remote); int anetKeepAlive(char *err, int fd, int interval); int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port); int anetPipe(int fds[2], int read_flags, int write_flags); int anetSetSockMarkId(char *err, int fd, uint32_t id); int anetGetError(int fd); int anetIsFifo(char *filepath); #endif redis-8.0.2/src/aof.c000066400000000000000000003247731501533116600143500ustar00rootroot00000000000000/* * Copyright (c) 2009-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 "server.h" #include "bio.h" #include "rio.h" #include "functions.h" #include #include #include #include #include #include #include #include void freeClientArgv(client *c); off_t getAppendOnlyFileSize(sds filename, int *status); off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am, int *status); int getBaseAndIncrAppendOnlyFilesNum(aofManifest *am); int aofFileExist(char *filename); int rewriteAppendOnlyFile(char *filename); aofManifest *aofLoadManifestFromFile(sds am_filepath); void aofManifestFreeAndUpdate(aofManifest *am); void aof_background_fsync_and_close(int fd); /* When we call 'startAppendOnly', we will create a temp INCR AOF, and rename * it to the real INCR AOF name when the AOFRW is done, so if want to know the * accurate start offset of the INCR AOF, we need to record it when we create * the temp INCR AOF. This variable is used to record the start offset, and * set the start offset of the real INCR AOF when the AOFRW is done. */ static long long tempIncAofStartReplOffset = 0; /* ---------------------------------------------------------------------------- * AOF Manifest file implementation. * * The following code implements the read/write logic of AOF manifest file, which * is used to track and manage all AOF files. * * Append-only files consist of three types: * * BASE: Represents a Redis snapshot from the time of last AOF rewrite. The manifest * file contains at most a single BASE file, which will always be the first file in the * list. * * INCR: Represents all write commands executed by Redis following the last successful * AOF rewrite. In some cases it is possible to have several ordered INCR files. For * example: * - During an on-going AOF rewrite * - After an AOF rewrite was aborted/failed, and before the next one succeeded. * * HISTORY: After a successful rewrite, the previous BASE and INCR become HISTORY files. * They will be automatically removed unless garbage collection is disabled. * * The following is a possible AOF manifest file content: * * file appendonly.aof.2.base.rdb seq 2 type b * file appendonly.aof.1.incr.aof seq 1 type h * file appendonly.aof.2.incr.aof seq 2 type h * file appendonly.aof.3.incr.aof seq 3 type h * file appendonly.aof.4.incr.aof seq 4 type i * file appendonly.aof.5.incr.aof seq 5 type i * ------------------------------------------------------------------------- */ /* Naming rules. */ #define BASE_FILE_SUFFIX ".base" #define INCR_FILE_SUFFIX ".incr" #define RDB_FORMAT_SUFFIX ".rdb" #define AOF_FORMAT_SUFFIX ".aof" #define MANIFEST_NAME_SUFFIX ".manifest" #define TEMP_FILE_NAME_PREFIX "temp-" /* AOF manifest key. */ #define AOF_MANIFEST_KEY_FILE_NAME "file" #define AOF_MANIFEST_KEY_FILE_SEQ "seq" #define AOF_MANIFEST_KEY_FILE_TYPE "type" #define AOF_MANIFEST_KEY_FILE_STARTOFFSET "startoffset" #define AOF_MANIFEST_KEY_FILE_ENDOFFSET "endoffset" /* Create an empty aofInfo. */ aofInfo *aofInfoCreate(void) { aofInfo *ai = zcalloc(sizeof(aofInfo)); ai->start_offset = -1; ai->end_offset = -1; return ai; } /* Free the aofInfo structure (pointed to by ai) and its embedded file_name. */ void aofInfoFree(aofInfo *ai) { serverAssert(ai != NULL); if (ai->file_name) sdsfree(ai->file_name); zfree(ai); } /* Deep copy an aofInfo. */ aofInfo *aofInfoDup(aofInfo *orig) { serverAssert(orig != NULL); aofInfo *ai = aofInfoCreate(); ai->file_name = sdsdup(orig->file_name); ai->file_seq = orig->file_seq; ai->file_type = orig->file_type; ai->start_offset = orig->start_offset; ai->end_offset = orig->end_offset; return ai; } /* Format aofInfo as a string and it will be a line in the manifest. * * When update this format, make sure to update redis-check-aof as well. */ sds aofInfoFormat(sds buf, aofInfo *ai) { sds filename_repr = NULL; if (sdsneedsrepr(ai->file_name)) filename_repr = sdscatrepr(sdsempty(), ai->file_name, sdslen(ai->file_name)); sds ret = sdscatprintf(buf, "%s %s %s %lld %s %c", AOF_MANIFEST_KEY_FILE_NAME, filename_repr ? filename_repr : ai->file_name, AOF_MANIFEST_KEY_FILE_SEQ, ai->file_seq, AOF_MANIFEST_KEY_FILE_TYPE, ai->file_type); if (ai->start_offset != -1) { ret = sdscatprintf(ret, " %s %lld", AOF_MANIFEST_KEY_FILE_STARTOFFSET, ai->start_offset); if (ai->end_offset != -1) { ret = sdscatprintf(ret, " %s %lld", AOF_MANIFEST_KEY_FILE_ENDOFFSET, ai->end_offset); } } ret = sdscatlen(ret, "\n", 1); sdsfree(filename_repr); return ret; } /* Method to free AOF list elements. */ void aofListFree(void *item) { aofInfo *ai = (aofInfo *)item; aofInfoFree(ai); } /* Method to duplicate AOF list elements. */ void *aofListDup(void *item) { return aofInfoDup(item); } /* Create an empty aofManifest, which will be called in `aofLoadManifestFromDisk`. */ aofManifest *aofManifestCreate(void) { aofManifest *am = zcalloc(sizeof(aofManifest)); am->incr_aof_list = listCreate(); am->history_aof_list = listCreate(); listSetFreeMethod(am->incr_aof_list, aofListFree); listSetDupMethod(am->incr_aof_list, aofListDup); listSetFreeMethod(am->history_aof_list, aofListFree); listSetDupMethod(am->history_aof_list, aofListDup); return am; } /* Free the aofManifest structure (pointed to by am) and its embedded members. */ void aofManifestFree(aofManifest *am) { if (am->base_aof_info) aofInfoFree(am->base_aof_info); if (am->incr_aof_list) listRelease(am->incr_aof_list); if (am->history_aof_list) listRelease(am->history_aof_list); zfree(am); } sds getAofManifestFileName(void) { return sdscatprintf(sdsempty(), "%s%s", server.aof_filename, MANIFEST_NAME_SUFFIX); } sds getTempAofManifestFileName(void) { return sdscatprintf(sdsempty(), "%s%s%s", TEMP_FILE_NAME_PREFIX, server.aof_filename, MANIFEST_NAME_SUFFIX); } /* Returns the string representation of aofManifest pointed to by am. * * The string is multiple lines separated by '\n', and each line represents * an AOF file. * * Each line is space delimited and contains 6 fields, as follows: * "file" [filename] "seq" [sequence] "type" [type] * * Where "file", "seq" and "type" are keywords that describe the next value, * [filename] and [sequence] describe file name and order, and [type] is one * of 'b' (base), 'h' (history) or 'i' (incr). * * The base file, if exists, will always be first, followed by history files, * and incremental files. */ sds getAofManifestAsString(aofManifest *am) { serverAssert(am != NULL); sds buf = sdsempty(); listNode *ln; listIter li; /* 1. Add BASE File information, it is always at the beginning * of the manifest file. */ if (am->base_aof_info) { buf = aofInfoFormat(buf, am->base_aof_info); } /* 2. Add HISTORY type AOF information. */ listRewind(am->history_aof_list, &li); while ((ln = listNext(&li)) != NULL) { aofInfo *ai = (aofInfo*)ln->value; buf = aofInfoFormat(buf, ai); } /* 3. Add INCR type AOF information. */ listRewind(am->incr_aof_list, &li); while ((ln = listNext(&li)) != NULL) { aofInfo *ai = (aofInfo*)ln->value; buf = aofInfoFormat(buf, ai); } return buf; } /* Load the manifest information from the disk to `server.aof_manifest` * when the Redis server start. * * During loading, this function does strict error checking and will abort * the entire Redis server process on error (I/O error, invalid format, etc.) * * If the AOF directory or manifest file do not exist, this will be ignored * in order to support seamless upgrades from previous versions which did not * use them. */ void aofLoadManifestFromDisk(void) { server.aof_manifest = aofManifestCreate(); if (!dirExists(server.aof_dirname)) { serverLog(LL_DEBUG, "The AOF directory %s doesn't exist", server.aof_dirname); return; } sds am_name = getAofManifestFileName(); sds am_filepath = makePath(server.aof_dirname, am_name); if (!fileExist(am_filepath)) { serverLog(LL_DEBUG, "The AOF manifest file %s doesn't exist", am_name); sdsfree(am_name); sdsfree(am_filepath); return; } aofManifest *am = aofLoadManifestFromFile(am_filepath); if (am) aofManifestFreeAndUpdate(am); sdsfree(am_name); sdsfree(am_filepath); } /* Generic manifest loading function, used in `aofLoadManifestFromDisk` and redis-check-aof tool. */ #define MANIFEST_MAX_LINE 1024 aofManifest *aofLoadManifestFromFile(sds am_filepath) { const char *err = NULL; long long maxseq = 0; aofManifest *am = aofManifestCreate(); FILE *fp = fopen(am_filepath, "r"); if (fp == NULL) { serverLog(LL_WARNING, "Fatal error: can't open the AOF manifest " "file %s for reading: %s", am_filepath, strerror(errno)); exit(1); } char buf[MANIFEST_MAX_LINE+1]; sds *argv = NULL; int argc; aofInfo *ai = NULL; sds line = NULL; int linenum = 0; while (1) { if (fgets(buf, MANIFEST_MAX_LINE+1, fp) == NULL) { if (feof(fp)) { if (linenum == 0) { err = "Found an empty AOF manifest"; goto loaderr; } else { break; } } else { err = "Read AOF manifest failed"; goto loaderr; } } linenum++; /* Skip comments lines */ if (buf[0] == '#') continue; if (strchr(buf, '\n') == NULL) { err = "The AOF manifest file contains too long line"; goto loaderr; } line = sdstrim(sdsnew(buf), " \t\r\n"); if (!sdslen(line)) { err = "Invalid AOF manifest file format"; goto loaderr; } argv = sdssplitargs(line, &argc); /* 'argc < 6' was done for forward compatibility. */ if (argv == NULL || argc < 6 || (argc % 2)) { err = "Invalid AOF manifest file format"; goto loaderr; } ai = aofInfoCreate(); for (int i = 0; i < argc; i += 2) { if (!strcasecmp(argv[i], AOF_MANIFEST_KEY_FILE_NAME)) { ai->file_name = sdsnew(argv[i+1]); if (!pathIsBaseName(ai->file_name)) { err = "File can't be a path, just a filename"; goto loaderr; } } else if (!strcasecmp(argv[i], AOF_MANIFEST_KEY_FILE_SEQ)) { ai->file_seq = atoll(argv[i+1]); } else if (!strcasecmp(argv[i], AOF_MANIFEST_KEY_FILE_TYPE)) { ai->file_type = (argv[i+1])[0]; } else if (!strcasecmp(argv[i], AOF_MANIFEST_KEY_FILE_STARTOFFSET)) { ai->start_offset = atoll(argv[i+1]); } else if (!strcasecmp(argv[i], AOF_MANIFEST_KEY_FILE_ENDOFFSET)) { ai->end_offset = atoll(argv[i+1]); } /* else if (!strcasecmp(argv[i], AOF_MANIFEST_KEY_OTHER)) {} */ } /* We have to make sure we load all the information. */ if (!ai->file_name || !ai->file_seq || !ai->file_type) { err = "Invalid AOF manifest file format"; goto loaderr; } sdsfreesplitres(argv, argc); argv = NULL; if (ai->file_type == AOF_FILE_TYPE_BASE) { if (am->base_aof_info) { err = "Found duplicate base file information"; goto loaderr; } am->base_aof_info = ai; am->curr_base_file_seq = ai->file_seq; } else if (ai->file_type == AOF_FILE_TYPE_HIST) { listAddNodeTail(am->history_aof_list, ai); } else if (ai->file_type == AOF_FILE_TYPE_INCR) { if (ai->file_seq <= maxseq) { err = "Found a non-monotonic sequence number"; goto loaderr; } listAddNodeTail(am->incr_aof_list, ai); am->curr_incr_file_seq = ai->file_seq; maxseq = ai->file_seq; } else { err = "Unknown AOF file type"; goto loaderr; } sdsfree(line); line = NULL; ai = NULL; } fclose(fp); return am; loaderr: /* Sanitizer suppression: may report a false positive if we goto loaderr * and exit(1) without freeing these allocations. */ if (argv) sdsfreesplitres(argv, argc); if (ai) aofInfoFree(ai); serverLog(LL_WARNING, "\n*** FATAL AOF MANIFEST FILE ERROR ***\n"); if (line) { serverLog(LL_WARNING, "Reading the manifest file, at line %d\n", linenum); serverLog(LL_WARNING, ">>> '%s'\n", line); } serverLog(LL_WARNING, "%s\n", err); exit(1); } /* Deep copy an aofManifest from orig. * * In `backgroundRewriteDoneHandler` and `openNewIncrAofForAppend`, we will * first deep copy a temporary AOF manifest from the `server.aof_manifest` and * try to modify it. Once everything is modified, we will atomically make the * `server.aof_manifest` point to this temporary aof_manifest. */ aofManifest *aofManifestDup(aofManifest *orig) { serverAssert(orig != NULL); aofManifest *am = zcalloc(sizeof(aofManifest)); am->curr_base_file_seq = orig->curr_base_file_seq; am->curr_incr_file_seq = orig->curr_incr_file_seq; am->dirty = orig->dirty; if (orig->base_aof_info) { am->base_aof_info = aofInfoDup(orig->base_aof_info); } am->incr_aof_list = listDup(orig->incr_aof_list); am->history_aof_list = listDup(orig->history_aof_list); serverAssert(am->incr_aof_list != NULL); serverAssert(am->history_aof_list != NULL); return am; } /* Change the `server.aof_manifest` pointer to 'am' and free the previous * one if we have. */ void aofManifestFreeAndUpdate(aofManifest *am) { serverAssert(am != NULL); if (server.aof_manifest) aofManifestFree(server.aof_manifest); server.aof_manifest = am; } /* Called in `backgroundRewriteDoneHandler` to get a new BASE file * name, and mark the previous (if we have) BASE file as HISTORY type. * * BASE file naming rules: `server.aof_filename`.seq.base.format * * for example: * appendonly.aof.1.base.aof (server.aof_use_rdb_preamble is no) * appendonly.aof.1.base.rdb (server.aof_use_rdb_preamble is yes) */ sds getNewBaseFileNameAndMarkPreAsHistory(aofManifest *am) { serverAssert(am != NULL); if (am->base_aof_info) { serverAssert(am->base_aof_info->file_type == AOF_FILE_TYPE_BASE); am->base_aof_info->file_type = AOF_FILE_TYPE_HIST; listAddNodeHead(am->history_aof_list, am->base_aof_info); } char *format_suffix = server.aof_use_rdb_preamble ? RDB_FORMAT_SUFFIX:AOF_FORMAT_SUFFIX; aofInfo *ai = aofInfoCreate(); ai->file_name = sdscatprintf(sdsempty(), "%s.%lld%s%s", server.aof_filename, ++am->curr_base_file_seq, BASE_FILE_SUFFIX, format_suffix); ai->file_seq = am->curr_base_file_seq; ai->file_type = AOF_FILE_TYPE_BASE; am->base_aof_info = ai; am->dirty = 1; return am->base_aof_info->file_name; } /* Get a new INCR type AOF name. * * INCR AOF naming rules: `server.aof_filename`.seq.incr.aof * * for example: * appendonly.aof.1.incr.aof */ sds getNewIncrAofName(aofManifest *am, long long start_reploff) { aofInfo *ai = aofInfoCreate(); ai->file_type = AOF_FILE_TYPE_INCR; ai->file_name = sdscatprintf(sdsempty(), "%s.%lld%s%s", server.aof_filename, ++am->curr_incr_file_seq, INCR_FILE_SUFFIX, AOF_FORMAT_SUFFIX); ai->file_seq = am->curr_incr_file_seq; ai->start_offset = start_reploff; listAddNodeTail(am->incr_aof_list, ai); am->dirty = 1; return ai->file_name; } /* Get temp INCR type AOF name. */ sds getTempIncrAofName(void) { return sdscatprintf(sdsempty(), "%s%s%s", TEMP_FILE_NAME_PREFIX, server.aof_filename, INCR_FILE_SUFFIX); } /* Get the last INCR AOF name or create a new one. */ sds getLastIncrAofName(aofManifest *am) { serverAssert(am != NULL); /* If 'incr_aof_list' is empty, just create a new one. */ if (!listLength(am->incr_aof_list)) { return getNewIncrAofName(am, server.master_repl_offset); } /* Or return the last one. */ listNode *lastnode = listIndex(am->incr_aof_list, -1); aofInfo *ai = listNodeValue(lastnode); return ai->file_name; } /* Called in `backgroundRewriteDoneHandler`. when AOFRW success, This * function will change the AOF file type in 'incr_aof_list' from * AOF_FILE_TYPE_INCR to AOF_FILE_TYPE_HIST, and move them to the * 'history_aof_list'. */ void markRewrittenIncrAofAsHistory(aofManifest *am) { serverAssert(am != NULL); if (!listLength(am->incr_aof_list)) { return; } listNode *ln; listIter li; listRewindTail(am->incr_aof_list, &li); /* "server.aof_fd != -1" means AOF enabled, then we must skip the * last AOF, because this file is our currently writing. */ if (server.aof_fd != -1) { ln = listNext(&li); serverAssert(ln != NULL); } /* Move aofInfo from 'incr_aof_list' to 'history_aof_list'. */ while ((ln = listNext(&li)) != NULL) { aofInfo *ai = (aofInfo*)ln->value; serverAssert(ai->file_type == AOF_FILE_TYPE_INCR); aofInfo *hai = aofInfoDup(ai); hai->file_type = AOF_FILE_TYPE_HIST; listAddNodeHead(am->history_aof_list, hai); listDelNode(am->incr_aof_list, ln); } am->dirty = 1; } /* Write the formatted manifest string to disk. */ int writeAofManifestFile(sds buf) { int ret = C_OK; ssize_t nwritten; int len; sds am_name = getAofManifestFileName(); sds am_filepath = makePath(server.aof_dirname, am_name); sds tmp_am_name = getTempAofManifestFileName(); sds tmp_am_filepath = makePath(server.aof_dirname, tmp_am_name); int fd = open(tmp_am_filepath, O_WRONLY|O_TRUNC|O_CREAT, 0644); if (fd == -1) { serverLog(LL_WARNING, "Can't open the AOF manifest file %s: %s", tmp_am_name, strerror(errno)); ret = C_ERR; goto cleanup; } len = sdslen(buf); while(len) { nwritten = write(fd, buf, len); if (nwritten < 0) { if (errno == EINTR) continue; serverLog(LL_WARNING, "Error trying to write the temporary AOF manifest file %s: %s", tmp_am_name, strerror(errno)); ret = C_ERR; goto cleanup; } len -= nwritten; buf += nwritten; } if (redis_fsync(fd) == -1) { serverLog(LL_WARNING, "Fail to fsync the temp AOF file %s: %s.", tmp_am_name, strerror(errno)); ret = C_ERR; goto cleanup; } if (rename(tmp_am_filepath, am_filepath) != 0) { serverLog(LL_WARNING, "Error trying to rename the temporary AOF manifest file %s into %s: %s", tmp_am_name, am_name, strerror(errno)); ret = C_ERR; goto cleanup; } /* Also sync the AOF directory as new AOF files may be added in the directory */ if (fsyncFileDir(am_filepath) == -1) { serverLog(LL_WARNING, "Fail to fsync AOF directory %s: %s.", am_filepath, strerror(errno)); ret = C_ERR; goto cleanup; } cleanup: if (fd != -1) close(fd); sdsfree(am_name); sdsfree(am_filepath); sdsfree(tmp_am_name); sdsfree(tmp_am_filepath); return ret; } /* Persist the aofManifest information pointed to by am to disk. */ int persistAofManifest(aofManifest *am) { if (am->dirty == 0) { return C_OK; } sds amstr = getAofManifestAsString(am); int ret = writeAofManifestFile(amstr); sdsfree(amstr); if (ret == C_OK) am->dirty = 0; return ret; } /* Called in `loadAppendOnlyFiles` when we upgrade from a old version redis. * * 1) Create AOF directory use 'server.aof_dirname' as the name. * 2) Use 'server.aof_filename' to construct a BASE type aofInfo and add it to * aofManifest, then persist the manifest file to AOF directory. * 3) Move the old AOF file (server.aof_filename) to AOF directory. * * If any of the above steps fails or crash occurs, this will not cause any * problems, and redis will retry the upgrade process when it restarts. */ void aofUpgradePrepare(aofManifest *am) { serverAssert(!aofFileExist(server.aof_filename)); /* Create AOF directory use 'server.aof_dirname' as the name. */ if (dirCreateIfMissing(server.aof_dirname) == -1) { serverLog(LL_WARNING, "Can't open or create append-only dir %s: %s", server.aof_dirname, strerror(errno)); exit(1); } /* Manually construct a BASE type aofInfo and add it to aofManifest. */ if (am->base_aof_info) aofInfoFree(am->base_aof_info); aofInfo *ai = aofInfoCreate(); ai->file_name = sdsnew(server.aof_filename); ai->file_seq = 1; ai->file_type = AOF_FILE_TYPE_BASE; am->base_aof_info = ai; am->curr_base_file_seq = 1; am->dirty = 1; /* Persist the manifest file to AOF directory. */ if (persistAofManifest(am) != C_OK) { exit(1); } /* Move the old AOF file to AOF directory. */ sds aof_filepath = makePath(server.aof_dirname, server.aof_filename); if (rename(server.aof_filename, aof_filepath) == -1) { serverLog(LL_WARNING, "Error trying to move the old AOF file %s into dir %s: %s", server.aof_filename, server.aof_dirname, strerror(errno)); sdsfree(aof_filepath); exit(1); } sdsfree(aof_filepath); serverLog(LL_NOTICE, "Successfully migrated an old-style AOF file (%s) into the AOF directory (%s).", server.aof_filename, server.aof_dirname); } /* When AOFRW success, the previous BASE and INCR AOFs will * become HISTORY type and be moved into 'history_aof_list'. * * The function will traverse the 'history_aof_list' and submit * the delete task to the bio thread. */ int aofDelHistoryFiles(void) { if (server.aof_manifest == NULL || server.aof_disable_auto_gc == 1 || !listLength(server.aof_manifest->history_aof_list)) { return C_OK; } listNode *ln; listIter li; listRewind(server.aof_manifest->history_aof_list, &li); while ((ln = listNext(&li)) != NULL) { aofInfo *ai = (aofInfo*)ln->value; serverAssert(ai->file_type == AOF_FILE_TYPE_HIST); serverLog(LL_NOTICE, "Removing the history file %s in the background", ai->file_name); sds aof_filepath = makePath(server.aof_dirname, ai->file_name); bg_unlink(aof_filepath); sdsfree(aof_filepath); listDelNode(server.aof_manifest->history_aof_list, ln); } server.aof_manifest->dirty = 1; return persistAofManifest(server.aof_manifest); } /* Used to clean up temp INCR AOF when AOFRW fails. */ void aofDelTempIncrAofFile(void) { sds aof_filename = getTempIncrAofName(); sds aof_filepath = makePath(server.aof_dirname, aof_filename); serverLog(LL_NOTICE, "Removing the temp incr aof file %s in the background", aof_filename); bg_unlink(aof_filepath); sdsfree(aof_filepath); sdsfree(aof_filename); return; } /* Called after `loadDataFromDisk` when redis start. If `server.aof_state` is * 'AOF_ON', It will do three things: * 1. Force create a BASE file when redis starts with an empty dataset * 2. Open the last opened INCR type AOF for writing, If not, create a new one * 3. Synchronously update the manifest file to the disk * * If any of the above steps fails, the redis process will exit. */ void aofOpenIfNeededOnServerStart(void) { if (server.aof_state != AOF_ON) { return; } serverAssert(server.aof_manifest != NULL); serverAssert(server.aof_fd == -1); if (dirCreateIfMissing(server.aof_dirname) == -1) { serverLog(LL_WARNING, "Can't open or create append-only dir %s: %s", server.aof_dirname, strerror(errno)); exit(1); } /* If we start with an empty dataset, we will force create a BASE file. */ size_t incr_aof_len = listLength(server.aof_manifest->incr_aof_list); if (!server.aof_manifest->base_aof_info && !incr_aof_len) { sds base_name = getNewBaseFileNameAndMarkPreAsHistory(server.aof_manifest); sds base_filepath = makePath(server.aof_dirname, base_name); if (rewriteAppendOnlyFile(base_filepath) != C_OK) { exit(1); } sdsfree(base_filepath); serverLog(LL_NOTICE, "Creating AOF base file %s on server start", base_name); } /* Because we will 'exit(1)' if open AOF or persistent manifest fails, so * we don't need atomic modification here. */ sds aof_name = getLastIncrAofName(server.aof_manifest); /* Here we should use 'O_APPEND' flag. */ sds aof_filepath = makePath(server.aof_dirname, aof_name); server.aof_fd = open(aof_filepath, O_WRONLY|O_APPEND|O_CREAT, 0644); sdsfree(aof_filepath); if (server.aof_fd == -1) { serverLog(LL_WARNING, "Can't open the append-only file %s: %s", aof_name, strerror(errno)); exit(1); } /* Persist our changes. */ int ret = persistAofManifest(server.aof_manifest); if (ret != C_OK) { exit(1); } server.aof_last_incr_size = getAppendOnlyFileSize(aof_name, NULL); server.aof_last_incr_fsync_offset = server.aof_last_incr_size; if (incr_aof_len) { serverLog(LL_NOTICE, "Opening AOF incr file %s on server start", aof_name); } else { serverLog(LL_NOTICE, "Creating AOF incr file %s on server start", aof_name); } } int aofFileExist(char *filename) { sds file_path = makePath(server.aof_dirname, filename); int ret = fileExist(file_path); sdsfree(file_path); return ret; } /* Called in `rewriteAppendOnlyFileBackground`. If `server.aof_state` * is 'AOF_ON', It will do two things: * 1. Open a new INCR type AOF for writing * 2. Synchronously update the manifest file to the disk * * The above two steps of modification are atomic, that is, if * any step fails, the entire operation will rollback and returns * C_ERR, and if all succeeds, it returns C_OK. * * If `server.aof_state` is 'AOF_WAIT_REWRITE', It will open a temporary INCR AOF * file to accumulate data during AOF_WAIT_REWRITE, and it will eventually be * renamed in the `backgroundRewriteDoneHandler` and written to the manifest file. * */ int openNewIncrAofForAppend(void) { serverAssert(server.aof_manifest != NULL); int newfd = -1; aofManifest *temp_am = NULL; sds new_aof_name = NULL; /* Only open new INCR AOF when AOF enabled. */ if (server.aof_state == AOF_OFF) return C_OK; /* Open new AOF. */ if (server.aof_state == AOF_WAIT_REWRITE) { /* Use a temporary INCR AOF file to accumulate data during AOF_WAIT_REWRITE. */ new_aof_name = getTempIncrAofName(); tempIncAofStartReplOffset = server.master_repl_offset; } else { /* Dup a temp aof_manifest to modify. */ temp_am = aofManifestDup(server.aof_manifest); new_aof_name = sdsdup(getNewIncrAofName(temp_am, server.master_repl_offset)); } sds new_aof_filepath = makePath(server.aof_dirname, new_aof_name); newfd = open(new_aof_filepath, O_WRONLY|O_TRUNC|O_CREAT, 0644); sdsfree(new_aof_filepath); if (newfd == -1) { serverLog(LL_WARNING, "Can't open the append-only file %s: %s", new_aof_name, strerror(errno)); goto cleanup; } if (temp_am) { /* Persist AOF Manifest. */ if (persistAofManifest(temp_am) == C_ERR) { goto cleanup; } } serverLog(LL_NOTICE, "Creating AOF incr file %s on background rewrite", new_aof_name); sdsfree(new_aof_name); /* If reaches here, we can safely modify the `server.aof_manifest` * and `server.aof_fd`. */ /* fsync and close old aof_fd if needed. In fsync everysec it's ok to delay * the fsync as long as we grantee it happens, and in fsync always the file * is already synced at this point so fsync doesn't matter. */ if (server.aof_fd != -1) { aof_background_fsync_and_close(server.aof_fd); server.aof_last_fsync = server.mstime; } server.aof_fd = newfd; /* Reset the aof_last_incr_size. */ server.aof_last_incr_size = 0; /* Reset the aof_last_incr_fsync_offset. */ server.aof_last_incr_fsync_offset = 0; /* Update `server.aof_manifest`. */ if (temp_am) aofManifestFreeAndUpdate(temp_am); return C_OK; cleanup: if (new_aof_name) sdsfree(new_aof_name); if (newfd != -1) close(newfd); if (temp_am) aofManifestFree(temp_am); return C_ERR; } /* When we close gracefully the AOF file, we have the chance to persist the * end replication offset of current INCR AOF. */ void updateCurIncrAofEndOffset(void) { if (server.aof_state != AOF_ON) return; serverAssert(server.aof_manifest != NULL); if (listLength(server.aof_manifest->incr_aof_list) == 0) return; aofInfo *ai = listNodeValue(listLast(server.aof_manifest->incr_aof_list)); ai->end_offset = server.master_repl_offset; server.aof_manifest->dirty = 1; /* It doesn't matter if the persistence fails since this information is not * critical, we can get an approximate value by start offset plus file size. */ persistAofManifest(server.aof_manifest); } /* After loading AOF data, we need to update the `server.master_repl_offset` * based on the information of the last INCR AOF, to avoid the rollback of * the start offset of new INCR AOF. */ void updateReplOffsetAndResetEndOffset(void) { if (server.aof_state != AOF_ON) return; serverAssert(server.aof_manifest != NULL); /* If the INCR file has an end offset, we directly use it, and clear it * to avoid the next time we load the manifest file, we will use the same * offset, but the real offset may have advanced. */ if (listLength(server.aof_manifest->incr_aof_list) == 0) return; aofInfo *ai = listNodeValue(listLast(server.aof_manifest->incr_aof_list)); if (ai->end_offset != -1) { server.master_repl_offset = ai->end_offset; ai->end_offset = -1; server.aof_manifest->dirty = 1; /* We must update the end offset of INCR file correctly, otherwise we * may keep wrong information in the manifest file, since we continue * to append data to the same INCR file. */ if (persistAofManifest(server.aof_manifest) != AOF_OK) exit(1); } else { /* If the INCR file doesn't have an end offset, we need to calculate * the replication offset by the start offset plus the file size. */ server.master_repl_offset = (ai->start_offset == -1 ? 0 : ai->start_offset) + getAppendOnlyFileSize(ai->file_name, NULL); } } /* Whether to limit the execution of Background AOF rewrite. * * At present, if AOFRW fails, redis will automatically retry. If it continues * to fail, we may get a lot of very small INCR files. so we need an AOFRW * limiting measure. * * We can't directly use `server.aof_current_size` and `server.aof_last_incr_size`, * because there may be no new writes after AOFRW fails. * * So, we use time delay to achieve our goal. When AOFRW fails, we delay the execution * of the next AOFRW by 1 minute. If the next AOFRW also fails, it will be delayed by 2 * minutes. The next is 4, 8, 16, the maximum delay is 60 minutes (1 hour). * * During the limit period, we can still use the 'bgrewriteaof' command to execute AOFRW * immediately. * * Return 1 means that AOFRW is limited and cannot be executed. 0 means that we can execute * AOFRW, which may be that we have reached the 'next_rewrite_time' or the number of INCR * AOFs has not reached the limit threshold. * */ #define AOF_REWRITE_LIMITE_THRESHOLD 3 #define AOF_REWRITE_LIMITE_MAX_MINUTES 60 /* 1 hour */ int aofRewriteLimited(void) { static int next_delay_minutes = 0; static time_t next_rewrite_time = 0; if (server.stat_aofrw_consecutive_failures < AOF_REWRITE_LIMITE_THRESHOLD) { /* We may be recovering from limited state, so reset all states. */ next_delay_minutes = 0; next_rewrite_time = 0; return 0; } /* if it is in the limiting state, then check if the next_rewrite_time is reached */ if (next_rewrite_time != 0) { if (server.unixtime < next_rewrite_time) { return 1; } else { next_rewrite_time = 0; return 0; } } next_delay_minutes = (next_delay_minutes == 0) ? 1 : (next_delay_minutes * 2); if (next_delay_minutes > AOF_REWRITE_LIMITE_MAX_MINUTES) { next_delay_minutes = AOF_REWRITE_LIMITE_MAX_MINUTES; } next_rewrite_time = server.unixtime + next_delay_minutes * 60; serverLog(LL_WARNING, "Background AOF rewrite has repeatedly failed and triggered the limit, will retry in %d minutes", next_delay_minutes); return 1; } /* ---------------------------------------------------------------------------- * AOF file implementation * ------------------------------------------------------------------------- */ /* Return true if an AOf fsync is currently already in progress in a * BIO thread. */ int aofFsyncInProgress(void) { /* Note that we don't care about aof_background_fsync_and_close because * server.aof_fd has been replaced by the new INCR AOF file fd, * see openNewIncrAofForAppend. */ return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0; } /* Starts a background task that performs fsync() against the specified * file descriptor (the one of the AOF file) in another thread. */ void aof_background_fsync(int fd) { bioCreateFsyncJob(fd, server.master_repl_offset, 1); } /* Close the fd on the basis of aof_background_fsync. */ void aof_background_fsync_and_close(int fd) { bioCreateCloseAofJob(fd, server.master_repl_offset, 1); } /* Kills an AOFRW child process if exists */ void killAppendOnlyChild(void) { int statloc; /* No AOFRW child? return. */ if (server.child_type != CHILD_TYPE_AOF) return; /* Kill AOFRW child, wait for child exit. */ serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld", (long) server.child_pid); if (kill(server.child_pid,SIGUSR1) != -1) { while(waitpid(-1, &statloc, 0) != server.child_pid); } aofRemoveTempFile(server.child_pid); resetChildState(); server.aof_rewrite_time_start = -1; } /* Called when the user switches from "appendonly yes" to "appendonly no" * at runtime using the CONFIG command. */ void stopAppendOnly(void) { serverAssert(server.aof_state != AOF_OFF); flushAppendOnlyFile(1); if (redis_fsync(server.aof_fd) == -1) { serverLog(LL_WARNING,"Fail to fsync the AOF file: %s",strerror(errno)); } else { server.aof_last_fsync = server.mstime; } close(server.aof_fd); updateCurIncrAofEndOffset(); server.aof_fd = -1; server.aof_selected_db = -1; server.aof_state = AOF_OFF; server.aof_rewrite_scheduled = 0; server.aof_last_incr_size = 0; server.aof_last_incr_fsync_offset = 0; server.fsynced_reploff = -1; atomicSet(server.fsynced_reploff_pending, 0); killAppendOnlyChild(); sdsfree(server.aof_buf); server.aof_buf = sdsempty(); } /* Called when the user switches from "appendonly no" to "appendonly yes" * at runtime using the CONFIG command. */ int startAppendOnly(void) { serverAssert(server.aof_state == AOF_OFF); server.aof_state = AOF_WAIT_REWRITE; if (hasActiveChildProcess() && server.child_type != CHILD_TYPE_AOF) { server.aof_rewrite_scheduled = 1; serverLog(LL_NOTICE,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible."); } else if (server.in_exec){ server.aof_rewrite_scheduled = 1; serverLog(LL_NOTICE,"AOF was enabled during a transaction. An AOF background was scheduled to start when possible."); } else { /* If there is a pending AOF rewrite, we need to switch it off and * start a new one: the old one cannot be reused because it is not * accumulating the AOF buffer. */ if (server.child_type == CHILD_TYPE_AOF) { serverLog(LL_NOTICE,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now."); killAppendOnlyChild(); } if (rewriteAppendOnlyFileBackground() == C_ERR) { server.aof_state = AOF_OFF; serverLog(LL_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error."); return C_ERR; } } server.aof_last_fsync = server.mstime; /* If AOF fsync error in bio job, we just ignore it and log the event. */ int aof_bio_fsync_status; atomicGet(server.aof_bio_fsync_status, aof_bio_fsync_status); if (aof_bio_fsync_status == C_ERR) { serverLog(LL_WARNING, "AOF reopen, just ignore the AOF fsync error in bio job"); atomicSet(server.aof_bio_fsync_status,C_OK); } /* If AOF was in error state, we just ignore it and log the event. */ if (server.aof_last_write_status == C_ERR) { serverLog(LL_WARNING,"AOF reopen, just ignore the last error."); server.aof_last_write_status = C_OK; } return C_OK; } void startAppendOnlyWithRetry(void) { unsigned int tries, max_tries = 10; for (tries = 0; tries < max_tries; ++tries) { if (startAppendOnly() == C_OK) break; serverLog(LL_WARNING, "Failed to enable AOF! Trying it again in one second."); sleep(1); } if (tries == max_tries) { serverLog(LL_WARNING, "FATAL: AOF can't be turned on. Exiting now."); exit(1); } } /* Called after "appendonly" config is changed. */ void applyAppendOnlyConfig(void) { if (!server.aof_enabled && server.aof_state != AOF_OFF) { stopAppendOnly(); } else if (server.aof_enabled && server.aof_state == AOF_OFF) { startAppendOnlyWithRetry(); } } /* This is a wrapper to the write syscall in order to retry on short writes * or if the syscall gets interrupted. It could look strange that we retry * on short writes given that we are writing to a block device: normally if * the first call is short, there is a end-of-space condition, so the next * is likely to fail. However apparently in modern systems this is no longer * true, and in general it looks just more resilient to retry the write. If * there is an actual error condition we'll get it at the next try. */ ssize_t aofWrite(int fd, const char *buf, size_t len) { ssize_t nwritten = 0, totwritten = 0; while(len) { nwritten = write(fd, buf, len); if (nwritten < 0) { if (errno == EINTR) continue; return totwritten ? totwritten : -1; } len -= nwritten; buf += nwritten; totwritten += nwritten; } return totwritten; } /* Write the append only file buffer on disk. * * Since we are required to write the AOF before replying to the client, * and the only way the client socket can get a write is entering when * the event loop, we accumulate all the AOF writes in a memory * buffer and write it on disk using this function just before entering * the event loop again. * * About the 'force' argument: * * When the fsync policy is set to 'everysec' we may delay the flush if there * is still an fsync() going on in the background thread, since for instance * on Linux write(2) will be blocked by the background fsync anyway. * When this happens we remember that there is some aof buffer to be * flushed ASAP, and will try to do that in the serverCron() function. * * However if force is set to 1 we'll write regardless of the background * fsync. */ #define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */ void flushAppendOnlyFile(int force) { ssize_t nwritten; int sync_in_progress = 0; mstime_t latency; if (sdslen(server.aof_buf) == 0) { if (server.aof_last_incr_fsync_offset == server.aof_last_incr_size) { /* All data is fsync'd already: Update fsynced_reploff_pending just in case. * This is needed to avoid a WAITAOF hang in case a module used RM_Call * with the NO_AOF flag, in which case master_repl_offset will increase but * fsynced_reploff_pending won't be updated (because there's no reason, from * the AOF POV, to call fsync) and then WAITAOF may wait on the higher offset * (which contains data that was only propagated to replicas, and not to AOF) */ if (!aofFsyncInProgress()) atomicSet(server.fsynced_reploff_pending, server.master_repl_offset); } else { /* Check if we need to do fsync even the aof buffer is empty, * because previously in AOF_FSYNC_EVERYSEC mode, fsync is * called only when aof buffer is not empty, so if users * stop write commands before fsync called in one second, * the data in page cache cannot be flushed in time. */ if (server.aof_fsync == AOF_FSYNC_EVERYSEC && server.mstime - server.aof_last_fsync >= 1000 && !(sync_in_progress = aofFsyncInProgress())) goto try_fsync; /* Check if we need to do fsync even the aof buffer is empty, * the reason is described in the previous AOF_FSYNC_EVERYSEC block, * and AOF_FSYNC_ALWAYS is also checked here to handle a case where * aof_fsync is changed from everysec to always. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) goto try_fsync; } return; } if (server.aof_fsync == AOF_FSYNC_EVERYSEC) sync_in_progress = aofFsyncInProgress(); if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) { /* With this append fsync policy we do background fsyncing. * If the fsync is still in progress we can try to delay * the write for a couple of seconds. */ if (sync_in_progress) { if (server.aof_flush_postponed_start == 0) { /* No previous write postponing, remember that we are * postponing the flush and return. */ server.aof_flush_postponed_start = server.mstime; return; } else if (server.mstime - server.aof_flush_postponed_start < 2000) { /* We were already waiting for fsync to finish, but for less * than two seconds this is still ok. Postpone again. */ return; } /* Otherwise fall through, and go write since we can't wait * over two seconds. */ server.aof_delayed_fsync++; serverLog(LL_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis."); } } /* We want to perform a single write. This should be guaranteed atomic * at least if the filesystem we are writing is a real physical one. * While this will save us against the server being killed I don't think * there is much to do about the whole server stopping for power problems * or alike */ if (server.aof_flush_sleep && sdslen(server.aof_buf)) { usleep(server.aof_flush_sleep); } latencyStartMonitor(latency); nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); latencyEndMonitor(latency); /* We want to capture different events for delayed writes: * when the delay happens with a pending fsync, or with a saving child * active, and when the above two conditions are missing. * We also use an additional event name to save all samples which is * useful for graphing / monitoring purposes. */ if (sync_in_progress) { latencyAddSampleIfNeeded("aof-write-pending-fsync",latency); } else if (hasActiveChildProcess()) { latencyAddSampleIfNeeded("aof-write-active-child",latency); } else { latencyAddSampleIfNeeded("aof-write-alone",latency); } latencyAddSampleIfNeeded("aof-write",latency); /* We performed the write so reset the postponed flush sentinel to zero. */ server.aof_flush_postponed_start = 0; if (nwritten != (ssize_t)sdslen(server.aof_buf)) { static time_t last_write_error_log = 0; int can_log = 0; /* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */ if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) { can_log = 1; last_write_error_log = server.unixtime; } /* Log the AOF write error and record the error code. */ if (nwritten == -1) { if (can_log) { serverLog(LL_WARNING,"Error writing to the AOF file: %s", strerror(errno)); } server.aof_last_write_errno = errno; } else { if (can_log) { serverLog(LL_WARNING,"Short write while writing to " "the AOF file: (nwritten=%lld, " "expected=%lld)", (long long)nwritten, (long long)sdslen(server.aof_buf)); } if (ftruncate(server.aof_fd, server.aof_last_incr_size) == -1) { if (can_log) { serverLog(LL_WARNING, "Could not remove short write " "from the append-only file. Redis may refuse " "to load the AOF the next time it starts. " "ftruncate: %s", strerror(errno)); } } else { /* If the ftruncate() succeeded we can set nwritten to * -1 since there is no longer partial data into the AOF. */ nwritten = -1; } server.aof_last_write_errno = ENOSPC; } /* Handle the AOF write error. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) { /* We can't recover when the fsync policy is ALWAYS since the reply * for the client is already in the output buffers (both writes and * reads), and the changes to the db can't be rolled back. Since we * have a contract with the user that on acknowledged or observed * writes are is synced on disk, we must exit. */ serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting..."); exit(1); } else { /* Recover from failed write leaving data into the buffer. However * set an error to stop accepting writes as long as the error * condition is not cleared. */ server.aof_last_write_status = C_ERR; /* Trim the sds buffer if there was a partial write, and there * was no way to undo it with ftruncate(2). */ if (nwritten > 0) { server.aof_current_size += nwritten; server.aof_last_incr_size += nwritten; sdsrange(server.aof_buf,nwritten,-1); } return; /* We'll try again on the next call... */ } } else { /* Successful write(2). If AOF was in error state, restore the * OK state and log the event. */ if (server.aof_last_write_status == C_ERR) { serverLog(LL_NOTICE, "AOF write error looks solved, Redis can write again."); server.aof_last_write_status = C_OK; } } server.aof_current_size += nwritten; server.aof_last_incr_size += nwritten; /* Re-use AOF buffer when it is small enough. The maximum comes from the * arena size of 4k minus some overhead (but is otherwise arbitrary). */ if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) { sdsclear(server.aof_buf); } else { sdsfree(server.aof_buf); server.aof_buf = sdsempty(); } try_fsync: /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are * children doing I/O in the background. */ if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess()) return; /* Perform the fsync if needed. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) { /* redis_fsync is defined as fdatasync() for Linux in order to avoid * flushing metadata. */ latencyStartMonitor(latency); /* Let's try to get this data on the disk. To guarantee data safe when * the AOF fsync policy is 'always', we should exit if failed to fsync * AOF (see comment next to the exit(1) after write error above). */ if (redis_fsync(server.aof_fd) == -1) { serverLog(LL_WARNING,"Can't persist AOF for fsync error when the " "AOF fsync policy is 'always': %s. Exiting...", strerror(errno)); exit(1); } latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-fsync-always",latency); server.aof_last_incr_fsync_offset = server.aof_last_incr_size; server.aof_last_fsync = server.mstime; atomicSet(server.fsynced_reploff_pending, server.master_repl_offset); } else if (server.aof_fsync == AOF_FSYNC_EVERYSEC && server.mstime - server.aof_last_fsync >= 1000) { if (!sync_in_progress) { aof_background_fsync(server.aof_fd); server.aof_last_incr_fsync_offset = server.aof_last_incr_size; } server.aof_last_fsync = server.mstime; } } sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) { char buf[32]; int len, j; robj *o; buf[0] = '*'; len = 1+ll2string(buf+1,sizeof(buf)-1,argc); buf[len++] = '\r'; buf[len++] = '\n'; dst = sdscatlen(dst,buf,len); for (j = 0; j < argc; j++) { o = getDecodedObject(argv[j]); buf[0] = '$'; len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr)); buf[len++] = '\r'; buf[len++] = '\n'; dst = sdscatlen(dst,buf,len); dst = sdscatlen(dst,o->ptr,sdslen(o->ptr)); dst = sdscatlen(dst,"\r\n",2); decrRefCount(o); } return dst; } /* Generate a piece of timestamp annotation for AOF if current record timestamp * in AOF is not equal server unix time. If we specify 'force' argument to 1, * we would generate one without check, currently, it is useful in AOF rewriting * child process which always needs to record one timestamp at the beginning of * rewriting AOF. * * Timestamp annotation format is "#TS:${timestamp}\r\n". "TS" is short of * timestamp and this method could save extra bytes in AOF. */ sds genAofTimestampAnnotationIfNeeded(int force) { sds ts = NULL; if (force || server.aof_cur_timestamp < server.unixtime) { server.aof_cur_timestamp = force ? time(NULL) : server.unixtime; ts = sdscatfmt(sdsempty(), "#TS:%I\r\n", server.aof_cur_timestamp); serverAssert(sdslen(ts) <= AOF_ANNOTATION_LINE_MAX_LEN); } return ts; } /* Write the given command to the aof file. * dictid - dictionary id the command should be applied to, * this is used in order to decide if a `select` command * should also be written to the aof. Value of -1 means * to avoid writing `select` command in any case. * argv - The command to write to the aof. * argc - Number of values in argv */ void feedAppendOnlyFile(int dictid, robj **argv, int argc) { sds buf = sdsempty(); serverAssert(dictid == -1 || (dictid >= 0 && dictid < server.dbnum)); /* Feed timestamp if needed */ if (server.aof_timestamp_enabled) { sds ts = genAofTimestampAnnotationIfNeeded(0); if (ts != NULL) { buf = sdscatsds(buf, ts); sdsfree(ts); } } /* The DB this command was targeting is not the same as the last command * we appended. To issue a SELECT command is needed. */ if (dictid != -1 && dictid != server.aof_selected_db) { char seldb[64]; snprintf(seldb,sizeof(seldb),"%d",dictid); buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n", (unsigned long)strlen(seldb),seldb); server.aof_selected_db = dictid; } /* All commands should be propagated the same way in AOF as in replication. * No need for AOF-specific translation. */ buf = catAppendOnlyGenericCommand(buf,argc,argv); /* Append to the AOF buffer. This will be flushed on disk just before * of re-entering the event loop, so before the client will get a * positive reply about the operation performed. */ if (server.aof_state == AOF_ON || (server.aof_state == AOF_WAIT_REWRITE && server.child_type == CHILD_TYPE_AOF)) { server.aof_buf = sdscatlen(server.aof_buf, buf, sdslen(buf)); } sdsfree(buf); } /* ---------------------------------------------------------------------------- * AOF loading * ------------------------------------------------------------------------- */ /* In Redis commands are always executed in the context of a client, so in * order to load the append only file we need to create a fake client. */ struct client *createAOFClient(void) { struct client *c = createClient(NULL); c->id = CLIENT_ID_AOF; /* So modules can identify it's the AOF client. */ /* * The AOF client should never be blocked (unlike master * replication connection). * This is because blocking the AOF client might cause * deadlock (because potentially no one will unblock it). * Also, if the AOF client will be blocked just for * background processing there is a chance that the * command execution order will be violated. */ c->flags = CLIENT_DENY_BLOCKING; /* We set the fake client as a slave waiting for the synchronization * so that Redis will not try to send replies to this client. */ c->replstate = SLAVE_STATE_WAIT_BGSAVE_START; return c; } /* Replay an append log file. On success AOF_OK or AOF_TRUNCATED is returned, * otherwise, one of the following is returned: * AOF_OPEN_ERR: Failed to open the AOF file. * AOF_NOT_EXIST: AOF file doesn't exist. * AOF_EMPTY: The AOF file is empty (nothing to load). * AOF_FAILED: Failed to load the AOF file. */ int loadSingleAppendOnlyFile(char *filename) { struct client *fakeClient; struct redis_stat sb; int old_aof_state = server.aof_state; long loops = 0; off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */ off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */ off_t last_progress_report_size = 0; int ret = AOF_OK; sds aof_filepath = makePath(server.aof_dirname, filename); FILE *fp = fopen(aof_filepath, "r"); if (fp == NULL) { int en = errno; if (redis_stat(aof_filepath, &sb) == 0 || errno != ENOENT) { serverLog(LL_WARNING,"Fatal error: can't open the append log file %s for reading: %s", filename, strerror(en)); sdsfree(aof_filepath); return AOF_OPEN_ERR; } else { serverLog(LL_WARNING,"The append log file %s doesn't exist: %s", filename, strerror(errno)); sdsfree(aof_filepath); return AOF_NOT_EXIST; } } if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) { fclose(fp); sdsfree(aof_filepath); return AOF_EMPTY; } /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI * to the same file we're about to read. */ server.aof_state = AOF_OFF; client *old_cur_client = server.current_client; client *old_exec_client = server.executing_client; fakeClient = createAOFClient(); server.current_client = server.executing_client = fakeClient; /* Check if the AOF file is in RDB format (it may be RDB encoded base AOF * or old style RDB-preamble AOF). In that case we need to load the RDB file * and later continue loading the AOF tail if it is an old style RDB-preamble AOF. */ char sig[5]; /* "REDIS" */ if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) { /* Not in RDB format, seek back at 0 offset. */ if (fseek(fp,0,SEEK_SET) == -1) goto readerr; } else { /* RDB format. Pass loading the RDB functions. */ rio rdb; int old_style = !strcmp(filename, server.aof_filename); if (old_style) serverLog(LL_NOTICE, "Reading RDB preamble from AOF file..."); else serverLog(LL_NOTICE, "Reading RDB base file on AOF loading..."); if (fseek(fp,0,SEEK_SET) == -1) goto readerr; rioInitWithFile(&rdb,fp); if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) { if (old_style) serverLog(LL_WARNING, "Error reading the RDB preamble of the AOF file %s, AOF loading aborted", filename); else serverLog(LL_WARNING, "Error reading the RDB base file %s, AOF loading aborted", filename); ret = AOF_FAILED; goto cleanup; } else { loadingAbsProgress(ftello(fp)); last_progress_report_size = ftello(fp); if (old_style) serverLog(LL_NOTICE, "Reading the remaining AOF tail..."); } } /* Read the actual AOF file, in REPL format, command by command. */ while(1) { int argc, j; unsigned long len; robj **argv; char buf[AOF_ANNOTATION_LINE_MAX_LEN]; sds argsds; struct redisCommand *cmd; /* Serve the clients from time to time */ if (!(loops++ % 1024)) { off_t progress_delta = ftello(fp) - last_progress_report_size; loadingIncrProgress(progress_delta); last_progress_report_size += progress_delta; processEventsWhileBlocked(); processModuleLoadingProgressEvent(1); } if (fgets(buf,sizeof(buf),fp) == NULL) { if (feof(fp)) { break; } else { goto readerr; } } if (buf[0] == '#') continue; /* Skip annotations */ if (buf[0] != '*') goto fmterr; if (buf[1] == '\0') goto readerr; argc = atoi(buf+1); if (argc < 1) goto fmterr; if ((size_t)argc > SIZE_MAX / sizeof(robj*)) goto fmterr; /* Load the next command in the AOF as our fake client * argv. */ argv = zmalloc(sizeof(robj*)*argc); fakeClient->argc = argc; fakeClient->argv = argv; fakeClient->argv_len = argc; for (j = 0; j < argc; j++) { /* Parse the argument len. */ char *readres = fgets(buf,sizeof(buf),fp); if (readres == NULL || buf[0] != '$') { fakeClient->argc = j; /* Free up to j-1. */ freeClientArgv(fakeClient); if (readres == NULL) goto readerr; else goto fmterr; } len = strtol(buf+1,NULL,10); /* Read it into a string object. */ argsds = sdsnewlen(SDS_NOINIT,len); if (len && fread(argsds,len,1,fp) == 0) { sdsfree(argsds); fakeClient->argc = j; /* Free up to j-1. */ freeClientArgv(fakeClient); goto readerr; } argv[j] = createObject(OBJ_STRING,argsds); /* Discard CRLF. */ if (fread(buf,2,1,fp) == 0) { fakeClient->argc = j+1; /* Free up to j. */ freeClientArgv(fakeClient); goto readerr; } } /* Command lookup */ cmd = lookupCommand(argv,argc); if (!cmd) { serverLog(LL_WARNING, "Unknown command '%s' reading the append only file %s", (char*)argv[0]->ptr, filename); freeClientArgv(fakeClient); ret = AOF_FAILED; goto cleanup; } if (cmd->proc == multiCommand) valid_before_multi = valid_up_to; /* Run the command in the context of a fake client */ fakeClient->cmd = fakeClient->lastcmd = cmd; if (fakeClient->flags & CLIENT_MULTI && fakeClient->cmd->proc != execCommand) { /* Note: we don't have to attempt calling evalGetCommandFlags, * since this is AOF, the checks in processCommand are not made * anyway.*/ queueMultiCommand(fakeClient, cmd->flags); } else { cmd->proc(fakeClient); } /* The fake client should not have a reply */ serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0); /* The fake client should never get blocked */ serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0); /* Clean up. Command code may have changed argv/argc so we use the * argv/argc of the client instead of the local variables. */ freeClientArgv(fakeClient); if (server.aof_load_truncated) valid_up_to = ftello(fp); if (server.key_load_delay) debugDelay(server.key_load_delay); } /* This point can only be reached when EOF is reached without errors. * If the client is in the middle of a MULTI/EXEC, handle it as it was * a short read, even if technically the protocol is correct: we want * to remove the unprocessed tail and continue. */ if (fakeClient->flags & CLIENT_MULTI) { serverLog(LL_WARNING, "Revert incomplete MULTI/EXEC transaction in AOF file %s", filename); valid_up_to = valid_before_multi; goto uxeof; } loaded_ok: /* DB loaded, cleanup and return success (AOF_OK or AOF_TRUNCATED). */ loadingIncrProgress(ftello(fp) - last_progress_report_size); server.aof_state = old_aof_state; goto cleanup; readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */ if (!feof(fp)) { serverLog(LL_WARNING,"Unrecoverable error reading the append only file %s: %s", filename, strerror(errno)); ret = AOF_FAILED; goto cleanup; } uxeof: /* Unexpected AOF end of file. */ if (server.aof_load_truncated) { serverLog(LL_WARNING,"!!! Warning: short read while loading the AOF file %s!!!", filename); serverLog(LL_WARNING,"!!! Truncating the AOF %s at offset %llu !!!", filename, (unsigned long long) valid_up_to); if (valid_up_to == -1 || truncate(aof_filepath,valid_up_to) == -1) { if (valid_up_to == -1) { serverLog(LL_WARNING,"Last valid command offset is invalid"); } else { serverLog(LL_WARNING,"Error truncating the AOF file %s: %s", filename, strerror(errno)); } } else { /* Make sure the AOF file descriptor points to the end of the * file after the truncate call. */ if (server.aof_fd != -1 && lseek(server.aof_fd,0,SEEK_END) == -1) { serverLog(LL_WARNING,"Can't seek the end of the AOF file %s: %s", filename, strerror(errno)); } else { serverLog(LL_WARNING, "AOF %s loaded anyway because aof-load-truncated is enabled", filename); ret = AOF_TRUNCATED; goto loaded_ok; } } } serverLog(LL_WARNING, "Unexpected end of file reading the append only file %s. You can: " "1) Make a backup of your AOF file, then use ./redis-check-aof --fix . " "2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.", filename); ret = AOF_FAILED; goto cleanup; fmterr: /* Format error. */ serverLog(LL_WARNING, "Bad file format reading the append only file %s: " "make a backup of your AOF file, then use ./redis-check-aof --fix ", filename); ret = AOF_FAILED; /* fall through to cleanup. */ cleanup: if (fakeClient) freeClient(fakeClient); server.current_client = old_cur_client; server.executing_client = old_exec_client; fclose(fp); sdsfree(aof_filepath); return ret; } /* Load the AOF files according the aofManifest pointed by am. */ int loadAppendOnlyFiles(aofManifest *am) { serverAssert(am != NULL); int status, ret = AOF_OK; long long start; off_t total_size = 0, base_size = 0; sds aof_name; int total_num, aof_num = 0, last_file; /* If the 'server.aof_filename' file exists in dir, we may be starting * from an old redis version. We will use enter upgrade mode in three situations. * * 1. If the 'server.aof_dirname' directory not exist * 2. If the 'server.aof_dirname' directory exists but the manifest file is missing * 3. If the 'server.aof_dirname' directory exists and the manifest file it contains * has only one base AOF record, and the file name of this base AOF is 'server.aof_filename', * and the 'server.aof_filename' file not exist in 'server.aof_dirname' directory * */ if (fileExist(server.aof_filename)) { if (!dirExists(server.aof_dirname) || (am->base_aof_info == NULL && listLength(am->incr_aof_list) == 0) || (am->base_aof_info != NULL && listLength(am->incr_aof_list) == 0 && !strcmp(am->base_aof_info->file_name, server.aof_filename) && !aofFileExist(server.aof_filename))) { aofUpgradePrepare(am); } } if (am->base_aof_info == NULL && listLength(am->incr_aof_list) == 0) { return AOF_NOT_EXIST; } total_num = getBaseAndIncrAppendOnlyFilesNum(am); serverAssert(total_num > 0); /* Here we calculate the total size of all BASE and INCR files in * advance, it will be set to `server.loading_total_bytes`. */ total_size = getBaseAndIncrAppendOnlyFilesSize(am, &status); if (status != AOF_OK) { /* If an AOF exists in the manifest but not on the disk, we consider this to be a fatal error. */ if (status == AOF_NOT_EXIST) status = AOF_FAILED; return status; } else if (total_size == 0) { return AOF_EMPTY; } startLoading(total_size, RDBFLAGS_AOF_PREAMBLE, 0); /* Load BASE AOF if needed. */ if (am->base_aof_info) { serverAssert(am->base_aof_info->file_type == AOF_FILE_TYPE_BASE); aof_name = (char*)am->base_aof_info->file_name; updateLoadingFileName(aof_name); base_size = getAppendOnlyFileSize(aof_name, NULL); last_file = ++aof_num == total_num; start = ustime(); ret = loadSingleAppendOnlyFile(aof_name); if (ret == AOF_OK || (ret == AOF_TRUNCATED && last_file)) { serverLog(LL_NOTICE, "DB loaded from base file %s: %.3f seconds", aof_name, (float)(ustime()-start)/1000000); } /* If the truncated file is not the last file, we consider this to be a fatal error. */ if (ret == AOF_TRUNCATED && !last_file) { ret = AOF_FAILED; serverLog(LL_WARNING, "Fatal error: the truncated file is not the last file"); } if (ret == AOF_OPEN_ERR || ret == AOF_FAILED) { goto cleanup; } } /* Load INCR AOFs if needed. */ if (listLength(am->incr_aof_list)) { listNode *ln; listIter li; listRewind(am->incr_aof_list, &li); while ((ln = listNext(&li)) != NULL) { aofInfo *ai = (aofInfo*)ln->value; serverAssert(ai->file_type == AOF_FILE_TYPE_INCR); aof_name = (char*)ai->file_name; updateLoadingFileName(aof_name); last_file = ++aof_num == total_num; start = ustime(); ret = loadSingleAppendOnlyFile(aof_name); if (ret == AOF_OK || (ret == AOF_TRUNCATED && last_file)) { serverLog(LL_NOTICE, "DB loaded from incr file %s: %.3f seconds", aof_name, (float)(ustime()-start)/1000000); } /* We know that (at least) one of the AOF files has data (total_size > 0), * so empty incr AOF file doesn't count as a AOF_EMPTY result */ if (ret == AOF_EMPTY) ret = AOF_OK; /* If the truncated file is not the last file, we consider this to be a fatal error. */ if (ret == AOF_TRUNCATED && !last_file) { ret = AOF_FAILED; serverLog(LL_WARNING, "Fatal error: the truncated file is not the last file"); } if (ret == AOF_OPEN_ERR || ret == AOF_FAILED) { goto cleanup; } } } server.aof_current_size = total_size; /* Ideally, the aof_rewrite_base_size variable should hold the size of the * AOF when the last rewrite ended, this should include the size of the * incremental file that was created during the rewrite since otherwise we * risk the next automatic rewrite to happen too soon (or immediately if * auto-aof-rewrite-percentage is low). However, since we do not persist * aof_rewrite_base_size information anywhere, we initialize it on restart * to the size of BASE AOF file. This might cause the first AOFRW to be * executed early, but that shouldn't be a problem since everything will be * fine after the first AOFRW. */ server.aof_rewrite_base_size = base_size; cleanup: stopLoading(ret == AOF_OK || ret == AOF_TRUNCATED); return ret; } /* ---------------------------------------------------------------------------- * AOF rewrite * ------------------------------------------------------------------------- */ /* Delegate writing an object to writing a bulk string or bulk long long. * This is not placed in rio.c since that adds the server.h dependency. */ int rioWriteBulkObject(rio *r, robj *obj) { /* Avoid using getDecodedObject to help copy-on-write (we are often * in a child process when this function is called). */ if (obj->encoding == OBJ_ENCODING_INT) { return rioWriteBulkLongLong(r,(long)obj->ptr); } else if (sdsEncodedObject(obj)) { return rioWriteBulkString(r,obj->ptr,sdslen(obj->ptr)); } else { serverPanic("Unknown string encoding"); } } /* Emit the commands needed to rebuild a list object. * The function returns 0 on error, 1 on success. */ int rewriteListObject(rio *r, robj *key, robj *o) { long long count = 0, items = listTypeLength(o); listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL); listTypeEntry entry; while (listTypeNext(li,&entry)) { if (count == 0) { int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? AOF_REWRITE_ITEMS_PER_CMD : items; if (!rioWriteBulkCount(r,'*',2+cmd_items) || !rioWriteBulkString(r,"RPUSH",5) || !rioWriteBulkObject(r,key)) { listTypeReleaseIterator(li); return 0; } } unsigned char *vstr; size_t vlen; long long lval; vstr = listTypeGetValue(&entry,&vlen,&lval); if (vstr) { if (!rioWriteBulkString(r,(char*)vstr,vlen)) { listTypeReleaseIterator(li); return 0; } } else { if (!rioWriteBulkLongLong(r,lval)) { listTypeReleaseIterator(li); return 0; } } if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; items--; } listTypeReleaseIterator(li); return 1; } /* Emit the commands needed to rebuild a set object. * 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); char *str; size_t len; int64_t llval; 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; if (!rioWriteBulkCount(r,'*',2+cmd_items) || !rioWriteBulkString(r,"SADD",4) || !rioWriteBulkObject(r,key)) { setTypeReleaseIterator(si); return 0; } } size_t written = str ? rioWriteBulkString(r, str, len) : rioWriteBulkLongLong(r, llval); if (!written) { setTypeReleaseIterator(si); return 0; } if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; items--; } setTypeReleaseIterator(si); return 1; } /* Emit the commands needed to rebuild a sorted set object. * The function returns 0 on error, 1 on success. */ int rewriteSortedSetObject(rio *r, robj *key, robj *o) { long long count = 0, items = zsetLength(o); if (o->encoding == OBJ_ENCODING_LISTPACK) { unsigned char *zl = o->ptr; unsigned char *eptr, *sptr; unsigned char *vstr; unsigned int vlen; long long vll; double score; eptr = lpSeek(zl,0); serverAssert(eptr != NULL); sptr = lpNext(zl,eptr); serverAssert(sptr != NULL); while (eptr != NULL) { vstr = lpGetValue(eptr,&vlen,&vll); score = zzlGetScore(sptr); if (count == 0) { int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? AOF_REWRITE_ITEMS_PER_CMD : items; if (!rioWriteBulkCount(r,'*',2+cmd_items*2) || !rioWriteBulkString(r,"ZADD",4) || !rioWriteBulkObject(r,key)) { return 0; } } if (!rioWriteBulkDouble(r,score)) return 0; if (vstr != NULL) { if (!rioWriteBulkString(r,(char*)vstr,vlen)) return 0; } else { if (!rioWriteBulkLongLong(r,vll)) return 0; } zzlNext(zl,&eptr,&sptr); if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; items--; } } else if (o->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = o->ptr; dictIterator *di = dictGetIterator(zs->dict); dictEntry *de; while((de = dictNext(di)) != NULL) { sds ele = dictGetKey(de); double *score = dictGetVal(de); if (count == 0) { int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? AOF_REWRITE_ITEMS_PER_CMD : items; if (!rioWriteBulkCount(r,'*',2+cmd_items*2) || !rioWriteBulkString(r,"ZADD",4) || !rioWriteBulkObject(r,key)) { dictReleaseIterator(di); return 0; } } if (!rioWriteBulkDouble(r,*score) || !rioWriteBulkString(r,ele,sdslen(ele))) { dictReleaseIterator(di); return 0; } if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; items--; } dictReleaseIterator(di); } else { serverPanic("Unknown sorted zset encoding"); } return 1; } /* Write either the key or the value of the currently selected item of a hash. * The 'hi' argument passes a valid Redis hash iterator. * The 'what' filed specifies if to write a key or a value and can be * either OBJ_HASH_KEY or OBJ_HASH_VALUE. * * The function returns 0 on error, non-zero on success. */ static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) { if ((hi->encoding == OBJ_ENCODING_LISTPACK) || (hi->encoding == OBJ_ENCODING_LISTPACK_EX)) { unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; hashTypeCurrentFromListpack(hi, what, &vstr, &vlen, &vll, NULL); if (vstr) return rioWriteBulkString(r, (char*)vstr, vlen); else return rioWriteBulkLongLong(r, vll); } else if (hi->encoding == OBJ_ENCODING_HT) { char *str; size_t len; hashTypeCurrentFromHashTable(hi, what, &str, &len, NULL); return rioWriteBulkString(r, str, len); } serverPanic("Unknown hash encoding"); return 0; } /* Emit the commands needed to rebuild a hash object. * The function returns 0 on error, 1 on success. */ int rewriteHashObject(rio *r, robj *key, robj *o) { int res = 0; /*fail*/ hashTypeIterator *hi; long long count = 0, items = hashTypeLength(o, 0); int isHFE = hashTypeGetMinExpire(o, 0) != EB_EXPIRE_TIME_INVALID; hi = hashTypeInitIterator(o); if (!isHFE) { while (hashTypeNext(hi, 0) != C_ERR) { if (count == 0) { int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? AOF_REWRITE_ITEMS_PER_CMD : items; if (!rioWriteBulkCount(r, '*', 2 + cmd_items * 2) || !rioWriteBulkString(r, "HMSET", 5) || !rioWriteBulkObject(r, key)) goto reHashEnd; } 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) { 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)) ) goto reHashEnd; 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)) || (!rioWriteBulkString(r, "FIELDS", 6)) || (!rioWriteBulkString(r, "1", 1)) || (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY)) ) goto reHashEnd; } } } res = 1; /* success */ reHashEnd: hashTypeReleaseIterator(hi); return res; } /* Helper for rewriteStreamObject() that generates a bulk string into the * AOF representing the ID 'id'. */ int rioWriteBulkStreamID(rio *r,streamID *id) { int retval; sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq); retval = rioWriteBulkString(r,replyid,sdslen(replyid)); sdsfree(replyid); return retval; } /* Helper for rewriteStreamObject(): emit the XCLAIM needed in order to * add the message described by 'nack' having the id 'rawid', into the pending * list of the specified consumer. All this in the context of the specified * key and group. */ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t groupname_len, streamConsumer *consumer, unsigned char *rawid, streamNACK *nack) { /* XCLAIM 0 TIME RETRYCOUNT JUSTID FORCE. */ streamID id; streamDecodeID(rawid,&id); if (rioWriteBulkCount(r,'*',12) == 0) return 0; if (rioWriteBulkString(r,"XCLAIM",6) == 0) return 0; if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0; if (rioWriteBulkString(r,consumer->name,sdslen(consumer->name)) == 0) return 0; if (rioWriteBulkString(r,"0",1) == 0) return 0; if (rioWriteBulkStreamID(r,&id) == 0) return 0; if (rioWriteBulkString(r,"TIME",4) == 0) return 0; if (rioWriteBulkLongLong(r,nack->delivery_time) == 0) return 0; if (rioWriteBulkString(r,"RETRYCOUNT",10) == 0) return 0; if (rioWriteBulkLongLong(r,nack->delivery_count) == 0) return 0; if (rioWriteBulkString(r,"JUSTID",6) == 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. */ int rioWriteStreamEmptyConsumer(rio *r, robj *key, const char *groupname, size_t groupname_len, streamConsumer *consumer) { /* XGROUP CREATECONSUMER */ if (rioWriteBulkCount(r,'*',5) == 0) return 0; if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0; if (rioWriteBulkString(r,"CREATECONSUMER",14) == 0) return 0; if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0; if (rioWriteBulkString(r,consumer->name,sdslen(consumer->name)) == 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. */ 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. */ /* Emit the XADD ...fields... command. */ if (!rioWriteBulkCount(r,'*',3+numfields*2) || !rioWriteBulkString(r,"XADD",4) || !rioWriteBulkObject(r,key) || !rioWriteBulkStreamID(r,&id)) { streamIteratorStop(&si); return 0; } while(numfields--) { unsigned char *field, *value; int64_t field_len, value_len; streamIteratorGetField(&si,&field,&value,&field_len,&value_len); if (!rioWriteBulkString(r,(char*)field,field_len) || !rioWriteBulkString(r,(char*)value,value_len)) { streamIteratorStop(&si); return 0; } } } } 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 * for the Stream type. */ id.ms = 0; id.seq = 1; if (!rioWriteBulkCount(r,'*',7) || !rioWriteBulkString(r,"XADD",4) || !rioWriteBulkObject(r,key) || !rioWriteBulkString(r,"MAXLEN",6) || !rioWriteBulkString(r,"0",1) || !rioWriteBulkStreamID(r,&id) || !rioWriteBulkString(r,"x",1) || !rioWriteBulkString(r,"y",1)) { streamIteratorStop(&si); return 0; } } /* Append XSETID after XADD, make sure lastid is correct, * in case of XDEL lastid. */ if (!rioWriteBulkCount(r,'*',7) || !rioWriteBulkString(r,"XSETID",6) || !rioWriteBulkObject(r,key) || !rioWriteBulkStreamID(r,&s->last_id) || !rioWriteBulkString(r,"ENTRIESADDED",12) || !rioWriteBulkLongLong(r,s->entries_added) || !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; raxStart(&ri,s->cgroups); raxSeek(&ri,"^",NULL,0); while(raxNext(&ri)) { streamCG *group = ri.data; /* Emit the XGROUP CREATE in order to create the group. */ if (!rioWriteBulkCount(r,'*',7) || !rioWriteBulkString(r,"XGROUP",6) || !rioWriteBulkString(r,"CREATE",6) || !rioWriteBulkObject(r,key) || !rioWriteBulkString(r,(char*)ri.key,ri.key_len) || !rioWriteBulkStreamID(r,&group->last_id) || !rioWriteBulkString(r,"ENTRIESREAD",11) || !rioWriteBulkLongLong(r,group->entries_read)) { raxStop(&ri); streamIteratorStop(&si); return 0; } /* Generate XCLAIMs for each consumer that happens to * have pending entries. Empty consumers would be generated with * XGROUP CREATECONSUMER. */ raxIterator ri_cons; raxStart(&ri_cons,group->consumers); raxSeek(&ri_cons,"^",NULL,0); while(raxNext(&ri_cons)) { streamConsumer *consumer = ri_cons.data; /* If there are no pending entries, just emit XGROUP CREATECONSUMER */ if (raxSize(consumer->pel) == 0) { if (rioWriteStreamEmptyConsumer(r,key,(char*)ri.key, ri.key_len,consumer) == 0) { raxStop(&ri_cons); raxStop(&ri); streamIteratorStop(&si); return 0; } continue; } /* For the current consumer, iterate all the PEL entries * to emit the XCLAIM protocol. */ raxIterator ri_pel; raxStart(&ri_pel,consumer->pel); raxSeek(&ri_pel,"^",NULL,0); while(raxNext(&ri_pel)) { streamNACK *nack = ri_pel.data; if (rioWriteStreamPendingEntry(r,key,(char*)ri.key, ri.key_len,consumer, ri_pel.key,nack) == 0) { raxStop(&ri_pel); raxStop(&ri_cons); raxStop(&ri); streamIteratorStop(&si); return 0; } } raxStop(&ri_pel); } raxStop(&ri_cons); } raxStop(&ri); } streamIteratorStop(&si); return 1; } /* 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. */ 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); mt->aof_rewrite(&io,key,mv->value); if (io.ctx) { moduleFreeContext(io.ctx); zfree(io.ctx); } return io.error ? 0 : 1; } static int rewriteFunctions(rio *aof) { dict *functions = functionsLibGet(); dictIterator *iter = dictGetIterator(functions); dictEntry *entry = NULL; while ((entry = dictNext(iter))) { functionLibInfo *li = dictGetVal(entry); if (rioWrite(aof, "*3\r\n", 4) == 0) goto werr; char function_load[] = "$8\r\nFUNCTION\r\n$4\r\nLOAD\r\n"; if (rioWrite(aof, function_load, sizeof(function_load) - 1) == 0) goto werr; if (rioWriteBulkString(aof, li->code, sdslen(li->code)) == 0) goto werr; } dictReleaseIterator(iter); return 1; werr: dictReleaseIterator(iter); return 0; } int rewriteAppendOnlyFileRio(rio *aof) { dictEntry *de; int j; long key_count = 0; long long updated_time = 0; kvstoreIterator *kvs_it = NULL; /* Record timestamp at the beginning of rewriting AOF. */ if (server.aof_timestamp_enabled) { sds ts = genAofTimestampAnnotationIfNeeded(1); if (rioWrite(aof,ts,sdslen(ts)) == 0) { sdsfree(ts); goto werr; } sdsfree(ts); } if (rewriteFunctions(aof) == 0) goto werr; for (j = 0; j < server.dbnum; j++) { char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n"; redisDb *db = server.db + j; if (kvstoreSize(db->keys) == 0) continue; /* SELECT the new DB */ if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr; if (rioWriteBulkLongLong(aof,j) == 0) goto werr; kvs_it = kvstoreIteratorInit(db->keys); /* Iterate this DB writing every entry */ while((de = kvstoreIteratorNext(kvs_it)) != NULL) { sds keystr; robj key, *o; long long expiretime; size_t aof_bytes_before_key = aof->processed_bytes; keystr = dictGetKey(de); o = dictGetVal(de); initStaticStringObject(key,keystr); expiretime = getExpire(db,&key); /* Save the key and associated value */ if (o->type == OBJ_STRING) { /* Emit a SET command */ char cmd[]="*3\r\n$3\r\nSET\r\n"; if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr; /* Key and value */ if (rioWriteBulkObject(aof,&key) == 0) goto werr; if (rioWriteBulkObject(aof,o) == 0) goto werr; } else if (o->type == OBJ_LIST) { if (rewriteListObject(aof,&key,o) == 0) goto werr; } else if (o->type == OBJ_SET) { if (rewriteSetObject(aof,&key,o) == 0) goto werr; } else if (o->type == OBJ_ZSET) { if (rewriteSortedSetObject(aof,&key,o) == 0) goto werr; } else if (o->type == OBJ_HASH) { if (rewriteHashObject(aof,&key,o) == 0) goto werr; } else if (o->type == OBJ_STREAM) { if (rewriteStreamObject(aof,&key,o) == 0) goto werr; } else if (o->type == OBJ_MODULE) { if (rewriteModuleObject(aof,&key,o,j) == 0) goto werr; } else { serverPanic("Unknown object type"); } /* 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); /* Save the expire time */ if (expiretime != -1) { char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n"; if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr; if (rioWriteBulkObject(aof,&key) == 0) goto werr; if (rioWriteBulkLongLong(aof,expiretime) == 0) goto werr; } /* Update info every 1 second (approximately). * in order to avoid calling mstime() on each iteration, we will * check the diff every 1024 keys */ if ((key_count++ & 1023) == 0) { long long now = mstime(); if (now - updated_time >= 1000) { sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, "AOF rewrite"); updated_time = now; } } /* Delay before next key if required (for testing) */ if (server.rdb_key_save_delay) debugDelay(server.rdb_key_save_delay); } kvstoreIteratorRelease(kvs_it); } return C_OK; werr: if (kvs_it) kvstoreIteratorRelease(kvs_it); return C_ERR; } /* Write a sequence of commands able to fully rebuild the dataset into * "filename". Used both by REWRITEAOF and BGREWRITEAOF. * * In order to minimize the number of commands needed in the rewritten * log Redis uses variadic commands when possible, such as RPUSH, SADD * and ZADD. However at max AOF_REWRITE_ITEMS_PER_CMD items per time * are inserted using a single command. */ int rewriteAppendOnlyFile(char *filename) { rio aof; FILE *fp = NULL; char tmpfile[256]; /* Note that we have to use a different temp name here compared to the * one used by rewriteAppendOnlyFileBackground() function. */ snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); fp = fopen(tmpfile,"w"); if (!fp) { serverLog(LL_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno)); return C_ERR; } rioInitWithFile(&aof,fp); if (server.aof_rewrite_incremental_fsync) { rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES); rioSetReclaimCache(&aof,1); } startSaving(RDBFLAGS_AOF_PREAMBLE); if (server.aof_use_rdb_preamble) { int error; if (rdbSaveRio(SLAVE_REQ_NONE,&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) { errno = error; goto werr; } } else { if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr; } /* Make sure data will not remain on the OS's output buffers */ if (fflush(fp)) goto werr; if (fsync(fileno(fp))) goto werr; if (reclaimFilePageCache(fileno(fp), 0, 0) == -1) { /* A minor error. Just log to know what happens */ serverLog(LL_NOTICE,"Unable to reclaim page cache: %s", strerror(errno)); } if (fclose(fp)) { fp = NULL; goto werr; } fp = NULL; /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ if (rename(tmpfile,filename) == -1) { serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno)); unlink(tmpfile); stopSaving(0); return C_ERR; } stopSaving(1); return C_OK; werr: serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno)); if (fp) fclose(fp); unlink(tmpfile); stopSaving(0); return C_ERR; } /* ---------------------------------------------------------------------------- * AOF background rewrite * ------------------------------------------------------------------------- */ /* This is how rewriting of the append only file in background works: * * 1) The user calls BGREWRITEAOF * 2) Redis calls this function, that forks(): * 2a) the child rewrite the append only file in a temp file. * 2b) the parent open a new INCR AOF file to continue writing. * 3) When the child finished '2a' exists. * 4) The parent will trap the exit code, if it's OK, it will: * 4a) get a new BASE file name and mark the previous (if we have) as the HISTORY type * 4b) rename(2) the temp file in new BASE file name * 4c) mark the rewritten INCR AOFs as history type * 4d) persist AOF manifest file * 4e) Delete the history files use bio */ int rewriteAppendOnlyFileBackground(void) { pid_t childpid; if (hasActiveChildProcess()) return C_ERR; if (dirCreateIfMissing(server.aof_dirname) == -1) { serverLog(LL_WARNING, "Can't open or create append-only dir %s: %s", server.aof_dirname, strerror(errno)); server.aof_lastbgrewrite_status = C_ERR; return C_ERR; } /* We set aof_selected_db to -1 in order to force the next call to the * feedAppendOnlyFile() to issue a SELECT command. */ server.aof_selected_db = -1; flushAppendOnlyFile(1); if (openNewIncrAofForAppend() != C_OK) { server.aof_lastbgrewrite_status = C_ERR; return C_ERR; } if (server.aof_state == AOF_WAIT_REWRITE) { /* Wait for all bio jobs related to AOF to drain. This prevents a race * between updates to `fsynced_reploff_pending` of the worker thread, belonging * to the previous AOF, and the new one. This concern is specific for a full * sync scenario where we don't wanna risk the ACKed replication offset * jumping backwards or forward when switching to a different master. */ bioDrainWorker(BIO_AOF_FSYNC); /* Set the initial repl_offset, which will be applied to fsynced_reploff * when AOFRW finishes (after possibly being updated by a bio thread) */ atomicSet(server.fsynced_reploff_pending, server.master_repl_offset); server.fsynced_reploff = 0; } server.stat_aof_rewrites++; if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) { char tmpfile[256]; /* Child */ redisSetProcTitle("redis-aof-rewrite"); redisSetCpuAffinity(server.aof_rewrite_cpulist); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); if (rewriteAppendOnlyFile(tmpfile) == C_OK) { serverLog(LL_NOTICE, "Successfully created the temporary AOF base file %s", tmpfile); sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite"); exitFromChild(0); } else { exitFromChild(1); } } else { /* Parent */ if (childpid == -1) { server.aof_lastbgrewrite_status = C_ERR; serverLog(LL_WARNING, "Can't rewrite append only file in background: fork: %s", strerror(errno)); return C_ERR; } serverLog(LL_NOTICE, "Background append only file rewriting started by pid %ld",(long) childpid); server.aof_rewrite_scheduled = 0; server.aof_rewrite_time_start = time(NULL); return C_OK; } return C_OK; /* unreached */ } void bgrewriteaofCommand(client *c) { if (server.child_type == CHILD_TYPE_AOF) { addReplyError(c,"Background append only file rewriting already in progress"); } else if (hasActiveChildProcess() || server.in_exec) { server.aof_rewrite_scheduled = 1; /* When manually triggering AOFRW we reset the count * so that it can be executed immediately. */ server.stat_aofrw_consecutive_failures = 0; addReplyStatus(c,"Background append only file rewriting scheduled"); } else if (rewriteAppendOnlyFileBackground() == C_OK) { addReplyStatus(c,"Background append only file rewriting started"); } else { addReplyError(c,"Can't execute an AOF background rewriting. " "Please check the server logs for more information."); } } void aofRemoveTempFile(pid_t childpid) { char tmpfile[256]; snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid); bg_unlink(tmpfile); snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) childpid); bg_unlink(tmpfile); } /* Get size of an AOF file. * The status argument is an optional output argument to be filled with * one of the AOF_ status values. */ off_t getAppendOnlyFileSize(sds filename, int *status) { struct redis_stat sb; off_t size; mstime_t latency; sds aof_filepath = makePath(server.aof_dirname, filename); latencyStartMonitor(latency); if (redis_stat(aof_filepath, &sb) == -1) { if (status) *status = errno == ENOENT ? AOF_NOT_EXIST : AOF_OPEN_ERR; serverLog(LL_WARNING, "Unable to obtain the AOF file %s length. stat: %s", filename, strerror(errno)); size = 0; } else { if (status) *status = AOF_OK; size = sb.st_size; } latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-fstat", latency); sdsfree(aof_filepath); return size; } /* Get size of all AOF files referred by the manifest (excluding history). * The status argument is an output argument to be filled with * one of the AOF_ status values. */ off_t getBaseAndIncrAppendOnlyFilesSize(aofManifest *am, int *status) { off_t size = 0; listNode *ln; listIter li; if (am->base_aof_info) { serverAssert(am->base_aof_info->file_type == AOF_FILE_TYPE_BASE); size += getAppendOnlyFileSize(am->base_aof_info->file_name, status); if (*status != AOF_OK) return 0; } listRewind(am->incr_aof_list, &li); while ((ln = listNext(&li)) != NULL) { aofInfo *ai = (aofInfo*)ln->value; serverAssert(ai->file_type == AOF_FILE_TYPE_INCR); size += getAppendOnlyFileSize(ai->file_name, status); if (*status != AOF_OK) return 0; } return size; } int getBaseAndIncrAppendOnlyFilesNum(aofManifest *am) { int num = 0; if (am->base_aof_info) num++; if (am->incr_aof_list) num += listLength(am->incr_aof_list); return num; } /* A background append only file rewriting (BGREWRITEAOF) terminated its work. * Handle this. */ void backgroundRewriteDoneHandler(int exitcode, int bysignal) { if (!bysignal && exitcode == 0) { char tmpfile[256]; long long now = ustime(); sds new_base_filepath = NULL; sds new_incr_filepath = NULL; aofManifest *temp_am; mstime_t latency; serverLog(LL_NOTICE, "Background AOF rewrite terminated with success"); snprintf(tmpfile, 256, "temp-rewriteaof-bg-%d.aof", (int)server.child_pid); serverAssert(server.aof_manifest != NULL); /* Dup a temporary aof_manifest for subsequent modifications. */ temp_am = aofManifestDup(server.aof_manifest); /* Get a new BASE file name and mark the previous (if we have) * as the HISTORY type. */ sds new_base_filename = getNewBaseFileNameAndMarkPreAsHistory(temp_am); serverAssert(new_base_filename != NULL); new_base_filepath = makePath(server.aof_dirname, new_base_filename); /* Rename the temporary aof file to 'new_base_filename'. */ latencyStartMonitor(latency); if (rename(tmpfile, new_base_filepath) == -1) { serverLog(LL_WARNING, "Error trying to rename the temporary AOF base file %s into %s: %s", tmpfile, new_base_filepath, strerror(errno)); aofManifestFree(temp_am); sdsfree(new_base_filepath); server.aof_lastbgrewrite_status = C_ERR; server.stat_aofrw_consecutive_failures++; goto cleanup; } latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-rename", latency); serverLog(LL_NOTICE, "Successfully renamed the temporary AOF base file %s into %s", tmpfile, new_base_filename); /* Rename the temporary incr aof file to 'new_incr_filename'. */ if (server.aof_state == AOF_WAIT_REWRITE) { /* Get temporary incr aof name. */ sds temp_incr_aof_name = getTempIncrAofName(); sds temp_incr_filepath = makePath(server.aof_dirname, temp_incr_aof_name); /* Get next new incr aof name. */ sds new_incr_filename = getNewIncrAofName(temp_am, tempIncAofStartReplOffset); new_incr_filepath = makePath(server.aof_dirname, new_incr_filename); latencyStartMonitor(latency); if (rename(temp_incr_filepath, new_incr_filepath) == -1) { serverLog(LL_WARNING, "Error trying to rename the temporary AOF incr file %s into %s: %s", temp_incr_filepath, new_incr_filepath, strerror(errno)); bg_unlink(new_base_filepath); sdsfree(new_base_filepath); aofManifestFree(temp_am); sdsfree(temp_incr_filepath); sdsfree(new_incr_filepath); sdsfree(temp_incr_aof_name); server.aof_lastbgrewrite_status = C_ERR; server.stat_aofrw_consecutive_failures++; goto cleanup; } latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-rename", latency); serverLog(LL_NOTICE, "Successfully renamed the temporary AOF incr file %s into %s", temp_incr_aof_name, new_incr_filename); sdsfree(temp_incr_filepath); sdsfree(temp_incr_aof_name); } /* Change the AOF file type in 'incr_aof_list' from AOF_FILE_TYPE_INCR * to AOF_FILE_TYPE_HIST, and move them to the 'history_aof_list'. */ markRewrittenIncrAofAsHistory(temp_am); /* Persist our modifications. */ if (persistAofManifest(temp_am) == C_ERR) { bg_unlink(new_base_filepath); aofManifestFree(temp_am); sdsfree(new_base_filepath); if (new_incr_filepath) { bg_unlink(new_incr_filepath); sdsfree(new_incr_filepath); } server.aof_lastbgrewrite_status = C_ERR; server.stat_aofrw_consecutive_failures++; goto cleanup; } sdsfree(new_base_filepath); if (new_incr_filepath) sdsfree(new_incr_filepath); /* We can safely let `server.aof_manifest` point to 'temp_am' and free the previous one. */ aofManifestFreeAndUpdate(temp_am); if (server.aof_state != AOF_OFF) { /* AOF enabled. */ server.aof_current_size = getAppendOnlyFileSize(new_base_filename, NULL) + server.aof_last_incr_size; server.aof_rewrite_base_size = server.aof_current_size; } /* We don't care about the return value of `aofDelHistoryFiles`, because the history * deletion failure will not cause any problems. */ aofDelHistoryFiles(); server.aof_lastbgrewrite_status = C_OK; server.stat_aofrw_consecutive_failures = 0; serverLog(LL_NOTICE, "Background AOF rewrite finished successfully"); /* Change state from WAIT_REWRITE to ON if needed */ if (server.aof_state == AOF_WAIT_REWRITE) { server.aof_state = AOF_ON; /* Update the fsynced replication offset that just now become valid. * This could either be the one we took in startAppendOnly, or a * newer one set by the bio thread. */ long long fsynced_reploff_pending; atomicGet(server.fsynced_reploff_pending, fsynced_reploff_pending); server.fsynced_reploff = fsynced_reploff_pending; } serverLog(LL_VERBOSE, "Background AOF rewrite signal handler took %lldus", ustime()-now); } else if (!bysignal && exitcode != 0) { server.aof_lastbgrewrite_status = C_ERR; server.stat_aofrw_consecutive_failures++; serverLog(LL_WARNING, "Background AOF rewrite terminated with error"); } else { /* SIGUSR1 is whitelisted, so we have a way to kill a child without * triggering an error condition. */ if (bysignal != SIGUSR1) { server.aof_lastbgrewrite_status = C_ERR; server.stat_aofrw_consecutive_failures++; } serverLog(LL_WARNING, "Background AOF rewrite terminated by signal %d", bysignal); } cleanup: aofRemoveTempFile(server.child_pid); /* Clear AOF buffer and delete temp incr aof for next rewrite. */ if (server.aof_state == AOF_WAIT_REWRITE) { sdsfree(server.aof_buf); server.aof_buf = sdsempty(); aofDelTempIncrAofFile(); } server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start; server.aof_rewrite_time_start = -1; /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */ if (server.aof_state == AOF_WAIT_REWRITE) server.aof_rewrite_scheduled = 1; } redis-8.0.2/src/asciilogo.h000066400000000000000000000027341501533116600155470ustar00rootroot00000000000000/* * Copyright (c) 2009-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). */ const char *ascii_logo = " _._ \n" " _.-``__ ''-._ \n" " _.-`` `. `_. ''-._ Redis Open Source \n" " .-`` .-```. ```\\/ _.,_ ''-._ %s (%s/%d) %s bit\n" " ( ' , .-` | `, ) Running in %s mode\n" " |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n" " | `-._ `._ / _.-' | PID: %ld\n" " `-._ `-._ `-./ _.-' _.-' \n" " |`-._`-._ `-.__.-' _.-'_.-'| \n" " | `-._`-._ _.-'_.-' | https://redis.io \n" " `-._ `-._`-.__.-'_.-' _.-' \n" " |`-._`-._ `-.__.-' _.-'_.-'| \n" " | `-._`-._ _.-'_.-' | \n" " `-._ `-._`-.__.-'_.-' _.-' \n" " `-._ `-.__.-' _.-' \n" " `-._ _.-' \n" " `-.__.-' \n\n"; redis-8.0.2/src/atomicvar.h000066400000000000000000000175331501533116600155660ustar00rootroot00000000000000/* This file implements atomic counters using c11 _Atomic, __atomic or __sync * macros if available, otherwise we will throw an error when compile. * * The exported interface is composed of the following macros: * * atomicIncr(var,count) -- Increment the atomic counter * atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter * atomicIncrGet(var,newvalue_var,count) -- Increment and get the atomic counter new value * atomicDecr(var,count) -- Decrement the atomic counter * atomicGet(var,dstvar) -- Fetch the atomic counter value * atomicSet(var,value) -- Set the atomic counter value * atomicGetWithSync(var,value) -- 'atomicGet' with inter-thread synchronization * atomicSetWithSync(var,value) -- 'atomicSet' with inter-thread synchronization * * Atomic operations on flags. * Flag type can be int, long, long long or their unsigned counterparts. * The value of the flag can be 1 or 0. * * atomicFlagGetSet(var,oldvalue_var) -- Get and set the atomic counter value * * NOTE1: __atomic* and _Atomic implementations can be actually elaborated to support any value by changing the * hardcoded new value passed to __atomic_exchange* from 1 to @param count * i.e oldvalue_var = atomic_exchange_explicit(&var, count). * However, in order to be compatible with the __sync functions family, we can use only 0 and 1. * The only exchange alternative suggested by __sync is __sync_lock_test_and_set, * But as described by the gnu manual for __sync_lock_test_and_set(): * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html * "A target may support reduced functionality here by which the only valid value to store is the immediate constant 1. The exact value * actually stored in *ptr is implementation defined." * Hence, we can't rely on it for a any value other than 1. * We eventually chose to implement this method with __sync_val_compare_and_swap since it satisfies functionality needed for atomicFlagGetSet * (if the flag was 0 -> set to 1, if it's already 1 -> do nothing, but the final result is that the flag is set), * and also it has a full barrier (__sync_lock_test_and_set has acquire barrier). * * NOTE2: Unlike other atomic type, which aren't guaranteed to be lock free, c11 atomic_flag does. * To check whether a type is lock free, atomic_is_lock_free() can be used. * It can be considered to limit the flag type to atomic_flag to improve performance. * * Never use return value from the macros, instead use the AtomicGetIncr() * if you need to get the current value and increment it atomically, like * in the following example: * * long oldvalue; * atomicGetIncr(myvar,oldvalue,1); * doSomethingWith(oldvalue); * * ---------------------------------------------------------------------------- * * Copyright (c) 2015-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 #include "config.h" #ifndef __ATOMIC_VAR_H #define __ATOMIC_VAR_H /* Define redisAtomic for atomic variable. */ #define redisAtomic /* To test Redis with Helgrind (a Valgrind tool) it is useful to define * the following macro, so that __sync macros are used: those can be detected * by Helgrind (even if they are less efficient) so that no false positive * is reported. */ // #define __ATOMIC_VAR_FORCE_SYNC_MACROS /* There will be many false positives if we test Redis with Helgrind, since * Helgrind can't understand we have imposed ordering on the program, so * we use macros in helgrind.h to tell Helgrind inter-thread happens-before * relationship explicitly for avoiding false positives. * * For more details, please see: valgrind/helgrind.h and * https://www.valgrind.org/docs/manual/hg-manual.html#hg-manual.effective-use * * These macros take effect only when 'make helgrind', and you must first * install Valgrind in the default path configuration. */ #ifdef __ATOMIC_VAR_FORCE_SYNC_MACROS #include #else #define ANNOTATE_HAPPENS_BEFORE(v) ((void) v) #define ANNOTATE_HAPPENS_AFTER(v) ((void) v) #endif #if !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && defined(__STDC_VERSION__) && \ (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) /* Use '_Atomic' keyword if the compiler supports. */ #undef redisAtomic #define redisAtomic _Atomic /* Implementation using _Atomic in C11. */ #include #define atomicIncr(var,count) atomic_fetch_add_explicit(&var,(count),memory_order_relaxed) #define atomicGetIncr(var,oldvalue_var,count) do { \ oldvalue_var = atomic_fetch_add_explicit(&var,(count),memory_order_relaxed); \ } while(0) #define atomicIncrGet(var, newvalue_var, count) \ newvalue_var = atomicIncr(var,count) + count #define atomicDecr(var,count) atomic_fetch_sub_explicit(&var,(count),memory_order_relaxed) #define atomicGet(var,dstvar) do { \ dstvar = atomic_load_explicit(&var,memory_order_relaxed); \ } while(0) #define atomicSet(var,value) atomic_store_explicit(&var,value,memory_order_relaxed) #define atomicGetWithSync(var,dstvar) do { \ dstvar = atomic_load_explicit(&var,memory_order_seq_cst); \ } while(0) #define atomicSetWithSync(var,value) \ atomic_store_explicit(&var,value,memory_order_seq_cst) #define atomicFlagGetSet(var,oldvalue_var) \ oldvalue_var = atomic_exchange_explicit(&var,1,memory_order_relaxed) #define REDIS_ATOMIC_API "c11-builtin" #elif !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && \ (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057) && \ defined(__ATOMIC_RELAXED) && defined(__ATOMIC_SEQ_CST) /* Implementation using __atomic macros. */ #define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED) #define atomicIncrGet(var, newvalue_var, count) \ newvalue_var = __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED) #define atomicGetIncr(var,oldvalue_var,count) do { \ oldvalue_var = __atomic_fetch_add(&var,(count),__ATOMIC_RELAXED); \ } while(0) #define atomicDecr(var,count) __atomic_sub_fetch(&var,(count),__ATOMIC_RELAXED) #define atomicGet(var,dstvar) do { \ dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \ } while(0) #define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED) #define atomicGetWithSync(var,dstvar) do { \ dstvar = __atomic_load_n(&var,__ATOMIC_SEQ_CST); \ } while(0) #define atomicSetWithSync(var,value) \ __atomic_store_n(&var,value,__ATOMIC_SEQ_CST) #define atomicFlagGetSet(var,oldvalue_var) \ oldvalue_var = __atomic_exchange_n(&var,1,__ATOMIC_RELAXED) #define REDIS_ATOMIC_API "atomic-builtin" #elif defined(HAVE_ATOMIC) /* Implementation using __sync macros. */ #define atomicIncr(var,count) __sync_add_and_fetch(&var,(count)) #define atomicIncrGet(var, newvalue_var, count) \ newvalue_var = __sync_add_and_fetch(&var,(count)) #define atomicGetIncr(var,oldvalue_var,count) do { \ oldvalue_var = __sync_fetch_and_add(&var,(count)); \ } while(0) #define atomicDecr(var,count) __sync_sub_and_fetch(&var,(count)) #define atomicGet(var,dstvar) do { \ dstvar = __sync_sub_and_fetch(&var,0); \ } while(0) #define atomicSet(var,value) do { \ while(!__sync_bool_compare_and_swap(&var,var,value)); \ } while(0) /* Actually the builtin issues a full memory barrier by default. */ #define atomicGetWithSync(var,dstvar) do { \ dstvar = __sync_sub_and_fetch(&var,0,__sync_synchronize); \ ANNOTATE_HAPPENS_AFTER(&var); \ } while(0) #define atomicSetWithSync(var,value) do { \ ANNOTATE_HAPPENS_BEFORE(&var); \ while(!__sync_bool_compare_and_swap(&var,var,value,__sync_synchronize)); \ } while(0) #define atomicFlagGetSet(var,oldvalue_var) \ oldvalue_var = __sync_val_compare_and_swap(&var,0,1) #define REDIS_ATOMIC_API "sync-builtin" #else #error "Unable to determine atomic operations for your platform" #endif #endif /* __ATOMIC_VAR_H */ redis-8.0.2/src/bio.c000066400000000000000000000404421501533116600143400ustar00rootroot00000000000000/* Background I/O service for Redis. * * This file implements operations that we need to perform in the background. * Currently there are 3 operations: * 1) a background close(2) system call. This is needed when the process is * the last owner of a reference to a file closing it means unlinking it, and * the deletion of the file is slow, blocking the server. * 2) AOF fsync * 3) lazyfree of memory * * In the future we'll either continue implementing new things we need or * we'll switch to libeio. However there are probably long term uses for this * file as we may want to put here Redis specific background tasks. * * DESIGN * ------ * * The design is simple: We have a structure representing a job to perform, * and several worker threads and job queues. Every job type is assigned to * a specific worker thread, and a single worker may handle several different * job types. * Every thread waits for new jobs in its queue, and processes every job * sequentially. * * Jobs handled by the same worker are guaranteed to be processed from the * least-recently-inserted to the most-recently-inserted (older jobs processed * first). * * To let the creator of the job to be notified about the completion of the * operation, it will need to submit additional dummy job, coined as * completion job request that will be written back eventually, by the * background thread, into completion job response queue. This notification * layout can simplify flows that might submit more than one job, such as * in case of FLUSHALL which for a single command submits multiple jobs. It * is also correct because jobs are processed in FIFO fashion. * * ---------------------------------------------------------------------------- * * Copyright (c) 2009-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 "server.h" #include "bio.h" #include static char* bio_worker_title[] = { "bio_close_file", "bio_aof", "bio_lazy_free", }; #define BIO_WORKER_NUM (sizeof(bio_worker_title) / sizeof(*bio_worker_title)) static unsigned int bio_job_to_worker[] = { [BIO_CLOSE_FILE] = 0, [BIO_AOF_FSYNC] = 1, [BIO_CLOSE_AOF] = 1, [BIO_LAZY_FREE] = 2, [BIO_COMP_RQ_CLOSE_FILE] = 0, [BIO_COMP_RQ_AOF_FSYNC] = 1, [BIO_COMP_RQ_LAZY_FREE] = 2 }; static pthread_t bio_threads[BIO_WORKER_NUM]; static pthread_mutex_t bio_mutex[BIO_WORKER_NUM]; static pthread_cond_t bio_newjob_cond[BIO_WORKER_NUM]; static list *bio_jobs[BIO_WORKER_NUM]; static unsigned long bio_jobs_counter[BIO_NUM_OPS] = {0}; /* The bio_comp_list is used to hold completion job responses and to handover * to main thread to callback as notification for job completion. Main * thread will be triggered to read the list by signaling via writing to a pipe */ static list *bio_comp_list; static pthread_mutex_t bio_mutex_comp; static int job_comp_pipe[2]; /* Pipe used to awake the event loop */ typedef struct bio_comp_item { comp_fn *func; /* callback after completion job will be processed */ uint64_t arg; /* user data to be passed to the function */ void *ptr; /* user pointer to be passed to the function */ } bio_comp_item; /* This structure represents a background Job. It is only used locally to this * file as the API does not expose the internals at all. */ typedef union bio_job { struct { int type; /* Job-type tag. This needs to appear as the first element in all union members. */ } header; /* Job specific arguments.*/ struct { int type; int fd; /* Fd for file based background jobs */ long long offset; /* A job-specific offset, if applicable */ unsigned need_fsync:1; /* A flag to indicate that a fsync is required before * the file is closed. */ unsigned need_reclaim_cache:1; /* A flag to indicate that reclaim cache is required before * the file is closed. */ } fd_args; struct { int type; lazy_free_fn *free_fn; /* Function that will free the provided arguments */ void *free_args[]; /* List of arguments to be passed to the free function */ } free_args; struct { int type; /* header */ comp_fn *fn; /* callback. Handover to main thread to cb as notify for job completion */ uint64_t arg; /* callback arguments */ void *ptr; /* callback pointer */ } comp_rq; } bio_job; void *bioProcessBackgroundJobs(void *arg); void bioPipeReadJobCompList(aeEventLoop *el, int fd, void *privdata, int mask); /* Make sure we have enough stack to perform all the things we do in the * main thread. */ #define REDIS_THREAD_STACK_SIZE (1024*1024*4) /* Initialize the background system, spawning the thread. */ void bioInit(void) { pthread_attr_t attr; pthread_t thread; size_t stacksize; unsigned long j; /* Initialization of state vars and objects */ for (j = 0; j < BIO_WORKER_NUM; j++) { pthread_mutex_init(&bio_mutex[j],NULL); pthread_cond_init(&bio_newjob_cond[j],NULL); bio_jobs[j] = listCreate(); } /* init jobs comp responses */ bio_comp_list = listCreate(); pthread_mutex_init(&bio_mutex_comp, NULL); /* Create a pipe for background thread to be able to wake up the redis main thread. * Make the pipe non blocking. This is just a best effort aware mechanism * and we do not want to block not in the read nor in the write half. * Enable close-on-exec flag on pipes in case of the fork-exec system calls in * sentinels or redis servers. */ if (anetPipe(job_comp_pipe, O_CLOEXEC|O_NONBLOCK, O_CLOEXEC|O_NONBLOCK) == -1) { serverLog(LL_WARNING, "Can't create the pipe for bio thread: %s", strerror(errno)); exit(1); } /* Register a readable event for the pipe used to awake the event loop on job completion */ if (aeCreateFileEvent(server.el, job_comp_pipe[0], AE_READABLE, bioPipeReadJobCompList, NULL) == AE_ERR) { serverPanic("Error registering the readable event for the bio pipe."); } /* Set the stack size as by default it may be small in some system */ pthread_attr_init(&attr); pthread_attr_getstacksize(&attr,&stacksize); if (!stacksize) stacksize = 1; /* The world is full of Solaris Fixes */ while (stacksize < REDIS_THREAD_STACK_SIZE) stacksize *= 2; pthread_attr_setstacksize(&attr, stacksize); /* Ready to spawn our threads. We use the single argument the thread * function accepts in order to pass the job ID the thread is * responsible for. */ for (j = 0; j < BIO_WORKER_NUM; j++) { void *arg = (void*)(unsigned long) j; if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) { serverLog(LL_WARNING, "Fatal: Can't initialize Background Jobs. Error message: %s", strerror(errno)); exit(1); } bio_threads[j] = thread; } } void bioSubmitJob(int type, bio_job *job) { job->header.type = type; unsigned long worker = bio_job_to_worker[type]; pthread_mutex_lock(&bio_mutex[worker]); listAddNodeTail(bio_jobs[worker],job); bio_jobs_counter[type]++; pthread_cond_signal(&bio_newjob_cond[worker]); pthread_mutex_unlock(&bio_mutex[worker]); } void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) { va_list valist; /* Allocate memory for the job structure and all required * arguments */ bio_job *job = zmalloc(sizeof(*job) + sizeof(void *) * (arg_count)); job->free_args.free_fn = free_fn; va_start(valist, arg_count); for (int i = 0; i < arg_count; i++) { job->free_args.free_args[i] = va_arg(valist, void *); } va_end(valist); bioSubmitJob(BIO_LAZY_FREE, job); } void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data, void *user_ptr) { int type; switch (assigned_worker) { case BIO_WORKER_CLOSE_FILE: type = BIO_COMP_RQ_CLOSE_FILE; break; case BIO_WORKER_AOF_FSYNC: type = BIO_COMP_RQ_AOF_FSYNC; break; case BIO_WORKER_LAZY_FREE: type = BIO_COMP_RQ_LAZY_FREE; break; default: serverPanic("Invalid worker type in bioCreateCompRq()."); } bio_job *job = zmalloc(sizeof(*job)); job->comp_rq.fn = func; job->comp_rq.arg = user_data; job->comp_rq.ptr = user_ptr; bioSubmitJob(type, job); } void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache) { bio_job *job = zmalloc(sizeof(*job)); job->fd_args.fd = fd; job->fd_args.need_fsync = need_fsync; job->fd_args.need_reclaim_cache = need_reclaim_cache; bioSubmitJob(BIO_CLOSE_FILE, job); } void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache) { bio_job *job = zmalloc(sizeof(*job)); job->fd_args.fd = fd; job->fd_args.offset = offset; job->fd_args.need_fsync = 1; job->fd_args.need_reclaim_cache = need_reclaim_cache; bioSubmitJob(BIO_CLOSE_AOF, job); } void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache) { bio_job *job = zmalloc(sizeof(*job)); job->fd_args.fd = fd; job->fd_args.offset = offset; job->fd_args.need_reclaim_cache = need_reclaim_cache; bioSubmitJob(BIO_AOF_FSYNC, job); } void *bioProcessBackgroundJobs(void *arg) { bio_job *job; unsigned long worker = (unsigned long) arg; sigset_t sigset; /* Check that the worker is within the right interval. */ serverAssert(worker < BIO_WORKER_NUM); redis_set_thread_title(bio_worker_title[worker]); redisSetCpuAffinity(server.bio_cpulist); makeThreadKillable(); pthread_mutex_lock(&bio_mutex[worker]); /* Block SIGALRM so we are sure that only the main thread will * receive the watchdog signal. */ sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); if (pthread_sigmask(SIG_BLOCK, &sigset, NULL)) serverLog(LL_WARNING, "Warning: can't mask SIGALRM in bio.c thread: %s", strerror(errno)); while(1) { listNode *ln; /* The loop always starts with the lock hold. */ if (listLength(bio_jobs[worker]) == 0) { pthread_cond_wait(&bio_newjob_cond[worker], &bio_mutex[worker]); continue; } /* Get the job from the queue. */ ln = listFirst(bio_jobs[worker]); job = ln->value; /* It is now possible to unlock the background system as we know have * a stand alone job structure to process.*/ pthread_mutex_unlock(&bio_mutex[worker]); /* Process the job accordingly to its type. */ int job_type = job->header.type; if (job_type == BIO_CLOSE_FILE) { if (job->fd_args.need_fsync && redis_fsync(job->fd_args.fd) == -1 && errno != EBADF && errno != EINVAL) { serverLog(LL_WARNING, "Fail to fsync the AOF file: %s",strerror(errno)); } if (job->fd_args.need_reclaim_cache) { if (reclaimFilePageCache(job->fd_args.fd, 0, 0) == -1) { serverLog(LL_NOTICE,"Unable to reclaim page cache: %s", strerror(errno)); } } close(job->fd_args.fd); } else if (job_type == BIO_AOF_FSYNC || job_type == BIO_CLOSE_AOF) { /* The fd may be closed by main thread and reused for another * socket, pipe, or file. We just ignore these errno because * aof fsync did not really fail. */ if (redis_fsync(job->fd_args.fd) == -1 && errno != EBADF && errno != EINVAL) { int last_status; atomicGet(server.aof_bio_fsync_status,last_status); atomicSet(server.aof_bio_fsync_status,C_ERR); atomicSet(server.aof_bio_fsync_errno,errno); if (last_status == C_OK) { serverLog(LL_WARNING, "Fail to fsync the AOF file: %s",strerror(errno)); } } else { atomicSet(server.aof_bio_fsync_status,C_OK); atomicSet(server.fsynced_reploff_pending, job->fd_args.offset); } if (job->fd_args.need_reclaim_cache) { if (reclaimFilePageCache(job->fd_args.fd, 0, 0) == -1) { serverLog(LL_NOTICE,"Unable to reclaim page cache: %s", strerror(errno)); } } if (job_type == BIO_CLOSE_AOF) close(job->fd_args.fd); } else if (job_type == BIO_LAZY_FREE) { job->free_args.free_fn(job->free_args.free_args); } else if ((job_type == BIO_COMP_RQ_CLOSE_FILE) || (job_type == BIO_COMP_RQ_AOF_FSYNC) || (job_type == BIO_COMP_RQ_LAZY_FREE)) { bio_comp_item *comp_rsp = zmalloc(sizeof(bio_comp_item)); comp_rsp->func = job->comp_rq.fn; comp_rsp->arg = job->comp_rq.arg; comp_rsp->ptr = job->comp_rq.ptr; /* just write it to completion job responses */ pthread_mutex_lock(&bio_mutex_comp); listAddNodeTail(bio_comp_list, comp_rsp); pthread_mutex_unlock(&bio_mutex_comp); if (write(job_comp_pipe[1],"A",1) != 1) { /* Pipe is non-blocking, write() may fail if it's full. */ } } else { serverPanic("Wrong job type in bioProcessBackgroundJobs()."); } zfree(job); /* Lock again before reiterating the loop, if there are no longer * jobs to process we'll block again in pthread_cond_wait(). */ pthread_mutex_lock(&bio_mutex[worker]); listDelNode(bio_jobs[worker], ln); bio_jobs_counter[job_type]--; pthread_cond_signal(&bio_newjob_cond[worker]); } } /* Return the number of pending jobs of the specified type. */ unsigned long bioPendingJobsOfType(int type) { unsigned int worker = bio_job_to_worker[type]; pthread_mutex_lock(&bio_mutex[worker]); unsigned long val = bio_jobs_counter[type]; pthread_mutex_unlock(&bio_mutex[worker]); return val; } /* Wait for the job queue of the worker for jobs of specified type to become empty. */ void bioDrainWorker(int job_type) { unsigned long worker = bio_job_to_worker[job_type]; pthread_mutex_lock(&bio_mutex[worker]); while (listLength(bio_jobs[worker]) > 0) { pthread_cond_wait(&bio_newjob_cond[worker], &bio_mutex[worker]); } pthread_mutex_unlock(&bio_mutex[worker]); } /* Kill the running bio threads in an unclean way. This function should be * used only when it's critical to stop the threads for some reason. * Currently Redis does this only on crash (for instance on SIGSEGV) in order * to perform a fast memory check without other threads messing with memory. */ void bioKillThreads(void) { int err; unsigned long j; for (j = 0; j < BIO_WORKER_NUM; j++) { if (bio_threads[j] == pthread_self()) continue; if (bio_threads[j] && pthread_cancel(bio_threads[j]) == 0) { if ((err = pthread_join(bio_threads[j],NULL)) != 0) { serverLog(LL_WARNING, "Bio worker thread #%lu can not be joined: %s", j, strerror(err)); } else { serverLog(LL_WARNING, "Bio worker thread #%lu terminated",j); } } } } void bioPipeReadJobCompList(aeEventLoop *el, int fd, void *privdata, int mask) { UNUSED(el); UNUSED(mask); UNUSED(privdata); char buf[128]; list *tmp_list = NULL; while (read(fd, buf, sizeof(buf)) == sizeof(buf)); /* Handle event loop events if pipe was written from event loop API */ pthread_mutex_lock(&bio_mutex_comp); if (listLength(bio_comp_list)) { tmp_list = bio_comp_list; bio_comp_list = listCreate(); } pthread_mutex_unlock(&bio_mutex_comp); if (!tmp_list) return; /* callback to all job completions */ while (listLength(tmp_list)) { listNode *ln = listFirst(tmp_list); bio_comp_item *rsp = ln->value; listDelNode(tmp_list, ln); rsp->func(rsp->arg, rsp->ptr); zfree(rsp); } listRelease(tmp_list); } redis-8.0.2/src/bio.h000066400000000000000000000032321501533116600143410ustar00rootroot00000000000000/* * Copyright (c) 2009-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). */ #ifndef __BIO_H #define __BIO_H typedef void lazy_free_fn(void *args[]); typedef void comp_fn(uint64_t user_data, void *user_ptr); typedef enum bio_worker_t { BIO_WORKER_CLOSE_FILE = 0, BIO_WORKER_AOF_FSYNC, BIO_WORKER_LAZY_FREE, BIO_WORKER_NUM } bio_worker_t; /* Background job opcodes */ typedef enum bio_job_type_t { BIO_CLOSE_FILE = 0, /* Deferred close(2) syscall. */ BIO_AOF_FSYNC, /* Deferred AOF fsync. */ BIO_LAZY_FREE, /* Deferred objects freeing. */ BIO_CLOSE_AOF, BIO_COMP_RQ_CLOSE_FILE, /* Job completion request, registered on close-file worker's queue */ BIO_COMP_RQ_AOF_FSYNC, /* Job completion request, registered on aof-fsync worker's queue */ BIO_COMP_RQ_LAZY_FREE, /* Job completion request, registered on lazy-free worker's queue */ BIO_NUM_OPS } bio_job_type_t; /* Exported API */ void bioInit(void); unsigned long bioPendingJobsOfType(int type); void bioDrainWorker(int job_type); void bioKillThreads(void); void bioCreateCloseJob(int fd, int need_fsync, int need_reclaim_cache); void bioCreateCloseAofJob(int fd, long long offset, int need_reclaim_cache); void bioCreateFsyncJob(int fd, long long offset, int need_reclaim_cache); void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...); void bioCreateCompRq(bio_worker_t assigned_worker, comp_fn *func, uint64_t user_data, void *user_ptr); #endif redis-8.0.2/src/bitops.c000066400000000000000000001376501501533116600150770ustar00rootroot00000000000000/* Bit operations. * * Copyright (c) 2009-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 "server.h" /* ----------------------------------------------------------------------------- * Helpers and low level bit functions. * -------------------------------------------------------------------------- */ /* Count number of bits set in the binary array pointed by 's' and long * 'count' bytes. The implementation of this function is required to * work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */ ATTRIBUTE_TARGET_POPCNT long long redisPopcount(void *s, long count) { long long bits = 0; unsigned char *p = s; uint32_t *p4; #if defined(HAVE_POPCNT) int use_popcnt = __builtin_cpu_supports("popcnt"); /* Check if CPU supports POPCNT instruction. */ #else int use_popcnt = 0; /* Assume CPU does not support POPCNT if * __builtin_cpu_supports() is not available. */ #endif static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8}; /* Count initial bytes not aligned to 64-bit when using the POPCNT instruction, * otherwise align to 32-bit. */ int align = use_popcnt ? 7 : 3; while ((unsigned long)p & align && count) { bits += bitsinbyte[*p++]; count--; } if (likely(use_popcnt)) { /* Use separate counters to make the CPU think there are no * dependencies between these popcnt operations. */ uint64_t cnt[4]; memset(cnt, 0, sizeof(cnt)); /* Count bits 32 bytes at a time by using popcnt. * Unroll the loop to avoid the overhead of a single popcnt per iteration, * allowing the CPU to extract more instruction-level parallelism. * Reference: https://danluu.com/assembly-intrinsics/ */ while (count >= 32) { cnt[0] += __builtin_popcountll(*(uint64_t*)(p)); cnt[1] += __builtin_popcountll(*(uint64_t*)(p + 8)); cnt[2] += __builtin_popcountll(*(uint64_t*)(p + 16)); cnt[3] += __builtin_popcountll(*(uint64_t*)(p + 24)); count -= 32; p += 32; } bits += cnt[0] + cnt[1] + cnt[2] + cnt[3]; goto remain; } /* Count bits 28 bytes at a time */ p4 = (uint32_t*)p; while(count>=28) { uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7; aux1 = *p4++; aux2 = *p4++; aux3 = *p4++; aux4 = *p4++; aux5 = *p4++; aux6 = *p4++; aux7 = *p4++; count -= 28; aux1 = aux1 - ((aux1 >> 1) & 0x55555555); aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333); aux2 = aux2 - ((aux2 >> 1) & 0x55555555); aux2 = (aux2 & 0x33333333) + ((aux2 >> 2) & 0x33333333); aux3 = aux3 - ((aux3 >> 1) & 0x55555555); aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333); aux4 = aux4 - ((aux4 >> 1) & 0x55555555); aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333); aux5 = aux5 - ((aux5 >> 1) & 0x55555555); aux5 = (aux5 & 0x33333333) + ((aux5 >> 2) & 0x33333333); aux6 = aux6 - ((aux6 >> 1) & 0x55555555); aux6 = (aux6 & 0x33333333) + ((aux6 >> 2) & 0x33333333); aux7 = aux7 - ((aux7 >> 1) & 0x55555555); aux7 = (aux7 & 0x33333333) + ((aux7 >> 2) & 0x33333333); bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) + ((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) + ((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) + ((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) + ((aux5 + (aux5 >> 4)) & 0x0F0F0F0F) + ((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) + ((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24; } p = (unsigned char*)p4; remain: /* Count the remaining bytes. */ while(count--) bits += bitsinbyte[*p++]; return bits; } /* Return the position of the first bit set to one (if 'bit' is 1) or * zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes. * * The function is guaranteed to return a value >= 0 if 'bit' is 0 since if * no zero bit is found, it returns count*8 assuming the string is zero * padded on the right. However if 'bit' is 1 it is possible that there is * not a single set bit in the bitmap. In this special case -1 is returned. */ long long redisBitpos(void *s, unsigned long count, int bit) { unsigned long *l; unsigned char *c; unsigned long skipval, word = 0, one; long long pos = 0; /* Position of bit, to return to the caller. */ unsigned long j; int found; /* Process whole words first, seeking for first word that is not * all ones or all zeros respectively if we are looking for zeros * or ones. This is much faster with large strings having contiguous * blocks of 1 or 0 bits compared to the vanilla bit per bit processing. * * Note that if we start from an address that is not aligned * to sizeof(unsigned long) we consume it byte by byte until it is * aligned. */ /* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */ skipval = bit ? 0 : UCHAR_MAX; c = (unsigned char*) s; found = 0; while((unsigned long)c & (sizeof(*l)-1) && count) { if (*c != skipval) { found = 1; break; } c++; count--; pos += 8; } /* Skip bits with full word step. */ l = (unsigned long*) c; if (!found) { skipval = bit ? 0 : ULONG_MAX; while (count >= sizeof(*l)) { if (*l != skipval) break; l++; count -= sizeof(*l); pos += sizeof(*l)*8; } } /* Load bytes into "word" considering the first byte as the most significant * (we basically consider it as written in big endian, since we consider the * string as a set of bits from left to right, with the first bit at position * zero. * * Note that the loading is designed to work even when the bytes left * (count) are less than a full word. We pad it with zero on the right. */ c = (unsigned char*)l; for (j = 0; j < sizeof(*l); j++) { word <<= 8; if (count) { word |= *c; c++; count--; } } /* Special case: * If bits in the string are all zero and we are looking for one, * return -1 to signal that there is not a single "1" in the whole * string. This can't happen when we are looking for "0" as we assume * that the right of the string is zero padded. */ if (bit == 1 && word == 0) return -1; /* Last word left, scan bit by bit. The first thing we need is to * have a single "1" set in the most significant position in an * unsigned long. We don't know the size of the long so we use a * simple trick. */ one = ULONG_MAX; /* All bits set to 1.*/ one >>= 1; /* All bits set to 1 but the MSB. */ one = ~one; /* All bits set to 0 but the MSB. */ while(one) { if (((one & word) != 0) == bit) return pos; pos++; one >>= 1; } /* If we reached this point, there is a bug in the algorithm, since * the case of no match is handled as a special case before. */ serverPanic("End of redisBitpos() reached."); return 0; /* Just to avoid warnings. */ } /* The following set.*Bitfield and get.*Bitfield functions implement setting * and getting arbitrary size (up to 64 bits) signed and unsigned integers * at arbitrary positions into a bitmap. * * The representation considers the bitmap as having the bit number 0 to be * the most significant bit of the first byte, and so forth, so for example * setting a 5 bits unsigned integer to value 23 at offset 7 into a bitmap * previously set to all zeroes, will produce the following representation: * * +--------+--------+ * |00000001|01110000| * +--------+--------+ * * When offsets and integer sizes are aligned to bytes boundaries, this is the * same as big endian, however when such alignment does not exist, its important * to also understand how the bits inside a byte are ordered. * * Note that this format follows the same convention as SETBIT and related * commands. */ void setUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, uint64_t value) { uint64_t byte, bit, byteval, bitval, j; for (j = 0; j < bits; j++) { bitval = (value & ((uint64_t)1<<(bits-1-j))) != 0; byte = offset >> 3; bit = 7 - (offset & 0x7); byteval = p[byte]; byteval &= ~(1 << bit); byteval |= bitval << bit; p[byte] = byteval & 0xff; offset++; } } void setSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, int64_t value) { uint64_t uv = value; /* Casting will add UINT64_MAX + 1 if v is negative. */ setUnsignedBitfield(p,offset,bits,uv); } uint64_t getUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) { uint64_t byte, bit, byteval, bitval, j, value = 0; for (j = 0; j < bits; j++) { byte = offset >> 3; bit = 7 - (offset & 0x7); byteval = p[byte]; bitval = (byteval >> bit) & 1; value = (value<<1) | bitval; offset++; } return value; } int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) { int64_t value; union {uint64_t u; int64_t i;} conv; /* Converting from unsigned to signed is undefined when the value does * not fit, however here we assume two's complement and the original value * was obtained from signed -> unsigned conversion, so we'll find the * most significant bit set if the original value was negative. * * Note that two's complement is mandatory for exact-width types * according to the C99 standard. */ conv.u = getUnsignedBitfield(p,offset,bits); value = conv.i; /* If the top significant bit is 1, propagate it to all the * higher bits for two's complement representation of signed * integers. */ if (bits < 64 && (value & ((uint64_t)1 << (bits-1)))) value |= ((uint64_t)-1) << bits; return value; } /* The following two functions detect overflow of a value in the context * of storing it as an unsigned or signed integer with the specified * number of bits. The functions both take the value and a possible increment. * If no overflow could happen and the value+increment fit inside the limits, * then zero is returned, otherwise in case of overflow, 1 is returned, * otherwise in case of underflow, -1 is returned. * * When non-zero is returned (overflow or underflow), if not NULL, *limit is * set to the value the operation should result when an overflow happens, * depending on the specified overflow semantics: * * For BFOVERFLOW_SAT if 1 is returned, *limit it is set maximum value that * you can store in that integer. when -1 is returned, *limit is set to the * minimum value that an integer of that size can represent. * * For BFOVERFLOW_WRAP *limit is set by performing the operation in order to * "wrap" around towards zero for unsigned integers, or towards the most * negative number that is possible to represent for signed integers. */ #define BFOVERFLOW_WRAP 0 #define BFOVERFLOW_SAT 1 #define BFOVERFLOW_FAIL 2 /* Used by the BITFIELD command implementation. */ int checkUnsignedBitfieldOverflow(uint64_t value, int64_t incr, uint64_t bits, int owtype, uint64_t *limit) { uint64_t max = (bits == 64) ? UINT64_MAX : (((uint64_t)1< max || (incr > 0 && incr > maxincr)) { if (limit) { if (owtype == BFOVERFLOW_WRAP) { goto handle_wrap; } else if (owtype == BFOVERFLOW_SAT) { *limit = max; } } return 1; } else if (incr < 0 && incr < minincr) { if (limit) { if (owtype == BFOVERFLOW_WRAP) { goto handle_wrap; } else if (owtype == BFOVERFLOW_SAT) { *limit = 0; } } return -1; } return 0; handle_wrap: { uint64_t mask = ((uint64_t)-1) << bits; uint64_t res = value+incr; res &= ~mask; *limit = res; } return 1; } int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int owtype, int64_t *limit) { int64_t max = (bits == 64) ? INT64_MAX : (((int64_t)1<<(bits-1))-1); int64_t min = (-max)-1; /* Note that maxincr and minincr could overflow, but we use the values * only after checking 'value' range, so when we use it no overflow * happens. 'uint64_t' cast is there just to prevent undefined behavior on * overflow */ int64_t maxincr = (uint64_t)max-value; int64_t minincr = min-value; if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr)) { if (limit) { if (owtype == BFOVERFLOW_WRAP) { goto handle_wrap; } else if (owtype == BFOVERFLOW_SAT) { *limit = max; } } return 1; } else if (value < min || (bits != 64 && incr < minincr) || (value < 0 && incr < 0 && incr < minincr)) { if (limit) { if (owtype == BFOVERFLOW_WRAP) { goto handle_wrap; } else if (owtype == BFOVERFLOW_SAT) { *limit = min; } } return -1; } return 0; handle_wrap: { uint64_t msb = (uint64_t)1 << (bits-1); uint64_t a = value, b = incr, c; c = a+b; /* Perform addition as unsigned so that's defined. */ /* If the sign bit is set, propagate to all the higher order * bits, to cap the negative value. If it's clear, mask to * the positive integer limit. */ if (bits < 64) { uint64_t mask = ((uint64_t)-1) << bits; if (c & msb) { c |= mask; } else { c &= ~mask; } } *limit = c; } return 1; } /* Debugging function. Just show bits in the specified bitmap. Not used * but here for not having to rewrite it when debugging is needed. */ void printBits(unsigned char *p, unsigned long count) { unsigned long j, i, byte; for (j = 0; j < count; j++) { byte = p[j]; for (i = 0x80; i > 0; i /= 2) printf("%c", (byte & i) ? '1' : '0'); printf("|"); } printf("\n"); } /* ----------------------------------------------------------------------------- * Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP. * -------------------------------------------------------------------------- */ #define BITOP_AND 0 #define BITOP_OR 1 #define BITOP_XOR 2 #define BITOP_NOT 3 #define BITFIELDOP_GET 0 #define BITFIELDOP_SET 1 #define BITFIELDOP_INCRBY 2 /* This helper function used by GETBIT / SETBIT parses the bit offset argument * making sure an error is returned if it is negative or if it overflows * Redis 512 MB limit for the string value or more (server.proto_max_bulk_len). * * If the 'hash' argument is true, and 'bits is positive, then the command * will also parse bit offsets prefixed by "#". In such a case the offset * is multiplied by 'bits'. This is useful for the BITFIELD command. */ int getBitOffsetFromArgument(client *c, robj *o, uint64_t *offset, int hash, int bits) { long long loffset; char *err = "bit offset is not an integer or out of range"; char *p = o->ptr; size_t plen = sdslen(p); int usehash = 0; /* Handle # form. */ if (p[0] == '#' && hash && bits > 0) usehash = 1; if (string2ll(p+usehash,plen-usehash,&loffset) == 0) { addReplyError(c,err); return C_ERR; } /* Adjust the offset by 'bits' for # form. */ if (usehash) loffset *= bits; /* Limit offset to server.proto_max_bulk_len (512MB in bytes by default) */ if (loffset < 0 || (!mustObeyClient(c) && (loffset >> 3) >= server.proto_max_bulk_len)) { addReplyError(c,err); return C_ERR; } *offset = loffset; return C_OK; } /* This helper function for BITFIELD parses a bitfield type in the form * where sign is 'u' or 'i' for unsigned and signed, and * the bits is a value between 1 and 64. However 64 bits unsigned integers * are reported as an error because of current limitations of Redis protocol * to return unsigned integer values greater than INT64_MAX. * * On error C_ERR is returned and an error is sent to the client. */ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) { char *p = o->ptr; char *err = "Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is."; long long llbits; if (p[0] == 'i') { *sign = 1; } else if (p[0] == 'u') { *sign = 0; } else { addReplyError(c,err); return C_ERR; } if ((string2ll(p+1,strlen(p+1),&llbits)) == 0 || llbits < 1 || (*sign == 1 && llbits > 64) || (*sign == 0 && llbits > 63)) { addReplyError(c,err); return C_ERR; } *bits = llbits; return C_OK; } /* This is a helper function for commands implementations that need to write * bits to a string object. The command creates or pad with zeroes the string * so that the 'maxbit' bit can be addressed. The object is finally * returned. Otherwise if the key holds a wrong type NULL is returned and * an error is sent to the client. * * (Must provide all the arguments to the function) */ static robj *lookupStringForBitCommand(client *c, uint64_t maxbit, size_t *strOldSize, size_t *strGrowSize) { size_t byte = maxbit >> 3; robj *o = lookupKeyWrite(c->db,c->argv[1]); if (checkType(c,o,OBJ_STRING)) return NULL; if (o == NULL) { o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1)); dbAdd(c->db,c->argv[1],o); *strGrowSize = byte + 1; *strOldSize = 0; } else { o = dbUnshareStringValue(c->db,c->argv[1],o); *strOldSize = sdslen(o->ptr); o->ptr = sdsgrowzero(o->ptr,byte+1); *strGrowSize = sdslen(o->ptr) - *strOldSize; } return o; } /* Return a pointer to the string object content, and stores its length * in 'len'. The user is required to pass (likely stack allocated) buffer * 'llbuf' of at least LONG_STR_SIZE bytes. Such a buffer is used in the case * the object is integer encoded in order to provide the representation * without using heap allocation. * * The function returns the pointer to the object array of bytes representing * the string it contains, that may be a pointer to 'llbuf' or to the * internal object representation. As a side effect 'len' is filled with * the length of such buffer. * * If the source object is NULL the function is guaranteed to return NULL * and set 'len' to 0. */ unsigned char *getObjectReadOnlyString(robj *o, long *len, char *llbuf) { serverAssert(!o || o->type == OBJ_STRING); unsigned char *p = NULL; /* Set the 'p' pointer to the string, that can be just a stack allocated * array if our string was integer encoded. */ if (o && o->encoding == OBJ_ENCODING_INT) { p = (unsigned char*) llbuf; if (len) *len = ll2string(llbuf,LONG_STR_SIZE,(long)o->ptr); } else if (o) { p = (unsigned char*) o->ptr; if (len) *len = sdslen(o->ptr); } else { if (len) *len = 0; } return p; } /* SETBIT key offset bitvalue */ void setbitCommand(client *c) { robj *o; char *err = "bit is not an integer or out of range"; uint64_t bitoffset; ssize_t byte, bit; int byteval, bitval; long on; if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK) return; if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != C_OK) return; /* Bits can only be set or cleared... */ if (on & ~1) { addReplyError(c,err); return; } size_t strOldSize, strGrowSize; if ((o = lookupStringForBitCommand(c,bitoffset,&strOldSize,&strGrowSize)) == NULL) return; /* Get current values */ byte = bitoffset >> 3; byteval = ((uint8_t*)o->ptr)[byte]; bit = 7 - (bitoffset & 0x7); bitval = byteval & (1 << bit); /* Either it is newly created, changed length, or the bit changes before and after. * Note that the bitval here is actually a decimal number. * So we need to use `!!` to convert it to 0 or 1 for comparison. */ if (strGrowSize || (!!bitval != on)) { /* Update byte with new bit value. */ byteval &= ~(1 << bit); byteval |= ((on & 0x1) << bit); ((uint8_t*)o->ptr)[byte] = byteval; signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id); server.dirty++; /* If this is not a new key (old size not 0) and size changed, then * 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); } /* Return original value. */ addReply(c, bitval ? shared.cone : shared.czero); } /* GETBIT key offset */ void getbitCommand(client *c) { robj *o; char llbuf[32]; uint64_t bitoffset; size_t byte, bit; size_t bitval = 0; if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK) return; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_STRING)) return; byte = bitoffset >> 3; bit = 7 - (bitoffset & 0x7); if (sdsEncodedObject(o)) { if (byte < sdslen(o->ptr)) bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit); } else { if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr)) bitval = llbuf[byte] & (1 << bit); } addReply(c, bitval ? shared.cone : shared.czero); } /* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */ REDIS_NO_SANITIZE("alignment") void bitopCommand(client *c) { char *opname = c->argv[1]->ptr; robj *o, *targetkey = c->argv[2]; unsigned long op, j, numkeys; robj **objects; /* Array of source objects. */ unsigned char **src; /* Array of source strings pointers. */ unsigned long *len, maxlen = 0; /* Array of length of src strings, and max len. */ unsigned long minlen = 0; /* Min len among the input keys. */ unsigned char *res = NULL; /* Resulting string. */ /* Parse the operation name. */ if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and")) op = BITOP_AND; else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or")) op = BITOP_OR; else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor")) op = BITOP_XOR; else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not")) op = BITOP_NOT; else { addReplyErrorObject(c,shared.syntaxerr); return; } /* Sanity check: NOT accepts only a single key argument. */ if (op == BITOP_NOT && c->argc != 4) { addReplyError(c,"BITOP NOT must be called with a single source key."); return; } /* Lookup keys, and store pointers to the string objects into an array. */ numkeys = c->argc - 3; src = zmalloc(sizeof(unsigned char*) * numkeys); len = zmalloc(sizeof(long) * numkeys); objects = zmalloc(sizeof(robj*) * numkeys); for (j = 0; j < numkeys; j++) { o = lookupKeyRead(c->db,c->argv[j+3]); /* Handle non-existing keys as empty strings. */ if (o == NULL) { objects[j] = NULL; src[j] = NULL; len[j] = 0; minlen = 0; continue; } /* Return an error if one of the keys is not a string. */ if (checkType(c,o,OBJ_STRING)) { unsigned long i; for (i = 0; i < j; i++) { if (objects[i]) decrRefCount(objects[i]); } zfree(src); zfree(len); zfree(objects); return; } objects[j] = getDecodedObject(o); src[j] = objects[j]->ptr; len[j] = sdslen(objects[j]->ptr); if (len[j] > maxlen) maxlen = len[j]; if (j == 0 || len[j] < minlen) minlen = len[j]; } /* Compute the bit operation, if at least one string is not empty. */ if (maxlen) { res = (unsigned char*) sdsnewlen(NULL,maxlen); unsigned char output, byte; unsigned long i; /* 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. */ j = 0; #ifndef USE_ALIGNED_ACCESS if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) { unsigned long *lp[16]; unsigned long *lres = (unsigned long*) res; memcpy(lp,src,sizeof(unsigned long*)*numkeys); memcpy(res,src[0],minlen); /* Different branches per different operations for speed (sorry). */ if (op == BITOP_AND) { while(minlen >= sizeof(unsigned long)*4) { for (i = 1; i < numkeys; i++) { lres[0] &= lp[i][0]; lres[1] &= lp[i][1]; lres[2] &= lp[i][2]; lres[3] &= lp[i][3]; lp[i]+=4; } lres+=4; j += sizeof(unsigned long)*4; minlen -= sizeof(unsigned long)*4; } } else if (op == BITOP_OR) { while(minlen >= sizeof(unsigned long)*4) { for (i = 1; i < numkeys; i++) { lres[0] |= lp[i][0]; lres[1] |= lp[i][1]; lres[2] |= lp[i][2]; lres[3] |= lp[i][3]; lp[i]+=4; } lres+=4; j += sizeof(unsigned long)*4; minlen -= sizeof(unsigned long)*4; } } else if (op == BITOP_XOR) { while(minlen >= sizeof(unsigned long)*4) { for (i = 1; i < numkeys; i++) { lres[0] ^= lp[i][0]; lres[1] ^= lp[i][1]; lres[2] ^= lp[i][2]; lres[3] ^= lp[i][3]; lp[i]+=4; } lres+=4; j += sizeof(unsigned long)*4; minlen -= sizeof(unsigned long)*4; } } else if (op == BITOP_NOT) { while(minlen >= sizeof(unsigned long)*4) { lres[0] = ~lres[0]; lres[1] = ~lres[1]; lres[2] = ~lres[2]; lres[3] = ~lres[3]; lres+=4; j += sizeof(unsigned long)*4; minlen -= sizeof(unsigned long)*4; } } } #endif /* j is set to the next byte to process by the previous loop. */ for (; j < maxlen; j++) { output = (len[0] <= j) ? 0 : src[0][j]; if (op == BITOP_NOT) output = ~output; for (i = 1; i < numkeys; i++) { int skip = 0; byte = (len[i] <= j) ? 0 : src[i][j]; switch(op) { case BITOP_AND: output &= byte; skip = (output == 0); break; case BITOP_OR: output |= byte; skip = (output == 0xff); break; case BITOP_XOR: output ^= byte; break; } if (skip) { break; } } res[j] = output; } } for (j = 0; j < numkeys; j++) { if (objects[j]) decrRefCount(objects[j]); } zfree(src); zfree(len); zfree(objects); /* Store the computed value into the target key */ if (maxlen) { o = createObject(OBJ_STRING,res); setKey(c,c->db,targetkey,o,0); notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id); decrRefCount(o); server.dirty++; } else if (dbDelete(c->db,targetkey)) { signalModifiedKey(c,c->db,targetkey); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id); server.dirty++; } addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */ } /* BITCOUNT key [start end [BIT|BYTE]] */ void bitcountCommand(client *c) { robj *o; long long start, end; long strlen; unsigned char *p; char llbuf[LONG_STR_SIZE]; int isbit = 0; unsigned char first_byte_neg_mask = 0, last_byte_neg_mask = 0; /* Parse start/end range if any. */ if (c->argc == 4 || c->argc == 5) { if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) return; if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) return; if (c->argc == 5) { if (!strcasecmp(c->argv[4]->ptr,"bit")) isbit = 1; else if (!strcasecmp(c->argv[4]->ptr,"byte")) isbit = 0; else { addReplyErrorObject(c,shared.syntaxerr); return; } } /* Lookup, check for type. */ o = lookupKeyRead(c->db, c->argv[1]); if (checkType(c, o, OBJ_STRING)) return; p = getObjectReadOnlyString(o,&strlen,llbuf); long long totlen = strlen; /* Make sure we will not overflow */ serverAssert(totlen <= LLONG_MAX >> 3); /* Convert negative indexes */ if (start < 0 && end < 0 && start > end) { addReply(c,shared.czero); return; } if (isbit) totlen <<= 3; if (start < 0) start = totlen+start; if (end < 0) end = totlen+end; if (start < 0) start = 0; if (end < 0) end = 0; if (end >= totlen) end = totlen-1; if (isbit && start <= end) { /* Before converting bit offset to byte offset, create negative masks * for the edges. */ first_byte_neg_mask = ~((1<<(8-(start&7)))-1) & 0xFF; last_byte_neg_mask = (1<<(7-(end&7)))-1; start >>= 3; end >>= 3; } } else if (c->argc == 2) { /* Lookup, check for type. */ o = lookupKeyRead(c->db, c->argv[1]); if (checkType(c, o, OBJ_STRING)) return; p = getObjectReadOnlyString(o,&strlen,llbuf); /* The whole string. */ start = 0; end = strlen-1; } else { /* Syntax error. */ addReplyErrorObject(c,shared.syntaxerr); return; } /* Return 0 for non existing keys. */ if (o == NULL) { addReply(c, shared.czero); return; } /* Precondition: end >= 0 && end < strlen, so the only condition where * zero can be returned is: start > end. */ if (start > end) { addReply(c,shared.czero); } else { long bytes = (long)(end-start+1); long long count = redisPopcount(p+start,bytes); if (first_byte_neg_mask != 0 || last_byte_neg_mask != 0) { unsigned char firstlast[2] = {0, 0}; /* We may count bits of first byte and last byte which are out of * range. So we need to subtract them. Here we use a trick. We set * bits in the range to zero. So these bit will not be excluded. */ if (first_byte_neg_mask != 0) firstlast[0] = p[start] & first_byte_neg_mask; if (last_byte_neg_mask != 0) firstlast[1] = p[end] & last_byte_neg_mask; count -= redisPopcount(firstlast,2); } addReplyLongLong(c,count); } } /* BITPOS key bit [start [end [BIT|BYTE]]] */ void bitposCommand(client *c) { robj *o; long long start, end; long bit, strlen; unsigned char *p; char llbuf[LONG_STR_SIZE]; int isbit = 0, end_given = 0; unsigned char first_byte_neg_mask = 0, last_byte_neg_mask = 0; /* Parse the bit argument to understand what we are looking for, set * or clear bits. */ if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != C_OK) return; if (bit != 0 && bit != 1) { addReplyError(c, "The bit argument must be 1 or 0."); return; } /* Parse start/end range if any. */ if (c->argc == 4 || c->argc == 5 || c->argc == 6) { if (getLongLongFromObjectOrReply(c,c->argv[3],&start,NULL) != C_OK) return; if (c->argc == 6) { if (!strcasecmp(c->argv[5]->ptr,"bit")) isbit = 1; else if (!strcasecmp(c->argv[5]->ptr,"byte")) isbit = 0; else { addReplyErrorObject(c,shared.syntaxerr); return; } } if (c->argc >= 5) { if (getLongLongFromObjectOrReply(c,c->argv[4],&end,NULL) != C_OK) return; end_given = 1; } /* Lookup, check for type. */ o = lookupKeyRead(c->db, c->argv[1]); if (checkType(c, o, OBJ_STRING)) return; p = getObjectReadOnlyString(o, &strlen, llbuf); /* Make sure we will not overflow */ long long totlen = strlen; serverAssert(totlen <= LLONG_MAX >> 3); if (c->argc < 5) { if (isbit) end = (totlen<<3) + 7; else end = totlen-1; } if (isbit) totlen <<= 3; /* Convert negative indexes */ if (start < 0) start = totlen+start; if (end < 0) end = totlen+end; if (start < 0) start = 0; if (end < 0) end = 0; if (end >= totlen) end = totlen-1; if (isbit && start <= end) { /* Before converting bit offset to byte offset, create negative masks * for the edges. */ first_byte_neg_mask = ~((1<<(8-(start&7)))-1) & 0xFF; last_byte_neg_mask = (1<<(7-(end&7)))-1; start >>= 3; end >>= 3; } } else if (c->argc == 3) { /* Lookup, check for type. */ o = lookupKeyRead(c->db, c->argv[1]); if (checkType(c,o,OBJ_STRING)) return; p = getObjectReadOnlyString(o,&strlen,llbuf); /* The whole string. */ start = 0; end = strlen-1; } else { /* Syntax error. */ addReplyErrorObject(c,shared.syntaxerr); return; } /* If the key does not exist, from our point of view it is an infinite * array of 0 bits. If the user is looking for the first clear bit return 0, * If the user is looking for the first set bit, return -1. */ if (o == NULL) { addReplyLongLong(c, bit ? -1 : 0); return; } /* For empty ranges (start > end) we return -1 as an empty range does * not contain a 0 nor a 1. */ if (start > end) { addReplyLongLong(c, -1); } else { long bytes = end-start+1; long long pos; unsigned char tmpchar; if (first_byte_neg_mask) { if (bit) tmpchar = p[start] & ~first_byte_neg_mask; else tmpchar = p[start] | first_byte_neg_mask; /* Special case, there is only one byte */ if (last_byte_neg_mask && bytes == 1) { if (bit) tmpchar = tmpchar & ~last_byte_neg_mask; else tmpchar = tmpchar | last_byte_neg_mask; } pos = redisBitpos(&tmpchar,1,bit); /* If there are no more bytes or we get valid pos, we can exit early */ if (bytes == 1 || (pos != -1 && pos != 8)) goto result; start++; bytes--; } /* If the last byte has not bits in the range, we should exclude it */ long curbytes = bytes - (last_byte_neg_mask ? 1 : 0); if (curbytes > 0) { pos = redisBitpos(p+start,curbytes,bit); /* If there is no more bytes or we get valid pos, we can exit early */ if (bytes == curbytes || (pos != -1 && pos != (long long)curbytes<<3)) goto result; start += curbytes; bytes -= curbytes; } if (bit) tmpchar = p[end] & ~last_byte_neg_mask; else tmpchar = p[end] | last_byte_neg_mask; pos = redisBitpos(&tmpchar,1,bit); result: /* If we are looking for clear bits, and the user specified an exact * range with start-end, we can't consider the right of the range as * zero padded (as we do when no explicit end is given). * * So if redisBitpos() returns the first bit outside the range, * we return -1 to the caller, to mean, in the specified range there * is not a single "0" bit. */ if (end_given && bit == 0 && pos == (long long)bytes<<3) { addReplyLongLong(c,-1); return; } if (pos != -1) pos += (long long)start<<3; /* Adjust for the bytes we skipped. */ addReplyLongLong(c,pos); } } /* BITFIELD key subcommand-1 arg ... subcommand-2 arg ... subcommand-N ... * * Supported subcommands: * * GET * SET * INCRBY * OVERFLOW [WRAP|SAT|FAIL] */ #define BITFIELD_FLAG_NONE 0 #define BITFIELD_FLAG_READONLY (1<<0) struct bitfieldOp { uint64_t offset; /* Bitfield offset. */ int64_t i64; /* Increment amount (INCRBY) or SET value */ int opcode; /* Operation id. */ int owtype; /* Overflow type to use. */ int bits; /* Integer bitfield bits width. */ int sign; /* True if signed, otherwise unsigned op. */ }; /* This implements both the BITFIELD command and the BITFIELD_RO command * when flags is set to BITFIELD_FLAG_READONLY: in this case only the * GET subcommand is allowed, other subcommands will return an error. */ void bitfieldGeneric(client *c, int flags) { robj *o; uint64_t bitoffset; int j, numops = 0, changes = 0; size_t strOldSize, strGrowSize = 0; struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */ int owtype = BFOVERFLOW_WRAP; /* Overflow type. */ int readonly = 1; uint64_t highest_write_offset = 0; for (j = 2; j < c->argc; j++) { int remargs = c->argc-j-1; /* Remaining args other than current. */ char *subcmd = c->argv[j]->ptr; /* Current command name. */ int opcode; /* Current operation code. */ long long i64 = 0; /* Signed SET value. */ int sign = 0; /* Signed or unsigned type? */ int bits = 0; /* Bitfield width in bits. */ if (!strcasecmp(subcmd,"get") && remargs >= 2) opcode = BITFIELDOP_GET; else if (!strcasecmp(subcmd,"set") && remargs >= 3) opcode = BITFIELDOP_SET; else if (!strcasecmp(subcmd,"incrby") && remargs >= 3) opcode = BITFIELDOP_INCRBY; else if (!strcasecmp(subcmd,"overflow") && remargs >= 1) { char *owtypename = c->argv[j+1]->ptr; j++; if (!strcasecmp(owtypename,"wrap")) owtype = BFOVERFLOW_WRAP; else if (!strcasecmp(owtypename,"sat")) owtype = BFOVERFLOW_SAT; else if (!strcasecmp(owtypename,"fail")) owtype = BFOVERFLOW_FAIL; else { addReplyError(c,"Invalid OVERFLOW type specified"); zfree(ops); return; } continue; } else { addReplyErrorObject(c,shared.syntaxerr); zfree(ops); return; } /* Get the type and offset arguments, common to all the ops. */ if (getBitfieldTypeFromArgument(c,c->argv[j+1],&sign,&bits) != C_OK) { zfree(ops); return; } if (getBitOffsetFromArgument(c,c->argv[j+2],&bitoffset,1,bits) != C_OK){ zfree(ops); return; } if (opcode != BITFIELDOP_GET) { readonly = 0; if (highest_write_offset < bitoffset + bits - 1) highest_write_offset = bitoffset + bits - 1; /* INCRBY and SET require another argument. */ if (getLongLongFromObjectOrReply(c,c->argv[j+3],&i64,NULL) != C_OK){ zfree(ops); return; } } /* Populate the array of operations we'll process. */ ops = zrealloc(ops,sizeof(*ops)*(numops+1)); ops[numops].offset = bitoffset; ops[numops].i64 = i64; ops[numops].opcode = opcode; ops[numops].owtype = owtype; ops[numops].bits = bits; ops[numops].sign = sign; numops++; j += 3 - (opcode == BITFIELDOP_GET); } if (readonly) { /* Lookup for read is ok if key doesn't exit, but errors * if it's not a string. */ o = lookupKeyRead(c->db,c->argv[1]); if (o != NULL && checkType(c,o,OBJ_STRING)) { zfree(ops); return; } } else { if (flags & BITFIELD_FLAG_READONLY) { zfree(ops); addReplyError(c, "BITFIELD_RO only supports the GET subcommand"); return; } /* Lookup by making room up to the farthest bit reached by * this operation. */ if ((o = lookupStringForBitCommand(c, highest_write_offset,&strOldSize,&strGrowSize)) == NULL) { zfree(ops); return; } } addReplyArrayLen(c,numops); /* Actually process the operations. */ for (j = 0; j < numops; j++) { struct bitfieldOp *thisop = ops+j; /* Execute the operation. */ if (thisop->opcode == BITFIELDOP_SET || thisop->opcode == BITFIELDOP_INCRBY) { /* SET and INCRBY: We handle both with the same code path * for simplicity. SET return value is the previous value so * we need fetch & store as well. */ /* We need two different but very similar code paths for signed * and unsigned operations, since the set of functions to get/set * the integers and the used variables types are different. */ if (thisop->sign) { int64_t oldval, newval, wrapped, retval; int overflow; oldval = getSignedBitfield(o->ptr,thisop->offset, thisop->bits); if (thisop->opcode == BITFIELDOP_INCRBY) { overflow = checkSignedBitfieldOverflow(oldval, thisop->i64,thisop->bits,thisop->owtype,&wrapped); newval = overflow ? wrapped : oldval + thisop->i64; retval = newval; } else { newval = thisop->i64; overflow = checkSignedBitfieldOverflow(newval, 0,thisop->bits,thisop->owtype,&wrapped); if (overflow) newval = wrapped; retval = oldval; } /* On overflow of type is "FAIL", don't write and return * NULL to signal the condition. */ if (!(overflow && thisop->owtype == BFOVERFLOW_FAIL)) { addReplyLongLong(c,retval); setSignedBitfield(o->ptr,thisop->offset, thisop->bits,newval); if (strGrowSize || (oldval != newval)) changes++; } else { addReplyNull(c); } } else { /* Initialization of 'wrapped' is required to avoid * false-positive warning "-Wmaybe-uninitialized" */ uint64_t oldval, newval, retval, wrapped = 0; int overflow; oldval = getUnsignedBitfield(o->ptr,thisop->offset, thisop->bits); if (thisop->opcode == BITFIELDOP_INCRBY) { newval = oldval + thisop->i64; overflow = checkUnsignedBitfieldOverflow(oldval, thisop->i64,thisop->bits,thisop->owtype,&wrapped); if (overflow) newval = wrapped; retval = newval; } else { newval = thisop->i64; overflow = checkUnsignedBitfieldOverflow(newval, 0,thisop->bits,thisop->owtype,&wrapped); if (overflow) newval = wrapped; retval = oldval; } /* On overflow of type is "FAIL", don't write and return * NULL to signal the condition. */ if (!(overflow && thisop->owtype == BFOVERFLOW_FAIL)) { addReplyLongLong(c,retval); setUnsignedBitfield(o->ptr,thisop->offset, thisop->bits,newval); if (strGrowSize || (oldval != newval)) changes++; } else { addReplyNull(c); } } } else { /* GET */ unsigned char buf[9]; long strlen = 0; unsigned char *src = NULL; char llbuf[LONG_STR_SIZE]; if (o != NULL) src = getObjectReadOnlyString(o,&strlen,llbuf); /* For GET we use a trick: before executing the operation * copy up to 9 bytes to a local buffer, so that we can easily * execute up to 64 bit operations that are at actual string * object boundaries. */ memset(buf,0,9); int i; uint64_t byte = thisop->offset >> 3; for (i = 0; i < 9; i++) { if (src == NULL || i+byte >= (uint64_t)strlen) break; buf[i] = src[i+byte]; } /* Now operate on the copied buffer which is guaranteed * to be zero-padded. */ if (thisop->sign) { int64_t val = getSignedBitfield(buf,thisop->offset-(byte*8), thisop->bits); addReplyLongLong(c,val); } else { uint64_t val = getUnsignedBitfield(buf,thisop->offset-(byte*8), thisop->bits); addReplyLongLong(c,val); } } } if (changes) { /* If this is not a new key (old size not 0) and size changed, then * 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); signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id); server.dirty += changes; } zfree(ops); } void bitfieldCommand(client *c) { bitfieldGeneric(c, BITFIELD_FLAG_NONE); } void bitfieldroCommand(client *c) { bitfieldGeneric(c, BITFIELD_FLAG_READONLY); } redis-8.0.2/src/blocked.c000066400000000000000000001003641501533116600151720ustar00rootroot00000000000000/* blocked.c - generic support for blocking operations like BLPOP & WAIT. * * Copyright (c) 2009-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). * * --------------------------------------------------------------------------- * * API: * * blockClient() set the CLIENT_BLOCKED flag in the client, and set the * specified block type 'btype' filed to one of BLOCKED_* macros. * * unblockClient() unblocks the client doing the following: * 1) It calls the btype-specific function to cleanup the state. * 2) It unblocks the client by unsetting the CLIENT_BLOCKED flag. * 3) It puts the client into a list of just unblocked clients that are * processed ASAP in the beforeSleep() event loop callback, so that * if there is some query buffer to process, we do it. This is also * required because otherwise there is no 'readable' event fired, we * already read the pending commands. We also set the CLIENT_UNBLOCKED * flag to remember the client is in the unblocked_clients list. * * processUnblockedClients() is called inside the beforeSleep() function * to process the query buffer from unblocked clients and remove the clients * from the blocked_clients queue. * * replyToBlockedClientTimedOut() is called by the cron function when * a client blocked reaches the specified timeout (if the timeout is set * to 0, no timeout is processed). * It usually just needs to send a reply to the client. * * When implementing a new type of blocking operation, the implementation * should modify unblockClient() and replyToBlockedClientTimedOut() in order * to handle the btype-specific behavior of this two functions. * If the blocking operation waits for certain keys to change state, the * clusterRedirectBlockedClientIfNeeded() function should also be updated. */ #include "server.h" #include "slowlog.h" #include "latency.h" #include "monotonic.h" /* forward declarations */ static void unblockClientWaitingData(client *c); static void handleClientsBlockedOnKey(readyList *rl); static void unblockClientOnKey(client *c, robj *key); static void moduleUnblockClientOnKey(client *c, robj *key); static void releaseBlockedEntry(client *c, dictEntry *de, int remove_key); void initClientBlockingState(client *c) { c->bstate.btype = BLOCKED_NONE; c->bstate.timeout = 0; c->bstate.keys = dictCreate(&objectKeyHeapPointerValueDictType); c->bstate.numreplicas = 0; c->bstate.reploffset = 0; c->bstate.unblock_on_nokey = 0; c->bstate.async_rm_call_handle = NULL; } /* Block a client for the specific operation type. Once the CLIENT_BLOCKED * flag is set client query buffer is not longer processed, but accumulated, * and will be processed when the client is unblocked. */ void blockClient(client *c, int btype) { /* Master client should never be blocked unless pause or module */ serverAssert(!(c->flags & CLIENT_MASTER && btype != BLOCKED_MODULE && btype != BLOCKED_LAZYFREE && btype != BLOCKED_POSTPONE)); c->flags |= CLIENT_BLOCKED; c->bstate.btype = btype; if (!(c->flags & CLIENT_MODULE)) server.blocked_clients++; /* We count blocked client stats on regular clients and not on module clients */ server.blocked_clients_by_type[btype]++; addClientToTimeoutTable(c); } /* Usually when a client is unblocked due to being blocked while processing some command * he will attempt to reprocess the command which will update the statistics. * However in case the client was timed out or in case of module blocked client is being unblocked * the command will not be reprocessed and we need to make stats update. * This function will make updates to the commandstats, slowlog and monitors.*/ void updateStatsOnUnblock(client *c, long blocked_us, long reply_us, int had_errors){ const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us; c->lastcmd->microseconds += total_cmd_duration; c->lastcmd->calls++; server.stat_numcommands++; if (had_errors) c->lastcmd->failed_calls++; if (server.latency_tracking_enabled) updateCommandLatencyHistogram(&(c->lastcmd->latency_histogram), total_cmd_duration*1000); /* Log the command into the Slow log if needed. */ slowlogPushCurrentCommand(c, c->lastcmd, total_cmd_duration); c->duration = 0; /* Log the reply duration event. */ latencyAddSampleIfNeeded("command-unblocking",reply_us/1000); } /* This function is called in the beforeSleep() function of the event loop * in order to process the pending input buffer of clients that were * unblocked after a blocking operation. */ void processUnblockedClients(void) { listNode *ln; client *c; while (listLength(server.unblocked_clients)) { ln = listFirst(server.unblocked_clients); serverAssert(ln != NULL); c = ln->value; listDelNode(server.unblocked_clients,ln); c->flags &= ~CLIENT_UNBLOCKED; if (c->flags & CLIENT_MODULE) { if (!(c->flags & CLIENT_BLOCKED)) { moduleCallCommandUnblockedHandler(c); } continue; } /* Process remaining data in the input buffer, unless the client * is blocked again. Actually processInputBuffer() checks that the * client is not blocked before to proceed, but things may change and * the code is conceptually more correct this way. */ if (!(c->flags & CLIENT_BLOCKED)) { /* If we have a queued command, execute it now. */ if (processPendingCommandAndInputBuffer(c) == C_ERR) { c = NULL; } } beforeNextClient(c); } } /* This function will schedule the client for reprocessing at a safe time. * * This is useful when a client was blocked for some reason (blocking operation, * CLIENT PAUSE, or whatever), because it may end with some accumulated query * buffer that needs to be processed ASAP: * * 1. When a client is blocked, its readable handler is still active. * 2. However in this case it only gets data into the query buffer, but the * query is not parsed or executed once there is enough to proceed as * usually (because the client is blocked... so we can't execute commands). * 3. When the client is unblocked, without this function, the client would * have to write some query in order for the readable handler to finally * call processQueryBuffer*() on it. * 4. With this function instead we can put the client in a queue that will * process it for queries ready to be executed at a safe time. */ void queueClientForReprocessing(client *c) { /* The client may already be into the unblocked list because of a previous * blocking operation, don't add back it into the list multiple times. */ if (!(c->flags & CLIENT_UNBLOCKED)) { c->flags |= CLIENT_UNBLOCKED; listAddNodeTail(server.unblocked_clients,c); } } /* Unblock a client calling the right function depending on the kind * of operation the client is blocking for. */ void unblockClient(client *c, int queue_for_reprocessing) { if (c->bstate.btype == BLOCKED_LIST || c->bstate.btype == BLOCKED_ZSET || c->bstate.btype == BLOCKED_STREAM) { unblockClientWaitingData(c); } else if (c->bstate.btype == BLOCKED_WAIT || c->bstate.btype == BLOCKED_WAITAOF) { unblockClientWaitingReplicas(c); } else if (c->bstate.btype == BLOCKED_MODULE) { if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c); unblockClientFromModule(c); } else if (c->bstate.btype == BLOCKED_POSTPONE) { listDelNode(server.postponed_clients,c->postponed_list_node); c->postponed_list_node = NULL; } else if (c->bstate.btype == BLOCKED_SHUTDOWN) { /* No special cleanup. */ } else if (c->bstate.btype == BLOCKED_LAZYFREE) { /* No special cleanup. */ } else { serverPanic("Unknown btype in unblockClient()."); } /* Reset the client for a new query, unless the client has pending command to process * or in case a shutdown operation was canceled and we are still in the processCommand sequence */ if (!(c->flags & CLIENT_PENDING_COMMAND) && c->bstate.btype != BLOCKED_SHUTDOWN) { freeClientOriginalArgv(c); /* Clients that are not blocked on keys are not reprocessed so we must * call reqresAppendResponse here (for clients blocked on key, * unblockClientOnKey is called, which eventually calls processCommand, * which calls reqresAppendResponse) */ reqresAppendResponse(c); resetClient(c); } /* Clear the flags, and put the client in the unblocked list so that * we'll process new commands in its query buffer ASAP. */ if (!(c->flags & CLIENT_MODULE)) server.blocked_clients--; /* We count blocked client stats on regular clients and not on module clients */ server.blocked_clients_by_type[c->bstate.btype]--; c->flags &= ~CLIENT_BLOCKED; c->bstate.btype = BLOCKED_NONE; c->bstate.unblock_on_nokey = 0; removeClientFromTimeoutTable(c); if (queue_for_reprocessing) queueClientForReprocessing(c); } /* This function gets called when a blocked client timed out in order to * send it a reply of some kind. After this function is called, * 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 */ } else if (c->bstate.btype == BLOCKED_LIST || c->bstate.btype == BLOCKED_ZSET || c->bstate.btype == BLOCKED_STREAM) { addReplyNullArray(c); updateStatsOnUnblock(c, 0, 0, 0); } else if (c->bstate.btype == BLOCKED_WAIT) { addReplyLongLong(c,replicationCountAcksByOffset(c->bstate.reploffset)); } else if (c->bstate.btype == BLOCKED_WAITAOF) { addReplyArrayLen(c,2); addReplyLongLong(c,server.fsynced_reploff >= c->bstate.reploffset); addReplyLongLong(c,replicationCountAOFAcksByOffset(c->bstate.reploffset)); } else if (c->bstate.btype == BLOCKED_MODULE) { moduleBlockedClientTimedOut(c); } else { serverPanic("Unknown btype in replyToBlockedClientTimedOut()."); } } /* If one or more clients are blocked on the SHUTDOWN command, this function * sends them an error reply and unblocks them. */ void replyToClientsBlockedOnShutdown(void) { if (server.blocked_clients_by_type[BLOCKED_SHUTDOWN] == 0) return; listNode *ln; listIter li; listRewind(server.clients, &li); while((ln = listNext(&li))) { client *c = listNodeValue(ln); if (c->flags & CLIENT_BLOCKED && c->bstate.btype == BLOCKED_SHUTDOWN) { addReplyError(c, "Errors trying to SHUTDOWN. Check logs."); unblockClient(c, 1); } } } /* Mass-unblock clients because something changed in the instance that makes * blocking no longer safe. For example clients blocked in list operations * in an instance which turns from master to slave is unsafe, so this function * is called when a master turns into a slave. * * The semantics is to send an -UNBLOCKED error to the client, disconnecting * it at the same time. */ void disconnectAllBlockedClients(void) { listNode *ln; listIter li; listRewind(server.clients,&li); while((ln = listNext(&li))) { client *c = listNodeValue(ln); if (c->flags & CLIENT_BLOCKED) { /* POSTPONEd clients are an exception, when they'll be unblocked, the * command processing will start from scratch, and the command will * be either executed or rejected. (unlike LIST blocked clients for * which the command is already in progress in a way. */ if (c->bstate.btype == BLOCKED_POSTPONE) continue; if (c->bstate.btype == BLOCKED_LAZYFREE) { addReply(c, shared.ok); /* No reason lazy-free to fail */ updateStatsOnUnblock(c, 0, 0, 0); c->flags &= ~CLIENT_PENDING_COMMAND; unblockClient(c, 1); } else { unblockClientOnError(c, "-UNBLOCKED force unblock from blocking operation, " "instance state changed (master -> replica?)"); } c->flags |= CLIENT_CLOSE_AFTER_REPLY; } } } /* This function should be called by Redis every time a single command, * a MULTI/EXEC block, or a Lua script, terminated its execution after * being called by a client. It handles serving clients blocked in all scenarios * where a specific key access requires to block until that key is available. * * All the keys with at least one client blocked that are signaled as ready * are accumulated into the server.ready_keys list. This function will run * the list and will serve clients accordingly. * Note that the function will iterate again and again (for example as a result of serving BLMOVE * we can have new blocking clients to serve because of the PUSH side of BLMOVE.) * * This function is normally "fair", that is, it will serve clients * using a FIFO behavior. However this fairness is violated in certain * edge cases, that is, when we have clients blocked at the same time * in a sorted set and in a list, for the same key (a very odd thing to * do client side, indeed!). Because mismatching clients (blocking for * a different type compared to the current key type) are moved in the * other side of the linked list. However as long as the key starts to * be used only for a single type, like virtually any Redis application will * do, the function is already fair. */ void handleClientsBlockedOnKeys(void) { /* In case we are already in the process of unblocking clients we should * not make a recursive call, in order to prevent breaking fairness. */ static int in_handling_blocked_clients = 0; if (in_handling_blocked_clients) return; in_handling_blocked_clients = 1; /* This function is called only when also_propagate is in its basic state * (i.e. not from call(), module context, etc.) */ serverAssert(server.also_propagate.numops == 0); /* If a command being unblocked causes another command to get unblocked, * like a BLMOVE would do, then the new unblocked command will get processed * right away rather than wait for later. */ while(listLength(server.ready_keys) != 0) { list *l; /* Point server.ready_keys to a fresh list and save the current one * locally. This way as we run the old list we are free to call * signalKeyAsReady() that may push new elements in server.ready_keys * when handling clients blocked into BLMOVE. */ l = server.ready_keys; server.ready_keys = listCreate(); while(listLength(l) != 0) { listNode *ln = listFirst(l); readyList *rl = ln->value; /* First of all remove this key from db->ready_keys so that * we can safely call signalKeyAsReady() against this key. */ dictDelete(rl->db->ready_keys,rl->key); handleClientsBlockedOnKey(rl); /* Free this item. */ decrRefCount(rl->key); zfree(rl); listDelNode(l,ln); } listRelease(l); /* We have the new list on place at this point. */ } in_handling_blocked_clients = 0; } /* Set a client in blocking mode for the specified key, with the specified timeout. * The 'type' argument is BLOCKED_LIST,BLOCKED_ZSET or BLOCKED_STREAM depending on the kind of operation we are * waiting for an empty key in order to awake the client. The client is blocked * for all the 'numkeys' keys as in the 'keys' argument. * The client will unblocked as soon as one of the keys in 'keys' value was updated. * the parameter unblock_on_nokey can be used to force client to be unblocked even in the case the key * is updated to become unavailable, either by type change (override), deletion or swapdb */ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, int unblock_on_nokey) { dictEntry *db_blocked_entry, *db_blocked_existing_entry, *client_blocked_entry; list *l; int j; if (!(c->flags & CLIENT_REPROCESSING_COMMAND)) { /* If the client is re-processing the command, we do not set the timeout * because we need to retain the client's original timeout. */ c->bstate.timeout = timeout; } for (j = 0; j < numkeys; j++) { /* If the key already exists in the dictionary ignore it. */ if (!(client_blocked_entry = dictAddRaw(c->bstate.keys,keys[j],NULL))) { continue; } incrRefCount(keys[j]); /* And in the other "side", to map keys -> clients */ db_blocked_entry = dictAddRaw(c->db->blocking_keys,keys[j], &db_blocked_existing_entry); /* In case key[j] did not have blocking clients yet, we need to create a new list */ if (db_blocked_entry != NULL) { l = listCreate(); dictSetVal(c->db->blocking_keys, db_blocked_entry, l); incrRefCount(keys[j]); } else { l = dictGetVal(db_blocked_existing_entry); } listAddNodeTail(l,c); dictSetVal(c->bstate.keys,client_blocked_entry,listLast(l)); /* We need to add the key to blocking_keys_unblock_on_nokey, if the client * wants to be awakened if key is deleted (like XREADGROUP) */ if (unblock_on_nokey) { db_blocked_entry = dictAddRaw(c->db->blocking_keys_unblock_on_nokey, keys[j], &db_blocked_existing_entry); if (db_blocked_entry) { incrRefCount(keys[j]); dictSetUnsignedIntegerVal(db_blocked_entry, 1); } else { dictIncrUnsignedIntegerVal(db_blocked_existing_entry, 1); } } } c->bstate.unblock_on_nokey = unblock_on_nokey; /* Currently we assume key blocking will require reprocessing the command. * However in case of modules, they have a different way to handle the reprocessing * which does not require setting the pending command flag */ if (btype != BLOCKED_MODULE) c->flags |= CLIENT_PENDING_COMMAND; blockClient(c,btype); } /* Helper function to unblock a client that's waiting in a blocking operation such as BLPOP. * Internal function for unblockClient() */ static void unblockClientWaitingData(client *c) { dictEntry *de; dictIterator *di; if (dictSize(c->bstate.keys) == 0) return; di = dictGetIterator(c->bstate.keys); /* The client may wait for multiple keys, so unblock it for every key. */ while((de = dictNext(di)) != NULL) { releaseBlockedEntry(c, de, 0); } dictReleaseIterator(di); dictEmpty(c->bstate.keys, NULL); } static blocking_type getBlockedTypeByType(int type) { switch (type) { case OBJ_LIST: return BLOCKED_LIST; case OBJ_ZSET: return BLOCKED_ZSET; case OBJ_MODULE: return BLOCKED_MODULE; case OBJ_STREAM: return BLOCKED_STREAM; default: return BLOCKED_NONE; } } /* If the specified key has clients blocked waiting for list pushes, this * function will put the key reference into the server.ready_keys list. * Note that db->ready_keys is a hash table that allows us to avoid putting * the same key again and again in the list in case of multiple pushes * made by a script or in the context of MULTI/EXEC. * * The list will be finally processed by handleClientsBlockedOnKeys() */ static void signalKeyAsReadyLogic(redisDb *db, robj *key, int type, int deleted) { readyList *rl; /* Quick returns. */ int btype = getBlockedTypeByType(type); if (btype == BLOCKED_NONE) { /* The type can never block. */ return; } if (!server.blocked_clients_by_type[btype] && !server.blocked_clients_by_type[BLOCKED_MODULE]) { /* No clients block on this type. Note: Blocked modules are represented * by BLOCKED_MODULE, even if the intention is to wake up by normal * types (list, zset, stream), so we need to check that there are no * blocked modules before we do a quick return here. */ return; } if (deleted) { /* Key deleted and no clients blocking for this key? No need to queue it. */ if (dictFind(db->blocking_keys_unblock_on_nokey,key) == NULL) return; /* Note: if we made it here it means the key is also present in db->blocking_keys */ } else { /* No clients blocking for this key? No need to queue it. */ if (dictFind(db->blocking_keys,key) == NULL) return; } dictEntry *de, *existing; de = dictAddRaw(db->ready_keys, key, &existing); if (de) { /* We add the key in the db->ready_keys dictionary in order * to avoid adding it multiple times into a list with a simple O(1) * check. */ incrRefCount(key); } else { /* Key was already signaled? No need to queue it again. */ return; } /* Ok, we need to queue this key into server.ready_keys. */ rl = zmalloc(sizeof(*rl)); rl->key = key; rl->db = db; incrRefCount(key); listAddNodeTail(server.ready_keys,rl); } /* Helper function to wrap the logic of removing a client blocked key entry * In this case we would like to do the following: * 1. unlink the client from the global DB locked client list * 2. remove the entry from the global db blocking list in case the list is empty * 3. in case the global list is empty, also remove the key from the global dict of keys * which should trigger unblock on key deletion * 4. remove key from the client blocking keys list - NOTE, since client can be blocked on lots of keys, * but unblocked when only one of them is triggered, we would like to avoid deleting each key separately * and instead clear the dictionary in one-shot. this is why the remove_key argument is provided * to support this logic in unblockClientWaitingData */ static void releaseBlockedEntry(client *c, dictEntry *de, int remove_key) { list *l; listNode *pos; void *key; dictEntry *unblock_on_nokey_entry; key = dictGetKey(de); pos = dictGetVal(de); /* Remove this client from the list of clients waiting for this key. */ l = dictFetchValue(c->db->blocking_keys, key); serverAssertWithInfo(c,key,l != NULL); listUnlinkNode(l,pos); /* If the list is empty we need to remove it to avoid wasting memory * We will also remove the key (if exists) from the blocking_keys_unblock_on_nokey dict. * However, in case the list is not empty, we will have to still perform reference accounting * on the blocking_keys_unblock_on_nokey and delete the entry in case of zero reference. * Why? because it is possible that some more clients are blocked on the same key but without * require to be triggered on key deletion, we do not want these to be later triggered by the * signalDeletedKeyAsReady. */ if (listLength(l) == 0) { dictDelete(c->db->blocking_keys, key); dictDelete(c->db->blocking_keys_unblock_on_nokey,key); } else if (c->bstate.unblock_on_nokey) { unblock_on_nokey_entry = dictFind(c->db->blocking_keys_unblock_on_nokey,key); /* it is not possible to have a client blocked on nokey with no matching entry */ serverAssertWithInfo(c,key,unblock_on_nokey_entry != NULL); if (!dictIncrUnsignedIntegerVal(unblock_on_nokey_entry, -1)) { /* in case the count is zero, we can delete the entry */ dictDelete(c->db->blocking_keys_unblock_on_nokey,key); } } if (remove_key) dictDelete(c->bstate.keys, key); } void signalKeyAsReady(redisDb *db, robj *key, int type) { signalKeyAsReadyLogic(db, key, type, 0); } void signalDeletedKeyAsReady(redisDb *db, robj *key, int type) { signalKeyAsReadyLogic(db, key, type, 1); } /* Helper function for handleClientsBlockedOnKeys(). This function is called * whenever a key is ready. we iterate over all the clients blocked on this key * and try to re-execute the command (in case the key is still available). */ static void handleClientsBlockedOnKey(readyList *rl) { /* We serve clients in the same order they blocked for * this key, from the first blocked to the last. */ dictEntry *de = dictFind(rl->db->blocking_keys,rl->key); if (de) { list *clients = dictGetVal(de); listNode *ln; listIter li; listRewind(clients,&li); /* Avoid processing more than the initial count so that we're not stuck * in an endless loop in case the reprocessing of the command blocks again. */ long count = listLength(clients); while ((ln = listNext(&li)) && count--) { client *receiver = listNodeValue(ln); robj *o = lookupKeyReadWithFlags(rl->db, rl->key, LOOKUP_NOEFFECTS); /* 1. In case new key was added/touched we need to verify it satisfy the * blocked type, since we might process the wrong key type. * 2. We want to serve clients blocked on module keys * regardless of the object type: we don't know what the * module is trying to accomplish right now. * 3. In case of XREADGROUP call we will want to unblock on any change in object type * or in case the key was deleted, since the group is no longer valid. */ if ((o != NULL && (receiver->bstate.btype == getBlockedTypeByType(o->type))) || (o != NULL && (receiver->bstate.btype == BLOCKED_MODULE)) || (receiver->bstate.unblock_on_nokey)) { if (receiver->bstate.btype != BLOCKED_MODULE) unblockClientOnKey(receiver, rl->key); else moduleUnblockClientOnKey(receiver, rl->key); } } } } /* block a client due to wait command */ void blockForReplication(client *c, mstime_t timeout, long long offset, long numreplicas) { c->bstate.timeout = timeout; c->bstate.reploffset = offset; c->bstate.numreplicas = numreplicas; listAddNodeHead(server.clients_waiting_acks,c); blockClient(c,BLOCKED_WAIT); } /* block a client due to waitaof command */ void blockForAofFsync(client *c, mstime_t timeout, long long offset, int numlocal, long numreplicas) { c->bstate.timeout = timeout; c->bstate.reploffset = offset; c->bstate.numreplicas = numreplicas; c->bstate.numlocal = numlocal; listAddNodeHead(server.clients_waiting_acks,c); blockClient(c,BLOCKED_WAITAOF); } /* Postpone client from executing a command. For example the server might be busy * requesting to avoid processing clients commands which will be processed later * when the it is ready to accept them. */ void blockPostponeClient(client *c) { c->bstate.timeout = 0; blockClient(c,BLOCKED_POSTPONE); listAddNodeTail(server.postponed_clients, c); c->postponed_list_node = listLast(server.postponed_clients); /* Mark this client to execute its command */ c->flags |= CLIENT_PENDING_COMMAND; } /* Block client due to shutdown command */ void blockClientShutdown(client *c) { blockClient(c, BLOCKED_SHUTDOWN); } /* Unblock a client once a specific key became available for it. * This function will remove the client from the list of clients blocked on this key * and also remove the key from the dictionary of keys this client is blocked on. * in case the client has a command pending it will process it immediately. */ static void unblockClientOnKey(client *c, robj *key) { dictEntry *de; de = dictFind(c->bstate.keys, key); releaseBlockedEntry(c, de, 1); /* Only in case of blocking API calls, we might be blocked on several keys. however we should force unblock the entire blocking keys */ serverAssert(c->bstate.btype == BLOCKED_STREAM || c->bstate.btype == BLOCKED_LIST || c->bstate.btype == BLOCKED_ZSET); /* We need to unblock the client before calling processCommandAndResetClient * because it checks the CLIENT_BLOCKED flag */ unblockClient(c, 0); /* In case this client was blocked on keys during command * we need to re process the command again */ if (c->flags & CLIENT_PENDING_COMMAND) { c->flags &= ~CLIENT_PENDING_COMMAND; /* We want the command processing and the unblock handler (see RM_Call 'K' option) * to run atomically, this is why we must enter the execution unit here before * running the command, and exit the execution unit after calling the unblock handler (if exists). * Notice that we also must set the current client so it will be available * when we will try to send the client side caching notification (done on 'afterCommand'). */ client *old_client = server.current_client; server.current_client = c; enterExecutionUnit(1, 0); processCommandAndResetClient(c); if (!(c->flags & CLIENT_BLOCKED)) { if (c->flags & CLIENT_MODULE) { moduleCallCommandUnblockedHandler(c); } else { queueClientForReprocessing(c); } } exitExecutionUnit(); afterCommand(c); server.current_client = old_client; } } /* Unblock a client blocked on the specific key from module context. * This function will try to serve the module call, and in case it succeeds, * it will add the client to the list of module unblocked clients which will * be processed in moduleHandleBlockedClients. */ static void moduleUnblockClientOnKey(client *c, robj *key) { long long prev_error_replies = server.stat_total_error_replies; client *old_client = server.current_client; server.current_client = c; monotime replyTimer; elapsedStart(&replyTimer); if (moduleTryServeClientBlockedOnKey(c, key)) { updateStatsOnUnblock(c, 0, elapsedUs(replyTimer), server.stat_total_error_replies != prev_error_replies); moduleUnblockClient(c); } /* We need to call afterCommand even if the client was not unblocked * in order to propagate any changes that could have been done inside * moduleTryServeClientBlockedOnKey */ afterCommand(c); server.current_client = old_client; } /* Unblock a client which is currently Blocked on and provided a timeout. * The implementation will first reply to the blocked client with null response * or, in case of module blocked client the timeout callback will be used. * In this case since we might have a command pending * we want to remove the pending flag to indicate we already responded to the * command with timeout reply. */ void unblockClientOnTimeout(client *c) { /* The client has been unlocked (in the moduleUnblocked list), return ASAP. */ if (c->bstate.btype == BLOCKED_MODULE && isModuleClientUnblocked(c)) return; replyToBlockedClientTimedOut(c); if (c->flags & CLIENT_PENDING_COMMAND) c->flags &= ~CLIENT_PENDING_COMMAND; unblockClient(c, 1); } /* Unblock a client which is currently Blocked with error. * If err_str is provided it will be used to reply to the blocked client */ void unblockClientOnError(client *c, const char *err_str) { if (err_str) addReplyError(c, err_str); updateStatsOnUnblock(c, 0, 0, 1); if (c->flags & CLIENT_PENDING_COMMAND) c->flags &= ~CLIENT_PENDING_COMMAND; unblockClient(c, 1); } void blockedBeforeSleep(void) { /* Handle precise timeouts of blocked clients. */ handleBlockedClientsTimeout(); /* Unblock all the clients blocked for synchronous replication * in WAIT or WAITAOF. */ if (listLength(server.clients_waiting_acks)) processClientsWaitingReplicas(); /* Try to process blocked clients every once in while. * * Example: A module calls RM_SignalKeyAsReady from within a timer callback * (So we don't visit processCommand() at all). * * This may unblock clients, so must be done before processUnblockedClients */ handleClientsBlockedOnKeys(); /* Check if there are clients unblocked by modules that implement * blocking commands. */ if (moduleCount()) moduleHandleBlockedClients(); /* Try to process pending commands for clients that were just unblocked. */ if (listLength(server.unblocked_clients)) processUnblockedClients(); } redis-8.0.2/src/call_reply.c000066400000000000000000000466431501533116600157260ustar00rootroot00000000000000/* * Copyright (c) 2009-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 "server.h" #include "call_reply.h" #define REPLY_FLAG_ROOT (1<<0) #define REPLY_FLAG_PARSED (1<<1) #define REPLY_FLAG_RESP3 (1<<2) /* -------------------------------------------------------- * An opaque struct used to parse a RESP protocol reply and * represent it. Used when parsing replies such as in RM_Call * or Lua scripts. * -------------------------------------------------------- */ struct CallReply { void *private_data; sds original_proto; /* Available only for root reply. */ const char *proto; size_t proto_len; int type; /* REPLY_... */ int flags; /* REPLY_FLAG... */ size_t len; /* Length of a string, or the number elements in an array. */ union { const char *str; /* String pointer for string and error replies. This * does not need to be freed, always points inside * a reply->proto buffer of the reply object or, in * case of array elements, of parent reply objects. */ struct { const char *str; const char *format; } verbatim_str; /* Reply value for verbatim string */ long long ll; /* Reply value for integer reply. */ double d; /* Reply value for double reply. */ struct CallReply *array; /* Array of sub-reply elements. used for set, array, map, and attribute */ } val; list *deferred_error_list; /* list of errors in sds form or NULL */ struct CallReply *attribute; /* attribute reply, NULL if not exists */ }; static void callReplySetSharedData(CallReply *rep, int type, const char *proto, size_t proto_len, int extra_flags) { rep->type = type; rep->proto = proto; rep->proto_len = proto_len; rep->flags |= extra_flags; } static void callReplyNull(void *ctx, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, REPLY_FLAG_RESP3); } static void callReplyNullBulkString(void *ctx, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, 0); } static void callReplyNullArray(void *ctx, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_NULL, proto, proto_len, 0); } static void callReplyBulkString(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_STRING, proto, proto_len, 0); rep->len = len; rep->val.str = str; } static void callReplyError(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_ERROR, proto, proto_len, 0); rep->len = len; rep->val.str = str; } static void callReplySimpleStr(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_STRING, proto, proto_len, 0); rep->len = len; rep->val.str = str; } static void callReplyLong(void *ctx, long long val, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_INTEGER, proto, proto_len, 0); rep->val.ll = val; } static void callReplyDouble(void *ctx, double val, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_DOUBLE, proto, proto_len, REPLY_FLAG_RESP3); rep->val.d = val; } static void callReplyVerbatimString(void *ctx, const char *format, const char *str, size_t len, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_VERBATIM_STRING, proto, proto_len, REPLY_FLAG_RESP3); rep->len = len; rep->val.verbatim_str.str = str; rep->val.verbatim_str.format = format; } static void callReplyBigNumber(void *ctx, const char *str, size_t len, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_BIG_NUMBER, proto, proto_len, REPLY_FLAG_RESP3); rep->len = len; rep->val.str = str; } static void callReplyBool(void *ctx, int val, const char *proto, size_t proto_len) { CallReply *rep = ctx; callReplySetSharedData(rep, REDISMODULE_REPLY_BOOL, proto, proto_len, REPLY_FLAG_RESP3); rep->val.ll = val; } static void callReplyParseCollection(ReplyParser *parser, CallReply *rep, size_t len, const char *proto, size_t elements_per_entry) { rep->len = len; rep->val.array = zcalloc(elements_per_entry * len * sizeof(CallReply)); for (size_t i = 0; i < len * elements_per_entry; i += elements_per_entry) { for (size_t j = 0 ; j < elements_per_entry ; ++j) { rep->val.array[i + j].private_data = rep->private_data; parseReply(parser, rep->val.array + i + j); rep->val.array[i + j].flags |= REPLY_FLAG_PARSED; if (rep->val.array[i + j].flags & REPLY_FLAG_RESP3) { /* If one of the sub-replies is RESP3, then the current reply is also RESP3. */ rep->flags |= REPLY_FLAG_RESP3; } } } rep->proto = proto; rep->proto_len = parser->curr_location - proto; } static void callReplyAttribute(ReplyParser *parser, void *ctx, size_t len, const char *proto) { CallReply *rep = ctx; rep->attribute = zcalloc(sizeof(CallReply)); /* Continue parsing the attribute reply */ rep->attribute->len = len; rep->attribute->type = REDISMODULE_REPLY_ATTRIBUTE; callReplyParseCollection(parser, rep->attribute, len, proto, 2); rep->attribute->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_RESP3; rep->attribute->private_data = rep->private_data; /* Continue parsing the reply */ parseReply(parser, rep); /* In this case we need to fix the proto address and len, it should start from the attribute */ rep->proto = proto; rep->proto_len = parser->curr_location - proto; rep->flags |= REPLY_FLAG_RESP3; } static void callReplyArray(ReplyParser *parser, void *ctx, size_t len, const char *proto) { CallReply *rep = ctx; rep->type = REDISMODULE_REPLY_ARRAY; callReplyParseCollection(parser, rep, len, proto, 1); } static void callReplySet(ReplyParser *parser, void *ctx, size_t len, const char *proto) { CallReply *rep = ctx; rep->type = REDISMODULE_REPLY_SET; callReplyParseCollection(parser, rep, len, proto, 1); rep->flags |= REPLY_FLAG_RESP3; } static void callReplyMap(ReplyParser *parser, void *ctx, size_t len, const char *proto) { CallReply *rep = ctx; rep->type = REDISMODULE_REPLY_MAP; callReplyParseCollection(parser, rep, len, proto, 2); rep->flags |= REPLY_FLAG_RESP3; } static void callReplyParseError(void *ctx) { CallReply *rep = ctx; rep->type = REDISMODULE_REPLY_UNKNOWN; } /* Recursively free the current call reply and its sub-replies. */ static void freeCallReplyInternal(CallReply *rep) { if (rep->type == REDISMODULE_REPLY_ARRAY || rep->type == REDISMODULE_REPLY_SET) { for (size_t i = 0 ; i < rep->len ; ++i) { freeCallReplyInternal(rep->val.array + i); } zfree(rep->val.array); } if (rep->type == REDISMODULE_REPLY_MAP || rep->type == REDISMODULE_REPLY_ATTRIBUTE) { for (size_t i = 0 ; i < rep->len ; ++i) { freeCallReplyInternal(rep->val.array + i * 2); freeCallReplyInternal(rep->val.array + i * 2 + 1); } zfree(rep->val.array); } if (rep->attribute) { freeCallReplyInternal(rep->attribute); zfree(rep->attribute); } } /* Free the given call reply and its children (in case of nested reply) recursively. * If private data was set when the CallReply was created it will not be freed, as it's * the caller's responsibility to free it before calling freeCallReply(). */ void freeCallReply(CallReply *rep) { if (!(rep->flags & REPLY_FLAG_ROOT)) { return; } if (rep->flags & REPLY_FLAG_PARSED) { if (rep->type == REDISMODULE_REPLY_PROMISE) { zfree(rep); return; } freeCallReplyInternal(rep); } sdsfree(rep->original_proto); if (rep->deferred_error_list) listRelease(rep->deferred_error_list); zfree(rep); } CallReply *callReplyCreatePromise(void *private_data) { CallReply *res = zmalloc(sizeof(*res)); res->type = REDISMODULE_REPLY_PROMISE; /* Mark the reply as parsed so there will be not attempt to parse * it when calling reply API such as freeCallReply. * Also mark the reply as root so freeCallReply will not ignore it. */ res->flags |= REPLY_FLAG_PARSED | REPLY_FLAG_ROOT; res->private_data = private_data; return res; } static const ReplyParserCallbacks DefaultParserCallbacks = { .null_callback = callReplyNull, .bulk_string_callback = callReplyBulkString, .null_bulk_string_callback = callReplyNullBulkString, .null_array_callback = callReplyNullArray, .error_callback = callReplyError, .simple_str_callback = callReplySimpleStr, .long_callback = callReplyLong, .array_callback = callReplyArray, .set_callback = callReplySet, .map_callback = callReplyMap, .double_callback = callReplyDouble, .bool_callback = callReplyBool, .big_number_callback = callReplyBigNumber, .verbatim_string_callback = callReplyVerbatimString, .attribute_callback = callReplyAttribute, .error = callReplyParseError, }; /* Parse the buffer located in rep->original_proto and update the CallReply * structure to represent its contents. */ static void callReplyParse(CallReply *rep) { if (rep->flags & REPLY_FLAG_PARSED) { return; } ReplyParser parser = {.curr_location = rep->proto, .callbacks = DefaultParserCallbacks}; parseReply(&parser, rep); rep->flags |= REPLY_FLAG_PARSED; } /* Return the call reply type (REDISMODULE_REPLY_...). */ int callReplyType(CallReply *rep) { if (!rep) return REDISMODULE_REPLY_UNKNOWN; callReplyParse(rep); return rep->type; } /* Return reply string as buffer and len. Applicable to: * - REDISMODULE_REPLY_STRING * - REDISMODULE_REPLY_ERROR * * The return value is borrowed from CallReply, so it must not be freed * explicitly or used after CallReply itself is freed. * * The returned value is not NULL terminated and its length is returned by * reference through len, which must not be NULL. */ const char *callReplyGetString(CallReply *rep, size_t *len) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_STRING && rep->type != REDISMODULE_REPLY_ERROR) return NULL; if (len) *len = rep->len; return rep->val.str; } /* Return a long long reply value. Applicable to: * - REDISMODULE_REPLY_INTEGER */ long long callReplyGetLongLong(CallReply *rep) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_INTEGER) return LLONG_MIN; return rep->val.ll; } /* Return a double reply value. Applicable to: * - REDISMODULE_REPLY_DOUBLE */ double callReplyGetDouble(CallReply *rep) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_DOUBLE) return LLONG_MIN; return rep->val.d; } /* Return a reply Boolean value. Applicable to: * - REDISMODULE_REPLY_BOOL */ int callReplyGetBool(CallReply *rep) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_BOOL) return INT_MIN; return rep->val.ll; } /* Return reply length. Applicable to: * - REDISMODULE_REPLY_STRING * - REDISMODULE_REPLY_ERROR * - REDISMODULE_REPLY_ARRAY * - REDISMODULE_REPLY_SET * - REDISMODULE_REPLY_MAP * - REDISMODULE_REPLY_ATTRIBUTE */ size_t callReplyGetLen(CallReply *rep) { callReplyParse(rep); switch(rep->type) { case REDISMODULE_REPLY_STRING: case REDISMODULE_REPLY_ERROR: case REDISMODULE_REPLY_ARRAY: case REDISMODULE_REPLY_SET: case REDISMODULE_REPLY_MAP: case REDISMODULE_REPLY_ATTRIBUTE: return rep->len; default: return 0; } } static CallReply *callReplyGetCollectionElement(CallReply *rep, size_t idx, int elements_per_entry) { if (idx >= rep->len * elements_per_entry) return NULL; // real len is rep->len * elements_per_entry return rep->val.array+idx; } /* Return a reply array element at a given index. Applicable to: * - REDISMODULE_REPLY_ARRAY * * The return value is borrowed from CallReply, so it must not be freed * explicitly or used after CallReply itself is freed. */ CallReply *callReplyGetArrayElement(CallReply *rep, size_t idx) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_ARRAY) return NULL; return callReplyGetCollectionElement(rep, idx, 1); } /* Return a reply set element at a given index. Applicable to: * - REDISMODULE_REPLY_SET * * The return value is borrowed from CallReply, so it must not be freed * explicitly or used after CallReply itself is freed. */ CallReply *callReplyGetSetElement(CallReply *rep, size_t idx) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_SET) return NULL; return callReplyGetCollectionElement(rep, idx, 1); } static int callReplyGetMapElementInternal(CallReply *rep, size_t idx, CallReply **key, CallReply **val, int type) { callReplyParse(rep); if (rep->type != type) return C_ERR; if (idx >= rep->len) return C_ERR; if (key) *key = callReplyGetCollectionElement(rep, idx * 2, 2); if (val) *val = callReplyGetCollectionElement(rep, idx * 2 + 1, 2); return C_OK; } /* Retrieve a map reply key and value at a given index. Applicable to: * - REDISMODULE_REPLY_MAP * * The key and value are returned by reference through key and val, * which may also be NULL if not needed. * * Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out * of range. * * The returned values are borrowed from CallReply, so they must not be freed * explicitly or used after CallReply itself is freed. */ int callReplyGetMapElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) { return callReplyGetMapElementInternal(rep, idx, key, val, REDISMODULE_REPLY_MAP); } /* Return reply attribute, or NULL if it does not exist. Applicable to all replies. * * The returned values are borrowed from CallReply, so they must not be freed * explicitly or used after CallReply itself is freed. */ CallReply *callReplyGetAttribute(CallReply *rep) { return rep->attribute; } /* Retrieve attribute reply key and value at a given index. Applicable to: * - REDISMODULE_REPLY_ATTRIBUTE * * The key and value are returned by reference through key and val, * which may also be NULL if not needed. * * Returns C_OK on success or C_ERR if reply type mismatches, or if idx is out * of range. * * The returned values are borrowed from CallReply, so they must not be freed * explicitly or used after CallReply itself is freed. */ int callReplyGetAttributeElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val) { return callReplyGetMapElementInternal(rep, idx, key, val, REDISMODULE_REPLY_MAP); } /* Return a big number reply value. Applicable to: * - REDISMODULE_REPLY_BIG_NUMBER * * The returned values are borrowed from CallReply, so they must not be freed * explicitly or used after CallReply itself is freed. * * The return value is guaranteed to be a big number, as described in the RESP3 * protocol specifications. * * The returned value is not NULL terminated and its length is returned by * reference through len, which must not be NULL. */ const char *callReplyGetBigNumber(CallReply *rep, size_t *len) { callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_BIG_NUMBER) return NULL; *len = rep->len; return rep->val.str; } /* Return a verbatim string reply value. Applicable to: * - REDISMODULE_REPLY_VERBATIM_STRING * * If format is non-NULL, the verbatim reply format is also returned by value. * * The optional output argument can be given to get a verbatim reply * format, or can be set NULL if not needed. * * The return value is borrowed from CallReply, so it must not be freed * explicitly or used after CallReply itself is freed. * * The returned value is not NULL terminated and its length is returned by * reference through len, which must not be NULL. */ const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **format){ callReplyParse(rep); if (rep->type != REDISMODULE_REPLY_VERBATIM_STRING) return NULL; *len = rep->len; if (format) *format = rep->val.verbatim_str.format; return rep->val.verbatim_str.str; } /* Return the current reply blob. * * The return value is borrowed from CallReply, so it must not be freed * explicitly or used after CallReply itself is freed. */ const char *callReplyGetProto(CallReply *rep, size_t *proto_len) { *proto_len = rep->proto_len; return rep->proto; } /* Return CallReply private data, as set by the caller on callReplyCreate(). */ void *callReplyGetPrivateData(CallReply *rep) { return rep->private_data; } /* Return true if the reply or one of it sub-replies is RESP3 formatted. */ int callReplyIsResp3(CallReply *rep) { return rep->flags & REPLY_FLAG_RESP3; } /* Returns a list of errors in sds form, or NULL. */ list *callReplyDeferredErrorList(CallReply *rep) { return rep->deferred_error_list; } /* Create a new CallReply struct from the reply blob. * * The function will own the reply blob, so it must not be used or freed by * the caller after passing it to this function. * * The reply blob will be freed when the returned CallReply struct is later * freed using freeCallReply(). * * The deferred_error_list is an optional list of errors that are present * in the reply blob, if given, this function will take ownership on it. * * The private_data is optional and can later be accessed using * callReplyGetPrivateData(). * * NOTE: The parser used for parsing the reply and producing CallReply is * designed to handle valid replies created by Redis itself. IT IS NOT * DESIGNED TO HANDLE USER INPUT and using it to parse invalid replies is * unsafe. */ CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data) { CallReply *res = zmalloc(sizeof(*res)); res->flags = REPLY_FLAG_ROOT; res->original_proto = reply; res->proto = reply; res->proto_len = sdslen(reply); res->private_data = private_data; res->attribute = NULL; res->deferred_error_list = deferred_error_list; return res; } /* Create a new CallReply struct from the reply blob representing an error message. * Automatically creating deferred_error_list and set a copy of the reply in it. * Refer to callReplyCreate for detailed explanation. * Reply string can come in one of two forms: * 1. A protocol reply starting with "-CODE" and ending with "\r\n" * 2. A plain string, in which case this function adds the protocol header and footer. */ CallReply *callReplyCreateError(sds reply, void *private_data) { sds err_buff = reply; if (err_buff[0] != '-') { err_buff = sdscatfmt(sdsempty(), "-ERR %S\r\n", reply); sdsfree(reply); } list *deferred_error_list = listCreate(); listSetFreeMethod(deferred_error_list, sdsfreegeneric); listAddNodeTail(deferred_error_list, sdsnew(err_buff)); return callReplyCreate(err_buff, deferred_error_list, private_data); } redis-8.0.2/src/call_reply.h000066400000000000000000000033001501533116600157120ustar00rootroot00000000000000/* * Copyright (c) 2009-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). */ #ifndef SRC_CALL_REPLY_H_ #define SRC_CALL_REPLY_H_ #include "resp_parser.h" typedef struct CallReply CallReply; typedef void (*RedisModuleOnUnblocked)(void *ctx, CallReply *reply, void *private_data); CallReply *callReplyCreate(sds reply, list *deferred_error_list, void *private_data); CallReply *callReplyCreateError(sds reply, void *private_data); int callReplyType(CallReply *rep); const char *callReplyGetString(CallReply *rep, size_t *len); long long callReplyGetLongLong(CallReply *rep); double callReplyGetDouble(CallReply *rep); int callReplyGetBool(CallReply *rep); size_t callReplyGetLen(CallReply *rep); CallReply *callReplyGetArrayElement(CallReply *rep, size_t idx); CallReply *callReplyGetSetElement(CallReply *rep, size_t idx); int callReplyGetMapElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val); CallReply *callReplyGetAttribute(CallReply *rep); int callReplyGetAttributeElement(CallReply *rep, size_t idx, CallReply **key, CallReply **val); const char *callReplyGetBigNumber(CallReply *rep, size_t *len); const char *callReplyGetVerbatim(CallReply *rep, size_t *len, const char **format); const char *callReplyGetProto(CallReply *rep, size_t *len); void *callReplyGetPrivateData(CallReply *rep); int callReplyIsResp3(CallReply *rep); list *callReplyDeferredErrorList(CallReply *rep); void freeCallReply(CallReply *rep); CallReply *callReplyCreatePromise(void *private_data); #endif /* SRC_CALL_REPLY_H_ */ redis-8.0.2/src/childinfo.c000066400000000000000000000135121501533116600155240ustar00rootroot00000000000000/* * Copyright (c) 2016-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 "server.h" #include #include typedef struct { size_t keys; size_t cow; monotime cow_updated; double progress; childInfoType information_type; /* Type of information */ } child_info_data; /* Open a child-parent channel used in order to move information about the * RDB / AOF saving process from the child to the parent (for instance * the amount of copy on write memory used) */ void openChildInfoPipe(void) { if (anetPipe(server.child_info_pipe, O_NONBLOCK, 0) == -1) { /* On error our two file descriptors should be still set to -1, * but we call anyway closeChildInfoPipe() since can't hurt. */ closeChildInfoPipe(); } else { server.child_info_nread = 0; } } /* Close the pipes opened with openChildInfoPipe(). */ void closeChildInfoPipe(void) { if (server.child_info_pipe[0] != -1 || server.child_info_pipe[1] != -1) { close(server.child_info_pipe[0]); close(server.child_info_pipe[1]); server.child_info_pipe[0] = -1; server.child_info_pipe[1] = -1; server.child_info_nread = 0; } } /* Send save data to parent. */ void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, char *pname) { if (server.child_info_pipe[1] == -1) return; static monotime cow_updated = 0; static uint64_t cow_update_cost = 0; static size_t cow = 0; static size_t peak_cow = 0; static size_t update_count = 0; static unsigned long long sum_cow = 0; child_info_data data = {0}; /* zero everything, including padding to satisfy valgrind */ /* When called to report current info, we need to throttle down CoW updates as they * can be very expensive. To do that, we measure the time it takes to get a reading * and schedule the next reading to happen not before time*CHILD_COW_COST_FACTOR * passes. */ monotime now = getMonotonicUs(); if (info_type != CHILD_INFO_TYPE_CURRENT_INFO || !cow_updated || now - cow_updated > cow_update_cost * CHILD_COW_DUTY_CYCLE) { cow = zmalloc_get_private_dirty(-1); cow_updated = getMonotonicUs(); cow_update_cost = cow_updated - now; if (cow > peak_cow) peak_cow = cow; sum_cow += cow; update_count++; int cow_info = (info_type != CHILD_INFO_TYPE_CURRENT_INFO); if (cow || cow_info) { serverLog(cow_info ? LL_NOTICE : LL_VERBOSE, "Fork CoW for %s: current %zu MB, peak %zu MB, average %llu MB", pname, cow>>20, peak_cow>>20, (sum_cow/update_count)>>20); } } data.information_type = info_type; data.keys = keys; data.cow = cow; data.cow_updated = cow_updated; data.progress = progress; ssize_t wlen = sizeof(data); if (write(server.child_info_pipe[1], &data, wlen) != wlen) { /* Failed writing to parent, it could have been killed, exit. */ serverLog(LL_WARNING,"Child failed reporting info to parent, exiting. %s", strerror(errno)); exitFromChild(1); } } /* Update Child info. */ void updateChildInfo(childInfoType information_type, size_t cow, monotime cow_updated, size_t keys, double progress) { if (cow > server.stat_current_cow_peak) server.stat_current_cow_peak = cow; if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) { server.stat_current_cow_bytes = cow; server.stat_current_cow_updated = cow_updated; server.stat_current_save_keys_processed = keys; if (progress != -1) server.stat_module_progress = progress; } else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) { server.stat_aof_cow_bytes = server.stat_current_cow_peak; } else if (information_type == CHILD_INFO_TYPE_RDB_COW_SIZE) { server.stat_rdb_cow_bytes = server.stat_current_cow_peak; } else if (information_type == CHILD_INFO_TYPE_MODULE_COW_SIZE) { server.stat_module_cow_bytes = server.stat_current_cow_peak; } } /* Read child info data from the pipe. * if complete data read into the buffer, * data is stored into *buffer, and returns 1. * otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */ int readChildInfo(childInfoType *information_type, size_t *cow, monotime *cow_updated, size_t *keys, double* progress) { /* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */ static child_info_data buffer; ssize_t wlen = sizeof(buffer); /* Do not overlap */ if (server.child_info_nread == wlen) server.child_info_nread = 0; int nread = read(server.child_info_pipe[0], (char *)&buffer + server.child_info_nread, wlen - server.child_info_nread); if (nread > 0) { server.child_info_nread += nread; } /* We have complete child info */ if (server.child_info_nread == wlen) { *information_type = buffer.information_type; *cow = buffer.cow; *cow_updated = buffer.cow_updated; *keys = buffer.keys; *progress = buffer.progress; return 1; } else { return 0; } } /* Receive info data from child. */ void receiveChildInfo(void) { if (server.child_info_pipe[0] == -1) return; size_t cow; monotime cow_updated; size_t keys; double progress; childInfoType information_type; /* Drain the pipe and update child info so that we get the final message. */ while (readChildInfo(&information_type, &cow, &cow_updated, &keys, &progress)) { updateChildInfo(information_type, cow, cow_updated, keys, progress); } } redis-8.0.2/src/cli_commands.c000066400000000000000000000012361501533116600162150ustar00rootroot00000000000000#include #include "cli_commands.h" /* Definitions to configure commands.c to generate the above structs. */ #define MAKE_CMD(name,summary,complexity,since,doc_flags,replaced,deprecated,group,group_enum,history,num_history,tips,num_tips,function,arity,flags,acl,key_specs,key_specs_num,get_keys,numargs) name,summary,group,since,numargs #define MAKE_ARG(name,type,key_spec_index,token,summary,since,flags,numsubargs,deprecated_since) name,type,token,since,flags,numsubargs #define COMMAND_ARG cliCommandArg #define COMMAND_STRUCT commandDocs #define SKIP_CMD_HISTORY_TABLE #define SKIP_CMD_TIPS_TABLE #define SKIP_CMD_KEY_SPECS_TABLE #include "commands.def" redis-8.0.2/src/cli_commands.h000066400000000000000000000026131501533116600162220ustar00rootroot00000000000000/* This file is used by redis-cli in place of server.h when including commands.c * It contains alternative structs which omit the parts of the commands table * that are not suitable for redis-cli, e.g. the command proc. */ #ifndef __REDIS_CLI_COMMANDS_H #define __REDIS_CLI_COMMANDS_H #include #include "commands.h" /* Syntax specifications for a command argument. */ typedef struct cliCommandArg { char *name; redisCommandArgType type; char *token; char *since; int flags; int numsubargs; struct cliCommandArg *subargs; const char *display_text; /* * For use at runtime. * Fields used to keep track of input word matches for command-line hinting. */ int matched; /* How many input words have been matched by this argument? */ int matched_token; /* Has the token been matched? */ int matched_name; /* Has the name been matched? */ int matched_all; /* Has the whole argument been consumed (no hint needed)? */ } cliCommandArg; /* Command documentation info used for help output */ struct commandDocs { char *name; char *summary; char *group; char *since; int numargs; cliCommandArg *args; /* An array of the command arguments. */ struct commandDocs *subcommands; char *params; /* A string describing the syntax of the command arguments. */ }; extern struct commandDocs redisCommandTable[]; #endif redis-8.0.2/src/cli_common.c000066400000000000000000000303331501533116600157040ustar00rootroot00000000000000/* CLI (command line interface) common methods * * Copyright (c) 2020-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 "fmacros.h" #include "cli_common.h" #include "version.h" #include #include #include #include #include #include /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ #include /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ #include #include #include #ifdef USE_OPENSSL #include #include #include #endif #define UNUSED(V) ((void) V) char *redisGitSHA1(void); char *redisGitDirty(void); /* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if * not building with TLS support. */ int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err) { #ifdef USE_OPENSSL static SSL_CTX *ssl_ctx = NULL; if (!ssl_ctx) { ssl_ctx = SSL_CTX_new(SSLv23_client_method()); if (!ssl_ctx) { *err = "Failed to create SSL_CTX"; goto error; } SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_verify(ssl_ctx, config.skip_cert_verify ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL); if (config.cacert || config.cacertdir) { if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) { *err = "Invalid CA Certificate File/Directory"; goto error; } } else { if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { *err = "Failed to use default CA paths"; goto error; } } if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) { *err = "Invalid client certificate"; goto error; } if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) { *err = "Invalid private key"; goto error; } if (config.ciphers && !SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers)) { *err = "Error while configuring ciphers"; goto error; } #ifdef TLS1_3_VERSION if (config.ciphersuites && !SSL_CTX_set_ciphersuites(ssl_ctx, config.ciphersuites)) { *err = "Error while setting cypher suites"; goto error; } #endif } SSL *ssl = SSL_new(ssl_ctx); if (!ssl) { *err = "Failed to create SSL object"; return REDIS_ERR; } if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) { *err = "Failed to configure SNI"; SSL_free(ssl); return REDIS_ERR; } return redisInitiateSSL(c, ssl); error: SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; return REDIS_ERR; #else (void) config; (void) c; (void) err; return REDIS_OK; #endif } /* Wrapper around hiredis to allow arbitrary reads and writes. * * We piggybacks on top of hiredis to achieve transparent TLS support, * and use its internal buffers so it can co-exist with commands * previously/later issued on the connection. * * Interface is close to enough to read()/write() so things should mostly * work transparently. */ /* Write a raw buffer through a redisContext. If we already have something * in the buffer (leftovers from hiredis operations) it will be written * as well. */ ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len) { int done = 0; /* Append data to buffer which is *usually* expected to be empty * but we don't assume that, and write. */ c->obuf = sdscatlen(c->obuf, buf, buf_len); if (redisBufferWrite(c, &done) == REDIS_ERR) { if (!(c->flags & REDIS_BLOCK)) errno = EAGAIN; /* On error, we assume nothing was written and we roll back the * buffer to its original state. */ if (sdslen(c->obuf) > buf_len) sdsrange(c->obuf, 0, -(buf_len+1)); else sdsclear(c->obuf); return -1; } /* If we're done, free up everything. We may have written more than * buf_len (if c->obuf was not initially empty) but we don't have to * tell. */ if (done) { sdsclear(c->obuf); return buf_len; } /* Write was successful but we have some leftovers which we should * remove from the buffer. * * Do we still have data that was there prior to our buf? If so, * restore buffer to it's original state and report no new data was * written. */ if (sdslen(c->obuf) > buf_len) { sdsrange(c->obuf, 0, -(buf_len+1)); return 0; } /* At this point we're sure no prior data is left. We flush the buffer * and report how much we've written. */ size_t left = sdslen(c->obuf); sdsclear(c->obuf); return buf_len - left; } /* Wrapper around OpenSSL (libssl and libcrypto) initialisation */ int cliSecureInit(void) { #ifdef USE_OPENSSL ERR_load_crypto_strings(); SSL_load_error_strings(); SSL_library_init(); #endif return REDIS_OK; } /* Create an sds from stdin */ sds readArgFromStdin(void) { char buf[1024]; sds arg = sdsempty(); while(1) { int nread = read(fileno(stdin),buf,1024); if (nread == 0) break; else if (nread == -1) { perror("Reading from standard input"); exit(1); } arg = sdscatlen(arg,buf,nread); } return arg; } /* Create an sds array from argv, either as-is or by dequoting every * element. When quoted is non-zero, may return a NULL to indicate an * invalid quoted string. * * The caller should free the resulting array of sds strings with * sdsfreesplitres(). */ sds *getSdsArrayFromArgv(int argc,char **argv, int quoted) { sds *res = sds_malloc(sizeof(sds) * argc); for (int j = 0; j < argc; j++) { if (quoted) { sds unquoted = unquoteCString(argv[j]); if (!unquoted) { while (--j >= 0) sdsfree(res[j]); sds_free(res); return NULL; } res[j] = unquoted; } else { res[j] = sdsnew(argv[j]); } } return res; } /* Unquote a null-terminated string and return it as a binary-safe sds. */ sds unquoteCString(char *str) { int count; sds *unquoted = sdssplitargs(str, &count); sds res = NULL; if (unquoted && count == 1) { res = unquoted[0]; unquoted[0] = NULL; } if (unquoted) sdsfreesplitres(unquoted, count); return res; } /* URL-style percent decoding. */ #define isHexChar(c) (isdigit(c) || ((c) >= 'a' && (c) <= 'f')) #define decodeHexChar(c) (isdigit(c) ? (c) - '0' : (c) - 'a' + 10) #define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l)) static sds percentDecode(const char *pe, size_t len) { const char *end = pe + len; sds ret = sdsempty(); const char *curr = pe; while (curr < end) { if (*curr == '%') { if ((end - curr) < 2) { fprintf(stderr, "Incomplete URI encoding\n"); exit(1); } char h = tolower(*(++curr)); char l = tolower(*(++curr)); if (!isHexChar(h) || !isHexChar(l)) { fprintf(stderr, "Illegal character in URI encoding\n"); exit(1); } char c = decodeHex(h, l); ret = sdscatlen(ret, &c, 1); curr++; } else { ret = sdscatlen(ret, curr++, 1); } } return ret; } /* Parse a URI and extract the server connection information. * URI scheme is based on the provisional specification[1] excluding support * for query parameters. Valid URIs are: * scheme: "redis://" * authority: [[ ":"] "@"] [ [":" ]] * path: ["/" []] * * [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */ void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag) { #ifdef USE_OPENSSL UNUSED(tool_name); #else UNUSED(tls_flag); #endif const char *scheme = "redis://"; const char *tlsscheme = "rediss://"; const char *curr = uri; const char *end = uri + strlen(uri); const char *userinfo, *username, *port, *host, *path; /* URI must start with a valid scheme. */ if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) { #ifdef USE_OPENSSL *tls_flag = 1; curr += strlen(tlsscheme); #else fprintf(stderr,"rediss:// is only supported when %s is compiled with OpenSSL\n", tool_name); exit(1); #endif } else if (!strncasecmp(scheme, curr, strlen(scheme))) { curr += strlen(scheme); } else { fprintf(stderr,"Invalid URI scheme\n"); exit(1); } if (curr == end) return; /* Extract user info. */ if ((userinfo = strchr(curr,'@'))) { if ((username = strchr(curr, ':')) && username < userinfo) { connInfo->user = percentDecode(curr, username - curr); curr = username + 1; } connInfo->auth = percentDecode(curr, userinfo - curr); curr = userinfo + 1; } if (curr == end) return; /* Extract host and port. */ path = strchr(curr, '/'); if (*curr != '/') { host = path ? path - 1 : end; if (*curr == '[') { curr += 1; if ((port = strchr(curr, ']'))) { if (*(port+1) == ':') { connInfo->hostport = atoi(port + 2); } host = port - 1; } } else { if ((port = strchr(curr, ':'))) { connInfo->hostport = atoi(port + 1); host = port - 1; } } sdsfree(connInfo->hostip); connInfo->hostip = sdsnewlen(curr, host - curr + 1); } curr = path ? path + 1 : end; if (curr == end) return; /* Extract database number. */ connInfo->input_dbnum = atoi(curr); } void freeCliConnInfo(cliConnInfo connInfo){ if (connInfo.hostip) sdsfree(connInfo.hostip); if (connInfo.auth) sdsfree(connInfo.auth); if (connInfo.user) sdsfree(connInfo.user); } /* * Escape a Unicode string for JSON output (--json), following RFC 7159: * https://datatracker.ietf.org/doc/html/rfc7159#section-7 */ sds escapeJsonString(sds s, const char *p, size_t len) { s = sdscatlen(s,"\"",1); while(len--) { switch(*p) { case '\\': case '"': s = sdscatprintf(s,"\\%c",*p); break; case '\n': s = sdscatlen(s,"\\n",2); break; case '\f': s = sdscatlen(s,"\\f",2); break; case '\r': s = sdscatlen(s,"\\r",2); break; case '\t': s = sdscatlen(s,"\\t",2); break; case '\b': s = sdscatlen(s,"\\b",2); break; default: s = sdscatprintf(s,*(unsigned char *)p <= 0x1f ? "\\u%04x" : "%c",*p); } p++; } return sdscatlen(s,"\"",1); } sds cliVersion(void) { sds version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); /* Add git commit and working tree status when available. */ if (strtoll(redisGitSHA1(),NULL,16)) { version = sdscatprintf(version, " (git:%s", redisGitSHA1()); if (strtoll(redisGitDirty(),NULL,10)) version = sdscatprintf(version, "-dirty"); version = sdscat(version, ")"); } return version; } /* This is a wrapper to call redisConnect or redisConnectWithTimeout. */ redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv) { if (tv.tv_sec == 0 && tv.tv_usec == 0) { return redisConnect(ip, port); } else { return redisConnectWithTimeout(ip, port, tv); } } /* This is a wrapper to call redisConnectUnix or redisConnectUnixWithTimeout. */ redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv) { if (tv.tv_sec == 0 && tv.tv_usec == 0) { return redisConnectUnix(path); } else { return redisConnectUnixWithTimeout(path, tv); } } redis-8.0.2/src/cli_common.h000066400000000000000000000033751501533116600157170ustar00rootroot00000000000000#ifndef __CLICOMMON_H #define __CLICOMMON_H #include #include /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ typedef struct cliSSLconfig { /* Requested SNI, or NULL */ char *sni; /* CA Certificate file, or NULL */ char *cacert; /* Directory where trusted CA certificates are stored, or NULL */ char *cacertdir; /* Skip server certificate verification. */ int skip_cert_verify; /* Client certificate to authenticate with, or NULL */ char *cert; /* Private key file to authenticate with, or NULL */ char *key; /* Preferred cipher list, or NULL (applies only to <= TLSv1.2) */ char* ciphers; /* Preferred ciphersuites list, or NULL (applies only to TLSv1.3) */ char* ciphersuites; } cliSSLconfig; /* server connection information object, used to describe an ip:port pair, db num user input, and user:pass. */ typedef struct cliConnInfo { char *hostip; int hostport; int input_dbnum; char *auth; char *user; } cliConnInfo; int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err); ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len); int cliSecureInit(void); sds readArgFromStdin(void); sds *getSdsArrayFromArgv(int argc,char **argv, int quoted); sds unquoteCString(char *str); void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag); void freeCliConnInfo(cliConnInfo connInfo); sds escapeJsonString(sds s, const char *p, size_t len); sds cliVersion(void); redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv); redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv); #endif /* __CLICOMMON_H */ redis-8.0.2/src/cluster.c000066400000000000000000001775511501533116600152640ustar00rootroot00000000000000/* * Copyright (c) 2009-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). * * Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. */ /* * cluster.c contains the common parts of a clustering * implementation, the parts that are shared between * any implementation of Redis clustering. */ #include "server.h" #include "cluster.h" #include /* ----------------------------------------------------------------------------- * Key space handling * -------------------------------------------------------------------------- */ /* If it can be inferred that the given glob-style pattern, as implemented in * stringmatchlen() in util.c, only can match keys belonging to a single slot, * that slot is returned. Otherwise -1 is returned. */ int patternHashSlot(char *pattern, int length) { int s = -1; /* index of the first '{' */ for (int i = 0; i < length; i++) { if (pattern[i] == '*' || pattern[i] == '?' || pattern[i] == '[') { /* Wildcard or character class found. Keys can be in any slot. */ return -1; } else if (pattern[i] == '\\') { /* Escaped character. Computing slot in this case is not * implemented. We would need a temp buffer. */ return -1; } else if (s == -1 && pattern[i] == '{') { /* Opening brace '{' found. */ s = i; } else if (s >= 0 && pattern[i] == '}' && i == s + 1) { /* Empty tag '{}' found. The whole key is hashed. Ignore braces. */ s = -2; } else if (s >= 0 && pattern[i] == '}') { /* Non-empty tag '{...}' found. Hash what's between braces. */ return crc16(pattern + s + 1, i - s - 1) & 0x3FFF; } } /* The pattern matches a single key. Hash the whole pattern. */ return crc16(pattern, length) & 0x3FFF; } ConnectionType *connTypeOfCluster(void) { if (server.tls_cluster) { return connectionTypeTls(); } return connectionTypeTcp(); } /* ----------------------------------------------------------------------------- * DUMP, RESTORE and MIGRATE commands * -------------------------------------------------------------------------- */ /* Generates a DUMP-format representation of the object 'o', adding it to the * io stream pointed by 'rio'. This function can't fail. */ void createDumpPayload(rio *payload, robj *o, robj *key, int dbid) { unsigned char buf[2]; uint64_t crc; /* 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()); serverAssert(rdbSaveObjectType(payload,o)); serverAssert(rdbSaveObject(payload,o,key,dbid)); /* Write the footer, this is how it looks like: * ----------------+---------------------+---------------+ * ... RDB payload | 2 bytes RDB version | 8 bytes CRC64 | * ----------------+---------------------+---------------+ * RDB version and CRC are both in little endian. */ /* RDB version */ buf[0] = RDB_VERSION & 0xff; buf[1] = (RDB_VERSION >> 8) & 0xff; payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,buf,2); /* CRC64 */ crc = crc64(0,(unsigned char*)payload->io.buffer.ptr, sdslen(payload->io.buffer.ptr)); memrev64ifbe(&crc); payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,&crc,8); } /* Verify that the RDB version of the dump payload matches the one of this Redis * instance and that the checksum is ok. * If the DUMP payload looks valid C_OK is returned, otherwise C_ERR * is returned. If rdbver_ptr is not NULL, its populated with the value read * from the input buffer. */ int verifyDumpPayload(unsigned char *p, size_t len, uint16_t *rdbver_ptr) { unsigned char *footer; uint16_t rdbver; uint64_t crc; /* At least 2 bytes of RDB version and 8 of CRC64 should be present. */ if (len < 10) return C_ERR; footer = p+(len-10); /* Set and verify RDB version. */ rdbver = (footer[1] << 8) | footer[0]; if (rdbver_ptr) { *rdbver_ptr = rdbver; } if (rdbver > RDB_VERSION) return C_ERR; if (server.skip_checksum_validation) return C_OK; /* Verify CRC64 */ crc = crc64(0,p,len-8); memrev64ifbe(&crc); return (memcmp(&crc,footer+2,8) == 0) ? C_OK : C_ERR; } /* DUMP keyname * DUMP is actually not used by Redis Cluster but it is the obvious * complement of RESTORE and can be useful for different applications. */ void dumpCommand(client *c) { robj *o; rio payload; /* Check if the key is here. */ if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { addReplyNull(c); return; } /* Create the DUMP encoded representation. */ createDumpPayload(&payload,o,c->argv[1],c->db->id); /* Transfer to the client */ addReplyBulkSds(c,payload.io.buffer.ptr); return; } /* RESTORE key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency] */ void restoreCommand(client *c) { long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1; rio payload; int j, type, replace = 0, absttl = 0; robj *obj; /* Parse additional options */ for (j = 4; j < c->argc; j++) { int additional = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"replace")) { replace = 1; } else if (!strcasecmp(c->argv[j]->ptr,"absttl")) { absttl = 1; } else if (!strcasecmp(c->argv[j]->ptr,"idletime") && additional >= 1 && lfu_freq == -1) { if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lru_idle,NULL) != C_OK) return; if (lru_idle < 0) { addReplyError(c,"Invalid IDLETIME value, must be >= 0"); return; } lru_clock = LRU_CLOCK(); j++; /* Consume additional arg. */ } else if (!strcasecmp(c->argv[j]->ptr,"freq") && additional >= 1 && lru_idle == -1) { if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lfu_freq,NULL) != C_OK) return; if (lfu_freq < 0 || lfu_freq > 255) { addReplyError(c,"Invalid FREQ value, must be >= 0 and <= 255"); return; } j++; /* Consume additional arg. */ } else { addReplyErrorObject(c,shared.syntaxerr); return; } } /* Make sure this key does not already exist here... */ robj *key = c->argv[1]; if (!replace && lookupKeyWrite(c->db,key) != NULL) { addReplyErrorObject(c,shared.busykeyerr); return; } /* Check if the TTL value makes sense */ if (getLongLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != C_OK) { return; } else if (ttl < 0) { addReplyError(c,"Invalid TTL value, must be >= 0"); return; } /* Verify RDB version and data checksum. */ if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr),NULL) == C_ERR) { addReplyError(c,"DUMP payload version or checksum are wrong"); return; } rioInitWithBuffer(&payload,c->argv[3]->ptr); if (((type = rdbLoadObjectType(&payload)) == -1) || ((obj = rdbLoadObject(type,&payload,key->ptr,c->db->id,NULL)) == NULL)) { addReplyError(c,"Bad data format"); return; } /* Remove the old key if needed. */ int deleted = 0; 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); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id); server.dirty++; } decrRefCount(obj); addReply(c, shared.ok); return; } /* Create the key and set the TTL if any */ dictEntry *de = dbAdd(c->db,key,obj); /* If minExpiredField was set, then the object is hash with expiration * on fields and need to register it in global HFE DS */ if (obj->type == OBJ_HASH) { uint64_t minExpiredField = hashTypeGetMinExpire(obj, 1); if (minExpiredField != EB_EXPIRE_TIME_INVALID) hashTypeAddToExpires(c->db, dictGetKey(de), obj, minExpiredField); } if (ttl) { setExpire(c,c->db,key,ttl); if (!absttl) { /* Propagate TTL as absolute timestamp */ robj *ttl_obj = createStringObjectFromLongLong(ttl); rewriteClientCommandArgument(c,2,ttl_obj); decrRefCount(ttl_obj); rewriteClientCommandArgument(c,c->argc,shared.absttl); } } objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000); signalModifiedKey(c,c->db,key); notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",key,c->db->id); addReply(c,shared.ok); server.dirty++; } /* MIGRATE socket cache implementation. * * We take a map between host:ip and a TCP socket that we used to connect * to this instance in recent time. * This sockets are closed when the max number we cache is reached, and also * in serverCron() when they are around for more than a few seconds. */ #define MIGRATE_SOCKET_CACHE_ITEMS 64 /* max num of items in the cache. */ #define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */ typedef struct migrateCachedSocket { connection *conn; long last_dbid; time_t last_use_time; } migrateCachedSocket; /* Return a migrateCachedSocket containing a TCP socket connected with the * target instance, possibly returning a cached one. * * This function is responsible of sending errors to the client if a * connection can't be established. In this case -1 is returned. * Otherwise on success the socket is returned, and the caller should not * attempt to free it after usage. * * If the caller detects an error while using the socket, migrateCloseSocket() * should be called so that the connection will be created from scratch * the next time. */ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) { connection *conn; sds name = sdsempty(); migrateCachedSocket *cs; /* Check if we have an already cached socket for this ip:port pair. */ name = sdscatlen(name,host->ptr,sdslen(host->ptr)); name = sdscatlen(name,":",1); name = sdscatlen(name,port->ptr,sdslen(port->ptr)); cs = dictFetchValue(server.migrate_cached_sockets,name); if (cs) { sdsfree(name); cs->last_use_time = server.unixtime; return cs; } /* No cached socket, create one. */ if (dictSize(server.migrate_cached_sockets) == MIGRATE_SOCKET_CACHE_ITEMS) { /* Too many items, drop one at random. */ dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets); cs = dictGetVal(de); connClose(cs->conn); zfree(cs); dictDelete(server.migrate_cached_sockets,dictGetKey(de)); } /* Create the connection */ conn = connCreate(server.el, connTypeOfCluster()); if (connBlockingConnect(conn, host->ptr, atoi(port->ptr), timeout) != C_OK) { addReplyError(c,"-IOERR error or timeout connecting to the client"); connClose(conn); sdsfree(name); return NULL; } connEnableTcpNoDelay(conn); /* Add to the cache and return it to the caller. */ cs = zmalloc(sizeof(*cs)); cs->conn = conn; cs->last_dbid = -1; cs->last_use_time = server.unixtime; dictAdd(server.migrate_cached_sockets,name,cs); return cs; } /* Free a migrate cached connection. */ void migrateCloseSocket(robj *host, robj *port) { sds name = sdsempty(); migrateCachedSocket *cs; name = sdscatlen(name,host->ptr,sdslen(host->ptr)); name = sdscatlen(name,":",1); name = sdscatlen(name,port->ptr,sdslen(port->ptr)); cs = dictFetchValue(server.migrate_cached_sockets,name); if (!cs) { sdsfree(name); return; } connClose(cs->conn); zfree(cs); dictDelete(server.migrate_cached_sockets,name); sdsfree(name); } void migrateCloseTimedoutSockets(void) { dictIterator *di = dictGetSafeIterator(server.migrate_cached_sockets); dictEntry *de; while((de = dictNext(di)) != NULL) { migrateCachedSocket *cs = dictGetVal(de); if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) { connClose(cs->conn); zfree(cs); dictDelete(server.migrate_cached_sockets,dictGetKey(de)); } } dictReleaseIterator(di); } /* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password | * AUTH2 username password] * * On in the multiple keys form: * * MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password | * AUTH2 username password] KEYS key1 key2 ... keyN */ void migrateCommand(client *c) { migrateCachedSocket *cs; int copy = 0, replace = 0, j; char *username = NULL; char *password = NULL; long timeout; long dbid; robj **ov = NULL; /* Objects to migrate. */ robj **kv = NULL; /* Key names. */ robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */ rio cmd, payload; int may_retry = 1; int write_error = 0; int argv_rewritten = 0; /* To support the KEYS option we need the following additional state. */ int first_key = 3; /* Argument index of the first key. */ int num_keys = 1; /* By default only migrate the 'key' argument. */ /* Parse additional options */ for (j = 6; j < c->argc; j++) { int moreargs = (c->argc-1) - j; if (!strcasecmp(c->argv[j]->ptr,"copy")) { copy = 1; } else if (!strcasecmp(c->argv[j]->ptr,"replace")) { replace = 1; } else if (!strcasecmp(c->argv[j]->ptr,"auth")) { if (!moreargs) { addReplyErrorObject(c,shared.syntaxerr); return; } j++; password = c->argv[j]->ptr; redactClientCommandArgument(c,j); } else if (!strcasecmp(c->argv[j]->ptr,"auth2")) { if (moreargs < 2) { addReplyErrorObject(c,shared.syntaxerr); return; } username = c->argv[++j]->ptr; redactClientCommandArgument(c,j); password = c->argv[++j]->ptr; redactClientCommandArgument(c,j); } else if (!strcasecmp(c->argv[j]->ptr,"keys")) { if (sdslen(c->argv[3]->ptr) != 0) { addReplyError(c, "When using MIGRATE KEYS option, the key argument" " must be set to the empty string"); return; } first_key = j+1; num_keys = c->argc - j - 1; break; /* All the remaining args are keys. */ } else { addReplyErrorObject(c,shared.syntaxerr); return; } } /* Sanity check */ if (getLongFromObjectOrReply(c,c->argv[5],&timeout,NULL) != C_OK || getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != C_OK) { return; } if (timeout <= 0) timeout = 1000; /* Check if the keys are here. If at least one key is to migrate, do it * otherwise if all the keys are missing reply with "NOKEY" to signal * the caller there was nothing to migrate. We don't return an error in * this case, since often this is due to a normal condition like the key * expiring in the meantime. */ ov = zrealloc(ov,sizeof(robj*)*num_keys); kv = zrealloc(kv,sizeof(robj*)*num_keys); int oi = 0; for (j = 0; j < num_keys; j++) { if ((ov[oi] = lookupKeyRead(c->db,c->argv[first_key+j])) != NULL) { kv[oi] = c->argv[first_key+j]; oi++; } } num_keys = oi; if (num_keys == 0) { zfree(ov); zfree(kv); addReplySds(c,sdsnew("+NOKEY\r\n")); return; } try_again: write_error = 0; /* Connect */ cs = migrateGetSocket(c,c->argv[1],c->argv[2],timeout); if (cs == NULL) { zfree(ov); zfree(kv); return; /* error sent to the client by migrateGetSocket() */ } rioInitWithBuffer(&cmd,sdsempty()); /* Authentication */ if (password) { int arity = username ? 3 : 2; serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',arity)); serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4)); if (username) { serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,username, sdslen(username))); } serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password, sdslen(password))); } /* Send the SELECT command if the current DB is not already selected. */ int select = cs->last_dbid != dbid; /* Should we emit SELECT? */ if (select) { serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2)); serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"SELECT",6)); serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid)); } int non_expired = 0; /* Number of keys that we'll find non expired. Note that serializing large keys may take some time so certain keys that were found non expired by the lookupKey() function, may be expired later. */ /* Create RESTORE payload and generate the protocol to call the command. */ for (j = 0; j < num_keys; j++) { long long ttl = 0; long long expireat = getExpire(c->db,kv[j]); if (expireat != -1) { ttl = expireat-commandTimeSnapshot(); if (ttl < 0) { continue; } if (ttl < 1) ttl = 1; } /* Relocate valid (non expired) keys and values into the array in successive * positions to remove holes created by the keys that were present * in the first lookup but are now expired after the second lookup. */ ov[non_expired] = ov[j]; kv[non_expired++] = kv[j]; serverAssertWithInfo(c,NULL, rioWriteBulkCount(&cmd,'*',replace ? 5 : 4)); if (server.cluster_enabled) serverAssertWithInfo(c,NULL, rioWriteBulkString(&cmd,"RESTORE-ASKING",14)); else serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7)); serverAssertWithInfo(c,NULL,sdsEncodedObject(kv[j])); serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,kv[j]->ptr, sdslen(kv[j]->ptr))); serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl)); /* Emit the payload argument, that is the serialized object using * the DUMP format. */ createDumpPayload(&payload,ov[j],kv[j],dbid); serverAssertWithInfo(c,NULL, rioWriteBulkString(&cmd,payload.io.buffer.ptr, sdslen(payload.io.buffer.ptr))); sdsfree(payload.io.buffer.ptr); /* Add the REPLACE option to the RESTORE command if it was specified * as a MIGRATE option. */ if (replace) serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7)); } /* Fix the actual number of keys we are migrating. */ num_keys = non_expired; /* Transfer the query to the other node in 64K chunks. */ errno = 0; { sds buf = cmd.io.buffer.ptr; size_t pos = 0, towrite; int nwritten = 0; while ((towrite = sdslen(buf)-pos) > 0) { towrite = (towrite > (64*1024) ? (64*1024) : towrite); nwritten = connSyncWrite(cs->conn,buf+pos,towrite,timeout); if (nwritten != (signed)towrite) { write_error = 1; goto socket_err; } pos += nwritten; } } char buf0[1024]; /* Auth reply. */ char buf1[1024]; /* Select reply. */ char buf2[1024]; /* Restore reply. */ /* Read the AUTH reply if needed. */ if (password && connSyncReadLine(cs->conn, buf0, sizeof(buf0), timeout) <= 0) goto socket_err; /* Read the SELECT reply if needed. */ if (select && connSyncReadLine(cs->conn, buf1, sizeof(buf1), timeout) <= 0) goto socket_err; /* Read the RESTORE replies. */ int error_from_target = 0; int socket_error = 0; int del_idx = 1; /* Index of the key argument for the replicated DEL op. */ /* Allocate the new argument vector that will replace the current command, * to propagate the MIGRATE as a DEL command (if no COPY option was given). * We allocate num_keys+1 because the additional argument is for "DEL" * command name itself. */ if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1)); for (j = 0; j < num_keys; j++) { if (connSyncReadLine(cs->conn, buf2, sizeof(buf2), timeout) <= 0) { socket_error = 1; break; } if ((password && buf0[0] == '-') || (select && buf1[0] == '-') || buf2[0] == '-') { /* On error assume that last_dbid is no longer valid. */ if (!error_from_target) { cs->last_dbid = -1; char *errbuf; if (password && buf0[0] == '-') errbuf = buf0; else if (select && buf1[0] == '-') errbuf = buf1; else errbuf = buf2; error_from_target = 1; addReplyErrorFormat(c,"Target instance replied with error: %s", errbuf+1); } } else { if (!copy) { /* No COPY option: remove the local key, signal the change. */ dbDelete(c->db,kv[j]); signalModifiedKey(c,c->db,kv[j]); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id); server.dirty++; /* Populate the argument vector to replace the old one. */ newargv[del_idx++] = kv[j]; incrRefCount(kv[j]); } } } /* On socket error, if we want to retry, do it now before rewriting the * command vector. We only retry if we are sure nothing was processed * and we failed to read the first reply (j == 0 test). */ if (!error_from_target && socket_error && j == 0 && may_retry && errno != ETIMEDOUT) { goto socket_err; /* A retry is guaranteed because of tested conditions.*/ } /* On socket errors, close the migration socket now that we still have * the original host/port in the ARGV. Later the original command may be * rewritten to DEL and will be too later. */ if (socket_error) migrateCloseSocket(c->argv[1],c->argv[2]); if (!copy) { /* Translate MIGRATE as DEL for replication/AOF. Note that we do * this only for the keys for which we received an acknowledgement * from the receiving Redis server, by using the del_idx index. */ if (del_idx > 1) { newargv[0] = createStringObject("DEL",3); /* Note that the following call takes ownership of newargv. */ replaceClientCommandVector(c,del_idx,newargv); argv_rewritten = 1; } else { /* No key transfer acknowledged, no need to rewrite as DEL. */ zfree(newargv); } newargv = NULL; /* Make it safe to call zfree() on it in the future. */ } /* If we are here and a socket error happened, we don't want to retry. * Just signal the problem to the client, but only do it if we did not * already queue a different error reported by the destination server. */ if (!error_from_target && socket_error) { may_retry = 0; goto socket_err; } if (!error_from_target) { /* Success! Update the last_dbid in migrateCachedSocket, so that we can * avoid SELECT the next time if the target DB is the same. Reply +OK. * * Note: If we reached this point, even if socket_error is true * still the SELECT command succeeded (otherwise the code jumps to * socket_err label. */ cs->last_dbid = dbid; addReply(c,shared.ok); } else { /* On error we already sent it in the for loop above, and set * the currently selected socket to -1 to force SELECT the next time. */ } sdsfree(cmd.io.buffer.ptr); zfree(ov); zfree(kv); zfree(newargv); return; /* On socket errors we try to close the cached socket and try again. * It is very common for the cached socket to get closed, if just reopening * it works it's a shame to notify the error to the caller. */ socket_err: /* Cleanup we want to perform in both the retry and no retry case. * Note: Closing the migrate socket will also force SELECT next time. */ sdsfree(cmd.io.buffer.ptr); /* If the command was rewritten as DEL and there was a socket error, * we already closed the socket earlier. While migrateCloseSocket() * is idempotent, the host/port arguments are now gone, so don't do it * again. */ if (!argv_rewritten) migrateCloseSocket(c->argv[1],c->argv[2]); zfree(newargv); newargv = NULL; /* This will get reallocated on retry. */ /* Retry only if it's not a timeout and we never attempted a retry * (or the code jumping here did not set may_retry to zero). */ if (errno != ETIMEDOUT && may_retry) { may_retry = 0; goto try_again; } /* Cleanup we want to do if no retry is attempted. */ zfree(ov); zfree(kv); addReplyErrorSds(c, sdscatprintf(sdsempty(), "-IOERR error or timeout %s to target instance", write_error ? "writing" : "reading")); return; } /* Cluster node sanity check. Returns C_OK if the node id * is valid an C_ERR otherwise. */ int verifyClusterNodeId(const char *name, int length) { if (length != CLUSTER_NAMELEN) return C_ERR; for (int i = 0; i < length; i++) { if (name[i] >= 'a' && name[i] <= 'z') continue; if (name[i] >= '0' && name[i] <= '9') continue; return C_ERR; } return C_OK; } int isValidAuxChar(int c) { return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL); } int isValidAuxString(char *s, unsigned int length) { for (unsigned i = 0; i < length; i++) { if (!isValidAuxChar(s[i])) return 0; } return 1; } void clusterCommandMyId(client *c) { char *name = clusterNodeGetName(getMyClusterNode()); if (name) { addReplyBulkCBuffer(c,name, CLUSTER_NAMELEN); } else { addReplyError(c, "No ID yet"); } } char* getMyClusterId(void) { return clusterNodeGetName(getMyClusterNode()); } void clusterCommandMyShardId(client *c) { char *sid = clusterNodeGetShardId(getMyClusterNode()); if (sid) { addReplyBulkCBuffer(c,sid, CLUSTER_NAMELEN); } else { addReplyError(c, "No shard ID yet"); } } /* When a cluster command is called, we need to decide whether to return TLS info or * non-TLS info by the client's connection type. However if the command is called by * a Lua script or RM_call, there is no connection in the fake client, so we use * server.current_client here to get the real client if available. And if it is not * available (modules may call commands without a real client), we return the default * info, which is determined by server.tls_cluster. */ static int shouldReturnTlsInfo(void) { if (server.current_client && server.current_client->conn) { return connIsTLS(server.current_client->conn); } else { return server.tls_cluster; } } unsigned int countKeysInSlot(unsigned int slot) { return kvstoreDictSize(server.db->keys, slot); } /* Add detailed information of a node to the output buffer of the given client. */ void addNodeDetailsToShardReply(client *c, clusterNode *node) { int reply_count = 0; char *hostname; void *node_replylen = addReplyDeferredLen(c); addReplyBulkCString(c, "id"); addReplyBulkCBuffer(c, clusterNodeGetName(node), CLUSTER_NAMELEN); reply_count++; if (clusterNodeTcpPort(node)) { addReplyBulkCString(c, "port"); addReplyLongLong(c, clusterNodeTcpPort(node)); reply_count++; } if (clusterNodeTlsPort(node)) { addReplyBulkCString(c, "tls-port"); addReplyLongLong(c, clusterNodeTlsPort(node)); reply_count++; } addReplyBulkCString(c, "ip"); addReplyBulkCString(c, clusterNodeIp(node)); reply_count++; addReplyBulkCString(c, "endpoint"); addReplyBulkCString(c, clusterNodePreferredEndpoint(node)); reply_count++; hostname = clusterNodeHostname(node); if (hostname != NULL && *hostname != '\0') { addReplyBulkCString(c, "hostname"); addReplyBulkCString(c, hostname); reply_count++; } long long node_offset; if (clusterNodeIsMyself(node)) { node_offset = clusterNodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset; } else { node_offset = clusterNodeReplOffset(node); } addReplyBulkCString(c, "role"); addReplyBulkCString(c, clusterNodeIsSlave(node) ? "replica" : "master"); reply_count++; addReplyBulkCString(c, "replication-offset"); addReplyLongLong(c, node_offset); reply_count++; addReplyBulkCString(c, "health"); const char *health_msg = NULL; if (clusterNodeIsFailing(node)) { health_msg = "fail"; } else if (clusterNodeIsSlave(node) && node_offset == 0) { health_msg = "loading"; } else { health_msg = "online"; } addReplyBulkCString(c, health_msg); reply_count++; setDeferredMapLen(c, node_replylen, reply_count); } static clusterNode *clusterGetMasterFromShard(void *shard_handle) { clusterNode *n = NULL; void *node_it = clusterShardHandleGetNodeIterator(shard_handle); while((n = clusterShardNodeIteratorNext(node_it)) != NULL) { if (!clusterNodeIsFailing(n)) { break; } } clusterShardNodeIteratorFree(node_it); if (!n) return NULL; return clusterNodeGetMaster(n); } /* Add the shard reply of a single shard based off the given primary node. */ void addShardReplyForClusterShards(client *c, void *shard_handle) { serverAssert(clusterGetShardNodeCount(shard_handle) > 0); addReplyMapLen(c, 2); addReplyBulkCString(c, "slots"); /* Use slot_info_pairs from the primary only */ clusterNode *master_node = clusterGetMasterFromShard(shard_handle); if (master_node && clusterNodeHasSlotInfo(master_node)) { serverAssert((clusterNodeSlotInfoCount(master_node) % 2) == 0); addReplyArrayLen(c, clusterNodeSlotInfoCount(master_node)); for (int i = 0; i < clusterNodeSlotInfoCount(master_node); i++) addReplyLongLong(c, (unsigned long)clusterNodeSlotInfoEntry(master_node, i)); } else { /* If no slot info pair is provided, the node owns no slots */ addReplyArrayLen(c, 0); } addReplyBulkCString(c, "nodes"); addReplyArrayLen(c, clusterGetShardNodeCount(shard_handle)); void *node_it = clusterShardHandleGetNodeIterator(shard_handle); for (clusterNode *n = clusterShardNodeIteratorNext(node_it); n != NULL; n = clusterShardNodeIteratorNext(node_it)) { addNodeDetailsToShardReply(c, n); clusterFreeNodesSlotsInfo(n); } clusterShardNodeIteratorFree(node_it); } /* Add to the output buffer of the given client, an array of slot (start, end) * pair owned by the shard, also the primary and set of replica(s) along with * information about each node. */ void clusterCommandShards(client *c) { addReplyArrayLen(c, clusterGetShardCount()); /* This call will add slot_info_pairs to all nodes */ clusterGenNodesSlotsInfo(0); dictIterator *shard_it = clusterGetShardIterator(); for(void *shard_handle = clusterNextShardHandle(shard_it); shard_handle != NULL; shard_handle = clusterNextShardHandle(shard_it)) { addShardReplyForClusterShards(c, shard_handle); } clusterFreeShardIterator(shard_it); } void clusterCommandHelp(client *c) { const char *help[] = { "COUNTKEYSINSLOT ", " Return the number of keys in .", "GETKEYSINSLOT ", " Return key names stored by current node in a slot.", "INFO", " Return information about the cluster.", "KEYSLOT ", " Return the hash slot for .", "MYID", " Return the node id.", "MYSHARDID", " Return the node's shard id.", "NODES", " Return cluster configuration seen by node. Output format:", " ...", "REPLICAS ", " Return replicas.", "SLOTS", " Return information about slots range mappings. Each range is made of:", " start, end, master and replicas IP addresses, ports and ids", "SHARDS", " Return information about slot range mappings and the nodes associated with them.", NULL }; addExtendedReplyHelp(c, help, clusterCommandExtendedHelp()); } void clusterCommand(client *c) { if (server.cluster_enabled == 0) { addReplyError(c,"This instance has cluster support disabled"); return; } if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { clusterCommandHelp(c); } else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) { /* CLUSTER NODES */ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */ sds nodes = clusterGenNodesDescription(c, 0, shouldReturnTlsInfo()); addReplyVerbatim(c,nodes,sdslen(nodes),"txt"); sdsfree(nodes); } else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) { /* CLUSTER MYID */ clusterCommandMyId(c); } else if (!strcasecmp(c->argv[1]->ptr,"myshardid") && c->argc == 2) { /* CLUSTER MYSHARDID */ clusterCommandMyShardId(c); } else if (!strcasecmp(c->argv[1]->ptr,"slots") && c->argc == 2) { /* CLUSTER SLOTS */ clusterCommandSlots(c); } else if (!strcasecmp(c->argv[1]->ptr,"shards") && c->argc == 2) { /* CLUSTER SHARDS */ clusterCommandShards(c); } else if (!strcasecmp(c->argv[1]->ptr,"info") && c->argc == 2) { /* CLUSTER INFO */ sds info = genClusterInfoString(); /* Produce the reply protocol. */ addReplyVerbatim(c,info,sdslen(info),"txt"); sdsfree(info); } else if (!strcasecmp(c->argv[1]->ptr,"keyslot") && c->argc == 3) { /* CLUSTER KEYSLOT */ sds key = c->argv[2]->ptr; addReplyLongLong(c,keyHashSlot(key,sdslen(key))); } else if (!strcasecmp(c->argv[1]->ptr,"countkeysinslot") && c->argc == 3) { /* CLUSTER COUNTKEYSINSLOT */ long long slot; if (getLongLongFromObjectOrReply(c,c->argv[2],&slot,NULL) != C_OK) return; if (slot < 0 || slot >= CLUSTER_SLOTS) { addReplyError(c,"Invalid slot"); return; } addReplyLongLong(c,countKeysInSlot(slot)); } else if (!strcasecmp(c->argv[1]->ptr,"getkeysinslot") && c->argc == 4) { /* CLUSTER GETKEYSINSLOT */ long long maxkeys, slot; if (getLongLongFromObjectOrReply(c,c->argv[2],&slot,NULL) != C_OK) return; if (getLongLongFromObjectOrReply(c,c->argv[3],&maxkeys,NULL) != C_OK) return; if (slot < 0 || slot >= CLUSTER_SLOTS || maxkeys < 0) { addReplyError(c,"Invalid slot or number of keys"); return; } 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; dictEntry *de = NULL; kvs_di = kvstoreGetDictIterator(server.db->keys, slot); for (unsigned int i = 0; i < numkeys; i++) { de = kvstoreDictIteratorNext(kvs_di); serverAssert(de != NULL); sds sdskey = dictGetKey(de); addReplyBulkCBuffer(c, sdskey, sdslen(sdskey)); } kvstoreReleaseDictIterator(kvs_di); } else if ((!strcasecmp(c->argv[1]->ptr,"slaves") || !strcasecmp(c->argv[1]->ptr,"replicas")) && c->argc == 3) { /* CLUSTER SLAVES */ /* CLUSTER REPLICAS */ clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); int j; /* Lookup the specified node in our table. */ if (!n) { addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); return; } if (clusterNodeIsSlave(n)) { addReplyError(c,"The specified node is not a master"); return; } /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */ addReplyArrayLen(c, clusterNodeNumSlaves(n)); for (j = 0; j < clusterNodeNumSlaves(n); j++) { sds ni = clusterGenNodeDescription(c, clusterNodeGetSlave(n, j), shouldReturnTlsInfo()); addReplyBulkCString(c,ni); sdsfree(ni); } } else if(!clusterCommandSpecial(c)) { addReplySubcommandSyntaxError(c); return; } } /* Return the pointer to the cluster node that is able to serve the command. * For the function to succeed the command should only target either: * * 1) A single key (even multiple times like RPOPLPUSH mylist mylist). * 2) Multiple keys in the same hash slot, while the slot is stable (no * resharding in progress). * * On success the function returns the node that is able to serve the request. * If the node is not 'myself' a redirection must be performed. The kind of * redirection is specified setting the integer passed by reference * 'error_code', which will be set to CLUSTER_REDIR_ASK or * CLUSTER_REDIR_MOVED. * * When the node is 'myself' 'error_code' is set to CLUSTER_REDIR_NONE. * * If the command fails NULL is returned, and the reason of the failure is * provided via 'error_code', which will be set to: * * CLUSTER_REDIR_CROSS_SLOT if the request contains multiple keys that * don't belong to the same hash slot. * * CLUSTER_REDIR_UNSTABLE if the request contains multiple keys * belonging to the same slot, but the slot is not stable (in migration or * importing state, likely because a resharding is in progress). * * CLUSTER_REDIR_DOWN_UNBOUND if the request addresses a slot which is * not bound to any node. In this case the cluster global state should be * already "down" but it is fragile to rely on the update of the global state, * so we also handle it here. * * 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, uint64_t cmd_flags, int *error_code) { clusterNode *myself = getMyClusterNode(); clusterNode *n = NULL; robj *firstkey = NULL; int multiple_keys = 0; multiState *ms, _ms; multiCmd mc; int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0, existing_keys = 0; int pubsubshard_included = 0; /* Flag to indicate if a pubsub shard cmd is included. */ /* Allow any key to be set if a module disabled cluster redirections. */ if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION) return myself; /* Set error code optimistically for the base case. */ if (error_code) *error_code = CLUSTER_REDIR_NONE; /* Modules can turn off Redis Cluster redirection: this is useful * when writing a module that implements a completely different * distributed system. */ /* We handle all the cases as if they were EXEC commands, so we have * a common code path for everything */ if (cmd->proc == execCommand) { /* If CLIENT_MULTI flag is not set EXEC is just going to return an * error. */ if (!(c->flags & CLIENT_MULTI)) return myself; ms = &c->mstate; } else { /* In order to have a single codepath create a fake Multi State * structure if the client is not in MULTI/EXEC state, this way * we have a single codepath below. */ ms = &_ms; _ms.commands = &mc; _ms.count = 1; mc.argv = argv; mc.argc = argc; mc.cmd = cmd; } /* Check that all the keys are in the same hash slot, and obtain this * slot and the node associated. */ for (i = 0; i < ms->count; i++) { struct redisCommand *mcmd; robj **margv; int margc, numkeys, j; keyReference *keyindex; mcmd = ms->commands[i].cmd; margc = ms->commands[i].argc; margv = ms->commands[i].argv; /* Only valid for sharded pubsub as regular pubsub can operate on any node and bypasses this layer. */ if (!pubsubshard_included && doesCommandHaveChannelsWithFlags(mcmd, CMD_CHANNEL_PUBLISH | CMD_CHANNEL_SUBSCRIBE)) { pubsubshard_included = 1; } getKeysResult result = GETKEYS_RESULT_INIT; numkeys = getKeysFromCommand(mcmd,margv,margc,&result); keyindex = result.keys; for (j = 0; j < numkeys; j++) { robj *thiskey = margv[keyindex[j].pos]; int thisslot = keyHashSlot((char*)thiskey->ptr, sdslen(thiskey->ptr)); if (firstkey == NULL) { /* This is the first key we see. Check what is the slot * and node. */ firstkey = thiskey; slot = thisslot; n = getNodeBySlot(slot); /* Error: If a slot is not served, we are in "cluster down" * state. However the state is yet to be updated, so this was * not trapped earlier in processCommand(). Report the same * error to the client. */ if (n == NULL) { getKeysFreeResult(&result); if (error_code) *error_code = CLUSTER_REDIR_DOWN_UNBOUND; return NULL; } /* If we are migrating or importing this slot, we need to check * if we have all the keys in the request (the only way we * can safely serve the request, otherwise we return a TRYAGAIN * error). To do so we set the importing/migrating state and * increment a counter for every missing key. */ if (n == myself && getMigratingSlotDest(slot) != NULL) { migrating_slot = 1; } else if (getImportingSlotSource(slot) != NULL) { importing_slot = 1; } } else { /* If it is not the first key/channel, make sure it is exactly * the same key/channel as the first we saw. */ if (slot != thisslot) { /* Error: multiple keys from different slots. */ getKeysFreeResult(&result); if (error_code) *error_code = CLUSTER_REDIR_CROSS_SLOT; return NULL; } if (importing_slot && !multiple_keys && !equalStringObjects(firstkey,thiskey)) { /* Flag this request as one with multiple different * keys/channels when the slot is in importing state. */ multiple_keys = 1; } } /* Migrating / Importing slot? Count keys we don't have. * If it is pubsubshard command, it isn't required to check * the channel being present or not in the node during the * slot migration, the channel will be served from the source * node until the migration completes with CLUSTER SETSLOT * NODE . */ int flags = LOOKUP_NOTOUCH | LOOKUP_NOSTATS | LOOKUP_NONOTIFY | LOOKUP_NOEXPIRE; if ((migrating_slot || importing_slot) && !pubsubshard_included) { if (lookupKeyReadWithFlags(&server.db[0], thiskey, flags) == NULL) missing_keys++; else existing_keys++; } } getKeysFreeResult(&result); } /* No key at all in command? then we can serve the request * without redirections or errors in all the cases. */ if (n == NULL) return myself; /* Cluster is globally down but we got keys? We only serve the request * if it is a read command and when allow_reads_when_down is enabled. */ if (!isClusterHealthy()) { if (pubsubshard_included) { if (!server.cluster_allow_pubsubshard_when_down) { if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE; return NULL; } } else if (!server.cluster_allow_reads_when_down) { /* The cluster is configured to block commands when the * cluster is down. */ if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE; return NULL; } else if (cmd_flags & CMD_WRITE) { /* The cluster is configured to allow read only commands */ if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE; return NULL; } else { /* Fall through and allow the command to be executed: * this happens when server.cluster_allow_reads_when_down is * true and the command is not a write command */ } } /* Return the hashslot by reference. */ if (hashslot) *hashslot = slot; /* MIGRATE always works in the context of the local node if the slot * is open (migrating or importing state). We need to be able to freely * move keys among instances in this case. */ if ((migrating_slot || importing_slot) && cmd->proc == migrateCommand) return myself; /* If we don't have all the keys and we are migrating the slot, send * an ASK redirection or TRYAGAIN. */ if (migrating_slot && missing_keys) { /* If we have keys but we don't have all keys, we return TRYAGAIN */ if (existing_keys) { if (error_code) *error_code = CLUSTER_REDIR_UNSTABLE; return NULL; } else { if (error_code) *error_code = CLUSTER_REDIR_ASK; return getMigratingSlotDest(slot); } } /* If we are receiving the slot, and the client correctly flagged the * request as "ASKING", we can serve the request. However if the request * involves multiple keys and we don't have them all, the only option is * to send a TRYAGAIN error. */ if (importing_slot && (c->flags & CLIENT_ASKING || cmd_flags & CMD_ASKING)) { if (multiple_keys && missing_keys) { if (error_code) *error_code = CLUSTER_REDIR_UNSTABLE; return NULL; } else { return myself; } } /* Handle the read-only client case reading from a slave: if this * node is a slave and the request is about a hash slot our master * is serving, we can reply without redirection. */ int is_write_command = (cmd_flags & CMD_WRITE) || (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE)); if (((c->flags & CLIENT_READONLY) || pubsubshard_included) && !is_write_command && clusterNodeIsSlave(myself) && clusterNodeGetSlaveof(myself) == n) { return myself; } /* 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; return n; } /* Send the client the right redirection code, according to error_code * that should be set to one of CLUSTER_REDIR_* macros. * * If CLUSTER_REDIR_ASK or CLUSTER_REDIR_MOVED error codes * are used, then the node 'n' should not be NULL, but should be the * node we want to mention in the redirection. Moreover hashslot should * be set to the hash slot that caused the redirection. */ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) { if (error_code == CLUSTER_REDIR_CROSS_SLOT) { addReplyError(c,"-CROSSSLOT Keys in request don't hash to the same slot"); } else if (error_code == CLUSTER_REDIR_UNSTABLE) { /* The request spawns multiple keys in the same slot, * but the slot is not "stable" currently as there is * a migration or import in progress. */ addReplyError(c,"-TRYAGAIN Multiple keys request during rehashing of slot"); } else if (error_code == CLUSTER_REDIR_DOWN_STATE) { addReplyError(c,"-CLUSTERDOWN The cluster is down"); } else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) { addReplyError(c,"-CLUSTERDOWN The cluster is down and only accepts read commands"); } else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) { addReplyError(c,"-CLUSTERDOWN Hash slot not served"); } else if (error_code == CLUSTER_REDIR_MOVED || error_code == CLUSTER_REDIR_ASK) { /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */ int port = clusterNodeClientPort(n, shouldReturnTlsInfo()); addReplyErrorSds(c,sdscatprintf(sdsempty(), "-%s %d %s:%d", (error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED", hashslot, clusterNodePreferredEndpoint(n), port)); } else { serverPanic("getNodeByQuery() unknown error."); } } /* This function is called by the function processing clients incrementally * to detect timeouts, in order to handle the following case: * * 1) A client blocks with BLPOP or similar blocking operation. * 2) The master migrates the hash slot elsewhere or turns into a slave. * 3) The client may remain blocked forever (or up to the max timeout time) * waiting for a key change that will never happen. * * If the client is found to be blocked into a hash slot this node no * longer handles, the client is sent a redirection error, and the function * returns 1. Otherwise 0 is returned and no operation is performed. */ int clusterRedirectBlockedClientIfNeeded(client *c) { clusterNode *myself = getMyClusterNode(); if (c->flags & CLIENT_BLOCKED && (c->bstate.btype == BLOCKED_LIST || c->bstate.btype == BLOCKED_ZSET || c->bstate.btype == BLOCKED_STREAM || c->bstate.btype == BLOCKED_MODULE)) { dictEntry *de; dictIterator *di; /* If the cluster is down, unblock the client with the right error. * If the cluster is configured to allow reads on cluster down, we * still want to emit this error since a write will be required * to unblock them which may never come. */ if (!isClusterHealthy()) { clusterRedirectClient(c,NULL,0,CLUSTER_REDIR_DOWN_STATE); return 1; } /* If the client is blocked on module, but not on a specific key, * don't unblock it (except for the CLUSTER_FAIL case above). */ if (c->bstate.btype == BLOCKED_MODULE && !moduleClientIsBlockedOnKeys(c)) return 0; /* All keys must belong to the same slot, so check first key only. */ di = dictGetIterator(c->bstate.keys); if ((de = dictNext(di)) != NULL) { robj *key = dictGetKey(de); int slot = keyHashSlot((char*)key->ptr, sdslen(key->ptr)); clusterNode *node = getNodeBySlot(slot); /* if the client is read-only and attempting to access key that our * replica can handle, allow it. */ if ((c->flags & CLIENT_READONLY) && !(c->lastcmd->flags & CMD_WRITE) && clusterNodeIsSlave(myself) && clusterNodeGetSlaveof(myself) == node) { node = myself; } /* We send an error and unblock the client if: * 1) The slot is unassigned, emitting a cluster down error. * 2) The slot is not handled by this node, nor being imported. */ if (node != myself && getImportingSlotSource(slot) == NULL) { if (node == NULL) { clusterRedirectClient(c,NULL,0, CLUSTER_REDIR_DOWN_UNBOUND); } else { clusterRedirectClient(c,node,slot, CLUSTER_REDIR_MOVED); } dictReleaseIterator(di); return 1; } } dictReleaseIterator(di); } return 0; } /* Returns an indication if the replica node is fully available * and should be listed in CLUSTER SLOTS response. * Returns 1 for available nodes, 0 for nodes that have * not finished their initial sync, in failed state, or are * otherwise considered not available to serve read commands. */ static int isReplicaAvailable(clusterNode *node) { if (clusterNodeIsFailing(node)) { return 0; } long long repl_offset = clusterNodeReplOffset(node); if (clusterNodeIsMyself(node)) { /* Nodes do not update their own information * in the cluster node list. */ repl_offset = replicationGetSlaveOffset(); } return (repl_offset != 0); } void addNodeToNodeReply(client *c, clusterNode *node) { char* hostname = clusterNodeHostname(node); addReplyArrayLen(c, 4); if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_IP) { addReplyBulkCString(c, clusterNodeIp(node)); } else if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_HOSTNAME) { if (hostname != NULL && hostname[0] != '\0') { addReplyBulkCString(c, hostname); } else { addReplyBulkCString(c, "?"); } } else if (server.cluster_preferred_endpoint_type == CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT) { addReplyNull(c); } else { serverPanic("Unrecognized preferred endpoint type"); } /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */ addReplyLongLong(c, clusterNodeClientPort(node, shouldReturnTlsInfo())); addReplyBulkCBuffer(c, clusterNodeGetName(node), CLUSTER_NAMELEN); /* Add the additional endpoint information, this is all the known networking information * that is not the preferred endpoint. Note the logic is evaluated twice so we can * correctly report the number of additional network arguments without using a deferred * map, an assertion is made at the end to check we set the right length. */ int length = 0; if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_IP) { length++; } if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_HOSTNAME && hostname != NULL && hostname[0] != '\0') { length++; } addReplyMapLen(c, length); if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_IP) { addReplyBulkCString(c, "ip"); addReplyBulkCString(c, clusterNodeIp(node)); length--; } if (server.cluster_preferred_endpoint_type != CLUSTER_ENDPOINT_TYPE_HOSTNAME && hostname != NULL && hostname[0] != '\0') { addReplyBulkCString(c, "hostname"); addReplyBulkCString(c, hostname); length--; } serverAssert(length == 0); } void addNodeReplyForClusterSlot(client *c, clusterNode *node, int start_slot, int end_slot) { int i, nested_elements = 3; /* slots (2) + master addr (1) */ for (i = 0; i < clusterNodeNumSlaves(node); i++) { if (!isReplicaAvailable(clusterNodeGetSlave(node, i))) continue; nested_elements++; } addReplyArrayLen(c, nested_elements); addReplyLongLong(c, start_slot); addReplyLongLong(c, end_slot); addNodeToNodeReply(c, node); /* Remaining nodes in reply are replicas for slot range */ for (i = 0; i < clusterNodeNumSlaves(node); i++) { /* This loop is copy/pasted from clusterGenNodeDescription() * with modifications for per-slot node aggregation. */ if (!isReplicaAvailable(clusterNodeGetSlave(node, i))) continue; addNodeToNodeReply(c, clusterNodeGetSlave(node, i)); nested_elements--; } serverAssert(nested_elements == 3); /* Original 3 elements */ } void clusterCommandSlots(client * c) { /* Format: 1) 1) start slot * 2) end slot * 3) 1) master IP * 2) master port * 3) node ID * 4) 1) replica IP * 2) replica port * 3) node ID * ... continued until done */ clusterNode *n = NULL; int num_masters = 0, start = -1; void *slot_replylen = addReplyDeferredLen(c); for (int i = 0; i <= CLUSTER_SLOTS; i++) { /* Find start node and slot id. */ if (n == NULL) { if (i == CLUSTER_SLOTS) break; n = getNodeBySlot(i); start = i; continue; } /* Add cluster slots info when occur different node with start * or end of slot. */ if (i == CLUSTER_SLOTS || n != getNodeBySlot(i)) { addNodeReplyForClusterSlot(c, n, start, i-1); num_masters++; if (i == CLUSTER_SLOTS) break; n = getNodeBySlot(i); start = i; } } setDeferredArrayLen(c, slot_replylen, num_masters); } /* ----------------------------------------------------------------------------- * Cluster functions related to serving / redirecting clients * -------------------------------------------------------------------------- */ /* The ASKING command is required after a -ASK redirection. * The client should issue ASKING before to actually send the command to * the target instance. See the Redis Cluster specification for more * information. */ void askingCommand(client *c) { if (server.cluster_enabled == 0) { addReplyError(c,"This instance has cluster support disabled"); return; } c->flags |= CLIENT_ASKING; addReply(c,shared.ok); } /* The READONLY command is used by clients to enter the read-only mode. * In this mode slaves will not redirect clients as long as clients access * with read-only commands to keys that are served by the slave's master. */ void readonlyCommand(client *c) { if (server.cluster_enabled == 0) { addReplyError(c,"This instance has cluster support disabled"); return; } c->flags |= CLIENT_READONLY; addReply(c,shared.ok); } void replySlotsFlushAndFree(client *c, SlotsFlush *sflush) { addReplyArrayLen(c, sflush->numRanges); for (int i = 0 ; i < sflush->numRanges ; i++) { addReplyArrayLen(c, 2); addReplyLongLong(c, sflush->ranges[i].first); addReplyLongLong(c, sflush->ranges[i].last); } zfree(sflush); } /* Partially flush destination DB in a cluster node, based on the slot range. * * Usage: SFLUSH [ ]* [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 * optimization. */ void sflushCommand(client *c) { int flags = EMPTYDB_NO_FLAGS, argc = c->argc; if (server.cluster_enabled == 0) { addReplyError(c,"This instance has cluster support disabled"); return; } /* check if last argument is SYNC or ASYNC */ if (!strcasecmp(c->argv[c->argc-1]->ptr,"sync")) { flags = EMPTYDB_NO_FLAGS; argc--; } else if (!strcasecmp(c->argv[c->argc-1]->ptr,"async")) { flags = EMPTYDB_ASYNC; argc--; } else if (server.lazyfree_lazy_user_flush) { flags = EMPTYDB_ASYNC; } /* parse the slot range */ if (argc % 2 == 0) { addReplyErrorArity(c); return; } /* Verify slot pairs are valid and not overlapping */ long long j, first, last; unsigned char slotsToFlushRq[CLUSTER_SLOTS] = {0}; for (j = 1; j < argc; j += 2) { /* check if the first slot is valid */ if (getLongLongFromObject(c->argv[j], &first) != C_OK || first < 0 || first >= CLUSTER_SLOTS) { addReplyError(c,"Invalid or out of range slot"); return; } /* check if the last slot is valid */ if (getLongLongFromObject(c->argv[j+1], &last) != C_OK || last < 0 || last >= CLUSTER_SLOTS) { addReplyError(c,"Invalid or out of range slot"); return; } if (first > last) { addReplyErrorFormat(c,"start slot number %lld is greater than end slot number %lld", first, last); return; } /* Mark the slots in slotsToFlushRq[] */ for (int i = first; i <= last; i++) { if (slotsToFlushRq[i]) { addReplyErrorFormat(c, "Slot %d specified multiple times", i); return; } slotsToFlushRq[i] = 1; } } /* Verify slotsToFlushRq[] covers ALL slots of myNode. */ clusterNode *myNode = getMyClusterNode(); /* During iteration trace also the slot range pairs and save in SlotsFlush. * 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 */ int capacity = 32; /* Initial capacity */ SlotsFlush *sflush = zmalloc(sizeof(SlotsFlush) + sizeof(SlotRange) * capacity); sflush->numRanges = 0; int inSlotRange = 0; for (int i = 0; i < CLUSTER_SLOTS; i++) { if (myNode == getNodeBySlot(i)) { if (!slotsToFlushRq[i]) { addReplySetLen(c, 0); /* Not all slots of mynode got covered. See sflushCommand() comment. */ zfree(sflush); return; } if (!inSlotRange) { /* If start another slot range */ sflush->ranges[sflush->numRanges].first = i; inSlotRange = 1; } } else { if (inSlotRange) { /* If end another slot range */ sflush->ranges[sflush->numRanges++].last = i - 1; inSlotRange = 0; /* If reached 'sflush' capacity, double the capacity */ if (sflush->numRanges >= capacity) { capacity *= 2; sflush = zrealloc(sflush, sizeof(SlotsFlush) + sizeof(SlotRange) * capacity); } } } } /* Update last pair if last cluster slot is also end of last range */ if (inSlotRange) sflush->ranges[sflush->numRanges++].last = CLUSTER_SLOTS - 1; /* Flush selected slots. If not flush as blocking async, then reply immediately */ if (flushCommandCommon(c, FLUSH_TYPE_SLOTS, flags, sflush) == 0) replySlotsFlushAndFree(c, sflush); } /* The READWRITE command just clears the READONLY command state. */ void readwriteCommand(client *c) { if (server.cluster_enabled == 0) { addReplyError(c,"This instance has cluster support disabled"); return; } c->flags &= ~CLIENT_READONLY; addReply(c,shared.ok); } redis-8.0.2/src/cluster.h000066400000000000000000000157171501533116600152640ustar00rootroot00000000000000#ifndef __CLUSTER_H #define __CLUSTER_H /*----------------------------------------------------------------------------- * Redis cluster exported API. *----------------------------------------------------------------------------*/ #define CLUSTER_SLOT_MASK_BITS 14 /* Number of bits used for slot id. */ #define CLUSTER_SLOTS (1< #include #include #include #include #include #include #include /* A global reference to myself is handy to make code more clear. * Myself always points to server.cluster->myself, that is, the clusterNode * that represents this node. */ clusterNode *myself = NULL; clusterNode *createClusterNode(char *nodename, int flags); void clusterAddNode(clusterNode *node); void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void clusterReadHandler(connection *conn); void clusterSendPing(clusterLink *link, int type); void clusterSendFail(char *nodename); void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request); void clusterUpdateState(void); int clusterNodeCoversSlot(clusterNode *n, int slot); list *clusterGetNodesInMyShard(clusterNode *node); int clusterNodeAddSlave(clusterNode *master, clusterNode *slave); int clusterAddSlot(clusterNode *n, int slot); int clusterDelSlot(int slot); int clusterMoveNodeSlots(clusterNode *from_node, clusterNode *to_node); int clusterDelNodeSlots(clusterNode *node); int clusterNodeSetSlotBit(clusterNode *n, int slot); void clusterSetMaster(clusterNode *n); void clusterHandleSlaveFailover(void); void clusterHandleSlaveMigration(int max_slaves); int bitmapTestBit(unsigned char *bitmap, int pos); void bitmapSetBit(unsigned char *bitmap, int pos); void bitmapClearBit(unsigned char *bitmap, int pos); void clusterDoBeforeSleep(int flags); void clusterSendUpdate(clusterLink *link, clusterNode *node); void resetManualFailover(void); void clusterCloseAllSlots(void); void clusterSetNodeAsMaster(clusterNode *n); void clusterDelNode(clusterNode *delnode); sds representClusterNodeFlags(sds ci, uint16_t flags); sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_count); void clusterFreeNodesSlotsInfo(clusterNode *n); uint64_t clusterGetMaxEpoch(void); int clusterBumpConfigEpochWithoutConsensus(void); void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8_t type, const unsigned char *payload, uint32_t len); const char *clusterGetMessageTypeString(int type); void removeChannelsInSlot(unsigned int slot); unsigned int countKeysInSlot(unsigned int hashslot); unsigned int countChannelsInSlot(unsigned int hashslot); unsigned int delKeysInSlot(unsigned int hashslot); void clusterAddNodeToShard(const char *shard_id, clusterNode *node); list *clusterLookupNodeListByShardId(const char *shard_id); void clusterRemoveNodeFromShard(clusterNode *node); int auxShardIdSetter(clusterNode *n, void *value, int length); sds auxShardIdGetter(clusterNode *n, sds s); int auxShardIdPresent(clusterNode *n); int auxHumanNodenameSetter(clusterNode *n, void *value, int length); sds auxHumanNodenameGetter(clusterNode *n, sds s); int auxHumanNodenamePresent(clusterNode *n); int auxTcpPortSetter(clusterNode *n, void *value, int length); sds auxTcpPortGetter(clusterNode *n, sds s); int auxTcpPortPresent(clusterNode *n); int auxTlsPortSetter(clusterNode *n, void *value, int length); sds auxTlsPortGetter(clusterNode *n, sds s); int auxTlsPortPresent(clusterNode *n); static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen); void freeClusterLink(clusterLink *link); int verifyClusterNodeId(const char *name, int length); static void updateShardId(clusterNode *node, const char *shard_id); int getNodeDefaultClientPort(clusterNode *n) { return server.tls_cluster ? n->tls_port : n->tcp_port; } static inline int getNodeDefaultReplicationPort(clusterNode *n) { return server.tls_replication ? n->tls_port : n->tcp_port; } int clusterNodeClientPort(clusterNode *n, int use_tls) { return use_tls ? n->tls_port : n->tcp_port; } static inline int defaultClientPort(void) { return server.tls_cluster ? server.tls_port : server.port; } #define isSlotUnclaimed(slot) \ (server.cluster->slots[slot] == NULL || \ bitmapTestBit(server.cluster->owner_not_claiming_slot, slot)) #define RCVBUF_INIT_LEN 1024 #define RCVBUF_MAX_PREALLOC (1<<20) /* 1MB */ /* Cluster nodes hash table, mapping nodes addresses 1.2.3.4:6379 to * clusterNode structures. */ dictType clusterNodesDictType = { dictSdsHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCompare, /* key compare */ dictSdsDestructor, /* key destructor */ NULL, /* val destructor */ NULL /* allow to expand */ }; /* Cluster re-addition blacklist. This maps node IDs to the time * we can re-add this node. The goal is to avoid reading a removed * node for some time. */ dictType clusterNodesBlackListDictType = { dictSdsCaseHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCaseCompare, /* key compare */ dictSdsDestructor, /* key destructor */ NULL, /* val destructor */ NULL /* allow to expand */ }; /* Cluster shards hash table, mapping shard id to list of nodes */ dictType clusterSdsToListType = { dictSdsHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCompare, /* key compare */ dictSdsDestructor, /* key destructor */ dictListDestructor, /* val destructor */ NULL /* allow to expand */ }; /* Aux fields are introduced in Redis 7.2 to support the persistence * of various important node properties, such as shard id, in nodes.conf. * Aux fields take an explicit format of name=value pairs and have no * intrinsic order among them. Aux fields are always grouped together * at the end of the second column of each row after the node's IP * address/port/cluster_port and the optional hostname. Aux fields * are separated by ','. */ /* Aux field setter function prototype * return C_OK when the update is successful; C_ERR otherwise */ typedef int (aux_value_setter) (clusterNode* n, void *value, int length); /* Aux field getter function prototype * return an sds that is a concatenation of the input sds string and * the aux value */ typedef sds (aux_value_getter) (clusterNode* n, sds s); typedef int (aux_value_present) (clusterNode* n); typedef struct { char *field; aux_value_setter *setter; aux_value_getter *getter; aux_value_present *isPresent; } auxFieldHandler; /* Assign index to each aux field */ typedef enum { af_shard_id, af_human_nodename, af_tcp_port, af_tls_port, af_count, } auxFieldIndex; /* Note that * 1. the order of the elements below must match that of their * indices as defined in auxFieldIndex * 2. aux name can contain characters that pass the isValidAuxChar check only */ auxFieldHandler auxFieldHandlers[] = { {"shard-id", auxShardIdSetter, auxShardIdGetter, auxShardIdPresent}, {"nodename", auxHumanNodenameSetter, auxHumanNodenameGetter, auxHumanNodenamePresent}, {"tcp-port", auxTcpPortSetter, auxTcpPortGetter, auxTcpPortPresent}, {"tls-port", auxTlsPortSetter, auxTlsPortGetter, auxTlsPortPresent}, }; int auxShardIdSetter(clusterNode *n, void *value, int length) { if (verifyClusterNodeId(value, length) == C_ERR) { return C_ERR; } memcpy(n->shard_id, value, CLUSTER_NAMELEN); /* if n already has replicas, make sure they all use * the primary shard id */ for (int i = 0; i < n->numslaves; i++) { if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0) updateShardId(n->slaves[i], n->shard_id); } clusterAddNodeToShard(value, n); return C_OK; } sds auxShardIdGetter(clusterNode *n, sds s) { return sdscatprintf(s, "%.40s", n->shard_id); } int auxShardIdPresent(clusterNode *n) { return strlen(n->shard_id); } int auxHumanNodenameSetter(clusterNode *n, void *value, int length) { if (n && !strncmp(value, n->human_nodename, length)) { return C_OK; } else if (!n && (length == 0)) { return C_OK; } if (n) { n->human_nodename = sdscpylen(n->human_nodename, value, length); } else if (sdslen(n->human_nodename) != 0) { sdsclear(n->human_nodename); } else { return C_ERR; } return C_OK; } sds auxHumanNodenameGetter(clusterNode *n, sds s) { return sdscatprintf(s, "%s", n->human_nodename); } int auxHumanNodenamePresent(clusterNode *n) { return sdslen(n->human_nodename); } int auxTcpPortSetter(clusterNode *n, void *value, int length) { if (length > 5 || length < 1) { return C_ERR; } char buf[length + 1]; memcpy(buf, (char*)value, length); buf[length] = '\0'; n->tcp_port = atoi(buf); return (n->tcp_port < 0 || n->tcp_port >= 65536) ? C_ERR : C_OK; } sds auxTcpPortGetter(clusterNode *n, sds s) { return sdscatprintf(s, "%d", n->tcp_port); } int auxTcpPortPresent(clusterNode *n) { return n->tcp_port >= 0 && n->tcp_port < 65536; } int auxTlsPortSetter(clusterNode *n, void *value, int length) { if (length > 5 || length < 1) { return C_ERR; } char buf[length + 1]; memcpy(buf, (char*)value, length); buf[length] = '\0'; n->tls_port = atoi(buf); return (n->tls_port < 0 || n->tls_port >= 65536) ? C_ERR : C_OK; } sds auxTlsPortGetter(clusterNode *n, sds s) { return sdscatprintf(s, "%d", n->tls_port); } int auxTlsPortPresent(clusterNode *n) { return n->tls_port >= 0 && n->tls_port < 65536; } /* clusterLink send queue blocks */ typedef struct { size_t totlen; /* Total length of this block including the message */ int refcount; /* Number of cluster link send msg queues containing the message */ clusterMsg msg; } clusterMsgSendBlock; /* ----------------------------------------------------------------------------- * Initialization * -------------------------------------------------------------------------- */ /* Load the cluster config from 'filename'. * * If the file does not exist or is zero-length (this may happen because * when we lock the nodes.conf file, we create a zero-length one for the * sake of locking if it does not already exist), C_ERR is returned. * If the configuration was loaded from the file, C_OK is returned. */ int clusterLoadConfig(char *filename) { FILE *fp = fopen(filename,"r"); struct stat sb; char *line; int maxline, j; if (fp == NULL) { if (errno == ENOENT) { return C_ERR; } else { serverLog(LL_WARNING, "Loading the cluster node config from %s: %s", filename, strerror(errno)); exit(1); } } if (redis_fstat(fileno(fp),&sb) == -1) { serverLog(LL_WARNING, "Unable to obtain the cluster node config file stat %s: %s", filename, strerror(errno)); exit(1); } /* Check if the file is zero-length: if so return C_ERR to signal * we have to write the config. */ if (sb.st_size == 0) { fclose(fp); return C_ERR; } /* Parse the file. Note that single lines of the cluster config file can * be really long as they include all the hash slots of the node. * This means in the worst possible case, half of the Redis slots will be * present in a single line, possibly in importing or migrating state, so * together with the node ID of the sender/receiver. * * To simplify we allocate 1024+CLUSTER_SLOTS*128 bytes per line. */ maxline = 1024+CLUSTER_SLOTS*128; line = zmalloc(maxline); while(fgets(line,maxline,fp) != NULL) { int argc, aux_argc; sds *argv, *aux_argv; clusterNode *n, *master; char *p, *s; /* Skip blank lines, they can be created either by users manually * editing nodes.conf or by the config writing process if stopped * before the truncate() call. */ if (line[0] == '\n' || line[0] == '\0') continue; /* Split the line into arguments for processing. */ argv = sdssplitargs(line,&argc); if (argv == NULL) goto fmterr; /* Handle the special "vars" line. Don't pretend it is the last * line even if it actually is when generated by Redis. */ if (strcasecmp(argv[0],"vars") == 0) { if (!(argc % 2)) goto fmterr; for (j = 1; j < argc; j += 2) { if (strcasecmp(argv[j],"currentEpoch") == 0) { server.cluster->currentEpoch = strtoull(argv[j+1],NULL,10); } else if (strcasecmp(argv[j],"lastVoteEpoch") == 0) { server.cluster->lastVoteEpoch = strtoull(argv[j+1],NULL,10); } else { serverLog(LL_NOTICE, "Skipping unknown cluster config variable '%s'", argv[j]); } } sdsfreesplitres(argv,argc); continue; } /* Regular config lines have at least eight fields */ if (argc < 8) { sdsfreesplitres(argv,argc); goto fmterr; } /* Create this node if it does not exist */ if (verifyClusterNodeId(argv[0], sdslen(argv[0])) == C_ERR) { sdsfreesplitres(argv, argc); goto fmterr; } n = clusterLookupNode(argv[0], sdslen(argv[0])); if (!n) { n = createClusterNode(argv[0],0); clusterAddNode(n); } /* Format for the node address and auxiliary argument information: * ip:port[@cport][,hostname][,aux=val]*] */ aux_argv = sdssplitlen(argv[1], sdslen(argv[1]), ",", 1, &aux_argc); if (aux_argv == NULL) { sdsfreesplitres(argv,argc); goto fmterr; } /* Hostname is an optional argument that defines the endpoint * that can be reported to clients instead of IP. */ if (aux_argc > 1 && sdslen(aux_argv[1]) > 0) { n->hostname = sdscpy(n->hostname, aux_argv[1]); } else if (sdslen(n->hostname) != 0) { sdsclear(n->hostname); } /* All fields after hostname are auxiliary and they take on * the format of "aux=val" where both aux and val can contain * characters that pass the isValidAuxChar check only. The order * of the aux fields is insignificant. */ int aux_tcp_port = 0; int aux_tls_port = 0; for (int i = 2; i < aux_argc; i++) { int field_argc; sds *field_argv; field_argv = sdssplitlen(aux_argv[i], sdslen(aux_argv[i]), "=", 1, &field_argc); if (field_argv == NULL || field_argc != 2) { /* Invalid aux field format */ if (field_argv != NULL) sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(aux_argv, aux_argc); sdsfreesplitres(argv,argc); goto fmterr; } /* Validate that both aux and value contain valid characters only */ for (unsigned j = 0; j < 2; j++) { if (!isValidAuxString(field_argv[j],sdslen(field_argv[j]))){ /* Invalid aux field format */ sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(aux_argv, aux_argc); sdsfreesplitres(argv,argc); goto fmterr; } } /* Note that we don't expect lots of aux fields in the foreseeable * future so a linear search is completely fine. */ int field_found = 0; for (unsigned j = 0; j < numElements(auxFieldHandlers); j++) { if (sdslen(field_argv[0]) != strlen(auxFieldHandlers[j].field) || memcmp(field_argv[0], auxFieldHandlers[j].field, sdslen(field_argv[0])) != 0) { continue; } field_found = 1; aux_tcp_port |= j == af_tcp_port; aux_tls_port |= j == af_tls_port; if (auxFieldHandlers[j].setter(n, field_argv[1], sdslen(field_argv[1])) != C_OK) { /* Invalid aux field format */ sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(aux_argv, aux_argc); sdsfreesplitres(argv,argc); goto fmterr; } } if (field_found == 0) { /* Invalid aux field format */ sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(aux_argv, aux_argc); sdsfreesplitres(argv,argc); goto fmterr; } sdsfreesplitres(field_argv, field_argc); } /* Address and port */ if ((p = strrchr(aux_argv[0],':')) == NULL) { sdsfreesplitres(aux_argv, aux_argc); sdsfreesplitres(argv,argc); goto fmterr; } *p = '\0'; memcpy(n->ip,aux_argv[0],strlen(aux_argv[0])+1); char *port = p+1; char *busp = strchr(port,'@'); if (busp) { *busp = '\0'; busp++; } /* If neither TCP or TLS port is found in aux field, it is considered * an old version of nodes.conf file.*/ if (!aux_tcp_port && !aux_tls_port) { if (server.tls_cluster) { n->tls_port = atoi(port); } else { n->tcp_port = atoi(port); } } else if (!aux_tcp_port) { n->tcp_port = atoi(port); } else if (!aux_tls_port) { n->tls_port = atoi(port); } /* In older versions of nodes.conf the "@busport" part is missing. * In this case we set it to the default offset of 10000 from the * base port. */ n->cport = busp ? atoi(busp) : (getNodeDefaultClientPort(n) + CLUSTER_PORT_INCR); /* The plaintext port for client in a TLS cluster (n->pport) is not * stored in nodes.conf. It is received later over the bus protocol. */ sdsfreesplitres(aux_argv, aux_argc); /* Parse flags */ p = s = argv[2]; while(p) { p = strchr(s,','); if (p) *p = '\0'; if (!strcasecmp(s,"myself")) { serverAssert(server.cluster->myself == NULL); myself = server.cluster->myself = n; n->flags |= CLUSTER_NODE_MYSELF; } else if (!strcasecmp(s,"master")) { n->flags |= CLUSTER_NODE_MASTER; } else if (!strcasecmp(s,"slave")) { n->flags |= CLUSTER_NODE_SLAVE; } else if (!strcasecmp(s,"fail?")) { n->flags |= CLUSTER_NODE_PFAIL; } else if (!strcasecmp(s,"fail")) { n->flags |= CLUSTER_NODE_FAIL; n->fail_time = mstime(); } else if (!strcasecmp(s,"handshake")) { n->flags |= CLUSTER_NODE_HANDSHAKE; } else if (!strcasecmp(s,"noaddr")) { n->flags |= CLUSTER_NODE_NOADDR; } else if (!strcasecmp(s,"nofailover")) { n->flags |= CLUSTER_NODE_NOFAILOVER; } else if (!strcasecmp(s,"noflags")) { /* nothing to do */ } else { serverPanic("Unknown flag in redis cluster config file"); } if (p) s = p+1; } /* Get master if any. Set the master and populate master's * slave list. */ if (argv[3][0] != '-') { if (verifyClusterNodeId(argv[3], sdslen(argv[3])) == C_ERR) { sdsfreesplitres(argv, argc); goto fmterr; } master = clusterLookupNode(argv[3], sdslen(argv[3])); if (!master) { master = createClusterNode(argv[3],0); clusterAddNode(master); } /* shard_id can be absent if we are loading a nodes.conf generated * by an older version of Redis; * ignore replica's shard_id in the file, only use the primary's. * If replica precedes primary in file, it will be corrected * later by the auxShardIdSetter */ memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN); clusterAddNodeToShard(master->shard_id, n); n->slaveof = master; clusterNodeAddSlave(master,n); } else if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) { /* n is a primary but it does not have a persisted shard_id. * This happens if we are loading a nodes.conf generated by * an older version of Redis. We should manually update the * shard membership in this case */ clusterAddNodeToShard(n->shard_id, n); } /* Set ping sent / pong received timestamps */ if (atoi(argv[4])) n->ping_sent = mstime(); if (atoi(argv[5])) n->pong_received = mstime(); /* Set configEpoch for this node. * If the node is a replica, set its config epoch to 0. * If it's a primary, load the config epoch from the configuration file. */ n->configEpoch = (nodeIsSlave(n) && n->slaveof) ? 0 : strtoull(argv[6],NULL,10); /* Populate hash slots served by this instance. */ for (j = 8; j < argc; j++) { int start, stop; if (argv[j][0] == '[') { /* Here we handle migrating / importing slots */ int slot; char direction; clusterNode *cn; p = strchr(argv[j],'-'); serverAssert(p != NULL); *p = '\0'; direction = p[1]; /* Either '>' or '<' */ slot = atoi(argv[j]+1); if (slot < 0 || slot >= CLUSTER_SLOTS) { sdsfreesplitres(argv,argc); goto fmterr; } p += 3; char *pr = strchr(p, ']'); size_t node_len = pr - p; if (pr == NULL || verifyClusterNodeId(p, node_len) == C_ERR) { sdsfreesplitres(argv, argc); goto fmterr; } cn = clusterLookupNode(p, CLUSTER_NAMELEN); if (!cn) { cn = createClusterNode(p,0); clusterAddNode(cn); } if (direction == '>') { server.cluster->migrating_slots_to[slot] = cn; } else { server.cluster->importing_slots_from[slot] = cn; } continue; } else if ((p = strchr(argv[j],'-')) != NULL) { *p = '\0'; start = atoi(argv[j]); stop = atoi(p+1); } else { start = stop = atoi(argv[j]); } if (start < 0 || start >= CLUSTER_SLOTS || stop < 0 || stop >= CLUSTER_SLOTS) { sdsfreesplitres(argv,argc); goto fmterr; } while(start <= stop) clusterAddSlot(n, start++); } sdsfreesplitres(argv,argc); } /* Config sanity check */ if (server.cluster->myself == NULL) goto fmterr; if (!(myself->flags & (CLUSTER_NODE_MASTER | CLUSTER_NODE_SLAVE))) goto fmterr; if (nodeIsSlave(myself) && myself->slaveof == NULL) goto fmterr; zfree(line); fclose(fp); serverLog(LL_NOTICE,"Node configuration loaded, I'm %.40s", myself->name); /* Something that should never happen: currentEpoch smaller than * the max epoch found in the nodes configuration. However we handle this * as some form of protection against manual editing of critical files. */ if (clusterGetMaxEpoch() > server.cluster->currentEpoch) { server.cluster->currentEpoch = clusterGetMaxEpoch(); } return C_OK; fmterr: serverLog(LL_WARNING, "Unrecoverable error: corrupted cluster config file \"%s\".", line); zfree(line); if (fp) fclose(fp); exit(1); } /* Cluster node configuration is exactly the same as CLUSTER NODES output. * * This function writes the node config and returns 0, on error -1 * is returned. * * Note: we need to write the file in an atomic way from the point of view * of the POSIX filesystem semantics, so that if the server is stopped * or crashes during the write, we'll end with either the old file or the * new one. Since we have the full payload to write available we can use * a single write to write the whole file. If the pre-existing file was * bigger we pad our payload with newlines that are anyway ignored and truncate * the file afterward. */ int clusterSaveConfig(int do_fsync) { sds ci,tmpfilename; size_t content_size,offset = 0; ssize_t written_bytes; int fd = -1; int retval = C_ERR; server.cluster->todo_before_sleep &= ~CLUSTER_TODO_SAVE_CONFIG; /* Get the nodes description and concatenate our "vars" directive to * save currentEpoch and lastVoteEpoch. */ ci = clusterGenNodesDescription(NULL, CLUSTER_NODE_HANDSHAKE, 0); ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n", (unsigned long long) server.cluster->currentEpoch, (unsigned long long) server.cluster->lastVoteEpoch); content_size = sdslen(ci); /* Create a temp file with the new content. */ tmpfilename = sdscatfmt(sdsempty(),"%s.tmp-%i-%I", server.cluster_configfile,(int) getpid(),mstime()); if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) { serverLog(LL_WARNING,"Could not open temp cluster config file: %s",strerror(errno)); goto cleanup; } while (offset < content_size) { written_bytes = write(fd,ci + offset,content_size - offset); if (written_bytes <= 0) { if (errno == EINTR) continue; serverLog(LL_WARNING,"Failed after writing (%zd) bytes to tmp cluster config file: %s", offset,strerror(errno)); goto cleanup; } offset += written_bytes; } if (do_fsync) { server.cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG; if (redis_fsync(fd) == -1) { serverLog(LL_WARNING,"Could not sync tmp cluster config file: %s",strerror(errno)); goto cleanup; } } if (rename(tmpfilename, server.cluster_configfile) == -1) { serverLog(LL_WARNING,"Could not rename tmp cluster config file: %s",strerror(errno)); goto cleanup; } if (do_fsync) { if (fsyncFileDir(server.cluster_configfile) == -1) { serverLog(LL_WARNING,"Could not sync cluster config file dir: %s",strerror(errno)); goto cleanup; } } retval = C_OK; /* If we reached this point, everything is fine. */ cleanup: if (fd != -1) close(fd); if (retval) unlink(tmpfilename); sdsfree(tmpfilename); sdsfree(ci); return retval; } void clusterSaveConfigOrDie(int do_fsync) { if (clusterSaveConfig(do_fsync) == -1) { serverLog(LL_WARNING,"Fatal: can't update cluster config file."); exit(1); } } /* Lock the cluster config using flock(), and retain the file descriptor used to * acquire the lock so that the file will be locked as long as the process is up. * * This works because we always update nodes.conf with a new version * in-place, reopening the file, and writing to it in place (later adjusting * the length with ftruncate()). * * On success C_OK is returned, otherwise an error is logged and * the function returns C_ERR to signal a lock was not acquired. */ int clusterLockConfig(char *filename) { /* flock() does not exist on Solaris * and a fcntl-based solution won't help, as we constantly re-open that file, * which will release _all_ locks anyway */ #if !defined(__sun) /* To lock it, we need to open the file in a way it is created if * it does not exist, otherwise there is a race condition with other * processes. */ int fd = open(filename,O_WRONLY|O_CREAT|O_CLOEXEC,0644); if (fd == -1) { serverLog(LL_WARNING, "Can't open %s in order to acquire a lock: %s", filename, strerror(errno)); return C_ERR; } if (flock(fd,LOCK_EX|LOCK_NB) == -1) { if (errno == EWOULDBLOCK) { serverLog(LL_WARNING, "Sorry, the cluster configuration file %s is already used " "by a different Redis Cluster node. Please make sure that " "different nodes use different cluster configuration " "files.", filename); } else { serverLog(LL_WARNING, "Impossible to lock %s: %s", filename, strerror(errno)); } close(fd); return C_ERR; } /* Lock acquired: leak the 'fd' by not closing it until shutdown time, so that * we'll retain the lock to the file as long as the process exists. * * After fork, the child process will get the fd opened by the parent process, * we need save `fd` to `cluster_config_file_lock_fd`, so that in redisFork(), * it will be closed in the child process. * If it is not closed, when the main process is killed -9, but the child process * (redis-aof-rewrite) is still alive, the fd(lock) will still be held by the * child process, and the main process will fail to get lock, means fail to start. */ server.cluster_config_file_lock_fd = fd; #else UNUSED(filename); #endif /* __sun */ return C_OK; } /* Derives our ports to be announced in the cluster bus. */ void deriveAnnouncedPorts(int *announced_tcp_port, int *announced_tls_port, int *announced_cport) { /* Config overriding announced ports. */ *announced_tcp_port = server.cluster_announce_port ? server.cluster_announce_port : server.port; *announced_tls_port = server.cluster_announce_tls_port ? server.cluster_announce_tls_port : server.tls_port; /* Derive cluster bus port. */ if (server.cluster_announce_bus_port) { *announced_cport = server.cluster_announce_bus_port; } else if (server.cluster_port) { *announced_cport = server.cluster_port; } else { *announced_cport = defaultClientPort() + CLUSTER_PORT_INCR; } } /* Some flags (currently just the NOFAILOVER flag) may need to be updated * in the "myself" node based on the current configuration of the node, * that may change at runtime via CONFIG SET. This function changes the * set of flags in myself->flags accordingly. */ void clusterUpdateMyselfFlags(void) { if (!myself) return; int oldflags = myself->flags; int nofailover = server.cluster_slave_no_failover ? CLUSTER_NODE_NOFAILOVER : 0; myself->flags &= ~CLUSTER_NODE_NOFAILOVER; myself->flags |= nofailover; if (myself->flags != oldflags) { clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } } /* We want to take myself->port/cport/pport in sync with the * cluster-announce-port/cluster-announce-bus-port/cluster-announce-tls-port option. * The option can be set at runtime via CONFIG SET. */ void clusterUpdateMyselfAnnouncedPorts(void) { if (!myself) return; deriveAnnouncedPorts(&myself->tcp_port,&myself->tls_port,&myself->cport); } /* We want to take myself->ip in sync with the cluster-announce-ip option. * The option can be set at runtime via CONFIG SET. */ void clusterUpdateMyselfIp(void) { if (!myself) return; static char *prev_ip = NULL; char *curr_ip = server.cluster_announce_ip; int changed = 0; if (prev_ip == NULL && curr_ip != NULL) changed = 1; else if (prev_ip != NULL && curr_ip == NULL) changed = 1; else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1; if (changed) { if (prev_ip) zfree(prev_ip); prev_ip = curr_ip; if (curr_ip) { /* We always take a copy of the previous IP address, by * duplicating the string. This way later we can check if * the address really changed. */ prev_ip = zstrdup(prev_ip); redis_strlcpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN); } else { myself->ip[0] = '\0'; /* Force autodetection. */ } } } /* Update the hostname for the specified node with the provided C string. */ static void updateAnnouncedHostname(clusterNode *node, char *new) { /* Previous and new hostname are the same, no need to update. */ if (new && !strcmp(new, node->hostname)) { return; } else if (!new && (sdslen(node->hostname) == 0)) { return; } if (new) { node->hostname = sdscpy(node->hostname, new); } else if (sdslen(node->hostname) != 0) { sdsclear(node->hostname); } clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } static void updateAnnouncedHumanNodename(clusterNode *node, char *new) { if (new && !strcmp(new, node->human_nodename)) { return; } else if (!new && (sdslen(node->human_nodename) == 0)) { return; } if (new) { node->human_nodename = sdscpy(node->human_nodename, new); } else if (sdslen(node->human_nodename) != 0) { sdsclear(node->human_nodename); } clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } static void assignShardIdToNode(clusterNode *node, const char *shard_id, int flag) { clusterRemoveNodeFromShard(node); memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN); clusterAddNodeToShard(shard_id, node); clusterDoBeforeSleep(flag); } static void updateShardId(clusterNode *node, const char *shard_id) { if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) { /* We always make our best effort to keep the shard-id consistent * between the master and its replicas: * * 1. When updating the master's shard-id, we simultaneously update the * shard-id of all its replicas to ensure consistency. * 2. When updating replica's shard-id, if it differs from its master's shard-id, * we discard this replica's shard-id and continue using master's shard-id. * This applies even if the master does not support shard-id, in which * case we rely on the master's randomly generated shard-id. */ if (node->slaveof == NULL) { assignShardIdToNode(node, shard_id, CLUSTER_TODO_SAVE_CONFIG); for (int i = 0; i < clusterNodeNumSlaves(node); i++) { clusterNode *slavenode = clusterNodeGetSlave(node, i); if (memcmp(slavenode->shard_id, shard_id, CLUSTER_NAMELEN) != 0) assignShardIdToNode(slavenode, shard_id, CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); } } else if (memcmp(node->slaveof->shard_id, shard_id, CLUSTER_NAMELEN) == 0) { assignShardIdToNode(node, shard_id, CLUSTER_TODO_SAVE_CONFIG); } } } /* Update my hostname based on server configuration values */ void clusterUpdateMyselfHostname(void) { if (!myself) return; updateAnnouncedHostname(myself, server.cluster_announce_hostname); } void clusterUpdateMyselfHumanNodename(void) { if (!myself) return; updateAnnouncedHumanNodename(myself, server.cluster_announce_human_nodename); } void clusterInit(void) { int saveconf = 0; server.cluster = zmalloc(sizeof(struct clusterState)); server.cluster->myself = NULL; server.cluster->currentEpoch = 0; server.cluster->state = CLUSTER_FAIL; server.cluster->size = 0; server.cluster->todo_before_sleep = 0; server.cluster->nodes = dictCreate(&clusterNodesDictType); server.cluster->shards = dictCreate(&clusterSdsToListType); server.cluster->nodes_black_list = dictCreate(&clusterNodesBlackListDictType); server.cluster->failover_auth_time = 0; server.cluster->failover_auth_count = 0; server.cluster->failover_auth_rank = 0; server.cluster->failover_auth_epoch = 0; server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE; server.cluster->lastVoteEpoch = 0; /* Initialize stats */ for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) { server.cluster->stats_bus_messages_sent[i] = 0; server.cluster->stats_bus_messages_received[i] = 0; } server.cluster->stats_pfail_nodes = 0; server.cluster->stat_cluster_links_buffer_limit_exceeded = 0; memset(server.cluster->slots,0, sizeof(server.cluster->slots)); clusterCloseAllSlots(); memset(server.cluster->owner_not_claiming_slot, 0, sizeof(server.cluster->owner_not_claiming_slot)); /* Lock the cluster config file to make sure every node uses * its own nodes.conf. */ server.cluster_config_file_lock_fd = -1; if (clusterLockConfig(server.cluster_configfile) == C_ERR) exit(1); /* Load or create a new nodes configuration. */ if (clusterLoadConfig(server.cluster_configfile) == C_ERR) { /* No configuration found. We will just use the random name provided * by the createClusterNode() function. */ myself = server.cluster->myself = createClusterNode(NULL,CLUSTER_NODE_MYSELF|CLUSTER_NODE_MASTER); serverLog(LL_NOTICE,"No cluster configuration found, I'm %.40s", myself->name); clusterAddNode(myself); clusterAddNodeToShard(myself->shard_id, myself); saveconf = 1; } if (saveconf) clusterSaveConfigOrDie(1); /* Port sanity check II * The other handshake port check is triggered too late to stop * us from trying to use a too-high cluster port number. */ int port = defaultClientPort(); if (!server.cluster_port && port > (65535-CLUSTER_PORT_INCR)) { serverLog(LL_WARNING, "Redis port number too high. " "Cluster communication port is 10,000 port " "numbers higher than your Redis port. " "Your Redis port number must be 55535 or less."); exit(1); } if (!server.bindaddr_count) { serverLog(LL_WARNING, "No bind address is configured, but it is required for the Cluster bus."); exit(1); } /* Set myself->port/cport/pport to my listening ports, we'll just need to * discover the IP address via MEET messages. */ deriveAnnouncedPorts(&myself->tcp_port, &myself->tls_port, &myself->cport); server.cluster->mf_end = 0; server.cluster->mf_slave = NULL; resetManualFailover(); clusterUpdateMyselfFlags(); clusterUpdateMyselfIp(); clusterUpdateMyselfHostname(); clusterUpdateMyselfHumanNodename(); getRandomHexChars(server.cluster->internal_secret, CLUSTER_INTERNALSECRETLEN); } void clusterInitLast(void) { if (connectionIndexByType(connTypeOfCluster()->get_type(NULL)) < 0) { serverLog(LL_WARNING, "Missing connection type %s, but it is required for the Cluster bus.", connTypeOfCluster()->get_type(NULL)); exit(1); } int port = defaultClientPort(); connListener *listener = &server.clistener; listener->count = 0; listener->bindaddr = server.bindaddr; listener->bindaddr_count = server.bindaddr_count; listener->port = server.cluster_port ? server.cluster_port : port + CLUSTER_PORT_INCR; listener->ct = connTypeOfCluster(); if (connListen(listener) == C_ERR ) { /* Note: the following log text is matched by the test suite. */ serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", listener->port); exit(1); } if (createSocketAcceptHandler(&server.clistener, clusterAcceptHandler) != C_OK) { serverPanic("Unrecoverable error creating Redis Cluster socket accept handler."); } } /* Reset a node performing a soft or hard reset: * * 1) All other nodes are forgotten. * 2) All the assigned / open slots are released. * 3) If the node is a slave, it turns into a master. * 4) Only for hard reset: a new Node ID is generated. * 5) Only for hard reset: currentEpoch and configEpoch are set to 0. * 6) The new configuration is saved and the cluster state updated. * 7) If the node was a slave, the whole data set is flushed away. */ void clusterReset(int hard) { dictIterator *di; dictEntry *de; int j; /* Turn into master. */ if (nodeIsSlave(myself)) { clusterSetNodeAsMaster(myself); replicationUnsetMaster(); emptyData(-1,EMPTYDB_NO_FLAGS,NULL); } /* Close slots, reset manual failover state. */ clusterCloseAllSlots(); resetManualFailover(); /* Unassign all the slots. */ for (j = 0; j < CLUSTER_SLOTS; j++) clusterDelSlot(j); /* Recreate shards dict */ dictEmpty(server.cluster->shards, NULL); /* Forget all the nodes, but myself. */ di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node == myself) continue; clusterDelNode(node); } dictReleaseIterator(di); /* Empty the nodes blacklist. */ dictEmpty(server.cluster->nodes_black_list, NULL); /* Hard reset only: set epochs to 0, change node ID. */ if (hard) { sds oldname; server.cluster->currentEpoch = 0; server.cluster->lastVoteEpoch = 0; myself->configEpoch = 0; serverLog(LL_NOTICE, "configEpoch set to 0 via CLUSTER RESET HARD"); /* To change the Node ID we need to remove the old name from the * nodes table, change the ID, and re-add back with new name. */ oldname = sdsnewlen(myself->name, CLUSTER_NAMELEN); dictDelete(server.cluster->nodes,oldname); sdsfree(oldname); getRandomHexChars(myself->name, CLUSTER_NAMELEN); getRandomHexChars(myself->shard_id, CLUSTER_NAMELEN); clusterAddNode(myself); serverLog(LL_NOTICE,"Node hard reset, now I'm %.40s", myself->name); } /* Re-populate shards */ clusterAddNodeToShard(myself->shard_id, myself); /* Make sure to persist the new config and update the state. */ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); } /* ----------------------------------------------------------------------------- * CLUSTER communication link * -------------------------------------------------------------------------- */ static clusterMsgSendBlock *createClusterMsgSendBlock(int type, uint32_t msglen) { uint32_t blocklen = msglen + sizeof(clusterMsgSendBlock) - sizeof(clusterMsg); clusterMsgSendBlock *msgblock = zcalloc(blocklen); msgblock->refcount = 1; msgblock->totlen = blocklen; server.stat_cluster_links_memory += blocklen; clusterBuildMessageHdr(&msgblock->msg,type,msglen); return msgblock; } static void clusterMsgSendBlockDecrRefCount(void *node) { clusterMsgSendBlock *msgblock = (clusterMsgSendBlock*)node; msgblock->refcount--; serverAssert(msgblock->refcount >= 0); if (msgblock->refcount == 0) { server.stat_cluster_links_memory -= msgblock->totlen; zfree(msgblock); } } clusterLink *createClusterLink(clusterNode *node) { clusterLink *link = zmalloc(sizeof(*link)); link->ctime = mstime(); link->send_msg_queue = listCreate(); listSetFreeMethod(link->send_msg_queue, clusterMsgSendBlockDecrRefCount); link->head_msg_send_offset = 0; link->send_msg_queue_mem = sizeof(list); link->rcvbuf = zmalloc(link->rcvbuf_alloc = RCVBUF_INIT_LEN); link->rcvbuf_len = 0; server.stat_cluster_links_memory += link->rcvbuf_alloc + link->send_msg_queue_mem; link->conn = NULL; link->node = node; /* Related node can only possibly be known at link creation time if this is an outbound link */ link->inbound = (node == NULL); if (!link->inbound) { node->link = link; } return link; } /* Free a cluster link, but does not free the associated node of course. * This function will just make sure that the original node associated * with this link will have the 'link' field set to NULL. */ void freeClusterLink(clusterLink *link) { if (link->conn) { connClose(link->conn); link->conn = NULL; } server.stat_cluster_links_memory -= sizeof(list) + listLength(link->send_msg_queue)*sizeof(listNode); listRelease(link->send_msg_queue); server.stat_cluster_links_memory -= link->rcvbuf_alloc; zfree(link->rcvbuf); if (link->node) { if (link->node->link == link) { serverAssert(!link->inbound); link->node->link = NULL; } else if (link->node->inbound_link == link) { serverAssert(link->inbound); link->node->inbound_link = NULL; } } zfree(link); } void setClusterNodeToInboundClusterLink(clusterNode *node, clusterLink *link) { serverAssert(!link->node); serverAssert(link->inbound); if (node->inbound_link) { /* A peer may disconnect and then reconnect with us, and it's not guaranteed that * we would always process the disconnection of the existing inbound link before * accepting a new existing inbound link. Therefore, it's possible to have more than * one inbound link from the same node at the same time. Our cleanup logic assumes * a one to one relationship between nodes and inbound links, so we need to kill * one of the links. The existing link is more likely the outdated one, but it's * possible the other node may need to open another link. */ serverLog(LL_DEBUG, "Replacing inbound link fd %d from node %.40s with fd %d", node->inbound_link->conn->fd, node->name, link->conn->fd); freeClusterLink(node->inbound_link); } serverAssert(!node->inbound_link); node->inbound_link = link; link->node = node; } static void clusterConnAcceptHandler(connection *conn) { clusterLink *link; if (connGetState(conn) != CONN_STATE_CONNECTED) { serverLog(LL_VERBOSE, "Error accepting cluster node connection: %s", connGetLastError(conn)); connClose(conn); return; } /* Create a link object we use to handle the connection. * It gets passed to the readable handler when data is available. * Initially the link->node pointer is set to NULL as we don't know * which node is, but the right node is references once we know the * node identity. */ link = createClusterLink(NULL); link->conn = conn; connSetPrivateData(conn, link); /* Register read handler */ connSetReadHandler(conn, clusterReadHandler); } #define MAX_CLUSTER_ACCEPTS_PER_CALL 1000 void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { int cport, cfd; int max = MAX_CLUSTER_ACCEPTS_PER_CALL; char cip[NET_IP_STR_LEN]; int require_auth = TLS_CLIENT_AUTH_YES; UNUSED(el); UNUSED(mask); UNUSED(privdata); /* If the server is starting up, don't accept cluster connections: * UPDATE messages may interact with the database content. */ if (server.masterhost == NULL && server.loading) return; while(max--) { cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport); if (cfd == ANET_ERR) { if (errno != EWOULDBLOCK) serverLog(LL_VERBOSE, "Error accepting cluster node: %s", server.neterr); return; } connection *conn = connCreateAccepted(server.el, connTypeOfCluster(), cfd, &require_auth); /* Make sure connection is not in an error state */ if (connGetState(conn) != CONN_STATE_ACCEPTING) { serverLog(LL_VERBOSE, "Error creating an accepting connection for cluster node: %s", connGetLastError(conn)); connClose(conn); return; } connEnableTcpNoDelay(conn); connKeepAlive(conn,server.cluster_node_timeout / 1000 * 2); /* Use non-blocking I/O for cluster messages. */ serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport); /* Accept the connection now. connAccept() may call our handler directly * or schedule it for later depending on connection implementation. */ if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) { if (connGetState(conn) == CONN_STATE_ERROR) serverLog(LL_VERBOSE, "Error accepting cluster node connection: %s", connGetLastError(conn)); connClose(conn); return; } } } /* Return the approximated number of sockets we are using in order to * take the cluster bus connections. */ unsigned long getClusterConnectionsCount(void) { /* We decrement the number of nodes by one, since there is the * "myself" node too in the list. Each node uses two file descriptors, * one incoming and one outgoing, thus the multiplication by 2. */ return server.cluster_enabled ? ((dictSize(server.cluster->nodes)-1)*2) : 0; } /* ----------------------------------------------------------------------------- * CLUSTER node API * -------------------------------------------------------------------------- */ /* Create a new cluster node, with the specified flags. * If "nodename" is NULL this is considered a first handshake and a random * node name is assigned to this node (it will be fixed later when we'll * receive the first pong). * * The node is created and returned to the user, but it is not automatically * added to the nodes hash table. */ clusterNode *createClusterNode(char *nodename, int flags) { clusterNode *node = zmalloc(sizeof(*node)); if (nodename) memcpy(node->name, nodename, CLUSTER_NAMELEN); else getRandomHexChars(node->name, CLUSTER_NAMELEN); getRandomHexChars(node->shard_id, CLUSTER_NAMELEN); node->ctime = mstime(); node->configEpoch = 0; node->flags = flags; memset(node->slots,0,sizeof(node->slots)); node->slot_info_pairs = NULL; node->slot_info_pairs_count = 0; node->numslots = 0; node->numslaves = 0; node->slaves = NULL; node->slaveof = NULL; node->last_in_ping_gossip = 0; node->ping_sent = node->pong_received = 0; node->data_received = 0; node->fail_time = 0; node->link = NULL; node->inbound_link = NULL; memset(node->ip,0,sizeof(node->ip)); node->hostname = sdsempty(); node->human_nodename = sdsempty(); node->tcp_port = 0; node->cport = 0; node->tls_port = 0; node->fail_reports = listCreate(); node->voted_time = 0; node->orphaned_time = 0; node->repl_offset_time = 0; node->repl_offset = 0; listSetFreeMethod(node->fail_reports,zfree); return node; } /* This function is called every time we get a failure report from a node. * The side effect is to populate the fail_reports list (or to update * the timestamp of an existing report). * * 'failing' is the node that is in failure state according to the * 'sender' node. * * The function returns 0 if it just updates a timestamp of an existing * failure report from the same sender. 1 is returned if a new failure * report is created. */ int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) { list *l = failing->fail_reports; listNode *ln; listIter li; clusterNodeFailReport *fr; /* If a failure report from the same sender already exists, just update * the timestamp. */ listRewind(l,&li); while ((ln = listNext(&li)) != NULL) { fr = ln->value; if (fr->node == sender) { fr->time = mstime(); return 0; } } /* Otherwise create a new report. */ fr = zmalloc(sizeof(*fr)); fr->node = sender; fr->time = mstime(); listAddNodeTail(l,fr); return 1; } /* Remove failure reports that are too old, where too old means reasonably * older than the global node timeout. Note that anyway for a node to be * flagged as FAIL we need to have a local PFAIL state that is at least * older than the global node timeout, so we don't just trust the number * of failure reports from other nodes. */ void clusterNodeCleanupFailureReports(clusterNode *node) { list *l = node->fail_reports; listNode *ln; listIter li; clusterNodeFailReport *fr; mstime_t maxtime = server.cluster_node_timeout * CLUSTER_FAIL_REPORT_VALIDITY_MULT; mstime_t now = mstime(); listRewind(l,&li); while ((ln = listNext(&li)) != NULL) { fr = ln->value; if (now - fr->time > maxtime) listDelNode(l,ln); } } /* Remove the failing report for 'node' if it was previously considered * failing by 'sender'. This function is called when a node informs us via * gossip that a node is OK from its point of view (no FAIL or PFAIL flags). * * Note that this function is called relatively often as it gets called even * when there are no nodes failing, and is O(N), however when the cluster is * fine the failure reports list is empty so the function runs in constant * time. * * The function returns 1 if the failure report was found and removed. * Otherwise 0 is returned. */ int clusterNodeDelFailureReport(clusterNode *node, clusterNode *sender) { list *l = node->fail_reports; listNode *ln; listIter li; clusterNodeFailReport *fr; /* Search for a failure report from this sender. */ listRewind(l,&li); while ((ln = listNext(&li)) != NULL) { fr = ln->value; if (fr->node == sender) break; } if (!ln) return 0; /* No failure report from this sender. */ /* Remove the failure report. */ listDelNode(l,ln); clusterNodeCleanupFailureReports(node); return 1; } /* Return the number of external nodes that believe 'node' is failing, * not including this node, that may have a PFAIL or FAIL state for this * node as well. */ int clusterNodeFailureReportsCount(clusterNode *node) { clusterNodeCleanupFailureReports(node); return listLength(node->fail_reports); } int clusterNodeRemoveSlave(clusterNode *master, clusterNode *slave) { int j; for (j = 0; j < master->numslaves; j++) { if (master->slaves[j] == slave) { if ((j+1) < master->numslaves) { int remaining_slaves = (master->numslaves - j) - 1; memmove(master->slaves+j,master->slaves+(j+1), (sizeof(*master->slaves) * remaining_slaves)); } master->numslaves--; if (master->numslaves == 0) master->flags &= ~CLUSTER_NODE_MIGRATE_TO; return C_OK; } } return C_ERR; } int clusterNodeAddSlave(clusterNode *master, clusterNode *slave) { int j; /* If it's already a slave, don't add it again. */ for (j = 0; j < master->numslaves; j++) if (master->slaves[j] == slave) return C_ERR; master->slaves = zrealloc(master->slaves, sizeof(clusterNode*)*(master->numslaves+1)); master->slaves[master->numslaves] = slave; master->numslaves++; master->flags |= CLUSTER_NODE_MIGRATE_TO; return C_OK; } int clusterCountNonFailingSlaves(clusterNode *n) { int j, okslaves = 0; for (j = 0; j < n->numslaves; j++) if (!nodeFailed(n->slaves[j])) okslaves++; return okslaves; } /* Low level cleanup of the node structure. Only called by clusterDelNode(). */ void freeClusterNode(clusterNode *n) { sds nodename; int j; /* If the node has associated slaves, we have to set * all the slaves->slaveof fields to NULL (unknown). */ for (j = 0; j < n->numslaves; j++) n->slaves[j]->slaveof = NULL; /* Remove this node from the list of slaves of its master. */ if (nodeIsSlave(n) && n->slaveof) clusterNodeRemoveSlave(n->slaveof,n); /* Unlink from the set of nodes. */ nodename = sdsnewlen(n->name, CLUSTER_NAMELEN); serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK); sdsfree(nodename); sdsfree(n->hostname); sdsfree(n->human_nodename); /* Release links and associated data structures. */ if (n->link) freeClusterLink(n->link); if (n->inbound_link) freeClusterLink(n->inbound_link); listRelease(n->fail_reports); zfree(n->slaves); zfree(n); } /* Add a node to the nodes hash table */ void clusterAddNode(clusterNode *node) { int retval; retval = dictAdd(server.cluster->nodes, sdsnewlen(node->name,CLUSTER_NAMELEN), node); serverAssert(retval == DICT_OK); } /* Remove a node from the cluster. The function performs the high level * cleanup, calling freeClusterNode() for the low level cleanup. * Here we do the following: * * 1) Mark all the slots handled by it as unassigned. * 2) Remove all the failure reports sent by this node and referenced by * other nodes. * 3) Remove the node from the owning shard * 4) Free the node with freeClusterNode() that will in turn remove it * from the hash table and from the list of slaves of its master, if * it is a slave node. */ void clusterDelNode(clusterNode *delnode) { int j; dictIterator *di; dictEntry *de; /* 1) Mark slots as unassigned. */ for (j = 0; j < CLUSTER_SLOTS; j++) { if (server.cluster->importing_slots_from[j] == delnode) server.cluster->importing_slots_from[j] = NULL; if (server.cluster->migrating_slots_to[j] == delnode) server.cluster->migrating_slots_to[j] = NULL; if (server.cluster->slots[j] == delnode) clusterDelSlot(j); } /* 2) Remove failure reports. */ di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node == delnode) continue; clusterNodeDelFailureReport(node,delnode); } dictReleaseIterator(di); /* 3) Remove the node from the owning shard */ clusterRemoveNodeFromShard(delnode); /* 4) Free the node, unlinking it from the cluster. */ freeClusterNode(delnode); } /* Node lookup by name */ clusterNode *clusterLookupNode(const char *name, int length) { if (verifyClusterNodeId(name, length) != C_OK) return NULL; sds s = sdsnewlen(name, length); dictEntry *de = dictFind(server.cluster->nodes, s); sdsfree(s); if (de == NULL) return NULL; return dictGetVal(de); } const char *clusterGetSecret(size_t *len) { if (!server.cluster) { return NULL; } *len = CLUSTER_INTERNALSECRETLEN; return server.cluster->internal_secret; } /* Get all the nodes in my shard. * Note that the list returned is not computed on the fly * via slaveof; rather, it is maintained permanently to * track the shard membership and its life cycle is tied * to this Redis process. Therefore, the caller must not * release the list. */ list *clusterGetNodesInMyShard(clusterNode *node) { sds s = sdsnewlen(node->shard_id, CLUSTER_NAMELEN); dictEntry *de = dictFind(server.cluster->shards,s); sdsfree(s); return (de != NULL) ? dictGetVal(de) : NULL; } /* This is only used after the handshake. When we connect a given IP/PORT * as a result of CLUSTER MEET we don't have the node name yet, so we * pick a random one, and will fix it when we receive the PONG request using * this function. */ void clusterRenameNode(clusterNode *node, char *newname) { int retval; sds s = sdsnewlen(node->name, CLUSTER_NAMELEN); serverLog(LL_DEBUG,"Renaming node %.40s into %.40s", node->name, newname); retval = dictDelete(server.cluster->nodes, s); sdsfree(s); serverAssert(retval == DICT_OK); memcpy(node->name, newname, CLUSTER_NAMELEN); clusterAddNode(node); clusterAddNodeToShard(node->shard_id, node); } void clusterAddNodeToShard(const char *shard_id, clusterNode *node) { sds s = sdsnewlen(shard_id, CLUSTER_NAMELEN); dictEntry *de = dictFind(server.cluster->shards,s); if (de == NULL) { list *l = listCreate(); listAddNodeTail(l, node); serverAssert(dictAdd(server.cluster->shards, s, l) == DICT_OK); } else { list *l = dictGetVal(de); if (listSearchKey(l, node) == NULL) { listAddNodeTail(l, node); } sdsfree(s); } } void clusterRemoveNodeFromShard(clusterNode *node) { sds s = sdsnewlen(node->shard_id, CLUSTER_NAMELEN); dictEntry *de = dictFind(server.cluster->shards, s); if (de != NULL) { list *l = dictGetVal(de); listNode *ln = listSearchKey(l, node); if (ln != NULL) { listDelNode(l, ln); } if (listLength(l) == 0) { dictDelete(server.cluster->shards, s); } } sdsfree(s); } /* ----------------------------------------------------------------------------- * CLUSTER config epoch handling * -------------------------------------------------------------------------- */ /* Return the greatest configEpoch found in the cluster, or the current * epoch if greater than any node configEpoch. */ uint64_t clusterGetMaxEpoch(void) { uint64_t max = 0; dictIterator *di; dictEntry *de; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node->configEpoch > max) max = node->configEpoch; } dictReleaseIterator(di); if (max < server.cluster->currentEpoch) max = server.cluster->currentEpoch; return max; } /* If this node epoch is zero or is not already the greatest across the * cluster (from the POV of the local configuration), this function will: * * 1) Generate a new config epoch, incrementing the current epoch. * 2) Assign the new epoch to this node, WITHOUT any consensus. * 3) Persist the configuration on disk before sending packets with the * new configuration. * * If the new config epoch is generated and assigned, C_OK is returned, * otherwise C_ERR is returned (since the node has already the greatest * configuration around) and no operation is performed. * * Important note: this function violates the principle that config epochs * should be generated with consensus and should be unique across the cluster. * However Redis Cluster uses this auto-generated new config epochs in two * cases: * * 1) When slots are closed after importing. Otherwise resharding would be * too expensive. * 2) When CLUSTER FAILOVER is called with options that force a slave to * failover its master even if there is not master majority able to * create a new configuration epoch. * * Redis Cluster will not explode using this function, even in the case of * a collision between this node and another node, generating the same * configuration epoch unilaterally, because the config epoch conflict * resolution algorithm will eventually move colliding nodes to different * config epochs. However using this function may violate the "last failover * wins" rule, so should only be used with care. */ int clusterBumpConfigEpochWithoutConsensus(void) { uint64_t maxEpoch = clusterGetMaxEpoch(); if (myself->configEpoch == 0 || myself->configEpoch != maxEpoch) { server.cluster->currentEpoch++; myself->configEpoch = server.cluster->currentEpoch; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_FSYNC_CONFIG); serverLog(LL_NOTICE, "New configEpoch set to %llu", (unsigned long long) myself->configEpoch); return C_OK; } else { return C_ERR; } } /* This function is called when this node is a master, and we receive from * another master a configuration epoch that is equal to our configuration * epoch. * * BACKGROUND * * It is not possible that different slaves get the same config * epoch during a failover election, because the slaves need to get voted * by a majority. However when we perform a manual resharding of the cluster * the node will assign a configuration epoch to itself without to ask * for agreement. Usually resharding happens when the cluster is working well * and is supervised by the sysadmin, however it is possible for a failover * to happen exactly while the node we are resharding a slot to assigns itself * a new configuration epoch, but before it is able to propagate it. * * So technically it is possible in this condition that two nodes end with * the same configuration epoch. * * Another possibility is that there are bugs in the implementation causing * this to happen. * * Moreover when a new cluster is created, all the nodes start with the same * configEpoch. This collision resolution code allows nodes to automatically * end with a different configEpoch at startup automatically. * * In all the cases, we want a mechanism that resolves this issue automatically * as a safeguard. The same configuration epoch for masters serving different * set of slots is not harmful, but it is if the nodes end serving the same * slots for some reason (manual errors or software bugs) without a proper * failover procedure. * * In general we want a system that eventually always ends with different * masters having different configuration epochs whatever happened, since * nothing is worse than a split-brain condition in a distributed system. * * BEHAVIOR * * When this function gets called, what happens is that if this node * has the lexicographically smaller Node ID compared to the other node * with the conflicting epoch (the 'sender' node), it will assign itself * the greatest configuration epoch currently detected among nodes plus 1. * * This means that even if there are multiple nodes colliding, the node * with the greatest Node ID never moves forward, so eventually all the nodes * end with a different configuration epoch. */ void clusterHandleConfigEpochCollision(clusterNode *sender) { /* Prerequisites: nodes have the same configEpoch and are both masters. */ if (sender->configEpoch != myself->configEpoch || !clusterNodeIsMaster(sender) || !clusterNodeIsMaster(myself)) return; /* Don't act if the colliding node has a smaller Node ID. */ if (memcmp(sender->name,myself->name,CLUSTER_NAMELEN) <= 0) return; /* Get the next ID available at the best of this node knowledge. */ server.cluster->currentEpoch++; myself->configEpoch = server.cluster->currentEpoch; clusterSaveConfigOrDie(1); serverLog(LL_VERBOSE, "WARNING: configEpoch collision with node %.40s (%s)." " configEpoch set to %llu", sender->name,sender->human_nodename, (unsigned long long) myself->configEpoch); } /* ----------------------------------------------------------------------------- * CLUSTER nodes blacklist * * The nodes blacklist is just a way to ensure that a given node with a given * Node ID is not re-added before some time elapsed (this time is specified * in seconds in CLUSTER_BLACKLIST_TTL). * * This is useful when we want to remove a node from the cluster completely: * when CLUSTER FORGET is called, it also puts the node into the blacklist so * that even if we receive gossip messages from other nodes that still remember * about the node we want to remove, we don't re-add it before some time. * * Currently the CLUSTER_BLACKLIST_TTL is set to 1 minute, this means * that redis-cli has 60 seconds to send CLUSTER FORGET messages to nodes * in the cluster without dealing with the problem of other nodes re-adding * back the node to nodes we already sent the FORGET command to. * * The data structure used is a hash table with an sds string representing * the node ID as key, and the time when it is ok to re-add the node as * value. * -------------------------------------------------------------------------- */ #define CLUSTER_BLACKLIST_TTL 60 /* 1 minute. */ /* Before of the addNode() or Exists() operations we always remove expired * entries from the black list. This is an O(N) operation but it is not a * problem since add / exists operations are called very infrequently and * the hash table is supposed to contain very little elements at max. * However without the cleanup during long uptime and with some automated * node add/removal procedures, entries could accumulate. */ void clusterBlacklistCleanup(void) { dictIterator *di; dictEntry *de; di = dictGetSafeIterator(server.cluster->nodes_black_list); while((de = dictNext(di)) != NULL) { int64_t expire = dictGetUnsignedIntegerVal(de); if (expire < server.unixtime) dictDelete(server.cluster->nodes_black_list,dictGetKey(de)); } dictReleaseIterator(di); } /* Cleanup the blacklist and add a new node ID to the black list. */ void clusterBlacklistAddNode(clusterNode *node) { dictEntry *de; sds id = sdsnewlen(node->name,CLUSTER_NAMELEN); clusterBlacklistCleanup(); if (dictAdd(server.cluster->nodes_black_list,id,NULL) == DICT_OK) { /* If the key was added, duplicate the sds string representation of * the key for the next lookup. We'll free it at the end. */ id = sdsdup(id); } de = dictFind(server.cluster->nodes_black_list,id); dictSetUnsignedIntegerVal(de,time(NULL)+CLUSTER_BLACKLIST_TTL); sdsfree(id); } /* Return non-zero if the specified node ID exists in the blacklist. * You don't need to pass an sds string here, any pointer to 40 bytes * will work. */ int clusterBlacklistExists(char *nodeid) { sds id = sdsnewlen(nodeid,CLUSTER_NAMELEN); int retval; clusterBlacklistCleanup(); retval = dictFind(server.cluster->nodes_black_list,id) != NULL; sdsfree(id); return retval; } /* ----------------------------------------------------------------------------- * CLUSTER messages exchange - PING/PONG and gossip * -------------------------------------------------------------------------- */ /* This function checks if a given node should be marked as FAIL. * It happens if the following conditions are met: * * 1) We received enough failure reports from other master nodes via gossip. * Enough means that the majority of the masters signaled the node is * down recently. * 2) We believe this node is in PFAIL state. * * If a failure is detected we also inform the whole cluster about this * event trying to force every other node to set the FAIL flag for the node. * * Note that the form of agreement used here is weak, as we collect the majority * of masters state during some time, and even if we force agreement by * propagating the FAIL message, because of partitions we may not reach every * node. However: * * 1) Either we reach the majority and eventually the FAIL state will propagate * to all the cluster. * 2) Or there is no majority so no slave promotion will be authorized and the * FAIL flag will be cleared after some time. */ void markNodeAsFailingIfNeeded(clusterNode *node) { int failures; int needed_quorum = (server.cluster->size / 2) + 1; if (!nodeTimedOut(node)) return; /* We can reach it. */ if (nodeFailed(node)) return; /* Already FAILing. */ failures = clusterNodeFailureReportsCount(node); /* Also count myself as a voter if I'm a master. */ if (clusterNodeIsMaster(myself)) failures++; if (failures < needed_quorum) return; /* No weak agreement from masters. */ serverLog(LL_NOTICE, "Marking node %.40s (%s) as failing (quorum reached).", node->name, node->human_nodename); /* Mark the node as failing. */ node->flags &= ~CLUSTER_NODE_PFAIL; node->flags |= CLUSTER_NODE_FAIL; node->fail_time = mstime(); /* Broadcast the failing node name to everybody, forcing all the other * reachable nodes to flag the node as FAIL. * We do that even if this node is a replica and not a master: anyway * the failing state is triggered collecting failure reports from masters, * so here the replica is only helping propagating this status. */ clusterSendFail(node->name); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); } /* This function is called only if a node is marked as FAIL, but we are able * to reach it again. It checks if there are the conditions to undo the FAIL * state. */ void clearNodeFailureIfNeeded(clusterNode *node) { mstime_t now = mstime(); serverAssert(nodeFailed(node)); /* For slaves we always clear the FAIL flag if we can contact the * node again. */ if (nodeIsSlave(node) || node->numslots == 0) { serverLog(LL_NOTICE, "Clear FAIL state for node %.40s (%s):%s is reachable again.", node->name,node->human_nodename, nodeIsSlave(node) ? "replica" : "master without slots"); node->flags &= ~CLUSTER_NODE_FAIL; clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); } /* If it is a master and... * 1) The FAIL state is old enough. * 2) It is yet serving slots from our point of view (not failed over). * Apparently no one is going to fix these slots, clear the FAIL flag. */ if (clusterNodeIsMaster(node) && node->numslots > 0 && (now - node->fail_time) > (server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT)) { serverLog(LL_NOTICE, "Clear FAIL state for node %.40s (%s): is reachable again and nobody is serving its slots after some time.", node->name, node->human_nodename); node->flags &= ~CLUSTER_NODE_FAIL; clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); } } /* Return true if we already have a node in HANDSHAKE state matching the * specified ip address and port number. This function is used in order to * avoid adding a new handshake node for the same address multiple times. */ int clusterHandshakeInProgress(char *ip, int port, int cport) { dictIterator *di; dictEntry *de; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (!nodeInHandshake(node)) continue; if (!strcasecmp(node->ip,ip) && getNodeDefaultClientPort(node) == port && node->cport == cport) break; } dictReleaseIterator(di); return de != NULL; } /* Start a handshake with the specified address if there is not one * already in progress. Returns non-zero if the handshake was actually * started. On error zero is returned and errno is set to one of the * following values: * * EAGAIN - There is already a handshake in progress for this address. * EINVAL - IP or port are not valid. */ int clusterStartHandshake(char *ip, int port, int cport) { clusterNode *n; char norm_ip[NET_IP_STR_LEN]; struct sockaddr_storage sa; /* IP sanity check */ if (inet_pton(AF_INET,ip, &(((struct sockaddr_in *)&sa)->sin_addr))) { sa.ss_family = AF_INET; } else if (inet_pton(AF_INET6,ip, &(((struct sockaddr_in6 *)&sa)->sin6_addr))) { sa.ss_family = AF_INET6; } else { errno = EINVAL; return 0; } /* Port sanity check */ if (port <= 0 || port > 65535 || cport <= 0 || cport > 65535) { errno = EINVAL; return 0; } /* Set norm_ip as the normalized string representation of the node * IP address. */ memset(norm_ip,0,NET_IP_STR_LEN); if (sa.ss_family == AF_INET) inet_ntop(AF_INET, (void*)&(((struct sockaddr_in *)&sa)->sin_addr), norm_ip,NET_IP_STR_LEN); else inet_ntop(AF_INET6, (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr), norm_ip,NET_IP_STR_LEN); if (clusterHandshakeInProgress(norm_ip,port,cport)) { errno = EAGAIN; return 0; } /* Add the node with a random address (NULL as first argument to * createClusterNode()). Everything will be fixed during the * handshake. */ n = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET); memcpy(n->ip,norm_ip,sizeof(n->ip)); if (server.tls_cluster) { n->tls_port = port; } else { n->tcp_port = port; } n->cport = cport; clusterAddNode(n); return 1; } static void getClientPortFromClusterMsg(clusterMsg *hdr, int *tls_port, int *tcp_port) { if (server.tls_cluster) { *tls_port = ntohs(hdr->port); *tcp_port = ntohs(hdr->pport); } else { *tls_port = ntohs(hdr->pport); *tcp_port = ntohs(hdr->port); } } static void getClientPortFromGossip(clusterMsgDataGossip *g, int *tls_port, int *tcp_port) { if (server.tls_cluster) { *tls_port = ntohs(g->port); *tcp_port = ntohs(g->pport); } else { *tls_port = ntohs(g->pport); *tcp_port = ntohs(g->port); } } /* Returns a string with the byte representation of the node ID (i.e. nodename) * along with 8 trailing bytes for debugging purposes. */ char *getCorruptedNodeIdByteString(clusterMsgDataGossip *gossip_msg) { const int num_bytes = CLUSTER_NAMELEN + 8; /* Allocate enough room for 4 chars per byte + null terminator */ char *byte_string = (char*) zmalloc((num_bytes*4) + 1); const char *name_ptr = gossip_msg->nodename; /* Ensure we won't print beyond the bounds of the message */ serverAssert(name_ptr + num_bytes <= (char*)gossip_msg + sizeof(clusterMsgDataGossip)); for (int i = 0; i < num_bytes; i++) { snprintf(byte_string + 4*i, 5, "\\x%02hhX", name_ptr[i]); } return byte_string; } /* Returns the number of nodes in the gossip with invalid IDs. */ int verifyGossipSectionNodeIds(clusterMsgDataGossip *g, uint16_t count) { int invalid_ids = 0; for (int i = 0; i < count; i++) { const char *nodename = g[i].nodename; if (verifyClusterNodeId(nodename, CLUSTER_NAMELEN) != C_OK) { invalid_ids++; char *raw_node_id = getCorruptedNodeIdByteString(g); serverLog(LL_WARNING, "Received gossip about a node with invalid ID %.40s. For debugging purposes, " "the 48 bytes including the invalid ID and 8 trailing bytes are: %s", nodename, raw_node_id); zfree(raw_node_id); } } return invalid_ids; } /* Process the gossip section of PING or PONG packets. * Note that this function assumes that the packet is already sanity-checked * by the caller, not in the content of the gossip section, but in the * length. */ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { uint16_t count = ntohs(hdr->count); clusterMsgDataGossip *g = (clusterMsgDataGossip*) hdr->data.ping.gossip; clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); /* Abort if the gossip contains invalid node IDs to avoid adding incorrect information to * the nodes dictionary. An invalid ID indicates memory corruption on the sender side. */ int invalid_ids = verifyGossipSectionNodeIds(g, count); if (invalid_ids) { if (sender) { serverLog(LL_WARNING, "Node %.40s (%s) gossiped %d nodes with invalid IDs.", sender->name, sender->human_nodename, invalid_ids); } else { serverLog(LL_WARNING, "Unknown node gossiped %d nodes with invalid IDs.", invalid_ids); } return; } while(count--) { uint16_t flags = ntohs(g->flags); clusterNode *node; sds ci; if (server.verbosity == LL_DEBUG) { ci = representClusterNodeFlags(sdsempty(), flags); serverLog(LL_DEBUG,"GOSSIP %.40s %s:%d@%d %s", g->nodename, g->ip, ntohs(g->port), ntohs(g->cport), ci); sdsfree(ci); } /* Convert port and pport into TCP port and TLS port. */ int msg_tls_port, msg_tcp_port; getClientPortFromGossip(g, &msg_tls_port, &msg_tcp_port); /* Update our state accordingly to the gossip sections */ node = clusterLookupNode(g->nodename, CLUSTER_NAMELEN); /* Ignore gossips about self. */ if (node && node != myself) { /* We already know this node. Handle failure reports, only when the sender is a master. */ if (sender && clusterNodeIsMaster(sender)) { if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) { if (clusterNodeAddFailureReport(node,sender)) { serverLog(LL_VERBOSE, "Node %.40s (%s) reported node %.40s (%s) as not reachable.", sender->name, sender->human_nodename, node->name, node->human_nodename); } markNodeAsFailingIfNeeded(node); } else { if (clusterNodeDelFailureReport(node,sender)) { serverLog(LL_VERBOSE, "Node %.40s (%s) reported node %.40s (%s) is back online.", sender->name, sender->human_nodename, node->name, node->human_nodename); } } } /* If from our POV the node is up (no failure flags are set), * we have no pending ping for the node, nor we have failure * reports for this node, update the last pong time with the * one we see from the other nodes. */ if (!(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) && node->ping_sent == 0 && clusterNodeFailureReportsCount(node) == 0) { mstime_t pongtime = ntohl(g->pong_received); pongtime *= 1000; /* Convert back to milliseconds. */ /* Replace the pong time with the received one only if * it's greater than our view but is not in the future * (with 500 milliseconds tolerance) from the POV of our * clock. */ if (pongtime <= (server.mstime+500) && pongtime > node->pong_received) { node->pong_received = pongtime; } } /* If we already know this node, but it is not reachable, and * we see a different address in the gossip section of a node that * can talk with this other node, update the address, disconnect * the old link if any, so that we'll attempt to connect with the * new address. */ if (node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL) && !(flags & CLUSTER_NODE_NOADDR) && !(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) && (strcasecmp(node->ip,g->ip) || node->tls_port != (server.tls_cluster ? ntohs(g->port) : ntohs(g->pport)) || node->tcp_port != (server.tls_cluster ? ntohs(g->pport) : ntohs(g->port)) || node->cport != ntohs(g->cport))) { if (node->link) freeClusterLink(node->link); memcpy(node->ip,g->ip,NET_IP_STR_LEN); node->tcp_port = msg_tcp_port; node->tls_port = msg_tls_port; node->cport = ntohs(g->cport); node->flags &= ~CLUSTER_NODE_NOADDR; } } else if (!node) { /* If it's not in NOADDR state and we don't have it, we * add it to our trusted dict with exact nodeid and flag. * Note that we cannot simply start a handshake against * this IP/PORT pairs, since IP/PORT can be reused already, * otherwise we risk joining another cluster. * * Note that we require that the sender of this gossip message * is a well known node in our cluster, otherwise we risk * joining another cluster. */ if (sender && !(flags & CLUSTER_NODE_NOADDR) && !clusterBlacklistExists(g->nodename)) { clusterNode *node; node = createClusterNode(g->nodename, flags); memcpy(node->ip,g->ip,NET_IP_STR_LEN); node->tcp_port = msg_tcp_port; node->tls_port = msg_tls_port; node->cport = ntohs(g->cport); clusterAddNode(node); clusterAddNodeToShard(node->shard_id, node); } } /* Next node */ g++; } } /* IP -> string conversion. 'buf' is supposed to at least be 46 bytes. * If 'announced_ip' length is non-zero, it is used instead of extracting * the IP from the socket peer address. */ int nodeIp2String(char *buf, clusterLink *link, char *announced_ip) { if (announced_ip[0] != '\0') { memcpy(buf,announced_ip,NET_IP_STR_LEN); buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */ return C_OK; } else { if (connAddrPeerName(link->conn, buf, NET_IP_STR_LEN, NULL) == -1) { serverLog(LL_NOTICE, "Error converting peer IP to string: %s", link->conn ? connGetLastError(link->conn) : "no link"); return C_ERR; } return C_OK; } } /* Update the node address to the IP address that can be extracted * from link->fd, or if hdr->myip is non empty, to the address the node * is announcing us. The port is taken from the packet header as well. * * If the address or port changed, disconnect the node link so that we'll * connect again to the new address. * * If the ip/port pair are already correct no operation is performed at * all. * * The function returns 0 if the node address is still the same, * otherwise 1 is returned. */ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, clusterMsg *hdr) { char ip[NET_IP_STR_LEN] = {0}; int cport = ntohs(hdr->cport); int tcp_port, tls_port; getClientPortFromClusterMsg(hdr, &tls_port, &tcp_port); /* We don't proceed if the link is the same as the sender link, as this * function is designed to see if the node link is consistent with the * symmetric link that is used to receive PINGs from the node. * * As a side effect this function never frees the passed 'link', so * it is safe to call during packet processing. */ if (link == node->link) return 0; /* If the peer IP is unavailable for some reasons like invalid fd or closed * link, just give up the update this time, and the update will be retried * in the next round of PINGs */ if (nodeIp2String(ip,link,hdr->myip) == C_ERR) return 0; if (node->tcp_port == tcp_port && node->cport == cport && node->tls_port == tls_port && strcmp(ip,node->ip) == 0) return 0; /* IP / port is different, update it. */ memcpy(node->ip,ip,sizeof(ip)); node->tcp_port = tcp_port; node->tls_port = tls_port; node->cport = cport; if (node->link) freeClusterLink(node->link); node->flags &= ~CLUSTER_NODE_NOADDR; serverLog(LL_NOTICE,"Address updated for node %.40s (%s), now %s:%d", node->name, node->human_nodename, node->ip, getNodeDefaultClientPort(node)); /* Check if this is our master and we have to change the * replication target as well. */ if (nodeIsSlave(myself) && myself->slaveof == node) replicationSetMaster(node->ip, getNodeDefaultReplicationPort(node)); return 1; } /* Reconfigure the specified node 'n' as a master. This function is called when * a node that we believed to be a slave is now acting as master in order to * update the state of the node. */ void clusterSetNodeAsMaster(clusterNode *n) { if (clusterNodeIsMaster(n)) return; if (n->slaveof) { clusterNodeRemoveSlave(n->slaveof,n); if (n != myself) n->flags |= CLUSTER_NODE_MIGRATE_TO; } n->flags &= ~CLUSTER_NODE_SLAVE; n->flags |= CLUSTER_NODE_MASTER; n->slaveof = NULL; /* Update config and state. */ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } /* This function is called when we receive a master configuration via a * PING, PONG or UPDATE packet. What we receive is a node, a configEpoch of the * node, and the set of slots claimed under this configEpoch. * * What we do is to rebind the slots with newer configuration compared to our * local configuration, and if needed, we turn ourself into a replica of the * node (see the function comments for more info). * * The 'sender' is the node for which we received a configuration update. * Sometimes it is not actually the "Sender" of the information, like in the * case we receive the info via an UPDATE packet. */ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) { int j; clusterNode *curmaster = NULL, *newmaster = NULL; /* The dirty slots list is a list of slots for which we lose the ownership * while having still keys inside. This usually happens after a failover * or after a manual cluster reconfiguration operated by the admin. * * If the update message is not able to demote a master to slave (in this * case we'll resync with the master updating the whole key space), we * need to delete all the keys in the slots we lost ownership. */ uint16_t dirty_slots[CLUSTER_SLOTS]; int dirty_slots_count = 0; /* We should detect if sender is new master of our shard. * We will know it if all our slots were migrated to sender, and sender * has no slots except ours */ int sender_slots = 0; int migrated_our_slots = 0; /* Here we set curmaster to this node or the node this node * replicates to if it's a slave. In the for loop we are * interested to check if slots are taken away from curmaster. */ curmaster = clusterNodeIsMaster(myself) ? myself : myself->slaveof; if (sender == myself) { serverLog(LL_NOTICE,"Discarding UPDATE message about myself."); return; } for (j = 0; j < CLUSTER_SLOTS; j++) { if (bitmapTestBit(slots,j)) { sender_slots++; /* The slot is already bound to the sender of this message. */ if (server.cluster->slots[j] == sender) { bitmapClearBit(server.cluster->owner_not_claiming_slot, j); continue; } /* The slot is in importing state, it should be modified only * manually via redis-cli (example: a resharding is in progress * and the migrating side slot was already closed and is advertising * a new config. We still want the slot to be closed manually). */ if (server.cluster->importing_slots_from[j]) continue; /* We rebind the slot to the new node claiming it if: * 1) The slot was unassigned or the previous owner no longer owns the slot or * the new node claims it with a greater configEpoch. * 2) We are not currently importing the slot. */ if (isSlotUnclaimed(j) || server.cluster->slots[j]->configEpoch < senderConfigEpoch) { /* Was this slot mine, and still contains keys? Mark it as * a dirty slot. */ if (server.cluster->slots[j] == myself && countKeysInSlot(j) && sender != myself) { dirty_slots[dirty_slots_count] = j; dirty_slots_count++; } if (server.cluster->slots[j] == curmaster) { newmaster = sender; migrated_our_slots++; } clusterDelSlot(j); clusterAddSlot(sender,j); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); } } else if (server.cluster->slots[j] == sender) { /* The slot is currently bound to the sender but the sender is no longer * claiming it. We don't want to unbind the slot yet as it can cause the cluster * to move to FAIL state and also throw client error. Keeping the slot bound to * the previous owner will cause a few client side redirects, but won't throw * any errors. We will keep track of the uncertainty in ownership to avoid * propagating misinformation about this slot's ownership using UPDATE * messages. */ bitmapSetBit(server.cluster->owner_not_claiming_slot, j); } } /* After updating the slots configuration, don't do any actual change * in the state of the server if a module disabled Redis Cluster * keys redirections. */ if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION) return; /* If at least one slot was reassigned from a node to another node * with a greater configEpoch, it is possible that: * 1) We are a master left without slots. This means that we were * failed over and we should turn into a replica of the new * master. * 2) We are a slave and our master is left without slots. We need * to replicate to the new slots owner. */ if (newmaster && curmaster->numslots == 0 && (server.cluster_allow_replica_migration || sender_slots == migrated_our_slots)) { serverLog(LL_NOTICE, "Configuration change detected. Reconfiguring myself " "as a replica of %.40s (%s)", sender->name, sender->human_nodename); clusterSetMaster(sender); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); } else if (myself->slaveof && myself->slaveof->slaveof && /* In some rare case when CLUSTER FAILOVER TAKEOVER is used, it * can happen that myself is a replica of a replica of myself. If * this happens, we do nothing to avoid a crash and wait for the * admin to repair the cluster. */ myself->slaveof->slaveof != myself) { /* Safeguard against sub-replicas. A replica's master can turn itself * into a replica if its last slot is removed. If no other node takes * over the slot, there is nothing else to trigger replica migration. */ serverLog(LL_NOTICE, "I'm a sub-replica! Reconfiguring myself as a replica of grandmaster %.40s (%s)", myself->slaveof->slaveof->name, myself->slaveof->slaveof->human_nodename); clusterSetMaster(myself->slaveof->slaveof); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); } else if (dirty_slots_count) { /* If we are here, we received an update message which removed * ownership for certain slots we still have keys about, but still * we are serving some slots, so this master node was not demoted to * a slave. * * In order to maintain a consistent state between keys and slots * we need to remove all the keys from the slots we lost. */ for (j = 0; j < dirty_slots_count; j++) delKeysInSlot(dirty_slots[j]); } } /* Cluster ping extensions. * * The ping/pong/meet messages support arbitrary extensions to add additional * metadata to the messages that are sent between the various nodes in the * cluster. The extensions take the form: * [ Header length + type (8 bytes) ] * [ Extension information (Arbitrary length, but must be 8 byte padded) ] */ /* Returns the length of a given extension */ static uint32_t getPingExtLength(clusterMsgPingExt *ext) { return ntohl(ext->length); } /* Returns the initial position of ping extensions. May return an invalid * address if there are no ping extensions. */ static clusterMsgPingExt *getInitialPingExt(clusterMsg *hdr, int count) { clusterMsgPingExt *initial = (clusterMsgPingExt*) &(hdr->data.ping.gossip[count]); return initial; } /* Given a current ping extension, returns the start of the next extension. May return * an invalid address if there are no further ping extensions. */ static clusterMsgPingExt *getNextPingExt(clusterMsgPingExt *ext) { clusterMsgPingExt *next = (clusterMsgPingExt *) (((char *) ext) + getPingExtLength(ext)); return next; } /* All PING extensions must be 8-byte aligned */ uint32_t getAlignedPingExtSize(uint32_t dataSize) { return sizeof(clusterMsgPingExt) + EIGHT_BYTE_ALIGN(dataSize); } uint32_t getHostnamePingExtSize(void) { if (sdslen(myself->hostname) == 0) { return 0; } return getAlignedPingExtSize(sdslen(myself->hostname) + 1); } uint32_t getHumanNodenamePingExtSize(void) { if (sdslen(myself->human_nodename) == 0) { return 0; } return getAlignedPingExtSize(sdslen(myself->human_nodename) + 1); } uint32_t getShardIdPingExtSize(void) { return getAlignedPingExtSize(sizeof(clusterMsgPingExtShardId)); } uint32_t getInternalSecretPingExtSize(void) { return getAlignedPingExtSize(sizeof(clusterMsgPingExtInternalSecret)); } uint32_t getForgottenNodeExtSize(void) { return getAlignedPingExtSize(sizeof(clusterMsgPingExtForgottenNode)); } void *preparePingExt(clusterMsgPingExt *ext, uint16_t type, uint32_t length) { ext->type = htons(type); ext->length = htonl(length); return &ext->ext[0]; } clusterMsgPingExt *nextPingExt(clusterMsgPingExt *ext) { return (clusterMsgPingExt *)((char*)ext + ntohl(ext->length)); } /* 1. If a NULL hdr is provided, compute the extension size; * 2. If a non-NULL hdr is provided, write the hostname ping * extension at the start of the cursor. This function * will update the cursor to point to the end of the * written extension and will return the amount of bytes * written. */ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) { uint16_t extensions = 0; uint32_t totlen = 0; clusterMsgPingExt *cursor = NULL; /* Set the initial extension position */ if (hdr != NULL) { cursor = getInitialPingExt(hdr, gossipcount); } /* hostname is optional */ if (sdslen(myself->hostname) != 0) { if (cursor != NULL) { /* Populate hostname */ clusterMsgPingExtHostname *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HOSTNAME, getHostnamePingExtSize()); memcpy(ext->hostname, myself->hostname, sdslen(myself->hostname)); /* Move the write cursor */ cursor = nextPingExt(cursor); } totlen += getHostnamePingExtSize(); extensions++; } if (sdslen(myself->human_nodename) != 0) { if (cursor != NULL) { /* Populate human_nodename */ clusterMsgPingExtHumanNodename *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME, getHumanNodenamePingExtSize()); memcpy(ext->human_nodename, myself->human_nodename, sdslen(myself->human_nodename)); /* Move the write cursor */ cursor = nextPingExt(cursor); } totlen += getHumanNodenamePingExtSize(); extensions++; } /* Gossip forgotten nodes */ if (dictSize(server.cluster->nodes_black_list) > 0) { dictIterator *di = dictGetIterator(server.cluster->nodes_black_list); dictEntry *de; while ((de = dictNext(di)) != NULL) { if (cursor != NULL) { uint64_t expire = dictGetUnsignedIntegerVal(de); if ((time_t)expire < server.unixtime) continue; /* already expired */ uint64_t ttl = expire - server.unixtime; clusterMsgPingExtForgottenNode *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE, getForgottenNodeExtSize()); memcpy(ext->name, dictGetKey(de), CLUSTER_NAMELEN); ext->ttl = htonu64(ttl); /* Move the write cursor */ cursor = nextPingExt(cursor); } totlen += getForgottenNodeExtSize(); extensions++; } dictReleaseIterator(di); } /* Populate shard_id */ if (cursor != NULL) { clusterMsgPingExtShardId *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_SHARDID, getShardIdPingExtSize()); memcpy(ext->shard_id, myself->shard_id, CLUSTER_NAMELEN); /* Move the write cursor */ cursor = nextPingExt(cursor); } totlen += getShardIdPingExtSize(); extensions++; /* Populate internal secret */ if (cursor != NULL) { clusterMsgPingExtInternalSecret *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_INTERNALSECRET, getInternalSecretPingExtSize()); memcpy(ext->internal_secret, server.cluster->internal_secret, CLUSTER_INTERNALSECRETLEN); /* Move the write cursor */ cursor = nextPingExt(cursor); } totlen += getInternalSecretPingExtSize(); extensions++; if (hdr != NULL) { hdr->extensions = htons(extensions); } return totlen; } /* We previously validated the extensions, so this function just needs to * handle the extensions. */ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); char *ext_hostname = NULL; char *ext_humannodename = NULL; char *ext_shardid = NULL; uint16_t extensions = ntohs(hdr->extensions); /* Loop through all the extensions and process them */ clusterMsgPingExt *ext = getInitialPingExt(hdr, ntohs(hdr->count)); while (extensions--) { uint16_t type = ntohs(ext->type); if (type == CLUSTERMSG_EXT_TYPE_HOSTNAME) { clusterMsgPingExtHostname *hostname_ext = (clusterMsgPingExtHostname *) &(ext->ext[0].hostname); ext_hostname = hostname_ext->hostname; } else if (type == CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME) { clusterMsgPingExtHumanNodename *humannodename_ext = (clusterMsgPingExtHumanNodename *) &(ext->ext[0].human_nodename); ext_humannodename = humannodename_ext->human_nodename; } else if (type == CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE) { clusterMsgPingExtForgottenNode *forgotten_node_ext = &(ext->ext[0].forgotten_node); clusterNode *n = clusterLookupNode(forgotten_node_ext->name, CLUSTER_NAMELEN); if (n && n != myself && !(nodeIsSlave(myself) && myself->slaveof == n)) { sds id = sdsnewlen(forgotten_node_ext->name, CLUSTER_NAMELEN); dictEntry *de = dictAddOrFind(server.cluster->nodes_black_list, id); uint64_t expire = server.unixtime + ntohu64(forgotten_node_ext->ttl); dictSetUnsignedIntegerVal(de, expire); clusterDelNode(n); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_SAVE_CONFIG); } } else if (type == CLUSTERMSG_EXT_TYPE_SHARDID) { clusterMsgPingExtShardId *shardid_ext = (clusterMsgPingExtShardId *) &(ext->ext[0].shard_id); ext_shardid = shardid_ext->shard_id; } else if (type == CLUSTERMSG_EXT_TYPE_INTERNALSECRET) { clusterMsgPingExtInternalSecret *internal_secret_ext = (clusterMsgPingExtInternalSecret *) &(ext->ext[0].internal_secret); if (memcmp(server.cluster->internal_secret, internal_secret_ext->internal_secret, CLUSTER_INTERNALSECRETLEN) > 0 ) { memcpy(server.cluster->internal_secret, internal_secret_ext->internal_secret, CLUSTER_INTERNALSECRETLEN); } } else { /* Unknown type, we will ignore it but log what happened. */ serverLog(LL_VERBOSE, "Received unknown extension type %d", type); } /* We know this will be valid since we validated it ahead of time */ ext = getNextPingExt(ext); } /* If the node did not send us a hostname extension, assume * they don't have an announced hostname. Otherwise, we'll * set it now. */ updateAnnouncedHostname(sender, ext_hostname); updateAnnouncedHumanNodename(sender, ext_humannodename); /* If the node did not send us a shard-id extension, it means the sender * does not support it (old version), node->shard_id is randomly generated. * A cluster-wide consensus for the node's shard_id is not necessary. * The key is maintaining consistency of the shard_id on each individual 7.2 node. * As the cluster progressively upgrades to version 7.2, we can expect the shard_ids * across all nodes to naturally converge and align. * * If sender is a replica, set the shard_id to the shard_id of its master. * Otherwise, we'll set it now. */ if (ext_shardid == NULL) ext_shardid = clusterNodeGetMaster(sender)->shard_id; updateShardId(sender, ext_shardid); } static clusterNode *getNodeFromLinkAndMsg(clusterLink *link, clusterMsg *hdr) { clusterNode *sender; if (link->node && !nodeInHandshake(link->node)) { /* If the link has an associated node, use that so that we don't have to look it * up every time, except when the node is still in handshake, the node still has * a random name thus not truly "known". */ sender = link->node; } else { /* Otherwise, fetch sender based on the message */ sender = clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); /* We know the sender node but haven't associate it with the link. This must * be an inbound link because only for inbound links we didn't know which node * to associate when they were created. */ if (sender && !link->node) { setClusterNodeToInboundClusterLink(sender, link); } } return sender; } /* When this function is called, there is a packet to process starting * at link->rcvbuf. Releasing the buffer is up to the caller, so this * function should just handle the higher level stuff of processing the * packet, modifying the cluster state if needed. * * The function returns 1 if the link is still valid after the packet * was processed, otherwise 0 if the link was freed since the packet * processing lead to some inconsistency error (for instance a PONG * received from the wrong sender ID). */ int clusterProcessPacket(clusterLink *link) { clusterMsg *hdr = (clusterMsg*) link->rcvbuf; uint32_t totlen = ntohl(hdr->totlen); uint16_t type = ntohs(hdr->type); mstime_t now = mstime(); if (type < CLUSTERMSG_TYPE_COUNT) server.cluster->stats_bus_messages_received[type]++; serverLog(LL_DEBUG,"--- Processing packet of type %s, %lu bytes", clusterGetMessageTypeString(type), (unsigned long) totlen); /* Perform sanity checks */ if (totlen < 16) return 1; /* At least signature, version, totlen, count. */ if (totlen > link->rcvbuf_len) return 1; if (ntohs(hdr->ver) != CLUSTER_PROTO_VER) { /* Can't handle messages of different versions. */ return 1; } if (type == server.cluster_drop_packet_filter) { serverLog(LL_WARNING, "Dropping packet that matches debug drop filter"); return 1; } uint16_t flags = ntohs(hdr->flags); uint16_t extensions = ntohs(hdr->extensions); uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0; uint32_t explen; /* expected length of this packet */ clusterNode *sender; if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG || type == CLUSTERMSG_TYPE_MEET) { uint16_t count = ntohs(hdr->count); explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); explen += (sizeof(clusterMsgDataGossip)*count); /* If there is extension data, which doesn't have a fixed length, * loop through them and validate the length of it now. */ if (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA) { clusterMsgPingExt *ext = getInitialPingExt(hdr, count); while (extensions--) { uint16_t extlen = getPingExtLength(ext); if (extlen % 8 != 0) { serverLog(LL_WARNING, "Received a %s packet without proper padding (%d bytes)", clusterGetMessageTypeString(type), (int) extlen); return 1; } if ((totlen - explen) < extlen) { serverLog(LL_WARNING, "Received invalid %s packet with extension data that exceeds " "total packet length (%lld)", clusterGetMessageTypeString(type), (unsigned long long) totlen); return 1; } explen += extlen; ext = getNextPingExt(ext); } } } else if (type == CLUSTERMSG_TYPE_FAIL) { explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); explen += sizeof(clusterMsgDataFail); } else if (type == CLUSTERMSG_TYPE_PUBLISH || type == CLUSTERMSG_TYPE_PUBLISHSHARD) { explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); explen += sizeof(clusterMsgDataPublish) - 8 + ntohl(hdr->data.publish.msg.channel_len) + ntohl(hdr->data.publish.msg.message_len); } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST || type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK || type == CLUSTERMSG_TYPE_MFSTART) { explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); } else if (type == CLUSTERMSG_TYPE_UPDATE) { explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); explen += sizeof(clusterMsgDataUpdate); } else if (type == CLUSTERMSG_TYPE_MODULE) { explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); explen += sizeof(clusterMsgModule) - 3 + ntohl(hdr->data.module.msg.len); } else { /* We don't know this type of packet, so we assume it's well formed. */ explen = totlen; } if (totlen != explen) { serverLog(LL_WARNING, "Received invalid %s packet of length %lld but expected length %lld", clusterGetMessageTypeString(type), (unsigned long long) totlen, (unsigned long long) explen); return 1; } sender = getNodeFromLinkAndMsg(link, hdr); if (sender && (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA)) { sender->flags |= CLUSTER_NODE_EXTENSIONS_SUPPORTED; } /* Update the last time we saw any data from this node. We * use this in order to avoid detecting a timeout from a node that * is just sending a lot of data in the cluster bus, for instance * because of Pub/Sub. */ if (sender) sender->data_received = now; if (sender && !nodeInHandshake(sender)) { /* Update our currentEpoch if we see a newer epoch in the cluster. */ senderCurrentEpoch = ntohu64(hdr->currentEpoch); senderConfigEpoch = ntohu64(hdr->configEpoch); if (senderCurrentEpoch > server.cluster->currentEpoch) server.cluster->currentEpoch = senderCurrentEpoch; /* Update the sender configEpoch if it is publishing a newer one. */ if (senderConfigEpoch > sender->configEpoch) { sender->configEpoch = senderConfigEpoch; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_FSYNC_CONFIG); } /* Update the replication offset info for this node. */ sender->repl_offset = ntohu64(hdr->offset); sender->repl_offset_time = now; /* If we are a slave performing a manual failover and our master * sent its offset while already paused, populate the MF state. */ if (server.cluster->mf_end && nodeIsSlave(myself) && myself->slaveof == sender && hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED && server.cluster->mf_master_offset == -1) { server.cluster->mf_master_offset = sender->repl_offset; clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER); serverLog(LL_NOTICE, "Received replication offset for paused " "master manual failover: %lld", server.cluster->mf_master_offset); } } /* Initial processing of PING and MEET requests replying with a PONG. */ if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) { /* We use incoming MEET messages in order to set the address * for 'myself', since only other cluster nodes will send us * MEET messages on handshakes, when the cluster joins, or * later if we changed address, and those nodes will use our * official address to connect to us. So by obtaining this address * from the socket is a simple way to discover / update our own * address in the cluster without it being hardcoded in the config. * * However if we don't have an address at all, we update the address * even with a normal PING packet. If it's wrong it will be fixed * by MEET later. */ if ((type == CLUSTERMSG_TYPE_MEET || myself->ip[0] == '\0') && server.cluster_announce_ip == NULL) { char ip[NET_IP_STR_LEN]; if (connAddrSockName(link->conn,ip,sizeof(ip),NULL) != -1 && strcmp(ip,myself->ip)) { memcpy(myself->ip,ip,NET_IP_STR_LEN); serverLog(LL_NOTICE,"IP address for this node updated to %s", myself->ip); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } } /* Add this node if it is new for us and the msg type is MEET. * In this stage we don't try to add the node with the right * flags, slaveof pointer, and so forth, as this details will be * resolved when we'll receive PONGs from the node. */ if (!sender && type == CLUSTERMSG_TYPE_MEET) { clusterNode *node; node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE); serverAssert(nodeIp2String(node->ip,link,hdr->myip) == C_OK); getClientPortFromClusterMsg(hdr, &node->tls_port, &node->tcp_port); node->cport = ntohs(hdr->cport); clusterAddNode(node); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } /* If this is a MEET packet from an unknown node, we still process * the gossip section here since we have to trust the sender because * of the message type. */ if (!sender && type == CLUSTERMSG_TYPE_MEET) clusterProcessGossipSection(hdr,link); /* Anyway reply with a PONG */ clusterSendPing(link,CLUSTERMSG_TYPE_PONG); } /* PING, PONG, MEET: process config information. */ if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG || type == CLUSTERMSG_TYPE_MEET) { serverLog(LL_DEBUG,"%s packet received: %.40s", clusterGetMessageTypeString(type), link->node ? link->node->name : "NULL"); if (!link->inbound) { if (nodeInHandshake(link->node)) { /* If we already have this node, try to change the * IP/port of the node with the new one. */ if (sender) { serverLog(LL_VERBOSE, "Handshake: we already know node %.40s (%s), " "updating the address if needed.", sender->name, sender->human_nodename); if (nodeUpdateAddressIfNeeded(sender,link,hdr)) { clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } /* Free this node as we already have it. This will * cause the link to be freed as well. */ clusterDelNode(link->node); return 0; } /* First thing to do is replacing the random name with the * right node name if this was a handshake stage. */ clusterRenameNode(link->node, hdr->sender); serverLog(LL_DEBUG,"Handshake with node %.40s completed.", link->node->name); link->node->flags &= ~CLUSTER_NODE_HANDSHAKE; link->node->flags |= flags&(CLUSTER_NODE_MASTER|CLUSTER_NODE_SLAVE); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } else if (memcmp(link->node->name,hdr->sender, CLUSTER_NAMELEN) != 0) { /* If the reply has a non matching node ID we * disconnect this node and set it as not having an associated * address. */ serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d", link->node->name, (int)(now-(link->node->ctime)), link->node->flags); link->node->flags |= CLUSTER_NODE_NOADDR; link->node->ip[0] = '\0'; link->node->tcp_port = 0; link->node->tls_port = 0; link->node->cport = 0; freeClusterLink(link); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); return 0; } } /* Copy the CLUSTER_NODE_NOFAILOVER flag from what the sender * announced. This is a dynamic flag that we receive from the * sender, and the latest status must be trusted. We need it to * be propagated because the slave ranking used to understand the * delay of each slave in the voting process, needs to know * what are the instances really competing. */ if (sender) { int nofailover = flags & CLUSTER_NODE_NOFAILOVER; sender->flags &= ~CLUSTER_NODE_NOFAILOVER; sender->flags |= nofailover; } /* Update the node address if it changed. */ if (sender && type == CLUSTERMSG_TYPE_PING && !nodeInHandshake(sender) && nodeUpdateAddressIfNeeded(sender,link,hdr)) { clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } /* Update our info about the node */ if (!link->inbound && type == CLUSTERMSG_TYPE_PONG) { link->node->pong_received = now; link->node->ping_sent = 0; /* The PFAIL condition can be reversed without external * help if it is momentary (that is, if it does not * turn into a FAIL state). * * The FAIL condition is also reversible under specific * conditions detected by clearNodeFailureIfNeeded(). */ if (nodeTimedOut(link->node)) { link->node->flags &= ~CLUSTER_NODE_PFAIL; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } else if (nodeFailed(link->node)) { clearNodeFailureIfNeeded(link->node); } } /* Check for role switch: slave -> master or master -> slave. */ if (sender) { if (!memcmp(hdr->slaveof,CLUSTER_NODE_NULL_NAME, sizeof(hdr->slaveof))) { /* Node is a master. */ clusterSetNodeAsMaster(sender); } else { /* Node is a slave. */ clusterNode *master = clusterLookupNode(hdr->slaveof, CLUSTER_NAMELEN); if (clusterNodeIsMaster(sender)) { /* Master turned into a slave! Reconfigure the node. */ if (master && !memcmp(master->shard_id, sender->shard_id, CLUSTER_NAMELEN)) { /* `sender` was a primary and was in the same shard as `master`, its new primary */ if (sender->configEpoch > senderConfigEpoch) { serverLog(LL_NOTICE, "Ignore stale message from %.40s (%s) in shard %.40s;" " gossip config epoch: %llu, current config epoch: %llu", sender->name, sender->human_nodename, sender->shard_id, (unsigned long long)senderConfigEpoch, (unsigned long long)sender->configEpoch); } else { /* A failover occurred in the shard where `sender` belongs to and `sender` is no longer * a primary. Update slot assignment to `master`, which is the new primary in the shard */ int slots = clusterMoveNodeSlots(sender, master); /* `master` is still a `slave` in this observer node's view; update its role and configEpoch */ clusterSetNodeAsMaster(master); master->configEpoch = senderConfigEpoch; serverLog(LL_NOTICE, "A failover occurred in shard %.40s; node %.40s (%s)" " lost %d slot(s) to node %.40s (%s) with a config epoch of %llu", sender->shard_id, sender->name, sender->human_nodename, slots, master->name, master->human_nodename, (unsigned long long) master->configEpoch); } } else { /* `sender` was moved to another shard and has become a replica, remove its slot assignment */ int slots = clusterDelNodeSlots(sender); serverLog(LL_NOTICE, "Node %.40s (%s) is no longer master of shard %.40s;" " removed all %d slot(s) it used to own", sender->name, sender->human_nodename, sender->shard_id, slots); if (master != NULL) { serverLog(LL_NOTICE, "Node %.40s (%s) is now part of shard %.40s", sender->name, sender->human_nodename, master->shard_id); } } sender->flags &= ~(CLUSTER_NODE_MASTER| CLUSTER_NODE_MIGRATE_TO); sender->flags |= CLUSTER_NODE_SLAVE; /* Update config and state. */ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } /* Master node changed for this slave? */ if (master && sender->slaveof != master) { if (sender->slaveof) clusterNodeRemoveSlave(sender->slaveof,sender); clusterNodeAddSlave(master,sender); sender->slaveof = master; /* Update the shard_id when a replica is connected to its * primary in the very first time. */ updateShardId(sender, master->shard_id); /* Update config. */ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); } } } /* Update our info about served slots. * * Note: this MUST happen after we update the master/slave state * so that CLUSTER_NODE_MASTER flag will be set. */ /* Many checks are only needed if the set of served slots this * instance claims is different compared to the set of slots we have * for it. Check this ASAP to avoid other computational expansive * checks later. */ clusterNode *sender_master = NULL; /* Sender or its master if slave. */ int dirty_slots = 0; /* Sender claimed slots don't match my view? */ if (sender) { sender_master = clusterNodeIsMaster(sender) ? sender : sender->slaveof; if (sender_master) { dirty_slots = memcmp(sender_master->slots, hdr->myslots,sizeof(hdr->myslots)) != 0; } } /* 1) If the sender of the message is a master, and we detected that * the set of slots it claims changed, scan the slots to see if we * need to update our configuration. */ if (sender && clusterNodeIsMaster(sender) && dirty_slots) clusterUpdateSlotsConfigWith(sender,senderConfigEpoch,hdr->myslots); /* 2) We also check for the reverse condition, that is, the sender * claims to serve slots we know are served by a master with a * greater configEpoch. If this happens we inform the sender. * * This is useful because sometimes after a partition heals, a * reappearing master may be the last one to claim a given set of * hash slots, but with a configuration that other instances know to * be deprecated. Example: * * A and B are master and slave for slots 1,2,3. * A is partitioned away, B gets promoted. * B is partitioned away, and A returns available. * * Usually B would PING A publishing its set of served slots and its * configEpoch, but because of the partition B can't inform A of the * new configuration, so other nodes that have an updated table must * do it. In this way A will stop to act as a master (or can try to * failover if there are the conditions to win the election). */ if (sender && dirty_slots) { int j; for (j = 0; j < CLUSTER_SLOTS; j++) { if (bitmapTestBit(hdr->myslots,j)) { if (server.cluster->slots[j] == sender || isSlotUnclaimed(j)) continue; if (server.cluster->slots[j]->configEpoch > senderConfigEpoch) { serverLog(LL_VERBOSE, "Node %.40s has old slots configuration, sending " "an UPDATE message about %.40s", sender->name, server.cluster->slots[j]->name); clusterSendUpdate(sender->link, server.cluster->slots[j]); /* TODO: instead of exiting the loop send every other * UPDATE packet for other nodes that are the new owner * of sender's slots. */ break; } } } } /* If our config epoch collides with the sender's try to fix * the problem. */ if (sender && clusterNodeIsMaster(myself) && clusterNodeIsMaster(sender) && senderConfigEpoch == myself->configEpoch) { clusterHandleConfigEpochCollision(sender); } /* Get info from the gossip section */ if (sender) { clusterProcessGossipSection(hdr,link); clusterProcessPingExtensions(hdr,link); } } else if (type == CLUSTERMSG_TYPE_FAIL) { clusterNode *failing; if (sender) { failing = clusterLookupNode(hdr->data.fail.about.nodename, CLUSTER_NAMELEN); if (failing && !(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF))) { serverLog(LL_NOTICE, "FAIL message received from %.40s (%s) about %.40s (%s)", hdr->sender, sender->human_nodename, hdr->data.fail.about.nodename, failing->human_nodename); failing->flags |= CLUSTER_NODE_FAIL; failing->fail_time = now; failing->flags &= ~CLUSTER_NODE_PFAIL; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE); } } else { serverLog(LL_NOTICE, "Ignoring FAIL message from unknown node %.40s about %.40s", hdr->sender, hdr->data.fail.about.nodename); } } else if (type == CLUSTERMSG_TYPE_PUBLISH || type == CLUSTERMSG_TYPE_PUBLISHSHARD) { if (!sender) return 1; /* We don't know that node. */ robj *channel, *message; uint32_t channel_len, message_len; /* Don't bother creating useless objects if there are no * Pub/Sub subscribers. */ if ((type == CLUSTERMSG_TYPE_PUBLISH && serverPubsubSubscriptionCount() > 0) || (type == CLUSTERMSG_TYPE_PUBLISHSHARD && serverPubsubShardSubscriptionCount() > 0)) { channel_len = ntohl(hdr->data.publish.msg.channel_len); message_len = ntohl(hdr->data.publish.msg.message_len); channel = createStringObject( (char*)hdr->data.publish.msg.bulk_data,channel_len); message = createStringObject( (char*)hdr->data.publish.msg.bulk_data+channel_len, message_len); pubsubPublishMessage(channel, message, type == CLUSTERMSG_TYPE_PUBLISHSHARD); decrRefCount(channel); decrRefCount(message); } } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST) { if (!sender) return 1; /* We don't know that node. */ clusterSendFailoverAuthIfNeeded(sender,hdr); } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK) { if (!sender) return 1; /* We don't know that node. */ /* We consider this vote only if the sender is a master serving * a non zero number of slots, and its currentEpoch is greater or * equal to epoch where this node started the election. */ if (clusterNodeIsMaster(sender) && sender->numslots > 0 && senderCurrentEpoch >= server.cluster->failover_auth_epoch) { server.cluster->failover_auth_count++; /* Maybe we reached a quorum here, set a flag to make sure * we check ASAP. */ clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER); } } else if (type == CLUSTERMSG_TYPE_MFSTART) { /* This message is acceptable only if I'm a master and the sender * is one of my slaves. */ if (!sender || sender->slaveof != myself) return 1; /* Manual failover requested from slaves. Initialize the state * accordingly. */ resetManualFailover(); server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT; server.cluster->mf_slave = sender; pauseActions(PAUSE_DURING_FAILOVER, now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT), PAUSE_ACTIONS_CLIENT_WRITE_SET); serverLog(LL_NOTICE,"Manual failover requested by replica %.40s (%s).", sender->name, sender->human_nodename); /* We need to send a ping message to the replica, as it would carry * `server.cluster->mf_master_offset`, which means the master paused clients * at offset `server.cluster->mf_master_offset`, so that the replica would * know that it is safe to set its `server.cluster->mf_can_start` to 1 so as * to complete failover as quickly as possible. */ clusterSendPing(link, CLUSTERMSG_TYPE_PING); } else if (type == CLUSTERMSG_TYPE_UPDATE) { clusterNode *n; /* The node the update is about. */ uint64_t reportedConfigEpoch = ntohu64(hdr->data.update.nodecfg.configEpoch); if (!sender) return 1; /* We don't know the sender. */ n = clusterLookupNode(hdr->data.update.nodecfg.nodename, CLUSTER_NAMELEN); if (!n) return 1; /* We don't know the reported node. */ if (n->configEpoch >= reportedConfigEpoch) return 1; /* Nothing new. */ /* If in our current config the node is a slave, set it as a master. */ if (nodeIsSlave(n)) clusterSetNodeAsMaster(n); /* Update the node's configEpoch. */ n->configEpoch = reportedConfigEpoch; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_FSYNC_CONFIG); /* Check the bitmap of served slots and update our * config accordingly. */ clusterUpdateSlotsConfigWith(n,reportedConfigEpoch, hdr->data.update.nodecfg.slots); } else if (type == CLUSTERMSG_TYPE_MODULE) { if (!sender) return 1; /* Protect the module from unknown nodes. */ /* We need to route this message back to the right module subscribed * for the right message type. */ uint64_t module_id = hdr->data.module.msg.module_id; /* Endian-safe ID */ uint32_t len = ntohl(hdr->data.module.msg.len); uint8_t type = hdr->data.module.msg.type; unsigned char *payload = hdr->data.module.msg.bulk_data; moduleCallClusterReceivers(sender->name,module_id,type,payload,len); } else { serverLog(LL_WARNING,"Received unknown packet type: %d", type); } return 1; } /* This function is called when we detect the link with this node is lost. We set the node as no longer connected. The Cluster Cron will detect this connection and will try to get it connected again. Instead if the node is a temporary node used to accept a query, we completely free the node on error. */ void handleLinkIOError(clusterLink *link) { freeClusterLink(link); } /* Send the messages queued for the link. */ void clusterWriteHandler(connection *conn) { clusterLink *link = connGetPrivateData(conn); ssize_t nwritten; size_t totwritten = 0; while (totwritten < NET_MAX_WRITES_PER_EVENT && listLength(link->send_msg_queue) > 0) { listNode *head = listFirst(link->send_msg_queue); clusterMsgSendBlock *msgblock = (clusterMsgSendBlock*)head->value; clusterMsg *msg = &msgblock->msg; size_t msg_offset = link->head_msg_send_offset; size_t msg_len = ntohl(msg->totlen); nwritten = connWrite(conn, (char*)msg + msg_offset, msg_len - msg_offset); if (nwritten <= 0) { serverLog(LL_DEBUG,"I/O error writing to node link: %s", (nwritten == -1) ? connGetLastError(conn) : "short write"); handleLinkIOError(link); return; } if (msg_offset + nwritten < msg_len) { /* If full message wasn't written, record the offset * and continue sending from this point next time */ link->head_msg_send_offset += nwritten; return; } serverAssert((msg_offset + nwritten) == msg_len); link->head_msg_send_offset = 0; /* Delete the node and update our memory tracking */ uint32_t blocklen = msgblock->totlen; listDelNode(link->send_msg_queue, head); server.stat_cluster_links_memory -= sizeof(listNode); link->send_msg_queue_mem -= sizeof(listNode) + blocklen; totwritten += nwritten; } if (listLength(link->send_msg_queue) == 0) connSetWriteHandler(link->conn, NULL); } /* A connect handler that gets called when a connection to another node * gets established. */ void clusterLinkConnectHandler(connection *conn) { clusterLink *link = connGetPrivateData(conn); clusterNode *node = link->node; /* Check if connection succeeded */ if (connGetState(conn) != CONN_STATE_CONNECTED) { serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s", node->name, node->ip, node->cport, connGetLastError(conn)); freeClusterLink(link); return; } /* Register a read handler from now on */ connSetReadHandler(conn, clusterReadHandler); /* Queue a PING in the new connection ASAP: this is crucial * to avoid false positives in failure detection. * * If the node is flagged as MEET, we send a MEET message instead * of a PING one, to force the receiver to add us in its node * table. */ mstime_t old_ping_sent = node->ping_sent; clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ? CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING); if (old_ping_sent) { /* If there was an active ping before the link was * disconnected, we want to restore the ping time, otherwise * replaced by the clusterSendPing() call. */ node->ping_sent = old_ping_sent; } /* We can clear the flag after the first packet is sent. * If we'll never receive a PONG, we'll never send new packets * to this node. Instead after the PONG is received and we * are no longer in meet/handshake status, we want to send * normal PING packets. */ node->flags &= ~CLUSTER_NODE_MEET; serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d", node->name, node->ip, node->cport); } /* Read data. Try to read the first field of the header first to check the * full length of the packet. When a whole packet is in memory this function * will call the function to process the packet. And so forth. */ void clusterReadHandler(connection *conn) { clusterMsg buf[1]; ssize_t nread; clusterMsg *hdr; clusterLink *link = connGetPrivateData(conn); unsigned int readlen, rcvbuflen; while(1) { /* Read as long as there is data to read. */ rcvbuflen = link->rcvbuf_len; if (rcvbuflen < 8) { /* First, obtain the first 8 bytes to get the full message * length. */ readlen = 8 - rcvbuflen; } else { /* Finally read the full message. */ hdr = (clusterMsg*) link->rcvbuf; if (rcvbuflen == 8) { /* Perform some sanity check on the message signature * and length. */ if (memcmp(hdr->sig,"RCmb",4) != 0 || ntohl(hdr->totlen) < CLUSTERMSG_MIN_LEN) { char ip[NET_IP_STR_LEN]; int port; if (connAddrPeerName(conn, ip, sizeof(ip), &port) == -1) { serverLog(LL_WARNING, "Bad message length or signature received " "on the Cluster bus."); } else { serverLog(LL_WARNING, "Bad message length or signature received " "on the Cluster bus from %s:%d", ip, port); } handleLinkIOError(link); return; } } readlen = ntohl(hdr->totlen) - rcvbuflen; if (readlen > sizeof(buf)) readlen = sizeof(buf); } nread = connRead(conn,buf,readlen); if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */ if (nread <= 0) { /* I/O error... */ serverLog(LL_DEBUG,"I/O error reading from node link: %s", (nread == 0) ? "connection closed" : connGetLastError(conn)); handleLinkIOError(link); return; } else { /* Read data and recast the pointer to the new buffer. */ size_t unused = link->rcvbuf_alloc - link->rcvbuf_len; if ((size_t)nread > unused) { size_t required = link->rcvbuf_len + nread; size_t prev_rcvbuf_alloc = link->rcvbuf_alloc; /* If less than 1mb, grow to twice the needed size, if larger grow by 1mb. */ link->rcvbuf_alloc = required < RCVBUF_MAX_PREALLOC ? required * 2: required + RCVBUF_MAX_PREALLOC; link->rcvbuf = zrealloc(link->rcvbuf, link->rcvbuf_alloc); server.stat_cluster_links_memory += link->rcvbuf_alloc - prev_rcvbuf_alloc; } memcpy(link->rcvbuf + link->rcvbuf_len, buf, nread); link->rcvbuf_len += nread; hdr = (clusterMsg*) link->rcvbuf; rcvbuflen += nread; } /* Total length obtained? Process this packet. */ if (rcvbuflen >= 8 && rcvbuflen == ntohl(hdr->totlen)) { if (clusterProcessPacket(link)) { if (link->rcvbuf_alloc > RCVBUF_INIT_LEN) { size_t prev_rcvbuf_alloc = link->rcvbuf_alloc; zfree(link->rcvbuf); link->rcvbuf = zmalloc(link->rcvbuf_alloc = RCVBUF_INIT_LEN); server.stat_cluster_links_memory += link->rcvbuf_alloc - prev_rcvbuf_alloc; } link->rcvbuf_len = 0; } else { return; /* Link no longer valid. */ } } } } /* Put the message block into the link's send queue. * * It is guaranteed that this function will never have as a side effect * the link to be invalidated, so it is safe to call this function * from event handlers that will do stuff with the same link later. */ void clusterSendMessage(clusterLink *link, clusterMsgSendBlock *msgblock) { if (!link) { return; } if (listLength(link->send_msg_queue) == 0 && msgblock->msg.totlen != 0) connSetWriteHandlerWithBarrier(link->conn, clusterWriteHandler, 1); listAddNodeTail(link->send_msg_queue, msgblock); msgblock->refcount++; /* Update memory tracking */ link->send_msg_queue_mem += sizeof(listNode) + msgblock->totlen; server.stat_cluster_links_memory += sizeof(listNode); /* Populate sent messages stats. */ uint16_t type = ntohs(msgblock->msg.type); if (type < CLUSTERMSG_TYPE_COUNT) server.cluster->stats_bus_messages_sent[type]++; } /* Send a message to all the nodes that are part of the cluster having * a connected link. * * It is guaranteed that this function will never have as a side effect * some node->link to be invalidated, so it is safe to call this function * from event handlers that will do stuff with node links later. */ void clusterBroadcastMessage(clusterMsgSendBlock *msgblock) { dictIterator *di; dictEntry *de; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) continue; clusterSendMessage(node->link,msgblock); } dictReleaseIterator(di); } /* Build the message header. hdr must point to a buffer at least * sizeof(clusterMsg) in bytes. */ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) { uint64_t offset; clusterNode *master; /* If this node is a master, we send its slots bitmap and configEpoch. * If this node is a slave we send the master's information instead (the * node is flagged as slave so the receiver knows that it is NOT really * in charge for this slots. */ master = (nodeIsSlave(myself) && myself->slaveof) ? myself->slaveof : myself; hdr->ver = htons(CLUSTER_PROTO_VER); hdr->sig[0] = 'R'; hdr->sig[1] = 'C'; hdr->sig[2] = 'm'; hdr->sig[3] = 'b'; hdr->type = htons(type); memcpy(hdr->sender,myself->name,CLUSTER_NAMELEN); /* If cluster-announce-ip option is enabled, force the receivers of our * packets to use the specified address for this node. Otherwise if the * first byte is zero, they'll do auto discovery. */ memset(hdr->myip,0,NET_IP_STR_LEN); if (server.cluster_announce_ip) { redis_strlcpy(hdr->myip,server.cluster_announce_ip,NET_IP_STR_LEN); } /* Handle cluster-announce-[tls-|bus-]port. */ int announced_tcp_port, announced_tls_port, announced_cport; deriveAnnouncedPorts(&announced_tcp_port, &announced_tls_port, &announced_cport); memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots)); memset(hdr->slaveof,0,CLUSTER_NAMELEN); if (myself->slaveof != NULL) memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN); if (server.tls_cluster) { hdr->port = htons(announced_tls_port); hdr->pport = htons(announced_tcp_port); } else { hdr->port = htons(announced_tcp_port); hdr->pport = htons(announced_tls_port); } hdr->cport = htons(announced_cport); hdr->flags = htons(myself->flags); hdr->state = server.cluster->state; /* Set the currentEpoch and configEpochs. */ hdr->currentEpoch = htonu64(server.cluster->currentEpoch); hdr->configEpoch = htonu64(master->configEpoch); /* Set the replication offset. */ if (nodeIsSlave(myself)) offset = replicationGetSlaveOffset(); else offset = server.master_repl_offset; hdr->offset = htonu64(offset); /* Set the message flags. */ if (clusterNodeIsMaster(myself) && server.cluster->mf_end) hdr->mflags[0] |= CLUSTERMSG_FLAG0_PAUSED; hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; /* Always make other nodes know that * this node supports extension data. */ hdr->totlen = htonl(msglen); } /* Set the i-th entry of the gossip section in the message pointed by 'hdr' * to the info of the specified node 'n'. */ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) { clusterMsgDataGossip *gossip; gossip = &(hdr->data.ping.gossip[i]); memcpy(gossip->nodename,n->name,CLUSTER_NAMELEN); gossip->ping_sent = htonl(n->ping_sent/1000); gossip->pong_received = htonl(n->pong_received/1000); memcpy(gossip->ip,n->ip,sizeof(n->ip)); if (server.tls_cluster) { gossip->port = htons(n->tls_port); gossip->pport = htons(n->tcp_port); } else { gossip->port = htons(n->tcp_port); gossip->pport = htons(n->tls_port); } gossip->cport = htons(n->cport); gossip->flags = htons(n->flags); gossip->notused1 = 0; } /* Send a PING or PONG packet to the specified node, making sure to add enough * gossip information. */ void clusterSendPing(clusterLink *link, int type) { static unsigned long long cluster_pings_sent = 0; cluster_pings_sent++; int gossipcount = 0; /* Number of gossip sections added so far. */ int wanted; /* Number of gossip sections we want to append if possible. */ int estlen; /* Upper bound on estimated packet length */ /* freshnodes is the max number of nodes we can hope to append at all: * nodes available minus two (ourself and the node we are sending the * message to). However practically there may be less valid nodes since * nodes in handshake state, disconnected, are not considered. */ int freshnodes = dictSize(server.cluster->nodes)-2; /* How many gossip sections we want to add? 1/10 of the number of nodes * and anyway at least 3. Why 1/10? * * If we have N masters, with N/10 entries, and we consider that in * node_timeout we exchange with each other node at least 4 packets * (we ping in the worst case in node_timeout/2 time, and we also * receive two pings from the host), we have a total of 8 packets * in the node_timeout*2 failure reports validity time. So we have * that, for a single PFAIL node, we can expect to receive the following * number of failure reports (in the specified window of time): * * PROB * GOSSIP_ENTRIES_PER_PACKET * TOTAL_PACKETS: * * PROB = probability of being featured in a single gossip entry, * which is 1 / NUM_OF_NODES. * ENTRIES = 10. * TOTAL_PACKETS = 2 * 4 * NUM_OF_MASTERS. * * If we assume we have just masters (so num of nodes and num of masters * is the same), with 1/10 we always get over the majority, and specifically * 80% of the number of nodes, to account for many masters failing at the * same time. * * Since we have non-voting slaves that lower the probability of an entry * to feature our node, we set the number of entries per packet as * 10% of the total nodes we have. */ wanted = floor(dictSize(server.cluster->nodes)/10); if (wanted < 3) wanted = 3; if (wanted > freshnodes) wanted = freshnodes; /* Include all the nodes in PFAIL state, so that failure reports are * faster to propagate to go from PFAIL to FAIL state. */ int pfail_wanted = server.cluster->stats_pfail_nodes; /* Compute the maximum estlen to allocate our buffer. We'll fix the estlen * later according to the number of gossip sections we really were able * to put inside the packet. */ estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData); estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted)); if (link->node && nodeSupportsExtensions(link->node)) { estlen += writePingExt(NULL, 0); } /* Note: clusterBuildMessageHdr() expects the buffer to be always at least * sizeof(clusterMsg) or more. */ if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg); clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(type, estlen); clusterMsg *hdr = &msgblock->msg; if (!link->inbound && type == CLUSTERMSG_TYPE_PING) link->node->ping_sent = mstime(); /* Populate the gossip fields */ int maxiterations = wanted*3; while(freshnodes > 0 && gossipcount < wanted && maxiterations--) { dictEntry *de = dictGetRandomKey(server.cluster->nodes); clusterNode *this = dictGetVal(de); /* Don't include this node: the whole packet header is about us * already, so we just gossip about other nodes. * Also, don't include the receiver. Receiver will not update its state * based on gossips about itself. */ if (this == myself || this == link->node) continue; /* PFAIL nodes will be added later. */ if (this->flags & CLUSTER_NODE_PFAIL) continue; /* In the gossip section don't include: * 1) Nodes in HANDSHAKE state. * 3) Nodes with the NOADDR flag set. * 4) Disconnected nodes if they don't have configured slots. */ if (this->flags & (CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_NOADDR) || (this->link == NULL && this->numslots == 0)) { freshnodes--; /* Technically not correct, but saves CPU. */ continue; } /* Do not add a node we already have. */ if (this->last_in_ping_gossip == cluster_pings_sent) continue; /* Add it */ clusterSetGossipEntry(hdr,gossipcount,this); this->last_in_ping_gossip = cluster_pings_sent; freshnodes--; gossipcount++; } /* If there are PFAIL nodes, add them at the end. */ if (pfail_wanted) { dictIterator *di; dictEntry *de; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL && pfail_wanted > 0) { clusterNode *node = dictGetVal(de); if (node->flags & CLUSTER_NODE_HANDSHAKE) continue; if (node->flags & CLUSTER_NODE_NOADDR) continue; if (!(node->flags & CLUSTER_NODE_PFAIL)) continue; clusterSetGossipEntry(hdr,gossipcount,node); gossipcount++; /* We take the count of the slots we allocated, since the * PFAIL stats may not match perfectly with the current number * of PFAIL nodes. */ pfail_wanted--; } dictReleaseIterator(di); } /* Compute the actual total length and send! */ uint32_t totlen = 0; if (link->node && nodeSupportsExtensions(link->node)) { totlen += writePingExt(hdr, gossipcount); } totlen += sizeof(clusterMsg)-sizeof(union clusterMsgData); totlen += (sizeof(clusterMsgDataGossip)*gossipcount); serverAssert(gossipcount < USHRT_MAX); hdr->count = htons(gossipcount); hdr->totlen = htonl(totlen); clusterSendMessage(link,msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* Send a PONG packet to every connected node that's not in handshake state * and for which we have a valid link. * * In Redis Cluster pongs are not used just for failure detection, but also * to carry important configuration information. So broadcasting a pong is * useful when something changes in the configuration and we want to make * the cluster aware ASAP (for instance after a slave promotion). * * The 'target' argument specifies the receiving instances using the * defines below: * * CLUSTER_BROADCAST_ALL -> All known instances. * CLUSTER_BROADCAST_LOCAL_SLAVES -> All slaves in my master-slaves ring. */ #define CLUSTER_BROADCAST_ALL 0 #define CLUSTER_BROADCAST_LOCAL_SLAVES 1 void clusterBroadcastPong(int target) { dictIterator *di; dictEntry *de; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (!node->link) continue; if (node == myself || nodeInHandshake(node)) continue; if (target == CLUSTER_BROADCAST_LOCAL_SLAVES) { int local_slave = nodeIsSlave(node) && node->slaveof && (node->slaveof == myself || node->slaveof == myself->slaveof); if (!local_slave) continue; } clusterSendPing(node->link,CLUSTERMSG_TYPE_PONG); } dictReleaseIterator(di); } /* Create a PUBLISH message block. * * Sanitizer suppression: In clusterMsgDataPublish, sizeof(bulk_data) is 8. * As all the struct is used as a buffer, when more than 8 bytes are copied into * the 'bulk_data', sanitizer generates an out-of-bounds error which is a false * positive in this context. */ REDIS_NO_SANITIZE("bounds") clusterMsgSendBlock *clusterCreatePublishMsgBlock(robj *channel, robj *message, uint16_t type) { uint32_t channel_len, message_len; channel = getDecodedObject(channel); message = getDecodedObject(message); channel_len = sdslen(channel->ptr); message_len = sdslen(message->ptr); size_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); msglen += sizeof(clusterMsgDataPublish) - 8 + channel_len + message_len; clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(type, msglen); clusterMsg *hdr = &msgblock->msg; hdr->data.publish.msg.channel_len = htonl(channel_len); hdr->data.publish.msg.message_len = htonl(message_len); memcpy(hdr->data.publish.msg.bulk_data,channel->ptr,sdslen(channel->ptr)); memcpy(hdr->data.publish.msg.bulk_data+sdslen(channel->ptr), message->ptr,sdslen(message->ptr)); decrRefCount(channel); decrRefCount(message); return msgblock; } /* Send a FAIL message to all the nodes we are able to contact. * The FAIL message is sent when we detect that a node is failing * (CLUSTER_NODE_PFAIL) and we also receive a gossip confirmation of this: * we switch the node state to CLUSTER_NODE_FAIL and ask all the other * nodes to do the same ASAP. */ void clusterSendFail(char *nodename) { uint32_t msglen = sizeof(clusterMsg) - sizeof(union clusterMsgData) + sizeof(clusterMsgDataFail); clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_FAIL, msglen); clusterMsg *hdr = &msgblock->msg; memcpy(hdr->data.fail.about.nodename,nodename,CLUSTER_NAMELEN); clusterBroadcastMessage(msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* Send an UPDATE message to the specified link carrying the specified 'node' * slots configuration. The node name, slots bitmap, and configEpoch info * are included. */ void clusterSendUpdate(clusterLink *link, clusterNode *node) { if (link == NULL) return; uint32_t msglen = sizeof(clusterMsg) - sizeof(union clusterMsgData) + sizeof(clusterMsgDataUpdate); clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_UPDATE, msglen); clusterMsg *hdr = &msgblock->msg; memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN); hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch); memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots)); for (unsigned int i = 0; i < sizeof(node->slots); i++) { /* Don't advertise slots that the node stopped claiming */ hdr->data.update.nodecfg.slots[i] = hdr->data.update.nodecfg.slots[i] & (~server.cluster->owner_not_claiming_slot[i]); } clusterSendMessage(link,msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* Send a MODULE message. * * If link is NULL, then the message is broadcasted to the whole cluster. */ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type, const char *payload, uint32_t len) { uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); msglen += sizeof(clusterMsgModule) - 3 + len; clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_MODULE, msglen); clusterMsg *hdr = &msgblock->msg; hdr->data.module.msg.module_id = module_id; /* Already endian adjusted. */ hdr->data.module.msg.type = type; hdr->data.module.msg.len = htonl(len); memcpy(hdr->data.module.msg.bulk_data,payload,len); if (link) clusterSendMessage(link,msgblock); else clusterBroadcastMessage(msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* This function gets a cluster node ID string as target, the same way the nodes * addresses are represented in the modules side, resolves the node, and sends * the message. If the target is NULL the message is broadcasted. * * The function returns C_OK if the target is valid, otherwise C_ERR is * returned. */ int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, const char *payload, uint32_t len) { clusterNode *node = NULL; if (target != NULL) { node = clusterLookupNode(target, strlen(target)); if (node == NULL || node->link == NULL) return C_ERR; } clusterSendModule(target ? node->link : NULL, module_id, type, payload, len); return C_OK; } /* ----------------------------------------------------------------------------- * CLUSTER Pub/Sub support * * If `sharded` is 0: * For now we do very little, just propagating [S]PUBLISH messages across the whole * cluster. In the future we'll try to get smarter and avoiding propagating those * messages to hosts without receives for a given channel. * Otherwise: * Publish this message across the slot (primary/replica). * -------------------------------------------------------------------------- */ void clusterPropagatePublish(robj *channel, robj *message, int sharded) { clusterMsgSendBlock *msgblock; if (!sharded) { msgblock = clusterCreatePublishMsgBlock(channel, message, CLUSTERMSG_TYPE_PUBLISH); clusterBroadcastMessage(msgblock); clusterMsgSendBlockDecrRefCount(msgblock); return; } listIter li; listNode *ln; list *nodes_for_slot = clusterGetNodesInMyShard(server.cluster->myself); serverAssert(nodes_for_slot != NULL); listRewind(nodes_for_slot, &li); msgblock = clusterCreatePublishMsgBlock(channel, message, CLUSTERMSG_TYPE_PUBLISHSHARD); while((ln = listNext(&li))) { clusterNode *node = listNodeValue(ln); if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) continue; clusterSendMessage(node->link,msgblock); } clusterMsgSendBlockDecrRefCount(msgblock); } /* ----------------------------------------------------------------------------- * SLAVE node specific functions * -------------------------------------------------------------------------- */ /* This function sends a FAILOVER_AUTH_REQUEST message to every node in order to * see if there is the quorum for this slave instance to failover its failing * master. * * Note that we send the failover request to everybody, master and slave nodes, * but only the masters are supposed to reply to our query. */ void clusterRequestFailoverAuth(void) { uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST, msglen); clusterMsg *hdr = &msgblock->msg; /* If this is a manual failover, set the CLUSTERMSG_FLAG0_FORCEACK bit * in the header to communicate the nodes receiving the message that * they should authorized the failover even if the master is working. */ if (server.cluster->mf_end) hdr->mflags[0] |= CLUSTERMSG_FLAG0_FORCEACK; clusterBroadcastMessage(msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* Send a FAILOVER_AUTH_ACK message to the specified node. */ void clusterSendFailoverAuth(clusterNode *node) { if (!node->link) return; uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK, msglen); clusterSendMessage(node->link,msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* Send a MFSTART message to the specified node. */ void clusterSendMFStart(clusterNode *node) { if (!node->link) return; uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_MFSTART, msglen); clusterSendMessage(node->link,msgblock); clusterMsgSendBlockDecrRefCount(msgblock); } /* Vote for the node asking for our vote if there are the conditions. */ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { clusterNode *master = node->slaveof; uint64_t requestCurrentEpoch = ntohu64(request->currentEpoch); uint64_t requestConfigEpoch = ntohu64(request->configEpoch); unsigned char *claimed_slots = request->myslots; int force_ack = request->mflags[0] & CLUSTERMSG_FLAG0_FORCEACK; int j; /* IF we are not a master serving at least 1 slot, we don't have the * right to vote, as the cluster size in Redis Cluster is the number * of masters serving at least one slot, and quorum is the cluster * size + 1 */ if (nodeIsSlave(myself) || myself->numslots == 0) return; /* Request epoch must be >= our currentEpoch. * Note that it is impossible for it to actually be greater since * our currentEpoch was updated as a side effect of receiving this * request, if the request epoch was greater. */ if (requestCurrentEpoch < server.cluster->currentEpoch) { serverLog(LL_WARNING, "Failover auth denied to %.40s (%s): reqEpoch (%llu) < curEpoch(%llu)", node->name, node->human_nodename, (unsigned long long) requestCurrentEpoch, (unsigned long long) server.cluster->currentEpoch); return; } /* I already voted for this epoch? Return ASAP. */ if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) { serverLog(LL_WARNING, "Failover auth denied to %.40s (%s): already voted for epoch %llu", node->name, node->human_nodename, (unsigned long long) server.cluster->currentEpoch); return; } /* Node must be a slave and its master down. * The master can be non failing if the request is flagged * with CLUSTERMSG_FLAG0_FORCEACK (manual failover). */ if (clusterNodeIsMaster(node) || master == NULL || (!nodeFailed(master) && !force_ack)) { if (clusterNodeIsMaster(node)) { serverLog(LL_WARNING, "Failover auth denied to %.40s (%s): it is a master node", node->name, node->human_nodename); } else if (master == NULL) { serverLog(LL_WARNING, "Failover auth denied to %.40s (%s): I don't know its master", node->name, node->human_nodename); } else if (!nodeFailed(master)) { serverLog(LL_WARNING, "Failover auth denied to %.40s (%s): its master is up", node->name, node->human_nodename); } return; } /* We did not voted for a slave about this master for two * times the node timeout. This is not strictly needed for correctness * of the algorithm but makes the base case more linear. */ if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2) { serverLog(LL_WARNING, "Failover auth denied to %.40s %s: " "can't vote about this master before %lld milliseconds", node->name, node->human_nodename, (long long) ((server.cluster_node_timeout*2)- (mstime() - node->slaveof->voted_time))); return; } /* The slave requesting the vote must have a configEpoch for the claimed * slots that is >= the one of the masters currently serving the same * slots in the current configuration. */ for (j = 0; j < CLUSTER_SLOTS; j++) { if (bitmapTestBit(claimed_slots, j) == 0) continue; if (isSlotUnclaimed(j) || server.cluster->slots[j]->configEpoch <= requestConfigEpoch) { continue; } /* If we reached this point we found a slot that in our current slots * is served by a master with a greater configEpoch than the one claimed * by the slave requesting our vote. Refuse to vote for this slave. */ serverLog(LL_WARNING, "Failover auth denied to %.40s (%s): " "slot %d epoch (%llu) > reqEpoch (%llu)", node->name, node->human_nodename, j, (unsigned long long) server.cluster->slots[j]->configEpoch, (unsigned long long) requestConfigEpoch); return; } /* We can vote for this slave. */ server.cluster->lastVoteEpoch = server.cluster->currentEpoch; node->slaveof->voted_time = mstime(); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); clusterSendFailoverAuth(node); serverLog(LL_NOTICE, "Failover auth granted to %.40s (%s) for epoch %llu", node->name, node->human_nodename, (unsigned long long) server.cluster->currentEpoch); } /* This function returns the "rank" of this instance, a slave, in the context * of its master-slaves ring. The rank of the slave is given by the number of * other slaves for the same master that have a better replication offset * compared to the local one (better means, greater, so they claim more data). * * A slave with rank 0 is the one with the greatest (most up to date) * replication offset, and so forth. Note that because how the rank is computed * multiple slaves may have the same rank, in case they have the same offset. * * The slave rank is used to add a delay to start an election in order to * get voted and replace a failing master. Slaves with better replication * offsets are more likely to win. */ int clusterGetSlaveRank(void) { long long myoffset; int j, rank = 0; clusterNode *master; serverAssert(nodeIsSlave(myself)); master = myself->slaveof; if (master == NULL) return 0; /* Never called by slaves without master. */ myoffset = replicationGetSlaveOffset(); for (j = 0; j < master->numslaves; j++) if (master->slaves[j] != myself && !nodeCantFailover(master->slaves[j]) && master->slaves[j]->repl_offset > myoffset) rank++; return rank; } /* This function is called by clusterHandleSlaveFailover() in order to * let the slave log why it is not able to failover. Sometimes there are * not the conditions, but since the failover function is called again and * again, we can't log the same things continuously. * * This function works by logging only if a given set of conditions are * true: * * 1) The reason for which the failover can't be initiated changed. * The reasons also include a NONE reason we reset the state to * when the slave finds that its master is fine (no FAIL flag). * 2) Also, the log is emitted again if the master is still down and * the reason for not failing over is still the same, but more than * CLUSTER_CANT_FAILOVER_RELOG_PERIOD seconds elapsed. * 3) Finally, the function only logs if the slave is down for more than * five seconds + NODE_TIMEOUT. This way nothing is logged when a * failover starts in a reasonable time. * * The function is called with the reason why the slave can't failover * which is one of the integer macros CLUSTER_CANT_FAILOVER_*. * * The function is guaranteed to be called only if 'myself' is a slave. */ void clusterLogCantFailover(int reason) { char *msg; static time_t lastlog_time = 0; mstime_t nolog_fail_time = server.cluster_node_timeout + 5000; /* Don't log if we have the same reason for some time. */ if (reason == server.cluster->cant_failover_reason && time(NULL)-lastlog_time < CLUSTER_CANT_FAILOVER_RELOG_PERIOD) return; server.cluster->cant_failover_reason = reason; /* We also don't emit any log if the master failed no long ago, the * goal of this function is to log slaves in a stalled condition for * a long time. */ if (myself->slaveof && nodeFailed(myself->slaveof) && (mstime() - myself->slaveof->fail_time) < nolog_fail_time) return; switch(reason) { case CLUSTER_CANT_FAILOVER_DATA_AGE: msg = "Disconnected from master for longer than allowed. " "Please check the 'cluster-replica-validity-factor' configuration " "option."; break; case CLUSTER_CANT_FAILOVER_WAITING_DELAY: msg = "Waiting the delay before I can start a new failover."; break; case CLUSTER_CANT_FAILOVER_EXPIRED: msg = "Failover attempt expired."; break; case CLUSTER_CANT_FAILOVER_WAITING_VOTES: msg = "Waiting for votes, but majority still not reached."; break; default: msg = "Unknown reason code."; break; } lastlog_time = time(NULL); serverLog(LL_NOTICE,"Currently unable to failover: %s", msg); int cur_vote = server.cluster->failover_auth_count; int cur_quorum = (server.cluster->size / 2) + 1; /* Emits a log when an election is in progress and waiting for votes or when the failover attempt expired. */ if (reason == CLUSTER_CANT_FAILOVER_WAITING_VOTES || reason == CLUSTER_CANT_FAILOVER_EXPIRED) { serverLog(LL_NOTICE, "Needed quorum: %d. Number of votes received so far: %d", cur_quorum, cur_vote); } } /* This function implements the final part of automatic and manual failovers, * where the slave grabs its master's hash slots, and propagates the new * configuration. * * Note that it's up to the caller to be sure that the node got a new * configuration epoch already. */ void clusterFailoverReplaceYourMaster(void) { int j; clusterNode *oldmaster = myself->slaveof; if (clusterNodeIsMaster(myself) || oldmaster == NULL) return; /* 1) Turn this node into a master. */ clusterSetNodeAsMaster(myself); replicationUnsetMaster(); /* 2) Claim all the slots assigned to our master. */ for (j = 0; j < CLUSTER_SLOTS; j++) { if (clusterNodeCoversSlot(oldmaster, j)) { clusterDelSlot(j); clusterAddSlot(myself,j); } } /* 3) Update state and save config. */ clusterUpdateState(); clusterSaveConfigOrDie(1); /* 4) Pong all the other nodes so that they can update the state * accordingly and detect that we switched to master role. */ clusterBroadcastPong(CLUSTER_BROADCAST_ALL); /* 5) If there was a manual failover in progress, clear the state. */ resetManualFailover(); } /* This function is called if we are a slave node and our master serving * a non-zero amount of hash slots is in FAIL state. * * The goal of this function is: * 1) To check if we are able to perform a failover, is our data updated? * 2) Try to get elected by masters. * 3) Perform the failover informing all the other nodes. */ void clusterHandleSlaveFailover(void) { mstime_t data_age; mstime_t auth_age = mstime() - server.cluster->failover_auth_time; int needed_quorum = (server.cluster->size / 2) + 1; int manual_failover = server.cluster->mf_end != 0 && server.cluster->mf_can_start; mstime_t auth_timeout, auth_retry_time; server.cluster->todo_before_sleep &= ~CLUSTER_TODO_HANDLE_FAILOVER; /* Compute the failover timeout (the max time we have to send votes * and wait for replies), and the failover retry time (the time to wait * before trying to get voted again). * * Timeout is MAX(NODE_TIMEOUT*2,2000) milliseconds. * Retry is two times the Timeout. */ auth_timeout = server.cluster_node_timeout*2; if (auth_timeout < 2000) auth_timeout = 2000; auth_retry_time = auth_timeout*2; /* Pre conditions to run the function, that must be met both in case * of an automatic or manual failover: * 1) We are a slave. * 2) Our master is flagged as FAIL, or this is a manual failover. * 3) We don't have the no failover configuration set, and this is * not a manual failover. * 4) It is serving slots. */ if (clusterNodeIsMaster(myself) || myself->slaveof == NULL || (!nodeFailed(myself->slaveof) && !manual_failover) || (server.cluster_slave_no_failover && !manual_failover) || myself->slaveof->numslots == 0) { /* There are no reasons to failover, so we set the reason why we * are returning without failing over to NONE. */ server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE; return; } /* Set data_age to the number of milliseconds we are disconnected from * the master. */ if (server.repl_state == REPL_STATE_CONNECTED) { data_age = (mstime_t)(server.unixtime - server.master->lastinteraction) * 1000; } else { data_age = (mstime_t)(server.unixtime - server.repl_down_since) * 1000; } /* Remove the node timeout from the data age as it is fine that we are * disconnected from our master at least for the time it was down to be * flagged as FAIL, that's the baseline. */ if (data_age > server.cluster_node_timeout) data_age -= server.cluster_node_timeout; /* Check if our data is recent enough according to the slave validity * factor configured by the user. * * Check bypassed for manual failovers. */ if (server.cluster_slave_validity_factor && data_age > (((mstime_t)server.repl_ping_slave_period * 1000) + (server.cluster_node_timeout * server.cluster_slave_validity_factor))) { if (!manual_failover) { clusterLogCantFailover(CLUSTER_CANT_FAILOVER_DATA_AGE); return; } } /* If the previous failover attempt timeout and the retry time has * elapsed, we can setup a new one. */ if (auth_age > auth_retry_time) { server.cluster->failover_auth_time = mstime() + 500 + /* Fixed delay of 500 milliseconds, let FAIL msg propagate. */ random() % 500; /* Random delay between 0 and 500 milliseconds. */ server.cluster->failover_auth_count = 0; server.cluster->failover_auth_sent = 0; server.cluster->failover_auth_rank = clusterGetSlaveRank(); /* We add another delay that is proportional to the slave rank. * Specifically 1 second * rank. This way slaves that have a probably * less updated replication offset, are penalized. */ server.cluster->failover_auth_time += server.cluster->failover_auth_rank * 1000; /* However if this is a manual failover, no delay is needed. */ if (server.cluster->mf_end) { server.cluster->failover_auth_time = mstime(); server.cluster->failover_auth_rank = 0; clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER); } serverLog(LL_NOTICE, "Start of election delayed for %lld milliseconds " "(rank #%d, offset %lld).", server.cluster->failover_auth_time - mstime(), server.cluster->failover_auth_rank, replicationGetSlaveOffset()); /* Now that we have a scheduled election, broadcast our offset * to all the other slaves so that they'll updated their offsets * if our offset is better. */ clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES); return; } /* It is possible that we received more updated offsets from other * slaves for the same master since we computed our election delay. * Update the delay if our rank changed. * * Not performed if this is a manual failover. */ if (server.cluster->failover_auth_sent == 0 && server.cluster->mf_end == 0) { int newrank = clusterGetSlaveRank(); if (newrank > server.cluster->failover_auth_rank) { long long added_delay = (newrank - server.cluster->failover_auth_rank) * 1000; server.cluster->failover_auth_time += added_delay; server.cluster->failover_auth_rank = newrank; serverLog(LL_NOTICE, "Replica rank updated to #%d, added %lld milliseconds of delay.", newrank, added_delay); } } /* Return ASAP if we can't still start the election. */ if (mstime() < server.cluster->failover_auth_time) { clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_DELAY); return; } /* Return ASAP if the election is too old to be valid. */ if (auth_age > auth_timeout) { clusterLogCantFailover(CLUSTER_CANT_FAILOVER_EXPIRED); return; } /* Ask for votes if needed. */ if (server.cluster->failover_auth_sent == 0) { server.cluster->currentEpoch++; server.cluster->failover_auth_epoch = server.cluster->currentEpoch; serverLog(LL_NOTICE,"Starting a failover election for epoch %llu.", (unsigned long long) server.cluster->currentEpoch); clusterRequestFailoverAuth(); server.cluster->failover_auth_sent = 1; clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_FSYNC_CONFIG); return; /* Wait for replies. */ } /* Check if we reached the quorum. */ if (server.cluster->failover_auth_count >= needed_quorum) { /* We have the quorum, we can finally failover the master. */ serverLog(LL_NOTICE, "Failover election won: I'm the new master."); /* Update my configEpoch to the epoch of the election. */ if (myself->configEpoch < server.cluster->failover_auth_epoch) { myself->configEpoch = server.cluster->failover_auth_epoch; serverLog(LL_NOTICE, "configEpoch set to %llu after successful failover", (unsigned long long) myself->configEpoch); } /* Take responsibility for the cluster slots. */ clusterFailoverReplaceYourMaster(); } else { clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_VOTES); } } /* ----------------------------------------------------------------------------- * CLUSTER slave migration * * Slave migration is the process that allows a slave of a master that is * already covered by at least another slave, to "migrate" to a master that * is orphaned, that is, left with no working slaves. * ------------------------------------------------------------------------- */ /* This function is responsible to decide if this replica should be migrated * to a different (orphaned) master. It is called by the clusterCron() function * only if: * * 1) We are a slave node. * 2) It was detected that there is at least one orphaned master in * the cluster. * 3) We are a slave of one of the masters with the greatest number of * slaves. * * This checks are performed by the caller since it requires to iterate * the nodes anyway, so we spend time into clusterHandleSlaveMigration() * if definitely needed. * * The function is called with a pre-computed max_slaves, that is the max * number of working (not in FAIL state) slaves for a single master. * * Additional conditions for migration are examined inside the function. */ void clusterHandleSlaveMigration(int max_slaves) { int j, okslaves = 0; clusterNode *mymaster = myself->slaveof, *target = NULL, *candidate = NULL; dictIterator *di; dictEntry *de; /* Step 1: Don't migrate if the cluster state is not ok. */ if (server.cluster->state != CLUSTER_OK) return; /* Step 2: Don't migrate if my master will not be left with at least * 'migration-barrier' slaves after my migration. */ if (mymaster == NULL) return; for (j = 0; j < mymaster->numslaves; j++) if (!nodeFailed(mymaster->slaves[j]) && !nodeTimedOut(mymaster->slaves[j])) okslaves++; if (okslaves <= server.cluster_migration_barrier) return; /* Step 3: Identify a candidate for migration, and check if among the * masters with the greatest number of ok slaves, I'm the one with the * smallest node ID (the "candidate slave"). * * Note: this means that eventually a replica migration will occur * since slaves that are reachable again always have their FAIL flag * cleared, so eventually there must be a candidate. * There is a possible race condition causing multiple * slaves to migrate at the same time, but this is unlikely to * happen and relatively harmless when it does. */ candidate = myself; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); int okslaves = 0, is_orphaned = 1; /* We want to migrate only if this master is working, orphaned, and * used to have slaves or if failed over a master that had slaves * (MIGRATE_TO flag). This way we only migrate to instances that were * supposed to have replicas. */ if (nodeIsSlave(node) || nodeFailed(node)) is_orphaned = 0; if (!(node->flags & CLUSTER_NODE_MIGRATE_TO)) is_orphaned = 0; /* Check number of working slaves. */ if (clusterNodeIsMaster(node)) okslaves = clusterCountNonFailingSlaves(node); if (okslaves > 0) is_orphaned = 0; if (is_orphaned) { if (!target && node->numslots > 0) target = node; /* Track the starting time of the orphaned condition for this * master. */ if (!node->orphaned_time) node->orphaned_time = mstime(); } else { node->orphaned_time = 0; } /* Check if I'm the slave candidate for the migration: attached * to a master with the maximum number of slaves and with the smallest * node ID. */ if (okslaves == max_slaves) { for (j = 0; j < node->numslaves; j++) { if (memcmp(node->slaves[j]->name, candidate->name, CLUSTER_NAMELEN) < 0) { candidate = node->slaves[j]; } } } } dictReleaseIterator(di); /* Step 4: perform the migration if there is a target, and if I'm the * candidate, but only if the master is continuously orphaned for a * couple of seconds, so that during failovers, we give some time to * the natural slaves of this instance to advertise their switch from * the old master to the new one. */ if (target && candidate == myself && (mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY && !(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) { serverLog(LL_NOTICE,"Migrating to orphaned master %.40s", target->name); clusterSetMaster(target); } } /* ----------------------------------------------------------------------------- * CLUSTER manual failover * * This are the important steps performed by slaves during a manual failover: * 1) User send CLUSTER FAILOVER command. The failover state is initialized * setting mf_end to the millisecond unix time at which we'll abort the * attempt. * 2) Slave sends a MFSTART message to the master requesting to pause clients * for two times the manual failover timeout CLUSTER_MF_TIMEOUT. * When master is paused for manual failover, it also starts to flag * packets with CLUSTERMSG_FLAG0_PAUSED. * 3) Slave waits for master to send its replication offset flagged as PAUSED. * 4) If slave received the offset from the master, and its offset matches, * mf_can_start is set to 1, and clusterHandleSlaveFailover() will perform * the failover as usually, with the difference that the vote request * will be modified to force masters to vote for a slave that has a * working master. * * From the point of view of the master things are simpler: when a * PAUSE_CLIENTS packet is received the master sets mf_end as well and * the sender in mf_slave. During the time limit for the manual failover * the master will just send PINGs more often to this slave, flagged with * the PAUSED flag, so that the slave will set mf_master_offset when receiving * a packet from the master with this flag set. * * The goal of the manual failover is to perform a fast failover without * data loss due to the asynchronous master-slave replication. * -------------------------------------------------------------------------- */ /* Reset the manual failover state. This works for both masters and slaves * as all the state about manual failover is cleared. * * The function can be used both to initialize the manual failover state at * startup or to abort a manual failover in progress. */ void resetManualFailover(void) { if (server.cluster->mf_slave) { /* We were a master failing over, so we paused clients and related actions. * Regardless of the outcome we unpause now to allow traffic again. */ unpauseActions(PAUSE_DURING_FAILOVER); } server.cluster->mf_end = 0; /* No manual failover in progress. */ server.cluster->mf_can_start = 0; server.cluster->mf_slave = NULL; server.cluster->mf_master_offset = -1; } /* If a manual failover timed out, abort it. */ void manualFailoverCheckTimeout(void) { if (server.cluster->mf_end && server.cluster->mf_end < mstime()) { serverLog(LL_WARNING,"Manual failover timed out."); resetManualFailover(); } } /* This function is called from the cluster cron function in order to go * forward with a manual failover state machine. */ void clusterHandleManualFailover(void) { /* Return ASAP if no manual failover is in progress. */ if (server.cluster->mf_end == 0) return; /* If mf_can_start is non-zero, the failover was already triggered so the * next steps are performed by clusterHandleSlaveFailover(). */ if (server.cluster->mf_can_start) return; if (server.cluster->mf_master_offset == -1) return; /* Wait for offset... */ if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) { /* Our replication offset matches the master replication offset * announced after clients were paused. We can start the failover. */ server.cluster->mf_can_start = 1; serverLog(LL_NOTICE, "All master replication stream processed, " "manual failover can start."); clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER); return; } clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER); } /* ----------------------------------------------------------------------------- * CLUSTER cron job * -------------------------------------------------------------------------- */ /* Check if the node is disconnected and re-establish the connection. * Also update a few stats while we are here, that can be used to make * better decisions in other part of the code. */ static int clusterNodeCronHandleReconnect(clusterNode *node, mstime_t handshake_timeout, mstime_t now) { /* Not interested in reconnecting the link with myself or nodes * for which we have no address. */ if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR)) return 1; if (node->flags & CLUSTER_NODE_PFAIL) server.cluster->stats_pfail_nodes++; /* A Node in HANDSHAKE state has a limited lifespan equal to the * configured node timeout. */ if (nodeInHandshake(node) && now - node->ctime > handshake_timeout) { clusterDelNode(node); return 1; } if (node->link == NULL) { clusterLink *link = createClusterLink(node); link->conn = connCreate(server.el, connTypeOfCluster()); connSetPrivateData(link->conn, link); if (connConnect(link->conn, node->ip, node->cport, server.bind_source_addr, clusterLinkConnectHandler) == C_ERR) { /* We got a synchronous error from connect before * clusterSendPing() had a chance to be called. * If node->ping_sent is zero, failure detection can't work, * so we claim we actually sent a ping now (that will * be really sent as soon as the link is obtained). */ if (node->ping_sent == 0) node->ping_sent = mstime(); serverLog(LL_DEBUG, "Unable to connect to " "Cluster Node [%s]:%d -> %s", node->ip, node->cport, server.neterr); freeClusterLink(link); return 0; } } return 0; } static void freeClusterLinkOnBufferLimitReached(clusterLink *link) { if (link == NULL || server.cluster_link_msg_queue_limit_bytes == 0) { return; } unsigned long long mem_link = link->send_msg_queue_mem; if (mem_link > server.cluster_link_msg_queue_limit_bytes) { serverLog(LL_WARNING, "Freeing cluster link(%s node %.40s, used memory: %llu) due to " "exceeding send buffer memory limit.", link->inbound ? "from" : "to", link->node ? link->node->name : "", mem_link); freeClusterLink(link); server.cluster->stat_cluster_links_buffer_limit_exceeded++; } } /* Free outbound link to a node if its send buffer size exceeded limit. */ static void clusterNodeCronFreeLinkOnBufferLimitReached(clusterNode *node) { freeClusterLinkOnBufferLimitReached(node->link); freeClusterLinkOnBufferLimitReached(node->inbound_link); } /* This is executed 10 times every second */ void clusterCron(void) { dictIterator *di; dictEntry *de; int update_state = 0; int orphaned_masters; /* How many masters there are without ok slaves. */ int max_slaves; /* Max number of ok slaves for a single master. */ int this_slaves; /* Number of ok slaves for our master (if we are slave). */ mstime_t min_pong = 0, now = mstime(); clusterNode *min_pong_node = NULL; static unsigned long long iteration = 0; mstime_t handshake_timeout; iteration++; /* Number of times this function was called so far. */ clusterUpdateMyselfHostname(); /* The handshake timeout is the time after which a handshake node that was * not turned into a normal node is removed from the nodes. Usually it is * just the NODE_TIMEOUT value, but when NODE_TIMEOUT is too small we use * the value of 1 second. */ handshake_timeout = server.cluster_node_timeout; if (handshake_timeout < 1000) handshake_timeout = 1000; /* Clear so clusterNodeCronHandleReconnect can count the number of nodes in PFAIL. */ server.cluster->stats_pfail_nodes = 0; /* Run through some of the operations we want to do on each cluster node. */ di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); /* We free the inbound or outboud link to the node if the link has an * oversized message send queue and immediately try reconnecting. */ clusterNodeCronFreeLinkOnBufferLimitReached(node); /* The protocol is that function(s) below return non-zero if the node was * terminated. */ if(clusterNodeCronHandleReconnect(node, handshake_timeout, now)) continue; } dictReleaseIterator(di); /* Ping some random node 1 time every 10 iterations, so that we usually ping * one random node every second. */ if (!(iteration % 10)) { int j; /* Check a few random nodes and ping the one with the oldest * pong_received time. */ for (j = 0; j < 5; j++) { de = dictGetRandomKey(server.cluster->nodes); clusterNode *this = dictGetVal(de); /* Don't ping nodes disconnected or with a ping currently active. */ if (this->link == NULL || this->ping_sent != 0) continue; if (this->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) continue; if (min_pong_node == NULL || min_pong > this->pong_received) { min_pong_node = this; min_pong = this->pong_received; } } if (min_pong_node) { serverLog(LL_DEBUG,"Pinging node %.40s", min_pong_node->name); clusterSendPing(min_pong_node->link, CLUSTERMSG_TYPE_PING); } } /* Iterate nodes to check if we need to flag something as failing. * This loop is also responsible to: * 1) Check if there are orphaned masters (masters without non failing * slaves). * 2) Count the max number of non failing slaves for a single master. * 3) Count the number of slaves for our master, if we are a slave. */ orphaned_masters = 0; max_slaves = 0; this_slaves = 0; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); now = mstime(); /* Use an updated time at every iteration. */ if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) continue; /* Orphaned master check, useful only if the current instance * is a slave that may migrate to another master. */ if (nodeIsSlave(myself) && clusterNodeIsMaster(node) && !nodeFailed(node)) { int okslaves = clusterCountNonFailingSlaves(node); /* A master is orphaned if it is serving a non-zero number of * slots, have no working slaves, but used to have at least one * slave, or failed over a master that used to have slaves. */ if (okslaves == 0 && node->numslots > 0 && node->flags & CLUSTER_NODE_MIGRATE_TO) { orphaned_masters++; } if (okslaves > max_slaves) max_slaves = okslaves; if (myself->slaveof == node) this_slaves = okslaves; } /* If we are not receiving any data for more than half the cluster * timeout, reconnect the link: maybe there is a connection * issue even if the node is alive. */ mstime_t ping_delay = now - node->ping_sent; mstime_t data_delay = now - node->data_received; if (node->link && /* is connected */ now - node->link->ctime > server.cluster_node_timeout && /* was not already reconnected */ node->ping_sent && /* we already sent a ping */ /* and we are waiting for the pong more than timeout/2 */ ping_delay > server.cluster_node_timeout/2 && /* and in such interval we are not seeing any traffic at all. */ data_delay > server.cluster_node_timeout/2) { /* Disconnect the link, it will be reconnected automatically. */ freeClusterLink(node->link); } /* If we have currently no active ping in this instance, and the * received PONG is older than half the cluster timeout, send * a new ping now, to ensure all the nodes are pinged without * a too big delay. */ mstime_t ping_interval = server.cluster_ping_interval ? server.cluster_ping_interval : server.cluster_node_timeout/2; if (node->link && node->ping_sent == 0 && (now - node->pong_received) > ping_interval) { clusterSendPing(node->link, CLUSTERMSG_TYPE_PING); continue; } /* If we are a master and one of the slaves requested a manual * failover, ping it continuously. */ if (server.cluster->mf_end && clusterNodeIsMaster(myself) && server.cluster->mf_slave == node && node->link) { clusterSendPing(node->link, CLUSTERMSG_TYPE_PING); continue; } /* Check only if we have an active ping for this instance. */ if (node->ping_sent == 0) continue; /* Check if this node looks unreachable. * Note that if we already received the PONG, then node->ping_sent * is zero, so can't reach this code at all, so we don't risk of * checking for a PONG delay if we didn't sent the PING. * * We also consider every incoming data as proof of liveness, since * our cluster bus link is also used for data: under heavy data * load pong delays are possible. */ mstime_t node_delay = (ping_delay < data_delay) ? ping_delay : data_delay; if (node_delay > server.cluster_node_timeout) { /* Timeout reached. Set the node as possibly failing if it is * not already in this state. */ if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) { node->flags |= CLUSTER_NODE_PFAIL; update_state = 1; if (clusterNodeIsMaster(myself) && server.cluster->size == 1) { markNodeAsFailingIfNeeded(node); } else { serverLog(LL_DEBUG,"*** NODE %.40s possibly failing", node->name); } } } } dictReleaseIterator(di); /* If we are a slave node but the replication is still turned off, * enable it if we know the address of our master and it appears to * be up. */ if (nodeIsSlave(myself) && server.masterhost == NULL && myself->slaveof && nodeHasAddr(myself->slaveof)) { replicationSetMaster(myself->slaveof->ip, getNodeDefaultReplicationPort(myself->slaveof)); } /* Abort a manual failover if the timeout is reached. */ manualFailoverCheckTimeout(); if (nodeIsSlave(myself)) { clusterHandleManualFailover(); if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) clusterHandleSlaveFailover(); /* If there are orphaned slaves, and we are a slave among the masters * with the max number of non-failing slaves, consider migrating to * the orphaned masters. Note that it does not make sense to try * a migration if there is no master with at least *two* working * slaves. */ if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves && server.cluster_allow_replica_migration) clusterHandleSlaveMigration(max_slaves); } if (update_state || server.cluster->state == CLUSTER_FAIL) clusterUpdateState(); } /* This function is called before the event handler returns to sleep for * events. It is useful to perform operations that must be done ASAP in * reaction to events fired but that are not safe to perform inside event * handlers, or to perform potentially expansive tasks that we need to do * a single time before replying to clients. */ void clusterBeforeSleep(void) { int flags = server.cluster->todo_before_sleep; /* Reset our flags (not strictly needed since every single function * called for flags set should be able to clear its flag). */ server.cluster->todo_before_sleep = 0; if (flags & CLUSTER_TODO_HANDLE_MANUALFAILOVER) { /* Handle manual failover as soon as possible so that won't have a 100ms * as it was handled only in clusterCron */ if(nodeIsSlave(myself)) { clusterHandleManualFailover(); if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) clusterHandleSlaveFailover(); } } else if (flags & CLUSTER_TODO_HANDLE_FAILOVER) { /* Handle failover, this is needed when it is likely that there is already * the quorum from masters in order to react fast. */ clusterHandleSlaveFailover(); } /* Update the cluster state. */ if (flags & CLUSTER_TODO_UPDATE_STATE) clusterUpdateState(); /* Save the config, possibly using fsync. */ if (flags & CLUSTER_TODO_SAVE_CONFIG) { int fsync = flags & CLUSTER_TODO_FSYNC_CONFIG; clusterSaveConfigOrDie(fsync); } } void clusterDoBeforeSleep(int flags) { server.cluster->todo_before_sleep |= flags; } /* ----------------------------------------------------------------------------- * Slots management * -------------------------------------------------------------------------- */ /* Test bit 'pos' in a generic bitmap. Return 1 if the bit is set, * otherwise 0. */ int bitmapTestBit(unsigned char *bitmap, int pos) { off_t byte = pos/8; int bit = pos&7; return (bitmap[byte] & (1<nodes); dictEntry *de; int slaves = 0; while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (nodeIsSlave(node)) continue; slaves += node->numslaves; } dictReleaseIterator(di); return slaves != 0; } /* Set the slot bit and return the old value. */ int clusterNodeSetSlotBit(clusterNode *n, int slot) { int old = bitmapTestBit(n->slots,slot); if (!old) { bitmapSetBit(n->slots,slot); n->numslots++; /* When a master gets its first slot, even if it has no slaves, * it gets flagged with MIGRATE_TO, that is, the master is a valid * target for replicas migration, if and only if at least one of * the other masters has slaves right now. * * Normally masters are valid targets of replica migration if: * 1. The used to have slaves (but no longer have). * 2. They are slaves failing over a master that used to have slaves. * * However new masters with slots assigned are considered valid * migration targets if the rest of the cluster is not a slave-less. * * See https://github.com/redis/redis/issues/3043 for more info. */ if (n->numslots == 1 && clusterMastersHaveSlaves()) n->flags |= CLUSTER_NODE_MIGRATE_TO; } return old; } /* Clear the slot bit and return the old value. */ int clusterNodeClearSlotBit(clusterNode *n, int slot) { int old = bitmapTestBit(n->slots,slot); if (old) { bitmapClearBit(n->slots,slot); n->numslots--; } return old; } /* Return the slot bit from the cluster node structure. */ int clusterNodeCoversSlot(clusterNode *n, int slot) { return bitmapTestBit(n->slots,slot); } /* Add the specified slot to the list of slots that node 'n' will * serve. Return C_OK if the operation ended with success. * If the slot is already assigned to another instance this is considered * an error and C_ERR is returned. */ int clusterAddSlot(clusterNode *n, int slot) { if (server.cluster->slots[slot]) return C_ERR; clusterNodeSetSlotBit(n,slot); server.cluster->slots[slot] = n; return C_OK; } /* Delete the specified slot marking it as unassigned. * Returns C_OK if the slot was assigned, otherwise if the slot was * already unassigned C_ERR is returned. */ int clusterDelSlot(int slot) { clusterNode *n = server.cluster->slots[slot]; if (!n) return C_ERR; /* Cleanup the channels in master/replica as part of slot deletion. */ removeChannelsInSlot(slot); /* Clear the slot bit. */ serverAssert(clusterNodeClearSlotBit(n,slot) == 1); server.cluster->slots[slot] = NULL; /* Make owner_not_claiming_slot flag consistent with slot ownership information. */ bitmapClearBit(server.cluster->owner_not_claiming_slot, slot); return C_OK; } /* Transfer slots from `from_node` to `to_node`. * Iterates over all cluster slots, transferring each slot covered by `from_node` to `to_node`. * Counts and returns the number of slots transferred. */ int clusterMoveNodeSlots(clusterNode *from_node, clusterNode *to_node) { int processed = 0; for (int j = 0; j < CLUSTER_SLOTS; j++) { if (clusterNodeCoversSlot(from_node, j)) { clusterDelSlot(j); clusterAddSlot(to_node, j); processed++; } } return processed; } /* Delete all the slots associated with the specified node. * The number of deleted slots is returned. */ int clusterDelNodeSlots(clusterNode *node) { int deleted = 0, j; for (j = 0; j < CLUSTER_SLOTS; j++) { if (clusterNodeCoversSlot(node, j)) { clusterDelSlot(j); deleted++; } } return deleted; } /* Clear the migrating / importing state for all the slots. * This is useful at initialization and when turning a master into slave. */ void clusterCloseAllSlots(void) { memset(server.cluster->migrating_slots_to,0, sizeof(server.cluster->migrating_slots_to)); memset(server.cluster->importing_slots_from,0, sizeof(server.cluster->importing_slots_from)); } /* ----------------------------------------------------------------------------- * Cluster state evaluation function * -------------------------------------------------------------------------- */ /* The following are defines that are only used in the evaluation function * and are based on heuristics. Actually the main point about the rejoin and * writable delay is that they should be a few orders of magnitude larger * than the network latency. */ #define CLUSTER_MAX_REJOIN_DELAY 5000 #define CLUSTER_MIN_REJOIN_DELAY 500 #define CLUSTER_WRITABLE_DELAY 2000 void clusterUpdateState(void) { int j, new_state; int reachable_masters = 0; static mstime_t among_minority_time; static mstime_t first_call_time = 0; server.cluster->todo_before_sleep &= ~CLUSTER_TODO_UPDATE_STATE; /* If this is a master node, wait some time before turning the state * into OK, since it is not a good idea to rejoin the cluster as a writable * master, after a reboot, without giving the cluster a chance to * reconfigure this node. Note that the delay is calculated starting from * the first call to this function and not since the server start, in order * to not count the DB loading time. */ if (first_call_time == 0) first_call_time = mstime(); if (clusterNodeIsMaster(myself) && server.cluster->state == CLUSTER_FAIL && mstime() - first_call_time < CLUSTER_WRITABLE_DELAY) return; /* Start assuming the state is OK. We'll turn it into FAIL if there * are the right conditions. */ new_state = CLUSTER_OK; /* Check if all the slots are covered. */ if (server.cluster_require_full_coverage) { for (j = 0; j < CLUSTER_SLOTS; j++) { if (server.cluster->slots[j] == NULL || server.cluster->slots[j]->flags & (CLUSTER_NODE_FAIL)) { new_state = CLUSTER_FAIL; break; } } } /* Compute the cluster size, that is the number of master nodes * serving at least a single slot. * * At the same time count the number of reachable masters having * at least one slot. */ { dictIterator *di; dictEntry *de; server.cluster->size = 0; di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (clusterNodeIsMaster(node) && node->numslots) { server.cluster->size++; if ((node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) == 0) reachable_masters++; } } dictReleaseIterator(di); } /* If we are in a minority partition, change the cluster state * to FAIL. */ { int needed_quorum = (server.cluster->size / 2) + 1; if (reachable_masters < needed_quorum) { new_state = CLUSTER_FAIL; among_minority_time = mstime(); } } /* Log a state change */ if (new_state != server.cluster->state) { mstime_t rejoin_delay = server.cluster_node_timeout; /* If the instance is a master and was partitioned away with the * minority, don't let it accept queries for some time after the * partition heals, to make sure there is enough time to receive * a configuration update. */ if (rejoin_delay > CLUSTER_MAX_REJOIN_DELAY) rejoin_delay = CLUSTER_MAX_REJOIN_DELAY; if (rejoin_delay < CLUSTER_MIN_REJOIN_DELAY) rejoin_delay = CLUSTER_MIN_REJOIN_DELAY; if (new_state == CLUSTER_OK && clusterNodeIsMaster(myself) && mstime() - among_minority_time < rejoin_delay) { return; } /* Change the state and log the event. */ serverLog(new_state == CLUSTER_OK ? LL_NOTICE : LL_WARNING, "Cluster state changed: %s", new_state == CLUSTER_OK ? "ok" : "fail"); server.cluster->state = new_state; } } /* 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) { int j; int update_config = 0; /* 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 (nodeIsSlave(myself)) return C_OK; /* Make sure we only have keys in DB0. */ for (j = 1; j < server.dbnum; j++) { if (kvstoreSize(server.db[j].keys)) return C_ERR; } /* Check that all the slots we see populated memory have a corresponding * entry in the cluster table. Otherwise fix the table. */ for (j = 0; j < CLUSTER_SLOTS; j++) { if (!countKeysInSlot(j)) continue; /* No keys in this slot. */ /* Check if we are assigned to this slot or if we are importing it. * In both cases check the next slot as the configuration makes * sense. */ if (server.cluster->slots[j] == myself || server.cluster->importing_slots_from[j] != NULL) continue; /* If we are here data and cluster config don't agree, and we have * slot 'j' populated even if we are not importing it, nor we are * assigned to this slot. Fix this condition. */ update_config++; /* Case A: slot is unassigned. Take responsibility for it. */ if (server.cluster->slots[j] == NULL) { serverLog(LL_NOTICE, "I have keys for unassigned slot %d. " "Taking responsibility for it.",j); clusterAddSlot(myself,j); } else { serverLog(LL_NOTICE, "I have keys for slot %d, but the slot is " "assigned to another node. " "Setting it to importing state.",j); server.cluster->importing_slots_from[j] = server.cluster->slots[j]; } } if (update_config) clusterSaveConfigOrDie(1); return C_OK; } /* Remove all the shard channel related information not owned by the current shard. */ static inline void removeAllNotOwnedShardChannelSubscriptions(void) { if (!kvstoreSize(server.pubsubshard_channels)) return; clusterNode *currmaster = clusterNodeIsMaster(myself) ? myself : myself->slaveof; for (int j = 0; j < CLUSTER_SLOTS; j++) { if (server.cluster->slots[j] != currmaster) { removeChannelsInSlot(j); } } } /* ----------------------------------------------------------------------------- * SLAVE nodes handling * -------------------------------------------------------------------------- */ /* Set the specified node 'n' as master for this node. * If this node is currently a master, it is turned into a slave. */ void clusterSetMaster(clusterNode *n) { serverAssert(n != myself); serverAssert(myself->numslots == 0); if (clusterNodeIsMaster(myself)) { myself->flags &= ~(CLUSTER_NODE_MASTER|CLUSTER_NODE_MIGRATE_TO); myself->flags |= CLUSTER_NODE_SLAVE; clusterCloseAllSlots(); } else { if (myself->slaveof) clusterNodeRemoveSlave(myself->slaveof,myself); } myself->slaveof = n; updateShardId(myself, n->shard_id); clusterNodeAddSlave(n,myself); replicationSetMaster(n->ip, getNodeDefaultReplicationPort(n)); removeAllNotOwnedShardChannelSubscriptions(); resetManualFailover(); } /* ----------------------------------------------------------------------------- * Nodes to string representation functions. * -------------------------------------------------------------------------- */ struct redisNodeFlags { uint16_t flag; char *name; }; static struct redisNodeFlags redisNodeFlagsTable[] = { {CLUSTER_NODE_MYSELF, "myself,"}, {CLUSTER_NODE_MASTER, "master,"}, {CLUSTER_NODE_SLAVE, "slave,"}, {CLUSTER_NODE_PFAIL, "fail?,"}, {CLUSTER_NODE_FAIL, "fail,"}, {CLUSTER_NODE_HANDSHAKE, "handshake,"}, {CLUSTER_NODE_NOADDR, "noaddr,"}, {CLUSTER_NODE_NOFAILOVER, "nofailover,"} }; /* Concatenate the comma separated list of node flags to the given SDS * string 'ci'. */ sds representClusterNodeFlags(sds ci, uint16_t flags) { size_t orig_len = sdslen(ci); int i, size = sizeof(redisNodeFlagsTable)/sizeof(struct redisNodeFlags); for (i = 0; i < size; i++) { struct redisNodeFlags *nodeflag = redisNodeFlagsTable + i; if (flags & nodeflag->flag) ci = sdscat(ci, nodeflag->name); } /* If no flag was added, add the "noflags" special flag. */ if (sdslen(ci) == orig_len) ci = sdscat(ci,"noflags,"); sdsIncrLen(ci,-1); /* Remove trailing comma. */ return ci; } /* Concatenate the slot ownership information to the given SDS string 'ci'. * If the slot ownership is in a contiguous block, it's represented as start-end pair, * else each slot is added separately. */ sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_count) { for (int i = 0; i< slot_info_pairs_count; i+=2) { unsigned long start = slot_info_pairs[i]; unsigned long end = slot_info_pairs[i+1]; if (start == end) { ci = sdscatfmt(ci, " %i", start); } else { ci = sdscatfmt(ci, " %i-%i", start, end); } } return ci; } /* Generate a csv-alike representation of the specified cluster node. * See clusterGenNodesDescription() top comment for more information. * * The function returns the string representation as an SDS string. */ sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary) { int j, start; sds ci; int port = clusterNodeClientPort(node, tls_primary); /* Node coordinates */ ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN); ci = sdscatfmt(ci," %s:%i@%i", node->ip, port, node->cport); if (sdslen(node->hostname) != 0) { ci = sdscatfmt(ci,",%s", node->hostname); } /* Don't expose aux fields to any clients yet but do allow them * to be persisted to nodes.conf */ if (c == NULL) { if (sdslen(node->hostname) == 0) { ci = sdscatfmt(ci,",", 1); } for (int i = af_count-1; i >=0; i--) { if ((tls_primary && i == af_tls_port) || (!tls_primary && i == af_tcp_port)) { continue; } if (auxFieldHandlers[i].isPresent(node)) { ci = sdscatprintf(ci, ",%s=", auxFieldHandlers[i].field); ci = auxFieldHandlers[i].getter(node, ci); } } } /* Flags */ ci = sdscatlen(ci," ",1); ci = representClusterNodeFlags(ci, node->flags); /* Slave of... or just "-" */ ci = sdscatlen(ci," ",1); if (node->slaveof) ci = sdscatlen(ci,node->slaveof->name,CLUSTER_NAMELEN); else ci = sdscatlen(ci,"-",1); unsigned long long nodeEpoch = node->configEpoch; if (nodeIsSlave(node) && node->slaveof) { nodeEpoch = node->slaveof->configEpoch; } /* Latency from the POV of this node, config epoch, link status */ ci = sdscatfmt(ci," %I %I %U %s", (long long) node->ping_sent, (long long) node->pong_received, nodeEpoch, (node->link || node->flags & CLUSTER_NODE_MYSELF) ? "connected" : "disconnected"); /* Slots served by this instance. If we already have slots info, * append it directly, otherwise, generate slots only if it has. */ if (node->slot_info_pairs) { ci = representSlotInfo(ci, node->slot_info_pairs, node->slot_info_pairs_count); } else if (node->numslots > 0) { start = -1; for (j = 0; j < CLUSTER_SLOTS; j++) { int bit; if ((bit = clusterNodeCoversSlot(node, j)) != 0) { if (start == -1) start = j; } if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) { if (bit && j == CLUSTER_SLOTS-1) j++; if (start == j-1) { ci = sdscatfmt(ci," %i",start); } else { ci = sdscatfmt(ci," %i-%i",start,j-1); } start = -1; } } } /* Just for MYSELF node we also dump info about slots that * we are migrating to other instances or importing from other * instances. */ if (node->flags & CLUSTER_NODE_MYSELF) { for (j = 0; j < CLUSTER_SLOTS; j++) { if (server.cluster->migrating_slots_to[j]) { ci = sdscatprintf(ci," [%d->-%.40s]",j, server.cluster->migrating_slots_to[j]->name); } else if (server.cluster->importing_slots_from[j]) { ci = sdscatprintf(ci," [%d-<-%.40s]",j, server.cluster->importing_slots_from[j]->name); } } } return ci; } /* Generate the slot topology for all nodes and store the string representation * in the slots_info struct on the node. This is used to improve the efficiency * of clusterGenNodesDescription() because it removes looping of the slot space * for generating the slot info for each node individually. */ void clusterGenNodesSlotsInfo(int filter) { clusterNode *n = NULL; int start = -1; for (int i = 0; i <= CLUSTER_SLOTS; i++) { /* Find start node and slot id. */ if (n == NULL) { if (i == CLUSTER_SLOTS) break; n = server.cluster->slots[i]; start = i; continue; } /* Generate slots info when occur different node with start * or end of slot. */ if (i == CLUSTER_SLOTS || n != server.cluster->slots[i]) { if (!(n->flags & filter)) { if (!n->slot_info_pairs) { n->slot_info_pairs = zmalloc(2 * n->numslots * sizeof(uint16_t)); } serverAssert((n->slot_info_pairs_count + 1) < (2 * n->numslots)); n->slot_info_pairs[n->slot_info_pairs_count++] = start; n->slot_info_pairs[n->slot_info_pairs_count++] = i-1; } if (i == CLUSTER_SLOTS) break; n = server.cluster->slots[i]; start = i; } } } void clusterFreeNodesSlotsInfo(clusterNode *n) { zfree(n->slot_info_pairs); n->slot_info_pairs = NULL; n->slot_info_pairs_count = 0; } /* Generate a csv-alike representation of the nodes we are aware of, * including the "myself" node, and return an SDS string containing the * representation (it is up to the caller to free it). * * All the nodes matching at least one of the node flags specified in * "filter" are excluded from the output, so using zero as a filter will * include all the known nodes in the representation, including nodes in * the HANDSHAKE state. * * Setting tls_primary to 1 to put TLS port in the main : * field and put TCP port in aux field, instead of the opposite way. * * The representation obtained using this function is used for the output * of the CLUSTER NODES function, and as format for the cluster * configuration file (nodes.conf) for a given node. */ sds clusterGenNodesDescription(client *c, int filter, int tls_primary) { sds ci = sdsempty(), ni; dictIterator *di; dictEntry *de; /* Generate all nodes slots info firstly. */ clusterGenNodesSlotsInfo(filter); di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node->flags & filter) continue; ni = clusterGenNodeDescription(c, node, tls_primary); ci = sdscatsds(ci,ni); sdsfree(ni); ci = sdscatlen(ci,"\n",1); /* Release slots info. */ clusterFreeNodesSlotsInfo(node); } dictReleaseIterator(di); return ci; } /* Add to the output buffer of the given client the description of the given cluster link. * The description is a map with each entry being an attribute of the link. */ void addReplyClusterLinkDescription(client *c, clusterLink *link) { addReplyMapLen(c, 6); addReplyBulkCString(c, "direction"); addReplyBulkCString(c, link->inbound ? "from" : "to"); /* addReplyClusterLinkDescription is only called for links that have been * associated with nodes. The association is always bi-directional, so * in addReplyClusterLinkDescription, link->node should never be NULL. */ serverAssert(link->node); sds node_name = sdsnewlen(link->node->name, CLUSTER_NAMELEN); addReplyBulkCString(c, "node"); addReplyBulkCString(c, node_name); sdsfree(node_name); addReplyBulkCString(c, "create-time"); addReplyLongLong(c, link->ctime); char events[3], *p; p = events; if (link->conn) { if (connHasReadHandler(link->conn)) *p++ = 'r'; if (connHasWriteHandler(link->conn)) *p++ = 'w'; } *p = '\0'; addReplyBulkCString(c, "events"); addReplyBulkCString(c, events); addReplyBulkCString(c, "send-buffer-allocated"); addReplyLongLong(c, link->send_msg_queue_mem); addReplyBulkCString(c, "send-buffer-used"); addReplyLongLong(c, link->send_msg_queue_mem); } /* Add to the output buffer of the given client an array of cluster link descriptions, * with array entry being a description of a single current cluster link. */ void addReplyClusterLinksDescription(client *c) { dictIterator *di; dictEntry *de; void *arraylen_ptr = NULL; int num_links = 0; arraylen_ptr = addReplyDeferredLen(c); di = dictGetSafeIterator(server.cluster->nodes); while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node->link) { num_links++; addReplyClusterLinkDescription(c, node->link); } if (node->inbound_link) { num_links++; addReplyClusterLinkDescription(c, node->inbound_link); } } dictReleaseIterator(di); setDeferredArrayLen(c, arraylen_ptr, num_links); } /* ----------------------------------------------------------------------------- * CLUSTER command * -------------------------------------------------------------------------- */ const char *clusterGetMessageTypeString(int type) { switch(type) { case CLUSTERMSG_TYPE_PING: return "ping"; case CLUSTERMSG_TYPE_PONG: return "pong"; case CLUSTERMSG_TYPE_MEET: return "meet"; case CLUSTERMSG_TYPE_FAIL: return "fail"; case CLUSTERMSG_TYPE_PUBLISH: return "publish"; case CLUSTERMSG_TYPE_PUBLISHSHARD: return "publishshard"; case CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST: return "auth-req"; case CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK: return "auth-ack"; case CLUSTERMSG_TYPE_UPDATE: return "update"; case CLUSTERMSG_TYPE_MFSTART: return "mfstart"; case CLUSTERMSG_TYPE_MODULE: return "module"; } return "unknown"; } int getSlotOrReply(client *c, robj *o) { long long slot; if (getLongLongFromObject(o,&slot) != C_OK || slot < 0 || slot >= CLUSTER_SLOTS) { addReplyError(c,"Invalid or out of range slot"); return -1; } return (int) slot; } int checkSlotAssignmentsOrReply(client *c, unsigned char *slots, int del, int start_slot, int end_slot) { int slot; for (slot = start_slot; slot <= end_slot; slot++) { if (del && server.cluster->slots[slot] == NULL) { addReplyErrorFormat(c,"Slot %d is already unassigned", slot); return C_ERR; } else if (!del && server.cluster->slots[slot]) { addReplyErrorFormat(c,"Slot %d is already busy", slot); return C_ERR; } if (slots[slot]++ == 1) { addReplyErrorFormat(c,"Slot %d specified multiple times",(int)slot); return C_ERR; } } return C_OK; } void clusterUpdateSlots(client *c, unsigned char *slots, int del) { int j; for (j = 0; j < CLUSTER_SLOTS; j++) { if (slots[j]) { int retval; /* If this slot was set as importing we can clear this * state as now we are the real owner of the slot. */ if (server.cluster->importing_slots_from[j]) server.cluster->importing_slots_from[j] = NULL; retval = del ? clusterDelSlot(j) : clusterAddSlot(myself,j); serverAssertWithInfo(c,NULL,retval == C_OK); } } } int clusterGetShardCount(void) { return dictSize(server.cluster->shards); } void *clusterGetShardIterator(void) { return dictGetSafeIterator(server.cluster->shards); } void *clusterNextShardHandle(void *shard_iterator) { dictEntry *de = dictNext(shard_iterator); if(de == NULL) return NULL; return dictGetVal(de); } void clusterFreeShardIterator(void *shard_iterator) { dictReleaseIterator(shard_iterator); } int clusterNodeHasSlotInfo(clusterNode *n) { return n->slot_info_pairs != NULL; } int clusterNodeSlotInfoCount(clusterNode *n) { return n->slot_info_pairs_count; } uint16_t clusterNodeSlotInfoEntry(clusterNode *n, int idx) { return n->slot_info_pairs[idx]; } int clusterGetShardNodeCount(void *shard) { return listLength((list*)shard); } void *clusterShardHandleGetNodeIterator(void *shard) { listIter *li = zmalloc(sizeof(listIter)); listRewind((list*)shard, li); return li; } void clusterShardNodeIteratorFree(void *node_iterator) { zfree(node_iterator); } clusterNode *clusterShardNodeIteratorNext(void *node_iterator) { listNode *item = listNext((listIter*)node_iterator); if (item == NULL) return NULL; return listNodeValue(item); } clusterNode *clusterShardNodeFirst(void *shard) { listNode *item = listFirst((list*)shard); if (item == NULL) return NULL; return listNodeValue(item); } int clusterNodeTcpPort(clusterNode *node) { return node->tcp_port; } int clusterNodeTlsPort(clusterNode *node) { return node->tls_port; } sds genClusterInfoString(void) { sds info = sdsempty(); char *statestr[] = {"ok","fail"}; int slots_assigned = 0, slots_ok = 0, slots_pfail = 0, slots_fail = 0; uint64_t myepoch; int j; for (j = 0; j < CLUSTER_SLOTS; j++) { clusterNode *n = server.cluster->slots[j]; if (n == NULL) continue; slots_assigned++; if (nodeFailed(n)) { slots_fail++; } else if (nodeTimedOut(n)) { slots_pfail++; } else { slots_ok++; } } myepoch = (nodeIsSlave(myself) && myself->slaveof) ? myself->slaveof->configEpoch : myself->configEpoch; info = sdscatprintf(info, "cluster_state:%s\r\n" "cluster_slots_assigned:%d\r\n" "cluster_slots_ok:%d\r\n" "cluster_slots_pfail:%d\r\n" "cluster_slots_fail:%d\r\n" "cluster_known_nodes:%lu\r\n" "cluster_size:%d\r\n" "cluster_current_epoch:%llu\r\n" "cluster_my_epoch:%llu\r\n" , statestr[server.cluster->state], slots_assigned, slots_ok, slots_pfail, slots_fail, dictSize(server.cluster->nodes), server.cluster->size, (unsigned long long) server.cluster->currentEpoch, (unsigned long long) myepoch ); /* Show stats about messages sent and received. */ long long tot_msg_sent = 0; long long tot_msg_received = 0; for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) { if (server.cluster->stats_bus_messages_sent[i] == 0) continue; tot_msg_sent += server.cluster->stats_bus_messages_sent[i]; info = sdscatprintf(info, "cluster_stats_messages_%s_sent:%lld\r\n", clusterGetMessageTypeString(i), server.cluster->stats_bus_messages_sent[i]); } info = sdscatprintf(info, "cluster_stats_messages_sent:%lld\r\n", tot_msg_sent); for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) { if (server.cluster->stats_bus_messages_received[i] == 0) continue; tot_msg_received += server.cluster->stats_bus_messages_received[i]; info = sdscatprintf(info, "cluster_stats_messages_%s_received:%lld\r\n", clusterGetMessageTypeString(i), server.cluster->stats_bus_messages_received[i]); } info = sdscatprintf(info, "cluster_stats_messages_received:%lld\r\n", tot_msg_received); info = sdscatprintf(info, "total_cluster_links_buffer_limit_exceeded:%llu\r\n", server.cluster->stat_cluster_links_buffer_limit_exceeded); return info; } void removeChannelsInSlot(unsigned int slot) { if (countChannelsInSlot(slot) == 0) return; pubsubShardUnsubscribeAllChannelsInSlot(slot); } /* Remove all the keys in the specified hash slot. * The number of removed items is returned. */ unsigned int delKeysInSlot(unsigned int hashslot) { if (!kvstoreDictSize(server.db->keys, hashslot)) return 0; unsigned int j = 0; kvstoreDictIterator *kvs_di = NULL; dictEntry *de = NULL; kvs_di = kvstoreGetDictSafeIterator(server.db->keys, hashslot); while((de = kvstoreDictIteratorNext(kvs_di)) != NULL) { enterExecutionUnit(1, 0); sds sdskey = dictGetKey(de); robj *key = createStringObject(sdskey, sdslen(sdskey)); dbDelete(&server.db[0], key); propagateDeletion(&server.db[0], key, server.lazyfree_lazy_server_del); signalModifiedKey(NULL, &server.db[0], key); /* The keys are not actually logically deleted from the database, 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); exitExecutionUnit(); postExecutionUnitOperations(); decrRefCount(key); j++; server.dirty++; } kvstoreReleaseDictIterator(kvs_di); return j; } /* Get the count of the channels for a given slot. */ unsigned int countChannelsInSlot(unsigned int hashslot) { return kvstoreDictSize(server.pubsubshard_channels, hashslot); } int clusterNodeIsMyself(clusterNode *n) { return n == server.cluster->myself; } clusterNode *getMyClusterNode(void) { return server.cluster->myself; } int clusterManualFailoverTimeLimit(void) { return server.cluster->mf_end; } int getClusterSize(void) { return dictSize(server.cluster->nodes); } int getMyShardSlotCount(void) { if (!nodeIsSlave(server.cluster->myself)) { return server.cluster->myself->numslots; } else if (server.cluster->myself->slaveof) { return server.cluster->myself->slaveof->numslots; } else { return 0; } } char **getClusterNodesList(size_t *numnodes) { size_t count = dictSize(server.cluster->nodes); char **ids = zmalloc((count+1)*CLUSTER_NAMELEN); dictIterator *di = dictGetIterator(server.cluster->nodes); dictEntry *de; int j = 0; while((de = dictNext(di)) != NULL) { clusterNode *node = dictGetVal(de); if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) continue; ids[j] = zmalloc(CLUSTER_NAMELEN); memcpy(ids[j],node->name,CLUSTER_NAMELEN); j++; } *numnodes = j; ids[j] = NULL; /* Null term so that FreeClusterNodesList does not need * to also get the count argument. */ dictReleaseIterator(di); return ids; } int clusterNodeIsMaster(clusterNode *n) { return n->flags & CLUSTER_NODE_MASTER; } int handleDebugClusterCommand(client *c) { if (strcasecmp(c->argv[1]->ptr, "CLUSTERLINK") || strcasecmp(c->argv[2]->ptr, "KILL") || c->argc != 5) { return 0; } if (!server.cluster_enabled) { addReplyError(c, "Debug option only available for cluster mode enabled setup!"); return 1; } /* Find the node. */ clusterNode *n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); if (!n) { addReplyErrorFormat(c, "Unknown node %s", (char *) c->argv[4]->ptr); return 1; } /* Terminate the link based on the direction or all. */ if (!strcasecmp(c->argv[3]->ptr, "from")) { if (n->inbound_link) freeClusterLink(n->inbound_link); } else if (!strcasecmp(c->argv[3]->ptr, "to")) { if (n->link) freeClusterLink(n->link); } else if (!strcasecmp(c->argv[3]->ptr, "all")) { if (n->link) freeClusterLink(n->link); if (n->inbound_link) freeClusterLink(n->inbound_link); } else { addReplyErrorFormat(c, "Unknown direction %s", (char *) c->argv[3]->ptr); } addReply(c, shared.ok); return 1; } int clusterNodePending(clusterNode *node) { return node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE); } char *clusterNodeIp(clusterNode *node) { return node->ip; } int clusterNodeIsSlave(clusterNode *node) { return node->flags & CLUSTER_NODE_SLAVE; } clusterNode *clusterNodeGetSlaveof(clusterNode *node) { return node->slaveof; } clusterNode *clusterNodeGetMaster(clusterNode *node) { while (node->slaveof != NULL) node = node->slaveof; return node; } char *clusterNodeGetName(clusterNode *node) { return node->name; } int clusterNodeTimedOut(clusterNode *node) { return nodeTimedOut(node); } int clusterNodeIsFailing(clusterNode *node) { return nodeFailed(node); } int clusterNodeIsNoFailover(clusterNode *node) { return node->flags & CLUSTER_NODE_NOFAILOVER; } const char **clusterDebugCommandExtendedHelp(void) { static const char *help[] = { "CLUSTERLINK KILL ", " Kills the link based on the direction to/from (both) with the provided node.", NULL }; return help; } char *clusterNodeGetShardId(clusterNode *node) { return node->shard_id; } int clusterCommandSpecial(client *c) { if (!strcasecmp(c->argv[1]->ptr,"meet") && (c->argc == 4 || c->argc == 5)) { /* CLUSTER MEET [cport] */ long long port, cport; if (getLongLongFromObject(c->argv[3], &port) != C_OK) { addReplyErrorFormat(c,"Invalid base port specified: %s", (char*)c->argv[3]->ptr); return 1; } if (c->argc == 5) { if (getLongLongFromObject(c->argv[4], &cport) != C_OK) { addReplyErrorFormat(c,"Invalid bus port specified: %s", (char*)c->argv[4]->ptr); return 1; } } else { cport = port + CLUSTER_PORT_INCR; } if (clusterStartHandshake(c->argv[2]->ptr,port,cport) == 0 && errno == EINVAL) { addReplyErrorFormat(c,"Invalid node address specified: %s:%s", (char*)c->argv[2]->ptr, (char*)c->argv[3]->ptr); } else { addReply(c,shared.ok); } } else if (!strcasecmp(c->argv[1]->ptr,"flushslots") && c->argc == 2) { /* CLUSTER FLUSHSLOTS */ if (kvstoreSize(server.db[0].keys) != 0) { addReplyError(c,"DB must be empty to perform CLUSTER FLUSHSLOTS."); return 1; } clusterDelNodeSlots(myself); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); addReply(c,shared.ok); } else if ((!strcasecmp(c->argv[1]->ptr,"addslots") || !strcasecmp(c->argv[1]->ptr,"delslots")) && c->argc >= 3) { /* CLUSTER ADDSLOTS [slot] ... */ /* CLUSTER DELSLOTS [slot] ... */ int j, slot; unsigned char *slots = zmalloc(CLUSTER_SLOTS); int del = !strcasecmp(c->argv[1]->ptr,"delslots"); memset(slots,0,CLUSTER_SLOTS); /* Check that all the arguments are parseable.*/ for (j = 2; j < c->argc; j++) { if ((slot = getSlotOrReply(c,c->argv[j])) == C_ERR) { zfree(slots); return 1; } } /* Check that the slots are not already busy. */ for (j = 2; j < c->argc; j++) { slot = getSlotOrReply(c,c->argv[j]); if (checkSlotAssignmentsOrReply(c, slots, del, slot, slot) == C_ERR) { zfree(slots); return 1; } } clusterUpdateSlots(c, slots, del); zfree(slots); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); addReply(c,shared.ok); } else if ((!strcasecmp(c->argv[1]->ptr,"addslotsrange") || !strcasecmp(c->argv[1]->ptr,"delslotsrange")) && c->argc >= 4) { if (c->argc % 2 == 1) { addReplyErrorArity(c); return 1; } /* CLUSTER ADDSLOTSRANGE [ ...] */ /* CLUSTER DELSLOTSRANGE [ ...] */ int j, startslot, endslot; unsigned char *slots = zmalloc(CLUSTER_SLOTS); int del = !strcasecmp(c->argv[1]->ptr,"delslotsrange"); memset(slots,0,CLUSTER_SLOTS); /* Check that all the arguments are parseable and that all the * slots are not already busy. */ for (j = 2; j < c->argc; j += 2) { if ((startslot = getSlotOrReply(c,c->argv[j])) == C_ERR) { zfree(slots); return 1; } if ((endslot = getSlotOrReply(c,c->argv[j+1])) == C_ERR) { zfree(slots); return 1; } if (startslot > endslot) { addReplyErrorFormat(c,"start slot number %d is greater than end slot number %d", startslot, endslot); zfree(slots); return 1; } if (checkSlotAssignmentsOrReply(c, slots, del, startslot, endslot) == C_ERR) { zfree(slots); return 1; } } clusterUpdateSlots(c, slots, del); zfree(slots); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"setslot") && c->argc >= 4) { /* SETSLOT 10 MIGRATING */ /* SETSLOT 10 IMPORTING */ /* SETSLOT 10 STABLE */ /* SETSLOT 10 NODE */ int slot; clusterNode *n; if (nodeIsSlave(myself)) { addReplyError(c,"Please use SETSLOT only with masters."); return 1; } if ((slot = getSlotOrReply(c, c->argv[2])) == -1) return 1; if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) { if (server.cluster->slots[slot] != myself) { addReplyErrorFormat(c,"I'm not the owner of hash slot %u",slot); return 1; } n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); if (n == NULL) { addReplyErrorFormat(c,"I don't know about node %s", (char*)c->argv[4]->ptr); return 1; } if (nodeIsSlave(n)) { addReplyError(c,"Target node is not a master"); return 1; } server.cluster->migrating_slots_to[slot] = n; } else if (!strcasecmp(c->argv[3]->ptr,"importing") && c->argc == 5) { if (server.cluster->slots[slot] == myself) { addReplyErrorFormat(c, "I'm already the owner of hash slot %u",slot); return 1; } n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); if (n == NULL) { addReplyErrorFormat(c,"I don't know about node %s", (char*)c->argv[4]->ptr); return 1; } if (nodeIsSlave(n)) { addReplyError(c,"Target node is not a master"); return 1; } server.cluster->importing_slots_from[slot] = n; } else if (!strcasecmp(c->argv[3]->ptr,"stable") && c->argc == 4) { /* CLUSTER SETSLOT STABLE */ server.cluster->importing_slots_from[slot] = NULL; server.cluster->migrating_slots_to[slot] = NULL; } else if (!strcasecmp(c->argv[3]->ptr,"node") && c->argc == 5) { /* CLUSTER SETSLOT NODE */ n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); if (!n) { addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[4]->ptr); return 1; } if (nodeIsSlave(n)) { addReplyError(c,"Target node is not a master"); return 1; } /* If this hash slot was served by 'myself' before to switch * make sure there are no longer local keys for this hash slot. */ if (server.cluster->slots[slot] == myself && n != myself) { if (countKeysInSlot(slot) != 0) { addReplyErrorFormat(c, "Can't assign hashslot %d to a different node " "while I still hold keys for this hash slot.", slot); return 1; } } /* If this slot is in migrating status but we have no keys * for it assigning the slot to another node will clear * the migrating status. */ if (countKeysInSlot(slot) == 0 && server.cluster->migrating_slots_to[slot]) server.cluster->migrating_slots_to[slot] = NULL; int slot_was_mine = server.cluster->slots[slot] == myself; clusterDelSlot(slot); clusterAddSlot(n,slot); /* If we are a master left without slots, we should turn into a * replica of the new master. */ if (slot_was_mine && n != myself && myself->numslots == 0 && server.cluster_allow_replica_migration) { serverLog(LL_NOTICE, "Configuration change detected. Reconfiguring myself " "as a replica of %.40s (%s)", n->name, n->human_nodename); clusterSetMaster(n); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_FSYNC_CONFIG); } /* If this node was importing this slot, assigning the slot to * itself also clears the importing status. */ if (n == myself && server.cluster->importing_slots_from[slot]) { /* This slot was manually migrated, set this node configEpoch * to a new epoch so that the new version can be propagated * by the cluster. * * Note that if this ever results in a collision with another * node getting the same configEpoch, for example because a * failover happens at the same time we close the slot, the * configEpoch collision resolution will fix it assigning * a different epoch to each node. */ if (clusterBumpConfigEpochWithoutConsensus() == C_OK) { serverLog(LL_NOTICE, "configEpoch updated after importing slot %d", slot); } server.cluster->importing_slots_from[slot] = NULL; /* After importing this slot, let the other nodes know as * soon as possible. */ clusterBroadcastPong(CLUSTER_BROADCAST_ALL); } } else { addReplyError(c, "Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP"); return 1; } clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"bumpepoch") && c->argc == 2) { /* CLUSTER BUMPEPOCH */ int retval = clusterBumpConfigEpochWithoutConsensus(); sds reply = sdscatprintf(sdsempty(),"+%s %llu\r\n", (retval == C_OK) ? "BUMPED" : "STILL", (unsigned long long) myself->configEpoch); addReplySds(c,reply); } else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) { int retval = clusterSaveConfig(1); if (retval == 0) addReply(c,shared.ok); else addReplyErrorFormat(c,"error saving the cluster node config: %s", strerror(errno)); } else if (!strcasecmp(c->argv[1]->ptr,"forget") && c->argc == 3) { /* CLUSTER FORGET */ clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); if (!n) { if (clusterBlacklistExists((char*)c->argv[2]->ptr)) /* Already forgotten. The deletion may have been gossipped by * another node, so we pretend it succeeded. */ addReply(c,shared.ok); else addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); return 1; } else if (n == myself) { addReplyError(c,"I tried hard but I can't forget myself..."); return 1; } else if (nodeIsSlave(myself) && myself->slaveof == n) { addReplyError(c,"Can't forget my master!"); return 1; } clusterBlacklistAddNode(n); clusterDelNode(n); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_SAVE_CONFIG); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"replicate") && c->argc == 3) { /* CLUSTER REPLICATE */ /* Lookup the specified node in our table. */ clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); if (!n) { addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); return 1; } /* I can't replicate myself. */ if (n == myself) { addReplyError(c,"Can't replicate myself"); return 1; } /* Can't replicate a slave. */ if (nodeIsSlave(n)) { addReplyError(c,"I can only replicate a master, not a replica."); return 1; } /* If the instance is currently a master, it should have no assigned * slots nor keys to accept to replicate some other node. * Slaves can switch to another master without issues. */ if (clusterNodeIsMaster(myself) && (myself->numslots != 0 || kvstoreSize(server.db[0].keys) != 0)) { addReplyError(c, "To set a master the node must be empty and " "without assigned slots."); return 1; } /* Set the master. */ clusterSetMaster(n); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"count-failure-reports") && c->argc == 3) { /* CLUSTER COUNT-FAILURE-REPORTS */ clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); if (!n) { addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); return 1; } else { addReplyLongLong(c,clusterNodeFailureReportsCount(n)); } } else if (!strcasecmp(c->argv[1]->ptr,"failover") && (c->argc == 2 || c->argc == 3)) { /* CLUSTER FAILOVER [FORCE|TAKEOVER] */ int force = 0, takeover = 0; if (c->argc == 3) { if (!strcasecmp(c->argv[2]->ptr,"force")) { force = 1; } else if (!strcasecmp(c->argv[2]->ptr,"takeover")) { takeover = 1; force = 1; /* Takeover also implies force. */ } else { addReplyErrorObject(c,shared.syntaxerr); return 1; } } /* Check preconditions. */ if (clusterNodeIsMaster(myself)) { addReplyError(c,"You should send CLUSTER FAILOVER to a replica"); return 1; } else if (myself->slaveof == NULL) { addReplyError(c,"I'm a replica but my master is unknown to me"); return 1; } else if (!force && (nodeFailed(myself->slaveof) || myself->slaveof->link == NULL)) { addReplyError(c,"Master is down or failed, " "please use CLUSTER FAILOVER FORCE"); return 1; } resetManualFailover(); server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT; if (takeover) { /* A takeover does not perform any initial check. It just * generates a new configuration epoch for this node without * consensus, claims the master's slots, and broadcast the new * configuration. */ serverLog(LL_NOTICE,"Taking over the master (user request)."); clusterBumpConfigEpochWithoutConsensus(); clusterFailoverReplaceYourMaster(); } else if (force) { /* If this is a forced failover, we don't need to talk with our * master to agree about the offset. We just failover taking over * it without coordination. */ serverLog(LL_NOTICE,"Forced failover user request accepted."); server.cluster->mf_can_start = 1; } else { serverLog(LL_NOTICE,"Manual failover user request accepted."); clusterSendMFStart(myself->slaveof); } addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"set-config-epoch") && c->argc == 3) { /* CLUSTER SET-CONFIG-EPOCH * * The user is allowed to set the config epoch only when a node is * totally fresh: no config epoch, no other known node, and so forth. * This happens at cluster creation time to start with a cluster where * every node has a different node ID, without to rely on the conflicts * resolution system which is too slow when a big cluster is created. */ long long epoch; if (getLongLongFromObjectOrReply(c,c->argv[2],&epoch,NULL) != C_OK) return 1; if (epoch < 0) { addReplyErrorFormat(c,"Invalid config epoch specified: %lld",epoch); } else if (dictSize(server.cluster->nodes) > 1) { addReplyError(c,"The user can assign a config epoch only when the " "node does not know any other node."); } else if (myself->configEpoch != 0) { addReplyError(c,"Node config epoch is already non-zero"); } else { myself->configEpoch = epoch; serverLog(LL_NOTICE, "configEpoch set to %llu via CLUSTER SET-CONFIG-EPOCH", (unsigned long long) myself->configEpoch); if (server.cluster->currentEpoch < (uint64_t)epoch) server.cluster->currentEpoch = epoch; /* No need to fsync the config here since in the unlucky event * of a failure to persist the config, the conflict resolution code * will assign a unique config to this node. */ clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_SAVE_CONFIG); addReply(c,shared.ok); } } else if (!strcasecmp(c->argv[1]->ptr,"reset") && (c->argc == 2 || c->argc == 3)) { /* CLUSTER RESET [SOFT|HARD] */ int hard = 0; /* Parse soft/hard argument. Default is soft. */ if (c->argc == 3) { if (!strcasecmp(c->argv[2]->ptr,"hard")) { hard = 1; } else if (!strcasecmp(c->argv[2]->ptr,"soft")) { hard = 0; } else { addReplyErrorObject(c,shared.syntaxerr); return 1; } } /* Slaves can be reset while containing data, but not master nodes * that must be empty. */ if (clusterNodeIsMaster(myself) && kvstoreSize(c->db->keys) != 0) { addReplyError(c,"CLUSTER RESET can't be called with " "master nodes containing keys"); return 1; } clusterReset(hard); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"links") && c->argc == 2) { /* CLUSTER LINKS */ addReplyClusterLinksDescription(c); } else { return 0; } return 1; } const char **clusterCommandExtendedHelp(void) { static const char *help[] = { "ADDSLOTS [ ...]", " Assign slots to current node.", "ADDSLOTSRANGE [ ...]", " Assign slots which are between and to current node.", "BUMPEPOCH", " Advance the cluster config epoch.", "COUNT-FAILURE-REPORTS ", " Return number of failure reports for .", "DELSLOTS [ ...]", " Delete slots information from current node.", "DELSLOTSRANGE [ ...]", " Delete slots information which are between and from current node.", "FAILOVER [FORCE|TAKEOVER]", " Promote current replica node to being a master.", "FORGET ", " Remove a node from the cluster.", "FLUSHSLOTS", " Delete current node own slots information.", "MEET []", " Connect nodes into a working cluster.", "REPLICATE ", " Configure current node as replica to .", "RESET [HARD|SOFT]", " Reset current node (default: soft).", "SET-CONFIG-EPOCH ", " Set config epoch of current node.", "SETSLOT (IMPORTING |MIGRATING |STABLE|NODE )", " Set slot state.", "SAVECONFIG", " Force saving cluster configuration on disk.", "LINKS", " Return information about all network links between this node and its peers.", " Output format is an array where each array element is a map containing attributes of a link", NULL }; return help; } int clusterNodeNumSlaves(clusterNode *node) { return node->numslaves; } clusterNode *clusterNodeGetSlave(clusterNode *node, int slave_idx) { return node->slaves[slave_idx]; } clusterNode *getMigratingSlotDest(int slot) { return server.cluster->migrating_slots_to[slot]; } clusterNode *getImportingSlotSource(int slot) { return server.cluster->importing_slots_from[slot]; } int isClusterHealthy(void) { return server.cluster->state == CLUSTER_OK; } clusterNode *getNodeBySlot(int slot) { return server.cluster->slots[slot]; } char *clusterNodeHostname(clusterNode *node) { return node->hostname; } long long clusterNodeReplOffset(clusterNode *node) { return node->repl_offset; } const char *clusterNodePreferredEndpoint(clusterNode *n) { char *hostname = clusterNodeHostname(n); switch (server.cluster_preferred_endpoint_type) { case CLUSTER_ENDPOINT_TYPE_IP: return clusterNodeIp(n); case CLUSTER_ENDPOINT_TYPE_HOSTNAME: return (hostname != NULL && hostname[0] != '\0') ? hostname : "?"; case CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT: return ""; } return "unknown"; } int clusterAllowFailoverCmd(client *c) { if (!server.cluster_enabled) { return 1; } addReplyError(c,"FAILOVER not allowed in cluster mode. " "Use CLUSTER FAILOVER command instead."); return 0; } void clusterPromoteSelfToMaster(void) { replicationUnsetMaster(); } redis-8.0.2/src/cluster_legacy.h000066400000000000000000000460451501533116600166060ustar00rootroot00000000000000/* * Copyright (c) 2009-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). * * Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. */ #ifndef CLUSTER_LEGACY_H #define CLUSTER_LEGACY_H #define CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */ /* The following defines are amount of time, sometimes expressed as * multiplicators of the node timeout value (when ending with MULT). */ #define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */ #define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */ #define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */ #define CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */ #define CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration. */ /* Reasons why a slave is not able to failover. */ #define CLUSTER_CANT_FAILOVER_NONE 0 #define CLUSTER_CANT_FAILOVER_DATA_AGE 1 #define CLUSTER_CANT_FAILOVER_WAITING_DELAY 2 #define CLUSTER_CANT_FAILOVER_EXPIRED 3 #define CLUSTER_CANT_FAILOVER_WAITING_VOTES 4 #define CLUSTER_CANT_FAILOVER_RELOG_PERIOD (10) /* seconds. */ /* clusterState todo_before_sleep flags. */ #define CLUSTER_TODO_HANDLE_FAILOVER (1<<0) #define CLUSTER_TODO_UPDATE_STATE (1<<1) #define CLUSTER_TODO_SAVE_CONFIG (1<<2) #define CLUSTER_TODO_FSYNC_CONFIG (1<<3) #define CLUSTER_TODO_HANDLE_MANUALFAILOVER (1<<4) /* clusterLink encapsulates everything needed to talk with a remote node. */ typedef struct clusterLink { mstime_t ctime; /* Link creation time */ connection *conn; /* Connection to remote node */ list *send_msg_queue; /* List of messages to be sent */ size_t head_msg_send_offset; /* Number of bytes already sent of message at head of queue */ unsigned long long send_msg_queue_mem; /* Memory in bytes used by message queue */ char *rcvbuf; /* Packet reception buffer */ size_t rcvbuf_len; /* Used size of rcvbuf */ size_t rcvbuf_alloc; /* Allocated size of rcvbuf */ clusterNode *node; /* Node related to this link. Initialized to NULL when unknown */ int inbound; /* 1 if this link is an inbound link accepted from the related node */ } clusterLink; /* Cluster node flags and macros. */ #define CLUSTER_NODE_MASTER 1 /* The node is a master */ #define CLUSTER_NODE_SLAVE 2 /* The node is a slave */ #define CLUSTER_NODE_PFAIL 4 /* Failure? Need acknowledge */ #define CLUSTER_NODE_FAIL 8 /* The node is believed to be malfunctioning */ #define CLUSTER_NODE_MYSELF 16 /* This node is myself */ #define CLUSTER_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */ #define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */ #define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */ #define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */ #define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */ #define CLUSTER_NODE_EXTENSIONS_SUPPORTED 1024 /* This node supports extensions. */ #define CLUSTER_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" #define nodeIsSlave(n) ((n)->flags & CLUSTER_NODE_SLAVE) #define nodeInHandshake(n) ((n)->flags & CLUSTER_NODE_HANDSHAKE) #define nodeHasAddr(n) (!((n)->flags & CLUSTER_NODE_NOADDR)) #define nodeTimedOut(n) ((n)->flags & CLUSTER_NODE_PFAIL) #define nodeFailed(n) ((n)->flags & CLUSTER_NODE_FAIL) #define nodeCantFailover(n) ((n)->flags & CLUSTER_NODE_NOFAILOVER) #define nodeSupportsExtensions(n) ((n)->flags & CLUSTER_NODE_EXTENSIONS_SUPPORTED) /* This structure represent elements of node->fail_reports. */ typedef struct clusterNodeFailReport { clusterNode *node; /* Node reporting the failure condition. */ mstime_t time; /* Time of the last report from this node. */ } clusterNodeFailReport; /* Redis cluster messages header */ /* Message types. * * Note that the PING, PONG and MEET messages are actually the same exact * kind of packet. PONG is the reply to ping, in the exact format as a PING, * while MEET is a special PING that forces the receiver to add the sender * as a node (if it is not already in the list). */ #define CLUSTERMSG_TYPE_PING 0 /* Ping */ #define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */ #define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */ #define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */ #define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */ #define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */ #define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */ #define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */ #define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */ #define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */ #define CLUSTERMSG_TYPE_PUBLISHSHARD 10 /* Pub/Sub Publish shard propagation */ #define CLUSTERMSG_TYPE_COUNT 11 /* Total number of message types. */ /* Initially we don't know our "name", but we'll find it once we connect * to the first node, using the getsockname() function. Then we'll use this * address for all the next messages. */ typedef struct { char nodename[CLUSTER_NAMELEN]; uint32_t ping_sent; uint32_t pong_received; char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */ uint16_t port; /* primary port last time it was seen */ uint16_t cport; /* cluster port last time it was seen */ uint16_t flags; /* node->flags copy */ uint16_t pport; /* secondary port last time it was seen */ uint16_t notused1; } clusterMsgDataGossip; typedef struct { char nodename[CLUSTER_NAMELEN]; } clusterMsgDataFail; typedef struct { uint32_t channel_len; uint32_t message_len; unsigned char bulk_data[8]; /* 8 bytes just as placeholder. */ } clusterMsgDataPublish; typedef struct { uint64_t configEpoch; /* Config epoch of the specified instance. */ char nodename[CLUSTER_NAMELEN]; /* Name of the slots owner. */ unsigned char slots[CLUSTER_SLOTS/8]; /* Slots bitmap. */ } clusterMsgDataUpdate; typedef struct { uint64_t module_id; /* ID of the sender module. */ uint32_t len; /* ID of the sender module. */ uint8_t type; /* Type from 0 to 255. */ unsigned char bulk_data[3]; /* 3 bytes just as placeholder. */ } clusterMsgModule; /* The cluster supports optional extension messages that can be sent * along with ping/pong/meet messages to give additional info in a * consistent manner. */ typedef enum { CLUSTERMSG_EXT_TYPE_HOSTNAME, CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME, CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE, CLUSTERMSG_EXT_TYPE_SHARDID, CLUSTERMSG_EXT_TYPE_INTERNALSECRET, } clusterMsgPingtypes; /* Helper function for making sure extensions are eight byte aligned. */ #define EIGHT_BYTE_ALIGN(size) ((((size) + 7) / 8) * 8) #define CLUSTER_INTERNALSECRETLEN 40 /* sha1 hex length */ typedef struct { char hostname[1]; /* The announced hostname, ends with \0. */ } clusterMsgPingExtHostname; typedef struct { char human_nodename[1]; /* The announced nodename, ends with \0. */ } clusterMsgPingExtHumanNodename; typedef struct { char name[CLUSTER_NAMELEN]; /* Node name. */ uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */ } clusterMsgPingExtForgottenNode; static_assert(sizeof(clusterMsgPingExtForgottenNode) % 8 == 0, ""); typedef struct { char shard_id[CLUSTER_NAMELEN]; /* The shard_id, 40 bytes fixed. */ } clusterMsgPingExtShardId; typedef struct { char internal_secret[CLUSTER_INTERNALSECRETLEN]; /* Current shard internal secret */ } clusterMsgPingExtInternalSecret; typedef struct { uint32_t length; /* Total length of this extension message (including this header) */ uint16_t type; /* Type of this extension message (see clusterMsgPingExtTypes) */ uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */ union { clusterMsgPingExtHostname hostname; clusterMsgPingExtHumanNodename human_nodename; clusterMsgPingExtForgottenNode forgotten_node; clusterMsgPingExtShardId shard_id; clusterMsgPingExtInternalSecret internal_secret; } ext[]; /* Actual extension information, formatted so that the data is 8 * byte aligned, regardless of its content. */ } clusterMsgPingExt; union clusterMsgData { /* PING, MEET and PONG */ struct { /* Array of N clusterMsgDataGossip structures */ clusterMsgDataGossip gossip[1]; /* Extension data that can optionally be sent for ping/meet/pong * messages. We can't explicitly define them here though, since * the gossip array isn't the real length of the gossip data. */ } ping; /* FAIL */ struct { clusterMsgDataFail about; } fail; /* PUBLISH */ struct { clusterMsgDataPublish msg; } publish; /* UPDATE */ struct { clusterMsgDataUpdate nodecfg; } update; /* MODULE */ struct { clusterMsgModule msg; } module; }; #define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */ typedef struct { char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */ uint32_t totlen; /* Total length of this message */ uint16_t ver; /* Protocol version, currently set to 1. */ uint16_t port; /* Primary port number (TCP or TLS). */ uint16_t type; /* Message type */ uint16_t count; /* Only used for some kinds of messages. */ uint64_t currentEpoch; /* The epoch accordingly to the sending node. */ uint64_t configEpoch; /* The config epoch if it's a master, or the last epoch advertised by its master if it is a slave. */ uint64_t offset; /* Master replication offset if node is a master or processed replication offset if node is a slave. */ char sender[CLUSTER_NAMELEN]; /* Name of the sender node */ unsigned char myslots[CLUSTER_SLOTS/8]; char slaveof[CLUSTER_NAMELEN]; char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */ uint16_t extensions; /* Number of extensions sent along with this packet. */ char notused1[30]; /* 30 bytes reserved for future usage. */ uint16_t pport; /* Secondary port number: if primary port is TCP port, this is TLS port, and if primary port is TLS port, this is TCP port.*/ uint16_t cport; /* Sender TCP cluster bus port */ uint16_t flags; /* Sender node flags */ unsigned char state; /* Cluster state from the POV of the sender */ unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */ union clusterMsgData data; } clusterMsg; /* clusterMsg defines the gossip wire protocol exchanged among Redis cluster * members, which can be running different versions of redis-server bits, * especially during cluster rolling upgrades. * * Therefore, fields in this struct should remain at the same offset from * release to release. The static asserts below ensure that incompatible * changes in clusterMsg are caught at compile time. */ static_assert(offsetof(clusterMsg, sig) == 0, "unexpected field offset"); static_assert(offsetof(clusterMsg, totlen) == 4, "unexpected field offset"); static_assert(offsetof(clusterMsg, ver) == 8, "unexpected field offset"); static_assert(offsetof(clusterMsg, port) == 10, "unexpected field offset"); static_assert(offsetof(clusterMsg, type) == 12, "unexpected field offset"); static_assert(offsetof(clusterMsg, count) == 14, "unexpected field offset"); static_assert(offsetof(clusterMsg, currentEpoch) == 16, "unexpected field offset"); static_assert(offsetof(clusterMsg, configEpoch) == 24, "unexpected field offset"); static_assert(offsetof(clusterMsg, offset) == 32, "unexpected field offset"); static_assert(offsetof(clusterMsg, sender) == 40, "unexpected field offset"); static_assert(offsetof(clusterMsg, myslots) == 80, "unexpected field offset"); static_assert(offsetof(clusterMsg, slaveof) == 2128, "unexpected field offset"); static_assert(offsetof(clusterMsg, myip) == 2168, "unexpected field offset"); static_assert(offsetof(clusterMsg, extensions) == 2214, "unexpected field offset"); static_assert(offsetof(clusterMsg, notused1) == 2216, "unexpected field offset"); static_assert(offsetof(clusterMsg, pport) == 2246, "unexpected field offset"); static_assert(offsetof(clusterMsg, cport) == 2248, "unexpected field offset"); static_assert(offsetof(clusterMsg, flags) == 2250, "unexpected field offset"); static_assert(offsetof(clusterMsg, state) == 2252, "unexpected field offset"); static_assert(offsetof(clusterMsg, mflags) == 2253, "unexpected field offset"); static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset"); #define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData)) /* Message flags better specify the packet content or are used to * provide some information about the node state. */ #define CLUSTERMSG_FLAG0_PAUSED (1<<0) /* Master paused for manual failover. */ #define CLUSTERMSG_FLAG0_FORCEACK (1<<1) /* Give ACK to AUTH_REQUEST even if master is up. */ #define CLUSTERMSG_FLAG0_EXT_DATA (1<<2) /* Message contains extension data */ struct _clusterNode { mstime_t ctime; /* Node object creation time. */ char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */ char shard_id[CLUSTER_NAMELEN]; /* shard id, hex string, sha1-size */ int flags; /* CLUSTER_NODE_... */ uint64_t configEpoch; /* Last configEpoch observed for this node */ unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */ uint16_t *slot_info_pairs; /* Slots info represented as (start/end) pair (consecutive index). */ int slot_info_pairs_count; /* Used number of slots in slot_info_pairs */ int numslots; /* Number of slots handled by this node */ int numslaves; /* Number of slave nodes, if this is a master */ clusterNode **slaves; /* pointers to slave nodes */ clusterNode *slaveof; /* pointer to the master node. Note that it may be NULL even if the node is a slave if we don't have the master node in our tables. */ unsigned long long last_in_ping_gossip; /* The number of the last carried in the ping gossip section */ mstime_t ping_sent; /* Unix time we sent latest ping */ mstime_t pong_received; /* Unix time we received the pong */ mstime_t data_received; /* Unix time we received any data */ mstime_t fail_time; /* Unix time when FAIL flag was set */ mstime_t voted_time; /* Last time we voted for a slave of this master */ mstime_t repl_offset_time; /* Unix time we received offset for this node */ mstime_t orphaned_time; /* Starting time of orphaned master condition */ long long repl_offset; /* Last known repl offset for this node. */ char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */ sds hostname; /* The known hostname for this node */ sds human_nodename; /* The known human readable nodename for this node */ int tcp_port; /* Latest known clients TCP port. */ int tls_port; /* Latest known clients TLS port */ int cport; /* Latest known cluster port of this node. */ clusterLink *link; /* TCP/IP link established toward this node */ clusterLink *inbound_link; /* TCP/IP link accepted from this node */ list *fail_reports; /* List of nodes signaling this as failing */ }; struct clusterState { clusterNode *myself; /* This node */ uint64_t currentEpoch; int state; /* CLUSTER_OK, CLUSTER_FAIL, ... */ int size; /* Num of master nodes with at least one slot */ dict *nodes; /* Hash table of name -> clusterNode structures */ dict *shards; /* Hash table of shard_id -> list (of nodes) structures */ dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */ clusterNode *migrating_slots_to[CLUSTER_SLOTS]; clusterNode *importing_slots_from[CLUSTER_SLOTS]; clusterNode *slots[CLUSTER_SLOTS]; char internal_secret[CLUSTER_INTERNALSECRETLEN]; /* The following fields are used to take the slave state on elections. */ mstime_t failover_auth_time; /* Time of previous or next election. */ int failover_auth_count; /* Number of votes received so far. */ int failover_auth_sent; /* True if we already asked for votes. */ int failover_auth_rank; /* This slave rank for current auth request. */ uint64_t failover_auth_epoch; /* Epoch of the current election. */ int cant_failover_reason; /* Why a slave is currently not able to failover. See the CANT_FAILOVER_* macros. */ /* Manual failover state in common. */ mstime_t mf_end; /* Manual failover time limit (ms unixtime). It is zero if there is no MF in progress. */ /* Manual failover state of master. */ clusterNode *mf_slave; /* Slave performing the manual failover. */ /* Manual failover state of slave. */ long long mf_master_offset; /* Master offset the slave needs to start MF or -1 if still not received. */ int mf_can_start; /* If non-zero signal that the manual failover can start requesting masters vote. */ /* The following fields are used by masters to take state on elections. */ uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */ int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */ /* Stats */ /* Messages received and sent by type. */ long long stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT]; long long stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT]; long long stats_pfail_nodes; /* Number of nodes in PFAIL status, excluding nodes without address. */ unsigned long long stat_cluster_links_buffer_limit_exceeded; /* Total number of cluster links freed due to exceeding buffer limit */ /* Bit map for slots that are no longer claimed by the owner in cluster PING * messages. During slot migration, the owner will stop claiming the slot after * the ownership transfer. Set the bit corresponding to the slot when a node * stops claiming the slot. This prevents spreading incorrect information (that * source still owns the slot) using UPDATE messages. */ unsigned char owner_not_claiming_slot[CLUSTER_SLOTS / 8]; }; #endif //CLUSTER_LEGACY_H redis-8.0.2/src/commands.c000066400000000000000000000013641501533116600153700ustar00rootroot00000000000000#include "commands.h" #include "server.h" #define MAKE_CMD(name,summary,complexity,since,doc_flags,replaced,deprecated,group,group_enum,history,num_history,tips,num_tips,function,arity,flags,acl,key_specs,key_specs_num,get_keys,numargs) name,summary,complexity,since,doc_flags,replaced,deprecated,group_enum,history,num_history,tips,num_tips,function,arity,flags,acl,key_specs,key_specs_num,get_keys,numargs #define MAKE_ARG(name,type,key_spec_index,token,summary,since,flags,numsubargs,deprecated_since) name,type,key_spec_index,token,summary,since,flags,deprecated_since,numsubargs #define COMMAND_STRUCT redisCommand #define COMMAND_ARG redisCommandArg #ifdef LOG_REQ_RES #include "commands_with_reply_schema.def" #else #include "commands.def" #endif redis-8.0.2/src/commands.def000066400000000000000000015657221501533116600157220ustar00rootroot00000000000000/* Automatically generated by generate-command-code.py, do not edit. */ /* We have fabulous commands from * the fantastic * Redis Command Table! */ /* Must match redisCommandGroup */ const char *COMMAND_GROUP_STR[] = { "generic", "string", "list", "set", "sorted-set", "hash", "pubsub", "transactions", "connection", "server", "scripting", "hyperloglog", "cluster", "sentinel", "geo", "stream", "bitmap", "module" }; const char *commandGroupStr(int index) { return COMMAND_GROUP_STR[index]; } /********** BITCOUNT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BITCOUNT history */ commandHistory BITCOUNT_History[] = { {"7.0.0","Added the `BYTE|BIT` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BITCOUNT tips */ #define BITCOUNT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BITCOUNT key specs */ keySpec BITCOUNT_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* BITCOUNT range unit argument table */ struct COMMAND_ARG BITCOUNT_range_unit_Subargs[] = { {MAKE_ARG("byte",ARG_TYPE_PURE_TOKEN,-1,"BYTE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("bit",ARG_TYPE_PURE_TOKEN,-1,"BIT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITCOUNT range argument table */ struct COMMAND_ARG BITCOUNT_range_Subargs[] = { {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=BITCOUNT_range_unit_Subargs}, }; /* BITCOUNT argument table */ struct COMMAND_ARG BITCOUNT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=BITCOUNT_range_Subargs}, }; /********** BITFIELD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BITFIELD history */ #define BITFIELD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BITFIELD tips */ #define BITFIELD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BITFIELD key specs */ keySpec BITFIELD_Keyspecs[1] = { {"This command allows both access and modification of the key",CMD_KEY_RW|CMD_KEY_UPDATE|CMD_KEY_ACCESS|CMD_KEY_VARIABLE_FLAGS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* BITFIELD operation get_block argument table */ struct COMMAND_ARG BITFIELD_operation_get_block_Subargs[] = { {MAKE_ARG("encoding",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITFIELD operation write overflow_block argument table */ struct COMMAND_ARG BITFIELD_operation_write_overflow_block_Subargs[] = { {MAKE_ARG("wrap",ARG_TYPE_PURE_TOKEN,-1,"WRAP",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sat",ARG_TYPE_PURE_TOKEN,-1,"SAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fail",ARG_TYPE_PURE_TOKEN,-1,"FAIL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITFIELD operation write write_operation set_block argument table */ struct COMMAND_ARG BITFIELD_operation_write_write_operation_set_block_Subargs[] = { {MAKE_ARG("encoding",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITFIELD operation write write_operation incrby_block argument table */ struct COMMAND_ARG BITFIELD_operation_write_write_operation_incrby_block_Subargs[] = { {MAKE_ARG("encoding",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITFIELD operation write write_operation argument table */ struct COMMAND_ARG BITFIELD_operation_write_write_operation_Subargs[] = { {MAKE_ARG("set-block",ARG_TYPE_BLOCK,-1,"SET",NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=BITFIELD_operation_write_write_operation_set_block_Subargs}, {MAKE_ARG("incrby-block",ARG_TYPE_BLOCK,-1,"INCRBY",NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=BITFIELD_operation_write_write_operation_incrby_block_Subargs}, }; /* BITFIELD operation write argument table */ struct COMMAND_ARG BITFIELD_operation_write_Subargs[] = { {MAKE_ARG("overflow-block",ARG_TYPE_ONEOF,-1,"OVERFLOW",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=BITFIELD_operation_write_overflow_block_Subargs}, {MAKE_ARG("write-operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BITFIELD_operation_write_write_operation_Subargs}, }; /* BITFIELD operation argument table */ struct COMMAND_ARG BITFIELD_operation_Subargs[] = { {MAKE_ARG("get-block",ARG_TYPE_BLOCK,-1,"GET",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BITFIELD_operation_get_block_Subargs}, {MAKE_ARG("write",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BITFIELD_operation_write_Subargs}, }; /* BITFIELD argument table */ struct COMMAND_ARG BITFIELD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,2,NULL),.subargs=BITFIELD_operation_Subargs}, }; /********** BITFIELD_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BITFIELD_RO history */ #define BITFIELD_RO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BITFIELD_RO tips */ #define BITFIELD_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BITFIELD_RO key specs */ keySpec BITFIELD_RO_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* BITFIELD_RO get_block argument table */ struct COMMAND_ARG BITFIELD_RO_get_block_Subargs[] = { {MAKE_ARG("encoding",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITFIELD_RO argument table */ struct COMMAND_ARG BITFIELD_RO_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("get-block",ARG_TYPE_BLOCK,-1,"GET",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE|CMD_ARG_MULTIPLE_TOKEN,2,NULL),.subargs=BITFIELD_RO_get_block_Subargs}, }; /********** BITOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BITOP history */ #define BITOP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BITOP tips */ #define BITOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BITOP key specs */ keySpec BITOP_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={3},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* BITOP operation argument table */ struct COMMAND_ARG BITOP_operation_Subargs[] = { {MAKE_ARG("and",ARG_TYPE_PURE_TOKEN,-1,"AND",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("or",ARG_TYPE_PURE_TOKEN,-1,"OR",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xor",ARG_TYPE_PURE_TOKEN,-1,"XOR",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("not",ARG_TYPE_PURE_TOKEN,-1,"NOT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITOP argument table */ struct COMMAND_ARG BITOP_Args[] = { {MAKE_ARG("operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=BITOP_operation_Subargs}, {MAKE_ARG("destkey",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** BITPOS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BITPOS history */ commandHistory BITPOS_History[] = { {"7.0.0","Added the `BYTE|BIT` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BITPOS tips */ #define BITPOS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BITPOS key specs */ keySpec BITPOS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* BITPOS range end_unit_block unit argument table */ struct COMMAND_ARG BITPOS_range_end_unit_block_unit_Subargs[] = { {MAKE_ARG("byte",ARG_TYPE_PURE_TOKEN,-1,"BYTE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("bit",ARG_TYPE_PURE_TOKEN,-1,"BIT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BITPOS range end_unit_block argument table */ struct COMMAND_ARG BITPOS_range_end_unit_block_Subargs[] = { {MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=BITPOS_range_end_unit_block_unit_Subargs}, }; /* BITPOS range argument table */ struct COMMAND_ARG BITPOS_range_Subargs[] = { {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end-unit-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=BITPOS_range_end_unit_block_Subargs}, }; /* BITPOS argument table */ struct COMMAND_ARG BITPOS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("bit",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=BITPOS_range_Subargs}, }; /********** GETBIT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GETBIT history */ #define GETBIT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GETBIT tips */ #define GETBIT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GETBIT key specs */ keySpec GETBIT_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GETBIT argument table */ struct COMMAND_ARG GETBIT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SETBIT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SETBIT history */ #define SETBIT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SETBIT tips */ #define SETBIT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SETBIT key specs */ keySpec SETBIT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SETBIT argument table */ struct COMMAND_ARG SETBIT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ASKING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ASKING history */ #define ASKING_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ASKING tips */ #define ASKING_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ASKING key specs */ #define ASKING_Keyspecs NULL #endif /********** CLUSTER ADDSLOTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER ADDSLOTS history */ #define CLUSTER_ADDSLOTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER ADDSLOTS tips */ #define CLUSTER_ADDSLOTS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER ADDSLOTS key specs */ #define CLUSTER_ADDSLOTS_Keyspecs NULL #endif /* CLUSTER ADDSLOTS argument table */ struct COMMAND_ARG CLUSTER_ADDSLOTS_Args[] = { {MAKE_ARG("slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** CLUSTER ADDSLOTSRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER ADDSLOTSRANGE history */ #define CLUSTER_ADDSLOTSRANGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER ADDSLOTSRANGE tips */ #define CLUSTER_ADDSLOTSRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER ADDSLOTSRANGE key specs */ #define CLUSTER_ADDSLOTSRANGE_Keyspecs NULL #endif /* CLUSTER ADDSLOTSRANGE range argument table */ struct COMMAND_ARG CLUSTER_ADDSLOTSRANGE_range_Subargs[] = { {MAKE_ARG("start-slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end-slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLUSTER ADDSLOTSRANGE argument table */ struct COMMAND_ARG CLUSTER_ADDSLOTSRANGE_Args[] = { {MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=CLUSTER_ADDSLOTSRANGE_range_Subargs}, }; /********** CLUSTER BUMPEPOCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER BUMPEPOCH history */ #define CLUSTER_BUMPEPOCH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER BUMPEPOCH tips */ const char *CLUSTER_BUMPEPOCH_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER BUMPEPOCH key specs */ #define CLUSTER_BUMPEPOCH_Keyspecs NULL #endif /********** CLUSTER COUNT_FAILURE_REPORTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER COUNT_FAILURE_REPORTS history */ #define CLUSTER_COUNT_FAILURE_REPORTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER COUNT_FAILURE_REPORTS tips */ const char *CLUSTER_COUNT_FAILURE_REPORTS_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER COUNT_FAILURE_REPORTS key specs */ #define CLUSTER_COUNT_FAILURE_REPORTS_Keyspecs NULL #endif /* CLUSTER COUNT_FAILURE_REPORTS argument table */ struct COMMAND_ARG CLUSTER_COUNT_FAILURE_REPORTS_Args[] = { {MAKE_ARG("node-id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER COUNTKEYSINSLOT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER COUNTKEYSINSLOT history */ #define CLUSTER_COUNTKEYSINSLOT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER COUNTKEYSINSLOT tips */ #define CLUSTER_COUNTKEYSINSLOT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER COUNTKEYSINSLOT key specs */ #define CLUSTER_COUNTKEYSINSLOT_Keyspecs NULL #endif /* CLUSTER COUNTKEYSINSLOT argument table */ struct COMMAND_ARG CLUSTER_COUNTKEYSINSLOT_Args[] = { {MAKE_ARG("slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER DELSLOTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER DELSLOTS history */ #define CLUSTER_DELSLOTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER DELSLOTS tips */ #define CLUSTER_DELSLOTS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER DELSLOTS key specs */ #define CLUSTER_DELSLOTS_Keyspecs NULL #endif /* CLUSTER DELSLOTS argument table */ struct COMMAND_ARG CLUSTER_DELSLOTS_Args[] = { {MAKE_ARG("slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** CLUSTER DELSLOTSRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER DELSLOTSRANGE history */ #define CLUSTER_DELSLOTSRANGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER DELSLOTSRANGE tips */ #define CLUSTER_DELSLOTSRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER DELSLOTSRANGE key specs */ #define CLUSTER_DELSLOTSRANGE_Keyspecs NULL #endif /* CLUSTER DELSLOTSRANGE range argument table */ struct COMMAND_ARG CLUSTER_DELSLOTSRANGE_range_Subargs[] = { {MAKE_ARG("start-slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end-slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLUSTER DELSLOTSRANGE argument table */ struct COMMAND_ARG CLUSTER_DELSLOTSRANGE_Args[] = { {MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=CLUSTER_DELSLOTSRANGE_range_Subargs}, }; /********** CLUSTER FAILOVER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER FAILOVER history */ #define CLUSTER_FAILOVER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER FAILOVER tips */ #define CLUSTER_FAILOVER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER FAILOVER key specs */ #define CLUSTER_FAILOVER_Keyspecs NULL #endif /* CLUSTER FAILOVER options argument table */ struct COMMAND_ARG CLUSTER_FAILOVER_options_Subargs[] = { {MAKE_ARG("force",ARG_TYPE_PURE_TOKEN,-1,"FORCE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("takeover",ARG_TYPE_PURE_TOKEN,-1,"TAKEOVER",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLUSTER FAILOVER argument table */ struct COMMAND_ARG CLUSTER_FAILOVER_Args[] = { {MAKE_ARG("options",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLUSTER_FAILOVER_options_Subargs}, }; /********** CLUSTER FLUSHSLOTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER FLUSHSLOTS history */ #define CLUSTER_FLUSHSLOTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER FLUSHSLOTS tips */ #define CLUSTER_FLUSHSLOTS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER FLUSHSLOTS key specs */ #define CLUSTER_FLUSHSLOTS_Keyspecs NULL #endif /********** CLUSTER FORGET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER FORGET history */ #define CLUSTER_FORGET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER FORGET tips */ #define CLUSTER_FORGET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER FORGET key specs */ #define CLUSTER_FORGET_Keyspecs NULL #endif /* CLUSTER FORGET argument table */ struct COMMAND_ARG CLUSTER_FORGET_Args[] = { {MAKE_ARG("node-id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER GETKEYSINSLOT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER GETKEYSINSLOT history */ #define CLUSTER_GETKEYSINSLOT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER GETKEYSINSLOT tips */ const char *CLUSTER_GETKEYSINSLOT_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER GETKEYSINSLOT key specs */ #define CLUSTER_GETKEYSINSLOT_Keyspecs NULL #endif /* CLUSTER GETKEYSINSLOT argument table */ struct COMMAND_ARG CLUSTER_GETKEYSINSLOT_Args[] = { {MAKE_ARG("slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER HELP history */ #define CLUSTER_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER HELP tips */ #define CLUSTER_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER HELP key specs */ #define CLUSTER_HELP_Keyspecs NULL #endif /********** CLUSTER INFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER INFO history */ #define CLUSTER_INFO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER INFO tips */ const char *CLUSTER_INFO_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER INFO key specs */ #define CLUSTER_INFO_Keyspecs NULL #endif /********** CLUSTER KEYSLOT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER KEYSLOT history */ #define CLUSTER_KEYSLOT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER KEYSLOT tips */ #define CLUSTER_KEYSLOT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER KEYSLOT key specs */ #define CLUSTER_KEYSLOT_Keyspecs NULL #endif /* CLUSTER KEYSLOT argument table */ struct COMMAND_ARG CLUSTER_KEYSLOT_Args[] = { {MAKE_ARG("key",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER LINKS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER LINKS history */ #define CLUSTER_LINKS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER LINKS tips */ const char *CLUSTER_LINKS_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER LINKS key specs */ #define CLUSTER_LINKS_Keyspecs NULL #endif /********** CLUSTER MEET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER MEET history */ commandHistory CLUSTER_MEET_History[] = { {"4.0.0","Added the optional `cluster_bus_port` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER MEET tips */ #define CLUSTER_MEET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER MEET key specs */ #define CLUSTER_MEET_Keyspecs NULL #endif /* CLUSTER MEET argument table */ struct COMMAND_ARG CLUSTER_MEET_Args[] = { {MAKE_ARG("ip",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("cluster-bus-port",ARG_TYPE_INTEGER,-1,NULL,NULL,"4.0.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** CLUSTER MYID ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER MYID history */ #define CLUSTER_MYID_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER MYID tips */ #define CLUSTER_MYID_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER MYID key specs */ #define CLUSTER_MYID_Keyspecs NULL #endif /********** CLUSTER MYSHARDID ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER MYSHARDID history */ #define CLUSTER_MYSHARDID_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER MYSHARDID tips */ const char *CLUSTER_MYSHARDID_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER MYSHARDID key specs */ #define CLUSTER_MYSHARDID_Keyspecs NULL #endif /********** CLUSTER NODES ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER NODES history */ #define CLUSTER_NODES_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER NODES tips */ const char *CLUSTER_NODES_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER NODES key specs */ #define CLUSTER_NODES_Keyspecs NULL #endif /********** CLUSTER REPLICAS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER REPLICAS history */ #define CLUSTER_REPLICAS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER REPLICAS tips */ const char *CLUSTER_REPLICAS_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER REPLICAS key specs */ #define CLUSTER_REPLICAS_Keyspecs NULL #endif /* CLUSTER REPLICAS argument table */ struct COMMAND_ARG CLUSTER_REPLICAS_Args[] = { {MAKE_ARG("node-id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER REPLICATE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER REPLICATE history */ #define CLUSTER_REPLICATE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER REPLICATE tips */ #define CLUSTER_REPLICATE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER REPLICATE key specs */ #define CLUSTER_REPLICATE_Keyspecs NULL #endif /* CLUSTER REPLICATE argument table */ struct COMMAND_ARG CLUSTER_REPLICATE_Args[] = { {MAKE_ARG("node-id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER RESET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER RESET history */ #define CLUSTER_RESET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER RESET tips */ #define CLUSTER_RESET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER RESET key specs */ #define CLUSTER_RESET_Keyspecs NULL #endif /* CLUSTER RESET reset_type argument table */ struct COMMAND_ARG CLUSTER_RESET_reset_type_Subargs[] = { {MAKE_ARG("hard",ARG_TYPE_PURE_TOKEN,-1,"HARD",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("soft",ARG_TYPE_PURE_TOKEN,-1,"SOFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLUSTER RESET argument table */ struct COMMAND_ARG CLUSTER_RESET_Args[] = { {MAKE_ARG("reset-type",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLUSTER_RESET_reset_type_Subargs}, }; /********** CLUSTER SAVECONFIG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER SAVECONFIG history */ #define CLUSTER_SAVECONFIG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER SAVECONFIG tips */ #define CLUSTER_SAVECONFIG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER SAVECONFIG key specs */ #define CLUSTER_SAVECONFIG_Keyspecs NULL #endif /********** CLUSTER SET_CONFIG_EPOCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER SET_CONFIG_EPOCH history */ #define CLUSTER_SET_CONFIG_EPOCH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER SET_CONFIG_EPOCH tips */ #define CLUSTER_SET_CONFIG_EPOCH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER SET_CONFIG_EPOCH key specs */ #define CLUSTER_SET_CONFIG_EPOCH_Keyspecs NULL #endif /* CLUSTER SET_CONFIG_EPOCH argument table */ struct COMMAND_ARG CLUSTER_SET_CONFIG_EPOCH_Args[] = { {MAKE_ARG("config-epoch",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER SETSLOT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER SETSLOT history */ #define CLUSTER_SETSLOT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER SETSLOT tips */ #define CLUSTER_SETSLOT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER SETSLOT key specs */ #define CLUSTER_SETSLOT_Keyspecs NULL #endif /* CLUSTER SETSLOT subcommand argument table */ struct COMMAND_ARG CLUSTER_SETSLOT_subcommand_Subargs[] = { {MAKE_ARG("importing",ARG_TYPE_STRING,-1,"IMPORTING",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="node-id"}, {MAKE_ARG("migrating",ARG_TYPE_STRING,-1,"MIGRATING",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="node-id"}, {MAKE_ARG("node",ARG_TYPE_STRING,-1,"NODE",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="node-id"}, {MAKE_ARG("stable",ARG_TYPE_PURE_TOKEN,-1,"STABLE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLUSTER SETSLOT argument table */ struct COMMAND_ARG CLUSTER_SETSLOT_Args[] = { {MAKE_ARG("slot",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("subcommand",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=CLUSTER_SETSLOT_subcommand_Subargs}, }; /********** CLUSTER SHARDS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER SHARDS history */ #define CLUSTER_SHARDS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER SHARDS tips */ const char *CLUSTER_SHARDS_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER SHARDS key specs */ #define CLUSTER_SHARDS_Keyspecs NULL #endif /********** CLUSTER SLAVES ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER SLAVES history */ #define CLUSTER_SLAVES_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER SLAVES tips */ const char *CLUSTER_SLAVES_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER SLAVES key specs */ #define CLUSTER_SLAVES_Keyspecs NULL #endif /* CLUSTER SLAVES argument table */ struct COMMAND_ARG CLUSTER_SLAVES_Args[] = { {MAKE_ARG("node-id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLUSTER SLOTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER SLOTS history */ commandHistory CLUSTER_SLOTS_History[] = { {"4.0.0","Added node IDs."}, {"7.0.0","Added additional networking metadata field."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER SLOTS tips */ const char *CLUSTER_SLOTS_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER SLOTS key specs */ #define CLUSTER_SLOTS_Keyspecs NULL #endif /* CLUSTER command table */ struct COMMAND_STRUCT CLUSTER_Subcommands[] = { {MAKE_CMD("addslots","Assigns new hash slots to a node.","O(N) where N is the total number of hash slot arguments","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_ADDSLOTS_History,0,CLUSTER_ADDSLOTS_Tips,0,clusterCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_ADDSLOTS_Keyspecs,0,NULL,1),.args=CLUSTER_ADDSLOTS_Args}, {MAKE_CMD("addslotsrange","Assigns new hash slot ranges to a node.","O(N) where N is the total number of the slots between the start slot and end slot arguments.","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_ADDSLOTSRANGE_History,0,CLUSTER_ADDSLOTSRANGE_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_ADDSLOTSRANGE_Keyspecs,0,NULL,1),.args=CLUSTER_ADDSLOTSRANGE_Args}, {MAKE_CMD("bumpepoch","Advances the cluster config epoch.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_BUMPEPOCH_History,0,CLUSTER_BUMPEPOCH_Tips,1,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_BUMPEPOCH_Keyspecs,0,NULL,0)}, {MAKE_CMD("count-failure-reports","Returns the number of active failure reports active for a node.","O(N) where N is the number of failure reports","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_COUNT_FAILURE_REPORTS_History,0,CLUSTER_COUNT_FAILURE_REPORTS_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,CLUSTER_COUNT_FAILURE_REPORTS_Keyspecs,0,NULL,1),.args=CLUSTER_COUNT_FAILURE_REPORTS_Args}, {MAKE_CMD("countkeysinslot","Returns the number of keys in a hash slot.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_COUNTKEYSINSLOT_History,0,CLUSTER_COUNTKEYSINSLOT_Tips,0,clusterCommand,3,CMD_STALE,0,CLUSTER_COUNTKEYSINSLOT_Keyspecs,0,NULL,1),.args=CLUSTER_COUNTKEYSINSLOT_Args}, {MAKE_CMD("delslots","Sets hash slots as unbound for a node.","O(N) where N is the total number of hash slot arguments","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_DELSLOTS_History,0,CLUSTER_DELSLOTS_Tips,0,clusterCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_DELSLOTS_Keyspecs,0,NULL,1),.args=CLUSTER_DELSLOTS_Args}, {MAKE_CMD("delslotsrange","Sets hash slot ranges as unbound for a node.","O(N) where N is the total number of the slots between the start slot and end slot arguments.","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_DELSLOTSRANGE_History,0,CLUSTER_DELSLOTSRANGE_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_DELSLOTSRANGE_Keyspecs,0,NULL,1),.args=CLUSTER_DELSLOTSRANGE_Args}, {MAKE_CMD("failover","Forces a replica to perform a manual failover of its master.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FAILOVER_History,0,CLUSTER_FAILOVER_Tips,0,clusterCommand,-2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_FAILOVER_Keyspecs,0,NULL,1),.args=CLUSTER_FAILOVER_Args}, {MAKE_CMD("flushslots","Deletes all slots information from a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FLUSHSLOTS_History,0,CLUSTER_FLUSHSLOTS_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_FLUSHSLOTS_Keyspecs,0,NULL,0)}, {MAKE_CMD("forget","Removes a node from the nodes table.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_FORGET_History,0,CLUSTER_FORGET_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_FORGET_Keyspecs,0,NULL,1),.args=CLUSTER_FORGET_Args}, {MAKE_CMD("getkeysinslot","Returns the key names in a hash slot.","O(N) where N is the number of requested keys","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_GETKEYSINSLOT_History,0,CLUSTER_GETKEYSINSLOT_Tips,1,clusterCommand,4,CMD_STALE,0,CLUSTER_GETKEYSINSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_GETKEYSINSLOT_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_HELP_History,0,CLUSTER_HELP_Tips,0,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("info","Returns information about the state of a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_INFO_History,0,CLUSTER_INFO_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_INFO_Keyspecs,0,NULL,0)}, {MAKE_CMD("keyslot","Returns the hash slot for a key.","O(N) where N is the number of bytes in the key","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_KEYSLOT_History,0,CLUSTER_KEYSLOT_Tips,0,clusterCommand,3,CMD_STALE,0,CLUSTER_KEYSLOT_Keyspecs,0,NULL,1),.args=CLUSTER_KEYSLOT_Args}, {MAKE_CMD("links","Returns a list of all TCP links to and from peer nodes.","O(N) where N is the total number of Cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_LINKS_History,0,CLUSTER_LINKS_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_LINKS_Keyspecs,0,NULL,0)}, {MAKE_CMD("meet","Forces a node to handshake with another node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_MEET_History,1,CLUSTER_MEET_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_MEET_Keyspecs,0,NULL,3),.args=CLUSTER_MEET_Args}, {MAKE_CMD("myid","Returns the ID of a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_MYID_History,0,CLUSTER_MYID_Tips,0,clusterCommand,2,CMD_STALE,0,CLUSTER_MYID_Keyspecs,0,NULL,0)}, {MAKE_CMD("myshardid","Returns the shard ID of a node.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_MYSHARDID_History,0,CLUSTER_MYSHARDID_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_MYSHARDID_Keyspecs,0,NULL,0)}, {MAKE_CMD("nodes","Returns the cluster configuration for a node.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_NODES_History,0,CLUSTER_NODES_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_NODES_Keyspecs,0,NULL,0)}, {MAKE_CMD("replicas","Lists the replica nodes of a master node.","O(N) where N is the number of replicas.","5.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_REPLICAS_History,0,CLUSTER_REPLICAS_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,CLUSTER_REPLICAS_Keyspecs,0,NULL,1),.args=CLUSTER_REPLICAS_Args}, {MAKE_CMD("replicate","Configure a node as replica of a master node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_REPLICATE_History,0,CLUSTER_REPLICATE_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_REPLICATE_Keyspecs,0,NULL,1),.args=CLUSTER_REPLICATE_Args}, {MAKE_CMD("reset","Resets a node.","O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_RESET_History,0,CLUSTER_RESET_Tips,0,clusterCommand,-2,CMD_ADMIN|CMD_STALE|CMD_NOSCRIPT,0,CLUSTER_RESET_Keyspecs,0,NULL,1),.args=CLUSTER_RESET_Args}, {MAKE_CMD("saveconfig","Forces a node to save the cluster configuration to disk.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SAVECONFIG_History,0,CLUSTER_SAVECONFIG_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SAVECONFIG_Keyspecs,0,NULL,0)}, {MAKE_CMD("set-config-epoch","Sets the configuration epoch for a new node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SET_CONFIG_EPOCH_History,0,CLUSTER_SET_CONFIG_EPOCH_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SET_CONFIG_EPOCH_Keyspecs,0,NULL,1),.args=CLUSTER_SET_CONFIG_EPOCH_Args}, {MAKE_CMD("setslot","Binds a hash slot to a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SETSLOT_History,0,CLUSTER_SETSLOT_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SETSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_SETSLOT_Args}, {MAKE_CMD("shards","Returns the mapping of cluster slots to shards.","O(N) where N is the total number of cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SHARDS_History,0,CLUSTER_SHARDS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_SHARDS_Keyspecs,0,NULL,0)}, {MAKE_CMD("slaves","Lists the replica nodes of a master node.","O(N) where N is the number of replicas.","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER REPLICAS`","5.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLAVES_History,0,CLUSTER_SLAVES_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,CLUSTER_SLAVES_Keyspecs,0,NULL,1),.args=CLUSTER_SLAVES_Args}, {MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER SHARDS`","7.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,2,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_SLOTS_Keyspecs,0,NULL,0)}, {0} }; /********** CLUSTER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLUSTER history */ #define CLUSTER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLUSTER tips */ #define CLUSTER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLUSTER key specs */ #define CLUSTER_Keyspecs NULL #endif /********** READONLY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* READONLY history */ #define READONLY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* READONLY tips */ #define READONLY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* READONLY key specs */ #define READONLY_Keyspecs NULL #endif /********** READWRITE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* READWRITE history */ #define READWRITE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* READWRITE tips */ #define READWRITE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* READWRITE key specs */ #define READWRITE_Keyspecs NULL #endif /********** AUTH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* AUTH history */ commandHistory AUTH_History[] = { {"6.0.0","Added ACL style (username and password)."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* AUTH tips */ #define AUTH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* AUTH key specs */ #define AUTH_Keyspecs NULL #endif /* AUTH argument table */ struct COMMAND_ARG AUTH_Args[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,"6.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("password",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLIENT CACHING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT CACHING history */ #define CLIENT_CACHING_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT CACHING tips */ #define CLIENT_CACHING_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT CACHING key specs */ #define CLIENT_CACHING_Keyspecs NULL #endif /* CLIENT CACHING mode argument table */ struct COMMAND_ARG CLIENT_CACHING_mode_Subargs[] = { {MAKE_ARG("yes",ARG_TYPE_PURE_TOKEN,-1,"YES",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("no",ARG_TYPE_PURE_TOKEN,-1,"NO",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT CACHING argument table */ struct COMMAND_ARG CLIENT_CACHING_Args[] = { {MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_CACHING_mode_Subargs}, }; /********** CLIENT GETNAME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT GETNAME history */ #define CLIENT_GETNAME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT GETNAME tips */ #define CLIENT_GETNAME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT GETNAME key specs */ #define CLIENT_GETNAME_Keyspecs NULL #endif /********** CLIENT GETREDIR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT GETREDIR history */ #define CLIENT_GETREDIR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT GETREDIR tips */ #define CLIENT_GETREDIR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT GETREDIR key specs */ #define CLIENT_GETREDIR_Keyspecs NULL #endif /********** CLIENT HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT HELP history */ #define CLIENT_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT HELP tips */ #define CLIENT_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT HELP key specs */ #define CLIENT_HELP_Keyspecs NULL #endif /********** CLIENT ID ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT ID history */ #define CLIENT_ID_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT ID tips */ #define CLIENT_ID_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT ID key specs */ #define CLIENT_ID_Keyspecs NULL #endif /********** CLIENT INFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT INFO history */ #define CLIENT_INFO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT INFO tips */ const char *CLIENT_INFO_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT INFO key specs */ #define CLIENT_INFO_Keyspecs NULL #endif /********** CLIENT KILL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT KILL history */ commandHistory CLIENT_KILL_History[] = { {"2.8.12","Added new filter format."}, {"2.8.12","`ID` option."}, {"3.2.0","Added `master` type in for `TYPE` option."}, {"5.0.0","Replaced `slave` `TYPE` with `replica`. `slave` still supported for backward compatibility."}, {"6.2.0","`LADDR` option."}, {"7.4.0","`MAXAGE` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT KILL tips */ #define CLIENT_KILL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT KILL key specs */ #define CLIENT_KILL_Keyspecs NULL #endif /* CLIENT KILL filter new_format client_type argument table */ struct COMMAND_ARG CLIENT_KILL_filter_new_format_client_type_Subargs[] = { {MAKE_ARG("normal",ARG_TYPE_PURE_TOKEN,-1,"NORMAL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("master",ARG_TYPE_PURE_TOKEN,-1,"MASTER",NULL,"3.2.0",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("slave",ARG_TYPE_PURE_TOKEN,-1,"SLAVE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("replica",ARG_TYPE_PURE_TOKEN,-1,"REPLICA",NULL,"5.0.0",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pubsub",ARG_TYPE_PURE_TOKEN,-1,"PUBSUB",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT KILL filter new_format skipme argument table */ struct COMMAND_ARG CLIENT_KILL_filter_new_format_skipme_Subargs[] = { {MAKE_ARG("yes",ARG_TYPE_PURE_TOKEN,-1,"YES",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("no",ARG_TYPE_PURE_TOKEN,-1,"NO",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT KILL filter new_format argument table */ struct COMMAND_ARG CLIENT_KILL_filter_new_format_Subargs[] = { {MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"2.8.12",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("client-type",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"2.8.12",CMD_ARG_OPTIONAL,5,NULL),.subargs=CLIENT_KILL_filter_new_format_client_type_Subargs}, {MAKE_ARG("username",ARG_TYPE_STRING,-1,"USER",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("addr",ARG_TYPE_STRING,-1,"ADDR",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, {MAKE_ARG("laddr",ARG_TYPE_STRING,-1,"LADDR",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"}, {MAKE_ARG("skipme",ARG_TYPE_ONEOF,-1,"SKIPME",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_KILL_filter_new_format_skipme_Subargs}, {MAKE_ARG("maxage",ARG_TYPE_INTEGER,-1,"MAXAGE",NULL,"7.4.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /* CLIENT KILL filter argument table */ struct COMMAND_ARG CLIENT_KILL_filter_Subargs[] = { {MAKE_ARG("old-format",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,"2.8.12"),.display_text="ip:port"}, {MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,7,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs}, }; /* CLIENT KILL argument table */ struct COMMAND_ARG CLIENT_KILL_Args[] = { {MAKE_ARG("filter",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_KILL_filter_Subargs}, }; /********** CLIENT LIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT LIST history */ commandHistory CLIENT_LIST_History[] = { {"2.8.12","Added unique client `id` field."}, {"5.0.0","Added optional `TYPE` filter."}, {"6.0.0","Added `user` field."}, {"6.2.0","Added `argv-mem`, `tot-mem`, `laddr` and `redir` fields and the optional `ID` filter."}, {"7.0.0","Added `resp`, `multi-mem`, `rbs` and `rbp` fields."}, {"7.0.3","Added `ssub` field."}, {"7.2.0","Added `lib-name` and `lib-ver` fields."}, {"7.4.0","Added `watch` field."}, {"8.0.0","Added `io-thread` field."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT LIST tips */ const char *CLIENT_LIST_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT LIST key specs */ #define CLIENT_LIST_Keyspecs NULL #endif /* CLIENT LIST client_type argument table */ struct COMMAND_ARG CLIENT_LIST_client_type_Subargs[] = { {MAKE_ARG("normal",ARG_TYPE_PURE_TOKEN,-1,"NORMAL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("master",ARG_TYPE_PURE_TOKEN,-1,"MASTER",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("replica",ARG_TYPE_PURE_TOKEN,-1,"REPLICA",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pubsub",ARG_TYPE_PURE_TOKEN,-1,"PUBSUB",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT LIST argument table */ struct COMMAND_ARG CLIENT_LIST_Args[] = { {MAKE_ARG("client-type",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"5.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=CLIENT_LIST_client_type_Subargs}, {MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"6.2.0",CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** CLIENT NO_EVICT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT NO_EVICT history */ #define CLIENT_NO_EVICT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT NO_EVICT tips */ #define CLIENT_NO_EVICT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT NO_EVICT key specs */ #define CLIENT_NO_EVICT_Keyspecs NULL #endif /* CLIENT NO_EVICT enabled argument table */ struct COMMAND_ARG CLIENT_NO_EVICT_enabled_Subargs[] = { {MAKE_ARG("on",ARG_TYPE_PURE_TOKEN,-1,"ON",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("off",ARG_TYPE_PURE_TOKEN,-1,"OFF",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT NO_EVICT argument table */ struct COMMAND_ARG CLIENT_NO_EVICT_Args[] = { {MAKE_ARG("enabled",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_NO_EVICT_enabled_Subargs}, }; /********** CLIENT NO_TOUCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT NO_TOUCH history */ #define CLIENT_NO_TOUCH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT NO_TOUCH tips */ #define CLIENT_NO_TOUCH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT NO_TOUCH key specs */ #define CLIENT_NO_TOUCH_Keyspecs NULL #endif /* CLIENT NO_TOUCH enabled argument table */ struct COMMAND_ARG CLIENT_NO_TOUCH_enabled_Subargs[] = { {MAKE_ARG("on",ARG_TYPE_PURE_TOKEN,-1,"ON",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("off",ARG_TYPE_PURE_TOKEN,-1,"OFF",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT NO_TOUCH argument table */ struct COMMAND_ARG CLIENT_NO_TOUCH_Args[] = { {MAKE_ARG("enabled",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_NO_TOUCH_enabled_Subargs}, }; /********** CLIENT PAUSE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT PAUSE history */ commandHistory CLIENT_PAUSE_History[] = { {"6.2.0","`CLIENT PAUSE WRITE` mode added along with the `mode` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT PAUSE tips */ #define CLIENT_PAUSE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT PAUSE key specs */ #define CLIENT_PAUSE_Keyspecs NULL #endif /* CLIENT PAUSE mode argument table */ struct COMMAND_ARG CLIENT_PAUSE_mode_Subargs[] = { {MAKE_ARG("write",ARG_TYPE_PURE_TOKEN,-1,"WRITE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("all",ARG_TYPE_PURE_TOKEN,-1,"ALL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT PAUSE argument table */ struct COMMAND_ARG CLIENT_PAUSE_Args[] = { {MAKE_ARG("timeout",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_PAUSE_mode_Subargs}, }; /********** CLIENT REPLY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT REPLY history */ #define CLIENT_REPLY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT REPLY tips */ #define CLIENT_REPLY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT REPLY key specs */ #define CLIENT_REPLY_Keyspecs NULL #endif /* CLIENT REPLY action argument table */ struct COMMAND_ARG CLIENT_REPLY_action_Subargs[] = { {MAKE_ARG("on",ARG_TYPE_PURE_TOKEN,-1,"ON",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("off",ARG_TYPE_PURE_TOKEN,-1,"OFF",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("skip",ARG_TYPE_PURE_TOKEN,-1,"SKIP",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT REPLY argument table */ struct COMMAND_ARG CLIENT_REPLY_Args[] = { {MAKE_ARG("action",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=CLIENT_REPLY_action_Subargs}, }; /********** CLIENT SETINFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT SETINFO history */ #define CLIENT_SETINFO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT SETINFO tips */ const char *CLIENT_SETINFO_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT SETINFO key specs */ #define CLIENT_SETINFO_Keyspecs NULL #endif /* CLIENT SETINFO attr argument table */ struct COMMAND_ARG CLIENT_SETINFO_attr_Subargs[] = { {MAKE_ARG("libname",ARG_TYPE_STRING,-1,"LIB-NAME",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("libver",ARG_TYPE_STRING,-1,"LIB-VER",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT SETINFO argument table */ struct COMMAND_ARG CLIENT_SETINFO_Args[] = { {MAKE_ARG("attr",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_SETINFO_attr_Subargs}, }; /********** CLIENT SETNAME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT SETNAME history */ #define CLIENT_SETNAME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT SETNAME tips */ const char *CLIENT_SETNAME_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT SETNAME key specs */ #define CLIENT_SETNAME_Keyspecs NULL #endif /* CLIENT SETNAME argument table */ struct COMMAND_ARG CLIENT_SETNAME_Args[] = { {MAKE_ARG("connection-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** CLIENT TRACKING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT TRACKING history */ #define CLIENT_TRACKING_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT TRACKING tips */ #define CLIENT_TRACKING_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT TRACKING key specs */ #define CLIENT_TRACKING_Keyspecs NULL #endif /* CLIENT TRACKING status argument table */ struct COMMAND_ARG CLIENT_TRACKING_status_Subargs[] = { {MAKE_ARG("on",ARG_TYPE_PURE_TOKEN,-1,"ON",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("off",ARG_TYPE_PURE_TOKEN,-1,"OFF",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT TRACKING argument table */ struct COMMAND_ARG CLIENT_TRACKING_Args[] = { {MAKE_ARG("status",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=CLIENT_TRACKING_status_Subargs}, {MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,"REDIRECT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("prefix",ARG_TYPE_STRING,-1,"PREFIX",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE|CMD_ARG_MULTIPLE_TOKEN,0,NULL)}, {MAKE_ARG("bcast",ARG_TYPE_PURE_TOKEN,-1,"BCAST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("optin",ARG_TYPE_PURE_TOKEN,-1,"OPTIN",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("optout",ARG_TYPE_PURE_TOKEN,-1,"OPTOUT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("noloop",ARG_TYPE_PURE_TOKEN,-1,"NOLOOP",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** CLIENT TRACKINGINFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT TRACKINGINFO history */ #define CLIENT_TRACKINGINFO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT TRACKINGINFO tips */ #define CLIENT_TRACKINGINFO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT TRACKINGINFO key specs */ #define CLIENT_TRACKINGINFO_Keyspecs NULL #endif /********** CLIENT UNBLOCK ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT UNBLOCK history */ #define CLIENT_UNBLOCK_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT UNBLOCK tips */ #define CLIENT_UNBLOCK_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT UNBLOCK key specs */ #define CLIENT_UNBLOCK_Keyspecs NULL #endif /* CLIENT UNBLOCK unblock_type argument table */ struct COMMAND_ARG CLIENT_UNBLOCK_unblock_type_Subargs[] = { {MAKE_ARG("timeout",ARG_TYPE_PURE_TOKEN,-1,"TIMEOUT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("error",ARG_TYPE_PURE_TOKEN,-1,"ERROR",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CLIENT UNBLOCK argument table */ struct COMMAND_ARG CLIENT_UNBLOCK_Args[] = { {MAKE_ARG("client-id",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unblock-type",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_UNBLOCK_unblock_type_Subargs}, }; /********** CLIENT UNPAUSE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT UNPAUSE history */ #define CLIENT_UNPAUSE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT UNPAUSE tips */ #define CLIENT_UNPAUSE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT UNPAUSE key specs */ #define CLIENT_UNPAUSE_Keyspecs NULL #endif /* CLIENT command table */ struct COMMAND_STRUCT CLIENT_Subcommands[] = { {MAKE_CMD("caching","Instructs the server whether to track the keys in the next request.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,0,CLIENT_CACHING_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_CACHING_Keyspecs,0,NULL,1),.args=CLIENT_CACHING_Args}, {MAKE_CMD("getname","Returns the name of the connection.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,0,CLIENT_GETNAME_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETNAME_Keyspecs,0,NULL,0)}, {MAKE_CMD("getredir","Returns the client ID to which the connection's tracking notifications are redirected.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,0,CLIENT_GETREDIR_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_GETREDIR_Keyspecs,0,NULL,0)}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)}, {MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)}, {MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,6,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args}, {MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,9,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args}, {MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args}, {MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args}, {MAKE_CMD("pause","Suspends commands processing.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,1,CLIENT_PAUSE_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_PAUSE_Keyspecs,0,NULL,2),.args=CLIENT_PAUSE_Args}, {MAKE_CMD("reply","Instructs the server whether to reply to commands.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_REPLY_History,0,CLIENT_REPLY_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_REPLY_Keyspecs,0,NULL,1),.args=CLIENT_REPLY_Args}, {MAKE_CMD("setinfo","Sets information specific to the client or connection.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_SETINFO_History,0,CLIENT_SETINFO_Tips,2,clientSetinfoCommand,4,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_SETINFO_Keyspecs,0,NULL,1),.args=CLIENT_SETINFO_Args}, {MAKE_CMD("setname","Sets the connection name.","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_SETNAME_History,0,CLIENT_SETNAME_Tips,2,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_SETNAME_Keyspecs,0,NULL,1),.args=CLIENT_SETNAME_Args}, {MAKE_CMD("tracking","Controls server-assisted client-side caching for the connection.","O(1). Some options may introduce additional complexity.","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_TRACKING_History,0,CLIENT_TRACKING_Tips,0,clientCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_TRACKING_Keyspecs,0,NULL,7),.args=CLIENT_TRACKING_Args}, {MAKE_CMD("trackinginfo","Returns information about server-assisted client-side caching for the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_TRACKINGINFO_History,0,CLIENT_TRACKINGINFO_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_TRACKINGINFO_Keyspecs,0,NULL,0)}, {MAKE_CMD("unblock","Unblocks a client blocked by a blocking command from a different connection.","O(log N) where N is the number of client connections","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_UNBLOCK_History,0,CLIENT_UNBLOCK_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_UNBLOCK_Keyspecs,0,NULL,2),.args=CLIENT_UNBLOCK_Args}, {MAKE_CMD("unpause","Resumes processing commands from paused clients.","O(N) Where N is the number of paused clients","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_UNPAUSE_History,0,CLIENT_UNPAUSE_Tips,0,clientCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_UNPAUSE_Keyspecs,0,NULL,0)}, {0} }; /********** CLIENT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CLIENT history */ #define CLIENT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CLIENT tips */ #define CLIENT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CLIENT key specs */ #define CLIENT_Keyspecs NULL #endif /********** ECHO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ECHO history */ #define ECHO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ECHO tips */ #define ECHO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ECHO key specs */ #define ECHO_Keyspecs NULL #endif /* ECHO argument table */ struct COMMAND_ARG ECHO_Args[] = { {MAKE_ARG("message",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HELLO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HELLO history */ commandHistory HELLO_History[] = { {"6.2.0","`protover` made optional; when called without arguments the command reports the current connection's context."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* HELLO tips */ #define HELLO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HELLO key specs */ #define HELLO_Keyspecs NULL #endif /* HELLO arguments auth argument table */ struct COMMAND_ARG HELLO_arguments_auth_Subargs[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("password",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HELLO arguments argument table */ struct COMMAND_ARG HELLO_arguments_Subargs[] = { {MAKE_ARG("protover",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("auth",ARG_TYPE_BLOCK,-1,"AUTH",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=HELLO_arguments_auth_Subargs}, {MAKE_ARG("clientname",ARG_TYPE_STRING,-1,"SETNAME",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* HELLO argument table */ struct COMMAND_ARG HELLO_Args[] = { {MAKE_ARG("arguments",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=HELLO_arguments_Subargs}, }; /********** PING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PING history */ #define PING_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PING tips */ const char *PING_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PING key specs */ #define PING_Keyspecs NULL #endif /* PING argument table */ struct COMMAND_ARG PING_Args[] = { {MAKE_ARG("message",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** QUIT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* QUIT history */ #define QUIT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* QUIT tips */ #define QUIT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* QUIT key specs */ #define QUIT_Keyspecs NULL #endif /********** RESET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RESET history */ #define RESET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* RESET tips */ #define RESET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RESET key specs */ #define RESET_Keyspecs NULL #endif /********** SELECT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SELECT history */ #define SELECT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SELECT tips */ #define SELECT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SELECT key specs */ #define SELECT_Keyspecs NULL #endif /* SELECT argument table */ struct COMMAND_ARG SELECT_Args[] = { {MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** COPY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COPY history */ #define COPY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COPY tips */ #define COPY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COPY key specs */ keySpec COPY_Keyspecs[2] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* COPY argument table */ struct COMMAND_ARG COPY_Args[] = { {MAKE_ARG("source",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination-db",ARG_TYPE_INTEGER,-1,"DB",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** DEL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DEL history */ #define DEL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DEL tips */ const char *DEL_Tips[] = { "request_policy:multi_shard", "response_policy:agg_sum", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DEL key specs */ keySpec DEL_Keyspecs[1] = { {NULL,CMD_KEY_RM|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* DEL argument table */ struct COMMAND_ARG DEL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** DUMP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DUMP history */ #define DUMP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DUMP tips */ const char *DUMP_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DUMP key specs */ keySpec DUMP_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* DUMP argument table */ struct COMMAND_ARG DUMP_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** EXISTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EXISTS history */ commandHistory EXISTS_History[] = { {"3.0.3","Accepts multiple `key` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* EXISTS tips */ const char *EXISTS_Tips[] = { "request_policy:multi_shard", "response_policy:agg_sum", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EXISTS key specs */ keySpec EXISTS_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* EXISTS argument table */ struct COMMAND_ARG EXISTS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** EXPIRE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EXPIRE history */ commandHistory EXPIRE_History[] = { {"7.0.0","Added options: `NX`, `XX`, `GT` and `LT`."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* EXPIRE tips */ #define EXPIRE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EXPIRE key specs */ keySpec EXPIRE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* EXPIRE condition argument table */ struct COMMAND_ARG EXPIRE_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* EXPIRE argument table */ struct COMMAND_ARG EXPIRE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=EXPIRE_condition_Subargs}, }; /********** EXPIREAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EXPIREAT history */ commandHistory EXPIREAT_History[] = { {"7.0.0","Added options: `NX`, `XX`, `GT` and `LT`."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* EXPIREAT tips */ #define EXPIREAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EXPIREAT key specs */ keySpec EXPIREAT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* EXPIREAT condition argument table */ struct COMMAND_ARG EXPIREAT_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* EXPIREAT argument table */ struct COMMAND_ARG EXPIREAT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=EXPIREAT_condition_Subargs}, }; /********** EXPIRETIME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EXPIRETIME history */ #define EXPIRETIME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* EXPIRETIME tips */ #define EXPIRETIME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EXPIRETIME key specs */ keySpec EXPIRETIME_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* EXPIRETIME argument table */ struct COMMAND_ARG EXPIRETIME_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** KEYS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* KEYS history */ #define KEYS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* KEYS tips */ const char *KEYS_Tips[] = { "request_policy:all_shards", "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* KEYS key specs */ #define KEYS_Keyspecs NULL #endif /* KEYS argument table */ struct COMMAND_ARG KEYS_Args[] = { {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** MIGRATE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MIGRATE history */ commandHistory MIGRATE_History[] = { {"3.0.0","Added the `COPY` and `REPLACE` options."}, {"3.0.6","Added the `KEYS` option."}, {"4.0.7","Added the `AUTH` option."}, {"6.0.0","Added the `AUTH2` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* MIGRATE tips */ const char *MIGRATE_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MIGRATE key specs */ keySpec MIGRATE_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={3},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE|CMD_KEY_INCOMPLETE,KSPEC_BS_KEYWORD,.bs.keyword={"KEYS",-2},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* MIGRATE key_selector argument table */ struct COMMAND_ARG MIGRATE_key_selector_Subargs[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("empty-string",ARG_TYPE_PURE_TOKEN,-1,"""",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* MIGRATE authentication auth2 argument table */ struct COMMAND_ARG MIGRATE_authentication_auth2_Subargs[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("password",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* MIGRATE authentication argument table */ struct COMMAND_ARG MIGRATE_authentication_Subargs[] = { {MAKE_ARG("auth",ARG_TYPE_STRING,-1,"AUTH",NULL,"4.0.7",CMD_ARG_NONE,0,NULL),.display_text="password"}, {MAKE_ARG("auth2",ARG_TYPE_BLOCK,-1,"AUTH2",NULL,"6.0.0",CMD_ARG_NONE,2,NULL),.subargs=MIGRATE_authentication_auth2_Subargs}, }; /* MIGRATE argument table */ struct COMMAND_ARG MIGRATE_Args[] = { {MAKE_ARG("host",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key-selector",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=MIGRATE_key_selector_Subargs}, {MAKE_ARG("destination-db",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("copy",ARG_TYPE_PURE_TOKEN,-1,"COPY",NULL,"3.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,"3.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("authentication",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=MIGRATE_authentication_Subargs}, {MAKE_ARG("keys",ARG_TYPE_KEY,1,"KEYS",NULL,"3.0.6",CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL),.display_text="key"}, }; /********** MOVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MOVE history */ #define MOVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MOVE tips */ #define MOVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MOVE key specs */ keySpec MOVE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* MOVE argument table */ struct COMMAND_ARG MOVE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("db",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** OBJECT ENCODING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* OBJECT ENCODING history */ #define OBJECT_ENCODING_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* OBJECT ENCODING tips */ const char *OBJECT_ENCODING_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* OBJECT ENCODING key specs */ keySpec OBJECT_ENCODING_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* OBJECT ENCODING argument table */ struct COMMAND_ARG OBJECT_ENCODING_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** OBJECT FREQ ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* OBJECT FREQ history */ #define OBJECT_FREQ_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* OBJECT FREQ tips */ const char *OBJECT_FREQ_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* OBJECT FREQ key specs */ keySpec OBJECT_FREQ_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* OBJECT FREQ argument table */ struct COMMAND_ARG OBJECT_FREQ_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** OBJECT HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* OBJECT HELP history */ #define OBJECT_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* OBJECT HELP tips */ #define OBJECT_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* OBJECT HELP key specs */ #define OBJECT_HELP_Keyspecs NULL #endif /********** OBJECT IDLETIME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* OBJECT IDLETIME history */ #define OBJECT_IDLETIME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* OBJECT IDLETIME tips */ const char *OBJECT_IDLETIME_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* OBJECT IDLETIME key specs */ keySpec OBJECT_IDLETIME_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* OBJECT IDLETIME argument table */ struct COMMAND_ARG OBJECT_IDLETIME_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** OBJECT REFCOUNT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* OBJECT REFCOUNT history */ #define OBJECT_REFCOUNT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* OBJECT REFCOUNT tips */ const char *OBJECT_REFCOUNT_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* OBJECT REFCOUNT key specs */ keySpec OBJECT_REFCOUNT_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* OBJECT REFCOUNT argument table */ struct COMMAND_ARG OBJECT_REFCOUNT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* OBJECT command table */ struct COMMAND_STRUCT OBJECT_Subcommands[] = { {MAKE_CMD("encoding","Returns the internal encoding of a Redis object.","O(1)","2.2.3",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,OBJECT_ENCODING_History,0,OBJECT_ENCODING_Tips,1,objectCommand,3,CMD_READONLY,ACL_CATEGORY_KEYSPACE,OBJECT_ENCODING_Keyspecs,1,NULL,1),.args=OBJECT_ENCODING_Args}, {MAKE_CMD("freq","Returns the logarithmic access frequency counter of a Redis object.","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,OBJECT_FREQ_History,0,OBJECT_FREQ_Tips,1,objectCommand,3,CMD_READONLY,ACL_CATEGORY_KEYSPACE,OBJECT_FREQ_Keyspecs,1,NULL,1),.args=OBJECT_FREQ_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,OBJECT_HELP_History,0,OBJECT_HELP_Tips,0,objectCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_KEYSPACE,OBJECT_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("idletime","Returns the time since the last access to a Redis object.","O(1)","2.2.3",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,OBJECT_IDLETIME_History,0,OBJECT_IDLETIME_Tips,1,objectCommand,3,CMD_READONLY,ACL_CATEGORY_KEYSPACE,OBJECT_IDLETIME_Keyspecs,1,NULL,1),.args=OBJECT_IDLETIME_Args}, {MAKE_CMD("refcount","Returns the reference count of a value of a key.","O(1)","2.2.3",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,OBJECT_REFCOUNT_History,0,OBJECT_REFCOUNT_Tips,1,objectCommand,3,CMD_READONLY,ACL_CATEGORY_KEYSPACE,OBJECT_REFCOUNT_Keyspecs,1,NULL,1),.args=OBJECT_REFCOUNT_Args}, {0} }; /********** OBJECT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* OBJECT history */ #define OBJECT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* OBJECT tips */ #define OBJECT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* OBJECT key specs */ #define OBJECT_Keyspecs NULL #endif /********** PERSIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PERSIST history */ #define PERSIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PERSIST tips */ #define PERSIST_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PERSIST key specs */ keySpec PERSIST_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PERSIST argument table */ struct COMMAND_ARG PERSIST_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** PEXPIRE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PEXPIRE history */ commandHistory PEXPIRE_History[] = { {"7.0.0","Added options: `NX`, `XX`, `GT` and `LT`."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* PEXPIRE tips */ #define PEXPIRE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PEXPIRE key specs */ keySpec PEXPIRE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PEXPIRE condition argument table */ struct COMMAND_ARG PEXPIRE_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* PEXPIRE argument table */ struct COMMAND_ARG PEXPIRE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=PEXPIRE_condition_Subargs}, }; /********** PEXPIREAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PEXPIREAT history */ commandHistory PEXPIREAT_History[] = { {"7.0.0","Added options: `NX`, `XX`, `GT` and `LT`."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* PEXPIREAT tips */ #define PEXPIREAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PEXPIREAT key specs */ keySpec PEXPIREAT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PEXPIREAT condition argument table */ struct COMMAND_ARG PEXPIREAT_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* PEXPIREAT argument table */ struct COMMAND_ARG PEXPIREAT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,4,NULL),.subargs=PEXPIREAT_condition_Subargs}, }; /********** PEXPIRETIME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PEXPIRETIME history */ #define PEXPIRETIME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PEXPIRETIME tips */ #define PEXPIRETIME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PEXPIRETIME key specs */ keySpec PEXPIRETIME_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PEXPIRETIME argument table */ struct COMMAND_ARG PEXPIRETIME_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** PTTL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PTTL history */ commandHistory PTTL_History[] = { {"2.8.0","Added the -2 reply."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* PTTL tips */ const char *PTTL_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PTTL key specs */ keySpec PTTL_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PTTL argument table */ struct COMMAND_ARG PTTL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** RANDOMKEY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RANDOMKEY history */ #define RANDOMKEY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* RANDOMKEY tips */ const char *RANDOMKEY_Tips[] = { "request_policy:all_shards", "response_policy:special", "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RANDOMKEY key specs */ #define RANDOMKEY_Keyspecs NULL #endif /********** RENAME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RENAME history */ #define RENAME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* RENAME tips */ #define RENAME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RENAME key specs */ keySpec RENAME_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RENAME argument table */ struct COMMAND_ARG RENAME_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("newkey",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** RENAMENX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RENAMENX history */ commandHistory RENAMENX_History[] = { {"3.2.0","The command no longer returns an error when source and destination names are the same."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* RENAMENX tips */ #define RENAMENX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RENAMENX key specs */ keySpec RENAMENX_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RENAMENX argument table */ struct COMMAND_ARG RENAMENX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("newkey",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** RESTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RESTORE history */ commandHistory RESTORE_History[] = { {"3.0.0","Added the `REPLACE` modifier."}, {"5.0.0","Added the `ABSTTL` modifier."}, {"5.0.0","Added the `IDLETIME` and `FREQ` options."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* RESTORE tips */ #define RESTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RESTORE key specs */ keySpec RESTORE_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RESTORE argument table */ struct COMMAND_ARG RESTORE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ttl",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("serialized-value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,"3.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("absttl",ARG_TYPE_PURE_TOKEN,-1,"ABSTTL",NULL,"5.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"IDLETIME",NULL,"5.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("frequency",ARG_TYPE_INTEGER,-1,"FREQ",NULL,"5.0.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SCAN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCAN history */ commandHistory SCAN_History[] = { {"6.0.0","Added the `TYPE` subcommand."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCAN tips */ const char *SCAN_Tips[] = { "nondeterministic_output", "request_policy:special", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCAN key specs */ #define SCAN_Keyspecs NULL #endif /* SCAN argument table */ struct COMMAND_ARG SCAN_Args[] = { {MAKE_ARG("cursor",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,"MATCH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("type",ARG_TYPE_STRING,-1,"TYPE",NULL,"6.0.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SORT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SORT history */ #define SORT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SORT tips */ #define SORT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SORT key specs */ keySpec SORT_Keyspecs[3] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{"For the optional BY/GET keyword. It is marked 'unknown' because the key names derive from the content of the key we sort",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_UNKNOWN,{{0}},KSPEC_FK_UNKNOWN,{{0}}},{"For the optional STORE keyword. It is marked 'unknown' because the keyword can appear anywhere in the argument array",CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_UNKNOWN,{{0}},KSPEC_FK_UNKNOWN,{{0}}} }; #endif /* SORT limit argument table */ struct COMMAND_ARG SORT_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SORT order argument table */ struct COMMAND_ARG SORT_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SORT argument table */ struct COMMAND_ARG SORT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("by-pattern",ARG_TYPE_PATTERN,1,"BY",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="pattern"}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=SORT_limit_Subargs}, {MAKE_ARG("get-pattern",ARG_TYPE_PATTERN,1,"GET",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE|CMD_ARG_MULTIPLE_TOKEN,0,NULL),.display_text="pattern"}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=SORT_order_Subargs}, {MAKE_ARG("sorting",ARG_TYPE_PURE_TOKEN,-1,"ALPHA",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,2,"STORE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SORT_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SORT_RO history */ #define SORT_RO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SORT_RO tips */ #define SORT_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SORT_RO key specs */ keySpec SORT_RO_Keyspecs[2] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{"For the optional BY/GET keyword. It is marked 'unknown' because the key names derive from the content of the key we sort",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_UNKNOWN,{{0}},KSPEC_FK_UNKNOWN,{{0}}} }; #endif /* SORT_RO limit argument table */ struct COMMAND_ARG SORT_RO_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SORT_RO order argument table */ struct COMMAND_ARG SORT_RO_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SORT_RO argument table */ struct COMMAND_ARG SORT_RO_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("by-pattern",ARG_TYPE_PATTERN,1,"BY",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="pattern"}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=SORT_RO_limit_Subargs}, {MAKE_ARG("get-pattern",ARG_TYPE_PATTERN,1,"GET",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE|CMD_ARG_MULTIPLE_TOKEN,0,NULL),.display_text="pattern"}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=SORT_RO_order_Subargs}, {MAKE_ARG("sorting",ARG_TYPE_PURE_TOKEN,-1,"ALPHA",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** TOUCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* TOUCH history */ #define TOUCH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* TOUCH tips */ const char *TOUCH_Tips[] = { "request_policy:multi_shard", "response_policy:agg_sum", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* TOUCH key specs */ keySpec TOUCH_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* TOUCH argument table */ struct COMMAND_ARG TOUCH_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** TTL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* TTL history */ commandHistory TTL_History[] = { {"2.8.0","Added the -2 reply."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* TTL tips */ const char *TTL_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* TTL key specs */ keySpec TTL_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* TTL argument table */ struct COMMAND_ARG TTL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** TYPE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* TYPE history */ #define TYPE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* TYPE tips */ #define TYPE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* TYPE key specs */ keySpec TYPE_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* TYPE argument table */ struct COMMAND_ARG TYPE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** UNLINK ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* UNLINK history */ #define UNLINK_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* UNLINK tips */ const char *UNLINK_Tips[] = { "request_policy:multi_shard", "response_policy:agg_sum", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* UNLINK key specs */ keySpec UNLINK_Keyspecs[1] = { {NULL,CMD_KEY_RM|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* UNLINK argument table */ struct COMMAND_ARG UNLINK_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** WAIT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* WAIT history */ #define WAIT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* WAIT tips */ const char *WAIT_Tips[] = { "request_policy:all_shards", "response_policy:agg_min", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* WAIT key specs */ #define WAIT_Keyspecs NULL #endif /* WAIT argument table */ struct COMMAND_ARG WAIT_Args[] = { {MAKE_ARG("numreplicas",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** WAITAOF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* WAITAOF history */ #define WAITAOF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* WAITAOF tips */ const char *WAITAOF_Tips[] = { "request_policy:all_shards", "response_policy:agg_min", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* WAITAOF key specs */ #define WAITAOF_Keyspecs NULL #endif /* WAITAOF argument table */ struct COMMAND_ARG WAITAOF_Args[] = { {MAKE_ARG("numlocal",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numreplicas",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** GEOADD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEOADD history */ commandHistory GEOADD_History[] = { {"6.2.0","Added the `CH`, `NX` and `XX` options."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEOADD tips */ #define GEOADD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEOADD key specs */ keySpec GEOADD_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEOADD condition argument table */ struct COMMAND_ARG GEOADD_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOADD data argument table */ struct COMMAND_ARG GEOADD_data_Subargs[] = { {MAKE_ARG("longitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("latitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOADD argument table */ struct COMMAND_ARG GEOADD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=GEOADD_condition_Subargs}, {MAKE_ARG("change",ARG_TYPE_PURE_TOKEN,-1,"CH",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,3,NULL),.subargs=GEOADD_data_Subargs}, }; /********** GEODIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEODIST history */ #define GEODIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEODIST tips */ #define GEODIST_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEODIST key specs */ keySpec GEODIST_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEODIST unit argument table */ struct COMMAND_ARG GEODIST_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEODIST argument table */ struct COMMAND_ARG GEODIST_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member1",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member2",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=GEODIST_unit_Subargs}, }; /********** GEOHASH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEOHASH history */ #define GEOHASH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEOHASH tips */ #define GEOHASH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEOHASH key specs */ keySpec GEOHASH_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEOHASH argument table */ struct COMMAND_ARG GEOHASH_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** GEOPOS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEOPOS history */ #define GEOPOS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEOPOS tips */ #define GEOPOS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEOPOS key specs */ keySpec GEOPOS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEOPOS argument table */ struct COMMAND_ARG GEOPOS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** GEORADIUS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEORADIUS history */ commandHistory GEORADIUS_History[] = { {"6.2.0","Added the `ANY` option for `COUNT`."}, {"7.0.0","Added support for uppercase unit names."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEORADIUS tips */ #define GEORADIUS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEORADIUS key specs */ keySpec GEORADIUS_Keyspecs[3] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_KEYWORD,.bs.keyword={"STORE",6},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_KEYWORD,.bs.keyword={"STOREDIST",6},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEORADIUS unit argument table */ struct COMMAND_ARG GEORADIUS_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUS count_block argument table */ struct COMMAND_ARG GEORADIUS_count_block_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("any",ARG_TYPE_PURE_TOKEN,-1,"ANY",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /* GEORADIUS order argument table */ struct COMMAND_ARG GEORADIUS_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUS store argument table */ struct COMMAND_ARG GEORADIUS_store_Subargs[] = { {MAKE_ARG("storekey",ARG_TYPE_KEY,1,"STORE",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="key"}, {MAKE_ARG("storedistkey",ARG_TYPE_KEY,2,"STOREDIST",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="key"}, }; /* GEORADIUS argument table */ struct COMMAND_ARG GEORADIUS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("longitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("latitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("radius",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEORADIUS_unit_Subargs}, {MAKE_ARG("withcoord",ARG_TYPE_PURE_TOKEN,-1,"WITHCOORD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withdist",ARG_TYPE_PURE_TOKEN,-1,"WITHDIST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withhash",ARG_TYPE_PURE_TOKEN,-1,"WITHHASH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUS_count_block_Subargs}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUS_order_Subargs}, {MAKE_ARG("store",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUS_store_Subargs}, }; /********** GEORADIUSBYMEMBER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEORADIUSBYMEMBER history */ commandHistory GEORADIUSBYMEMBER_History[] = { {"6.2.0","Added the `ANY` option for `COUNT`."}, {"7.0.0","Added support for uppercase unit names."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEORADIUSBYMEMBER tips */ #define GEORADIUSBYMEMBER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEORADIUSBYMEMBER key specs */ keySpec GEORADIUSBYMEMBER_Keyspecs[3] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_KEYWORD,.bs.keyword={"STORE",5},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_KEYWORD,.bs.keyword={"STOREDIST",5},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEORADIUSBYMEMBER unit argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUSBYMEMBER count_block argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_count_block_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("any",ARG_TYPE_PURE_TOKEN,-1,"ANY",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* GEORADIUSBYMEMBER order argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUSBYMEMBER store argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_store_Subargs[] = { {MAKE_ARG("storekey",ARG_TYPE_KEY,1,"STORE",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="key"}, {MAKE_ARG("storedistkey",ARG_TYPE_KEY,2,"STOREDIST",NULL,NULL,CMD_ARG_NONE,0,NULL),.display_text="key"}, }; /* GEORADIUSBYMEMBER argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("radius",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEORADIUSBYMEMBER_unit_Subargs}, {MAKE_ARG("withcoord",ARG_TYPE_PURE_TOKEN,-1,"WITHCOORD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withdist",ARG_TYPE_PURE_TOKEN,-1,"WITHDIST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withhash",ARG_TYPE_PURE_TOKEN,-1,"WITHHASH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUSBYMEMBER_count_block_Subargs}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUSBYMEMBER_order_Subargs}, {MAKE_ARG("store",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUSBYMEMBER_store_Subargs}, }; /********** GEORADIUSBYMEMBER_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEORADIUSBYMEMBER_RO history */ commandHistory GEORADIUSBYMEMBER_RO_History[] = { {"6.2.0","Added the `ANY` option for `COUNT`."}, {"7.0.0","Added support for uppercase unit names."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEORADIUSBYMEMBER_RO tips */ #define GEORADIUSBYMEMBER_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEORADIUSBYMEMBER_RO key specs */ keySpec GEORADIUSBYMEMBER_RO_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEORADIUSBYMEMBER_RO unit argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_RO_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUSBYMEMBER_RO count_block argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_RO_count_block_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("any",ARG_TYPE_PURE_TOKEN,-1,"ANY",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* GEORADIUSBYMEMBER_RO order argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_RO_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUSBYMEMBER_RO argument table */ struct COMMAND_ARG GEORADIUSBYMEMBER_RO_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("radius",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEORADIUSBYMEMBER_RO_unit_Subargs}, {MAKE_ARG("withcoord",ARG_TYPE_PURE_TOKEN,-1,"WITHCOORD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withdist",ARG_TYPE_PURE_TOKEN,-1,"WITHDIST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withhash",ARG_TYPE_PURE_TOKEN,-1,"WITHHASH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUSBYMEMBER_RO_count_block_Subargs}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUSBYMEMBER_RO_order_Subargs}, }; /********** GEORADIUS_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEORADIUS_RO history */ commandHistory GEORADIUS_RO_History[] = { {"6.2.0","Added the `ANY` option for `COUNT`."}, {"7.0.0","Added support for uppercase unit names."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEORADIUS_RO tips */ #define GEORADIUS_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEORADIUS_RO key specs */ keySpec GEORADIUS_RO_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEORADIUS_RO unit argument table */ struct COMMAND_ARG GEORADIUS_RO_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUS_RO count_block argument table */ struct COMMAND_ARG GEORADIUS_RO_count_block_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("any",ARG_TYPE_PURE_TOKEN,-1,"ANY",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /* GEORADIUS_RO order argument table */ struct COMMAND_ARG GEORADIUS_RO_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEORADIUS_RO argument table */ struct COMMAND_ARG GEORADIUS_RO_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("longitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("latitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("radius",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEORADIUS_RO_unit_Subargs}, {MAKE_ARG("withcoord",ARG_TYPE_PURE_TOKEN,-1,"WITHCOORD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withdist",ARG_TYPE_PURE_TOKEN,-1,"WITHDIST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withhash",ARG_TYPE_PURE_TOKEN,-1,"WITHHASH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUS_RO_count_block_Subargs}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEORADIUS_RO_order_Subargs}, }; /********** GEOSEARCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEOSEARCH history */ commandHistory GEOSEARCH_History[] = { {"7.0.0","Added support for uppercase unit names."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEOSEARCH tips */ #define GEOSEARCH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEOSEARCH key specs */ keySpec GEOSEARCH_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEOSEARCH from fromlonlat argument table */ struct COMMAND_ARG GEOSEARCH_from_fromlonlat_Subargs[] = { {MAKE_ARG("longitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("latitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCH from argument table */ struct COMMAND_ARG GEOSEARCH_from_Subargs[] = { {MAKE_ARG("member",ARG_TYPE_STRING,-1,"FROMMEMBER",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fromlonlat",ARG_TYPE_BLOCK,-1,"FROMLONLAT",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCH_from_fromlonlat_Subargs}, }; /* GEOSEARCH by circle unit argument table */ struct COMMAND_ARG GEOSEARCH_by_circle_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCH by circle argument table */ struct COMMAND_ARG GEOSEARCH_by_circle_Subargs[] = { {MAKE_ARG("radius",ARG_TYPE_DOUBLE,-1,"BYRADIUS",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEOSEARCH_by_circle_unit_Subargs}, }; /* GEOSEARCH by box unit argument table */ struct COMMAND_ARG GEOSEARCH_by_box_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCH by box argument table */ struct COMMAND_ARG GEOSEARCH_by_box_Subargs[] = { {MAKE_ARG("width",ARG_TYPE_DOUBLE,-1,"BYBOX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("height",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEOSEARCH_by_box_unit_Subargs}, }; /* GEOSEARCH by argument table */ struct COMMAND_ARG GEOSEARCH_by_Subargs[] = { {MAKE_ARG("circle",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCH_by_circle_Subargs}, {MAKE_ARG("box",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=GEOSEARCH_by_box_Subargs}, }; /* GEOSEARCH order argument table */ struct COMMAND_ARG GEOSEARCH_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCH count_block argument table */ struct COMMAND_ARG GEOSEARCH_count_block_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("any",ARG_TYPE_PURE_TOKEN,-1,"ANY",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* GEOSEARCH argument table */ struct COMMAND_ARG GEOSEARCH_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("from",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCH_from_Subargs}, {MAKE_ARG("by",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCH_by_Subargs}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEOSEARCH_order_Subargs}, {MAKE_ARG("count-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEOSEARCH_count_block_Subargs}, {MAKE_ARG("withcoord",ARG_TYPE_PURE_TOKEN,-1,"WITHCOORD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withdist",ARG_TYPE_PURE_TOKEN,-1,"WITHDIST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withhash",ARG_TYPE_PURE_TOKEN,-1,"WITHHASH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** GEOSEARCHSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GEOSEARCHSTORE history */ commandHistory GEOSEARCHSTORE_History[] = { {"7.0.0","Added support for uppercase unit names."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* GEOSEARCHSTORE tips */ #define GEOSEARCHSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GEOSEARCHSTORE key specs */ keySpec GEOSEARCHSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GEOSEARCHSTORE from fromlonlat argument table */ struct COMMAND_ARG GEOSEARCHSTORE_from_fromlonlat_Subargs[] = { {MAKE_ARG("longitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("latitude",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCHSTORE from argument table */ struct COMMAND_ARG GEOSEARCHSTORE_from_Subargs[] = { {MAKE_ARG("member",ARG_TYPE_STRING,-1,"FROMMEMBER",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fromlonlat",ARG_TYPE_BLOCK,-1,"FROMLONLAT",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCHSTORE_from_fromlonlat_Subargs}, }; /* GEOSEARCHSTORE by circle unit argument table */ struct COMMAND_ARG GEOSEARCHSTORE_by_circle_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCHSTORE by circle argument table */ struct COMMAND_ARG GEOSEARCHSTORE_by_circle_Subargs[] = { {MAKE_ARG("radius",ARG_TYPE_DOUBLE,-1,"BYRADIUS",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEOSEARCHSTORE_by_circle_unit_Subargs}, }; /* GEOSEARCHSTORE by box unit argument table */ struct COMMAND_ARG GEOSEARCHSTORE_by_box_unit_Subargs[] = { {MAKE_ARG("m",ARG_TYPE_PURE_TOKEN,-1,"M",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("km",ARG_TYPE_PURE_TOKEN,-1,"KM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ft",ARG_TYPE_PURE_TOKEN,-1,"FT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("mi",ARG_TYPE_PURE_TOKEN,-1,"MI",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCHSTORE by box argument table */ struct COMMAND_ARG GEOSEARCHSTORE_by_box_Subargs[] = { {MAKE_ARG("width",ARG_TYPE_DOUBLE,-1,"BYBOX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("height",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=GEOSEARCHSTORE_by_box_unit_Subargs}, }; /* GEOSEARCHSTORE by argument table */ struct COMMAND_ARG GEOSEARCHSTORE_by_Subargs[] = { {MAKE_ARG("circle",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCHSTORE_by_circle_Subargs}, {MAKE_ARG("box",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=GEOSEARCHSTORE_by_box_Subargs}, }; /* GEOSEARCHSTORE order argument table */ struct COMMAND_ARG GEOSEARCHSTORE_order_Subargs[] = { {MAKE_ARG("asc",ARG_TYPE_PURE_TOKEN,-1,"ASC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("desc",ARG_TYPE_PURE_TOKEN,-1,"DESC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GEOSEARCHSTORE count_block argument table */ struct COMMAND_ARG GEOSEARCHSTORE_count_block_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("any",ARG_TYPE_PURE_TOKEN,-1,"ANY",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* GEOSEARCHSTORE argument table */ struct COMMAND_ARG GEOSEARCHSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("source",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("from",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCHSTORE_from_Subargs}, {MAKE_ARG("by",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=GEOSEARCHSTORE_by_Subargs}, {MAKE_ARG("order",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEOSEARCHSTORE_order_Subargs}, {MAKE_ARG("count-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=GEOSEARCHSTORE_count_block_Subargs}, {MAKE_ARG("storedist",ARG_TYPE_PURE_TOKEN,-1,"STOREDIST",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** HDEL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HDEL history */ commandHistory HDEL_History[] = { {"2.4.0","Accepts multiple `field` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* HDEL tips */ #define HDEL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HDEL key specs */ keySpec HDEL_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HDEL argument table */ struct COMMAND_ARG HDEL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** HEXISTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HEXISTS history */ #define HEXISTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HEXISTS tips */ #define HEXISTS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HEXISTS key specs */ keySpec HEXISTS_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HEXISTS argument table */ struct COMMAND_ARG HEXISTS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HEXPIRE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HEXPIRE history */ #define HEXPIRE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HEXPIRE tips */ #define HEXPIRE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HEXPIRE key specs */ keySpec HEXPIRE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HEXPIRE condition argument table */ struct COMMAND_ARG HEXPIRE_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HEXPIRE fields argument table */ struct COMMAND_ARG HEXPIRE_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HEXPIRE argument table */ struct COMMAND_ARG HEXPIRE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HEXPIRE_condition_Subargs}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIRE_fields_Subargs}, }; /********** HEXPIREAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HEXPIREAT history */ #define HEXPIREAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HEXPIREAT tips */ #define HEXPIREAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HEXPIREAT key specs */ keySpec HEXPIREAT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HEXPIREAT condition argument table */ struct COMMAND_ARG HEXPIREAT_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HEXPIREAT fields argument table */ struct COMMAND_ARG HEXPIREAT_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HEXPIREAT argument table */ struct COMMAND_ARG HEXPIREAT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HEXPIREAT_condition_Subargs}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIREAT_fields_Subargs}, }; /********** HEXPIRETIME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HEXPIRETIME history */ #define HEXPIRETIME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HEXPIRETIME tips */ #define HEXPIRETIME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HEXPIRETIME key specs */ keySpec HEXPIRETIME_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HEXPIRETIME fields argument table */ struct COMMAND_ARG HEXPIRETIME_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HEXPIRETIME argument table */ struct COMMAND_ARG HEXPIRETIME_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HEXPIRETIME_fields_Subargs}, }; /********** HGET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HGET history */ #define HGET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HGET tips */ #define HGET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HGET key specs */ keySpec HGET_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HGET argument table */ struct COMMAND_ARG HGET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HGETALL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HGETALL history */ #define HGETALL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HGETALL tips */ const char *HGETALL_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HGETALL key specs */ keySpec HGETALL_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HGETALL argument table */ struct COMMAND_ARG HGETALL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HGETDEL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HGETDEL history */ #define HGETDEL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HGETDEL tips */ #define HGETDEL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HGETDEL key specs */ keySpec HGETDEL_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HGETDEL fields argument table */ struct COMMAND_ARG HGETDEL_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HGETDEL argument table */ struct COMMAND_ARG HGETDEL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HGETDEL_fields_Subargs}, }; /********** HGETEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HGETEX history */ #define HGETEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HGETEX tips */ #define HGETEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HGETEX key specs */ keySpec HGETEX_Keyspecs[1] = { {"RW and UPDATE because it changes the TTL",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HGETEX expiration argument table */ struct COMMAND_ARG HGETEX_expiration_Subargs[] = { {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("persist",ARG_TYPE_PURE_TOKEN,-1,"PERSIST",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HGETEX fields argument table */ struct COMMAND_ARG HGETEX_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HGETEX argument table */ struct COMMAND_ARG HGETEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=HGETEX_expiration_Subargs}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HGETEX_fields_Subargs}, }; /********** HINCRBY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HINCRBY history */ #define HINCRBY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HINCRBY tips */ #define HINCRBY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HINCRBY key specs */ keySpec HINCRBY_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HINCRBY argument table */ struct COMMAND_ARG HINCRBY_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HINCRBYFLOAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HINCRBYFLOAT history */ #define HINCRBYFLOAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HINCRBYFLOAT tips */ #define HINCRBYFLOAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HINCRBYFLOAT key specs */ keySpec HINCRBYFLOAT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HINCRBYFLOAT argument table */ struct COMMAND_ARG HINCRBYFLOAT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HKEYS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HKEYS history */ #define HKEYS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HKEYS tips */ const char *HKEYS_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HKEYS key specs */ keySpec HKEYS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HKEYS argument table */ struct COMMAND_ARG HKEYS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HLEN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HLEN history */ #define HLEN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HLEN tips */ #define HLEN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HLEN key specs */ keySpec HLEN_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HLEN argument table */ struct COMMAND_ARG HLEN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HMGET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HMGET history */ #define HMGET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HMGET tips */ #define HMGET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HMGET key specs */ keySpec HMGET_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HMGET argument table */ struct COMMAND_ARG HMGET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** HMSET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HMSET history */ #define HMSET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HMSET tips */ #define HMSET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HMSET key specs */ keySpec HMSET_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HMSET data argument table */ struct COMMAND_ARG HMSET_data_Subargs[] = { {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HMSET argument table */ struct COMMAND_ARG HMSET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=HMSET_data_Subargs}, }; /********** HPERSIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HPERSIST history */ #define HPERSIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HPERSIST tips */ #define HPERSIST_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HPERSIST key specs */ keySpec HPERSIST_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HPERSIST fields argument table */ struct COMMAND_ARG HPERSIST_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HPERSIST argument table */ struct COMMAND_ARG HPERSIST_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPERSIST_fields_Subargs}, }; /********** HPEXPIRE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HPEXPIRE history */ #define HPEXPIRE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HPEXPIRE tips */ #define HPEXPIRE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HPEXPIRE key specs */ keySpec HPEXPIRE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HPEXPIRE condition argument table */ struct COMMAND_ARG HPEXPIRE_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HPEXPIRE fields argument table */ struct COMMAND_ARG HPEXPIRE_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HPEXPIRE argument table */ struct COMMAND_ARG HPEXPIRE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HPEXPIRE_condition_Subargs}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIRE_fields_Subargs}, }; /********** HPEXPIREAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HPEXPIREAT history */ #define HPEXPIREAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HPEXPIREAT tips */ #define HPEXPIREAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HPEXPIREAT key specs */ keySpec HPEXPIREAT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HPEXPIREAT condition argument table */ struct COMMAND_ARG HPEXPIREAT_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HPEXPIREAT fields argument table */ struct COMMAND_ARG HPEXPIREAT_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HPEXPIREAT argument table */ struct COMMAND_ARG HPEXPIREAT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=HPEXPIREAT_condition_Subargs}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIREAT_fields_Subargs}, }; /********** HPEXPIRETIME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HPEXPIRETIME history */ #define HPEXPIRETIME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HPEXPIRETIME tips */ #define HPEXPIRETIME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HPEXPIRETIME key specs */ keySpec HPEXPIRETIME_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HPEXPIRETIME fields argument table */ struct COMMAND_ARG HPEXPIRETIME_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HPEXPIRETIME argument table */ struct COMMAND_ARG HPEXPIRETIME_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPEXPIRETIME_fields_Subargs}, }; /********** HPTTL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HPTTL history */ #define HPTTL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HPTTL tips */ const char *HPTTL_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HPTTL key specs */ keySpec HPTTL_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HPTTL fields argument table */ struct COMMAND_ARG HPTTL_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HPTTL argument table */ struct COMMAND_ARG HPTTL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HPTTL_fields_Subargs}, }; /********** HRANDFIELD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HRANDFIELD history */ #define HRANDFIELD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HRANDFIELD tips */ const char *HRANDFIELD_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HRANDFIELD key specs */ keySpec HRANDFIELD_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HRANDFIELD options argument table */ struct COMMAND_ARG HRANDFIELD_options_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withvalues",ARG_TYPE_PURE_TOKEN,-1,"WITHVALUES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* HRANDFIELD argument table */ struct COMMAND_ARG HRANDFIELD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("options",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=HRANDFIELD_options_Subargs}, }; /********** HSCAN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HSCAN history */ #define HSCAN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HSCAN tips */ const char *HSCAN_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HSCAN key specs */ keySpec HSCAN_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HSCAN argument table */ struct COMMAND_ARG HSCAN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("cursor",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,"MATCH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("novalues",ARG_TYPE_PURE_TOKEN,-1,"NOVALUES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** HSET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HSET history */ commandHistory HSET_History[] = { {"4.0.0","Accepts multiple `field` and `value` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* HSET tips */ #define HSET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HSET key specs */ keySpec HSET_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HSET data argument table */ struct COMMAND_ARG HSET_data_Subargs[] = { {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HSET argument table */ struct COMMAND_ARG HSET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=HSET_data_Subargs}, }; /********** HSETEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HSETEX history */ #define HSETEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HSETEX tips */ #define HSETEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HSETEX key specs */ keySpec HSETEX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HSETEX condition argument table */ struct COMMAND_ARG HSETEX_condition_Subargs[] = { {MAKE_ARG("fnx",ARG_TYPE_PURE_TOKEN,-1,"FNX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fxx",ARG_TYPE_PURE_TOKEN,-1,"FXX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HSETEX expiration argument table */ struct COMMAND_ARG HSETEX_expiration_Subargs[] = { {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("keepttl",ARG_TYPE_PURE_TOKEN,-1,"KEEPTTL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HSETEX fields data argument table */ struct COMMAND_ARG HSETEX_fields_data_Subargs[] = { {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* HSETEX fields argument table */ struct COMMAND_ARG HSETEX_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=HSETEX_fields_data_Subargs}, }; /* HSETEX argument table */ struct COMMAND_ARG HSETEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=HSETEX_condition_Subargs}, {MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=HSETEX_expiration_Subargs}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HSETEX_fields_Subargs}, }; /********** HSETNX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HSETNX history */ #define HSETNX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HSETNX tips */ #define HSETNX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HSETNX key specs */ keySpec HSETNX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HSETNX argument table */ struct COMMAND_ARG HSETNX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HSTRLEN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HSTRLEN history */ #define HSTRLEN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HSTRLEN tips */ #define HSTRLEN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HSTRLEN key specs */ keySpec HSTRLEN_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HSTRLEN argument table */ struct COMMAND_ARG HSTRLEN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** HTTL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HTTL history */ #define HTTL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HTTL tips */ const char *HTTL_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HTTL key specs */ keySpec HTTL_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HTTL fields argument table */ struct COMMAND_ARG HTTL_fields_Subargs[] = { {MAKE_ARG("numfields",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* HTTL argument table */ struct COMMAND_ARG HTTL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("fields",ARG_TYPE_BLOCK,-1,"FIELDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=HTTL_fields_Subargs}, }; /********** HVALS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* HVALS history */ #define HVALS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* HVALS tips */ const char *HVALS_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* HVALS key specs */ keySpec HVALS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* HVALS argument table */ struct COMMAND_ARG HVALS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** PFADD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PFADD history */ #define PFADD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PFADD tips */ #define PFADD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PFADD key specs */ keySpec PFADD_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PFADD argument table */ struct COMMAND_ARG PFADD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** PFCOUNT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PFCOUNT history */ #define PFCOUNT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PFCOUNT tips */ #define PFCOUNT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PFCOUNT key specs */ keySpec PFCOUNT_Keyspecs[1] = { {"RW because it may change the internal representation of the key, and propagate to replicas",CMD_KEY_RW|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* PFCOUNT argument table */ struct COMMAND_ARG PFCOUNT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** PFDEBUG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PFDEBUG history */ #define PFDEBUG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PFDEBUG tips */ #define PFDEBUG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PFDEBUG key specs */ keySpec PFDEBUG_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PFDEBUG argument table */ struct COMMAND_ARG PFDEBUG_Args[] = { {MAKE_ARG("subcommand",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** PFMERGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PFMERGE history */ #define PFMERGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PFMERGE tips */ #define PFMERGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PFMERGE key specs */ keySpec PFMERGE_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* PFMERGE argument table */ struct COMMAND_ARG PFMERGE_Args[] = { {MAKE_ARG("destkey",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sourcekey",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** PFSELFTEST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PFSELFTEST history */ #define PFSELFTEST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PFSELFTEST tips */ #define PFSELFTEST_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PFSELFTEST key specs */ #define PFSELFTEST_Keyspecs NULL #endif /********** BLMOVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BLMOVE history */ #define BLMOVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BLMOVE tips */ #define BLMOVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BLMOVE key specs */ keySpec BLMOVE_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* BLMOVE wherefrom argument table */ struct COMMAND_ARG BLMOVE_wherefrom_Subargs[] = { {MAKE_ARG("left",ARG_TYPE_PURE_TOKEN,-1,"LEFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("right",ARG_TYPE_PURE_TOKEN,-1,"RIGHT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BLMOVE whereto argument table */ struct COMMAND_ARG BLMOVE_whereto_Subargs[] = { {MAKE_ARG("left",ARG_TYPE_PURE_TOKEN,-1,"LEFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("right",ARG_TYPE_PURE_TOKEN,-1,"RIGHT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BLMOVE argument table */ struct COMMAND_ARG BLMOVE_Args[] = { {MAKE_ARG("source",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("wherefrom",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BLMOVE_wherefrom_Subargs}, {MAKE_ARG("whereto",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BLMOVE_whereto_Subargs}, {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** BLMPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BLMPOP history */ #define BLMPOP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BLMPOP tips */ #define BLMPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BLMPOP key specs */ keySpec BLMPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* BLMPOP where argument table */ struct COMMAND_ARG BLMPOP_where_Subargs[] = { {MAKE_ARG("left",ARG_TYPE_PURE_TOKEN,-1,"LEFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("right",ARG_TYPE_PURE_TOKEN,-1,"RIGHT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BLMPOP argument table */ struct COMMAND_ARG BLMPOP_Args[] = { {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("where",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BLMPOP_where_Subargs}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** BLPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BLPOP history */ commandHistory BLPOP_History[] = { {"6.0.0","`timeout` is interpreted as a double instead of an integer."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BLPOP tips */ #define BLPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BLPOP key specs */ keySpec BLPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-2,1,0}} }; #endif /* BLPOP argument table */ struct COMMAND_ARG BLPOP_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** BRPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BRPOP history */ commandHistory BRPOP_History[] = { {"6.0.0","`timeout` is interpreted as a double instead of an integer."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BRPOP tips */ #define BRPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BRPOP key specs */ keySpec BRPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-2,1,0}} }; #endif /* BRPOP argument table */ struct COMMAND_ARG BRPOP_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** BRPOPLPUSH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BRPOPLPUSH history */ commandHistory BRPOPLPUSH_History[] = { {"6.0.0","`timeout` is interpreted as a double instead of an integer."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BRPOPLPUSH tips */ #define BRPOPLPUSH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BRPOPLPUSH key specs */ keySpec BRPOPLPUSH_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* BRPOPLPUSH argument table */ struct COMMAND_ARG BRPOPLPUSH_Args[] = { {MAKE_ARG("source",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LINDEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LINDEX history */ #define LINDEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LINDEX tips */ #define LINDEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LINDEX key specs */ keySpec LINDEX_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LINDEX argument table */ struct COMMAND_ARG LINDEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LINSERT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LINSERT history */ #define LINSERT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LINSERT tips */ #define LINSERT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LINSERT key specs */ keySpec LINSERT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LINSERT where argument table */ struct COMMAND_ARG LINSERT_where_Subargs[] = { {MAKE_ARG("before",ARG_TYPE_PURE_TOKEN,-1,"BEFORE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("after",ARG_TYPE_PURE_TOKEN,-1,"AFTER",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* LINSERT argument table */ struct COMMAND_ARG LINSERT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("where",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=LINSERT_where_Subargs}, {MAKE_ARG("pivot",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LLEN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LLEN history */ #define LLEN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LLEN tips */ #define LLEN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LLEN key specs */ keySpec LLEN_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LLEN argument table */ struct COMMAND_ARG LLEN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LMOVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LMOVE history */ #define LMOVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LMOVE tips */ #define LMOVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LMOVE key specs */ keySpec LMOVE_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LMOVE wherefrom argument table */ struct COMMAND_ARG LMOVE_wherefrom_Subargs[] = { {MAKE_ARG("left",ARG_TYPE_PURE_TOKEN,-1,"LEFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("right",ARG_TYPE_PURE_TOKEN,-1,"RIGHT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* LMOVE whereto argument table */ struct COMMAND_ARG LMOVE_whereto_Subargs[] = { {MAKE_ARG("left",ARG_TYPE_PURE_TOKEN,-1,"LEFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("right",ARG_TYPE_PURE_TOKEN,-1,"RIGHT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* LMOVE argument table */ struct COMMAND_ARG LMOVE_Args[] = { {MAKE_ARG("source",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("wherefrom",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=LMOVE_wherefrom_Subargs}, {MAKE_ARG("whereto",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=LMOVE_whereto_Subargs}, }; /********** LMPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LMPOP history */ #define LMPOP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LMPOP tips */ #define LMPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LMPOP key specs */ keySpec LMPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* LMPOP where argument table */ struct COMMAND_ARG LMPOP_where_Subargs[] = { {MAKE_ARG("left",ARG_TYPE_PURE_TOKEN,-1,"LEFT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("right",ARG_TYPE_PURE_TOKEN,-1,"RIGHT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* LMPOP argument table */ struct COMMAND_ARG LMPOP_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("where",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=LMPOP_where_Subargs}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** LPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LPOP history */ commandHistory LPOP_History[] = { {"6.2.0","Added the `count` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* LPOP tips */ #define LPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LPOP key specs */ keySpec LPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LPOP argument table */ struct COMMAND_ARG LPOP_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** LPOS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LPOS history */ #define LPOS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LPOS tips */ #define LPOS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LPOS key specs */ keySpec LPOS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LPOS argument table */ struct COMMAND_ARG LPOS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("rank",ARG_TYPE_INTEGER,-1,"RANK",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("num-matches",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("len",ARG_TYPE_INTEGER,-1,"MAXLEN",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** LPUSH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LPUSH history */ commandHistory LPUSH_History[] = { {"2.4.0","Accepts multiple `element` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* LPUSH tips */ #define LPUSH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LPUSH key specs */ keySpec LPUSH_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LPUSH argument table */ struct COMMAND_ARG LPUSH_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** LPUSHX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LPUSHX history */ commandHistory LPUSHX_History[] = { {"4.0.0","Accepts multiple `element` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* LPUSHX tips */ #define LPUSHX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LPUSHX key specs */ keySpec LPUSHX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LPUSHX argument table */ struct COMMAND_ARG LPUSHX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** LRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LRANGE history */ #define LRANGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LRANGE tips */ #define LRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LRANGE key specs */ keySpec LRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LRANGE argument table */ struct COMMAND_ARG LRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("stop",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LREM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LREM history */ #define LREM_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LREM tips */ #define LREM_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LREM key specs */ keySpec LREM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LREM argument table */ struct COMMAND_ARG LREM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LSET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LSET history */ #define LSET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LSET tips */ #define LSET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LSET key specs */ keySpec LSET_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LSET argument table */ struct COMMAND_ARG LSET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LTRIM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LTRIM history */ #define LTRIM_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LTRIM tips */ #define LTRIM_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LTRIM key specs */ keySpec LTRIM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* LTRIM argument table */ struct COMMAND_ARG LTRIM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("stop",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** RPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RPOP history */ commandHistory RPOP_History[] = { {"6.2.0","Added the `count` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* RPOP tips */ #define RPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RPOP key specs */ keySpec RPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RPOP argument table */ struct COMMAND_ARG RPOP_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** RPOPLPUSH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RPOPLPUSH history */ #define RPOPLPUSH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* RPOPLPUSH tips */ #define RPOPLPUSH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RPOPLPUSH key specs */ keySpec RPOPLPUSH_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RPOPLPUSH argument table */ struct COMMAND_ARG RPOPLPUSH_Args[] = { {MAKE_ARG("source",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** RPUSH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RPUSH history */ commandHistory RPUSH_History[] = { {"2.4.0","Accepts multiple `element` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* RPUSH tips */ #define RPUSH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RPUSH key specs */ keySpec RPUSH_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RPUSH argument table */ struct COMMAND_ARG RPUSH_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** RPUSHX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RPUSHX history */ commandHistory RPUSHX_History[] = { {"4.0.0","Accepts multiple `element` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* RPUSHX tips */ #define RPUSHX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RPUSHX key specs */ keySpec RPUSHX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RPUSHX argument table */ struct COMMAND_ARG RPUSHX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("element",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** PSUBSCRIBE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PSUBSCRIBE history */ #define PSUBSCRIBE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PSUBSCRIBE tips */ #define PSUBSCRIBE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PSUBSCRIBE key specs */ #define PSUBSCRIBE_Keyspecs NULL #endif /* PSUBSCRIBE argument table */ struct COMMAND_ARG PSUBSCRIBE_Args[] = { {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** PUBLISH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBLISH history */ #define PUBLISH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBLISH tips */ #define PUBLISH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBLISH key specs */ #define PUBLISH_Keyspecs NULL #endif /* PUBLISH argument table */ struct COMMAND_ARG PUBLISH_Args[] = { {MAKE_ARG("channel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("message",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** PUBSUB CHANNELS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB CHANNELS history */ #define PUBSUB_CHANNELS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB CHANNELS tips */ #define PUBSUB_CHANNELS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB CHANNELS key specs */ #define PUBSUB_CHANNELS_Keyspecs NULL #endif /* PUBSUB CHANNELS argument table */ struct COMMAND_ARG PUBSUB_CHANNELS_Args[] = { {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** PUBSUB HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB HELP history */ #define PUBSUB_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB HELP tips */ #define PUBSUB_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB HELP key specs */ #define PUBSUB_HELP_Keyspecs NULL #endif /********** PUBSUB NUMPAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB NUMPAT history */ #define PUBSUB_NUMPAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB NUMPAT tips */ #define PUBSUB_NUMPAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB NUMPAT key specs */ #define PUBSUB_NUMPAT_Keyspecs NULL #endif /********** PUBSUB NUMSUB ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB NUMSUB history */ #define PUBSUB_NUMSUB_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB NUMSUB tips */ #define PUBSUB_NUMSUB_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB NUMSUB key specs */ #define PUBSUB_NUMSUB_Keyspecs NULL #endif /* PUBSUB NUMSUB argument table */ struct COMMAND_ARG PUBSUB_NUMSUB_Args[] = { {MAKE_ARG("channel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** PUBSUB SHARDCHANNELS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB SHARDCHANNELS history */ #define PUBSUB_SHARDCHANNELS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB SHARDCHANNELS tips */ #define PUBSUB_SHARDCHANNELS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB SHARDCHANNELS key specs */ #define PUBSUB_SHARDCHANNELS_Keyspecs NULL #endif /* PUBSUB SHARDCHANNELS argument table */ struct COMMAND_ARG PUBSUB_SHARDCHANNELS_Args[] = { {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** PUBSUB SHARDNUMSUB ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB SHARDNUMSUB history */ #define PUBSUB_SHARDNUMSUB_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB SHARDNUMSUB tips */ #define PUBSUB_SHARDNUMSUB_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB SHARDNUMSUB key specs */ #define PUBSUB_SHARDNUMSUB_Keyspecs NULL #endif /* PUBSUB SHARDNUMSUB argument table */ struct COMMAND_ARG PUBSUB_SHARDNUMSUB_Args[] = { {MAKE_ARG("shardchannel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /* PUBSUB command table */ struct COMMAND_STRUCT PUBSUB_Subcommands[] = { {MAKE_CMD("channels","Returns the active channels.","O(N) where N is the number of active channels, and assuming constant time pattern matching (relatively short channels and patterns)","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_CHANNELS_History,0,PUBSUB_CHANNELS_Tips,0,pubsubCommand,-2,CMD_PUBSUB|CMD_LOADING|CMD_STALE,0,PUBSUB_CHANNELS_Keyspecs,0,NULL,1),.args=PUBSUB_CHANNELS_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_HELP_History,0,PUBSUB_HELP_Tips,0,pubsubCommand,2,CMD_LOADING|CMD_STALE,0,PUBSUB_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("numpat","Returns a count of unique pattern subscriptions.","O(1)","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_NUMPAT_History,0,PUBSUB_NUMPAT_Tips,0,pubsubCommand,2,CMD_PUBSUB|CMD_LOADING|CMD_STALE,0,PUBSUB_NUMPAT_Keyspecs,0,NULL,0)}, {MAKE_CMD("numsub","Returns a count of subscribers to channels.","O(N) for the NUMSUB subcommand, where N is the number of requested channels","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_NUMSUB_History,0,PUBSUB_NUMSUB_Tips,0,pubsubCommand,-2,CMD_PUBSUB|CMD_LOADING|CMD_STALE,0,PUBSUB_NUMSUB_Keyspecs,0,NULL,1),.args=PUBSUB_NUMSUB_Args}, {MAKE_CMD("shardchannels","Returns the active shard channels.","O(N) where N is the number of active shard channels, and assuming constant time pattern matching (relatively short shard channels).","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_SHARDCHANNELS_History,0,PUBSUB_SHARDCHANNELS_Tips,0,pubsubCommand,-2,CMD_PUBSUB|CMD_LOADING|CMD_STALE,0,PUBSUB_SHARDCHANNELS_Keyspecs,0,NULL,1),.args=PUBSUB_SHARDCHANNELS_Args}, {MAKE_CMD("shardnumsub","Returns the count of subscribers of shard channels.","O(N) for the SHARDNUMSUB subcommand, where N is the number of requested shard channels","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_SHARDNUMSUB_History,0,PUBSUB_SHARDNUMSUB_Tips,0,pubsubCommand,-2,CMD_PUBSUB|CMD_LOADING|CMD_STALE,0,PUBSUB_SHARDNUMSUB_Keyspecs,0,NULL,1),.args=PUBSUB_SHARDNUMSUB_Args}, {0} }; /********** PUBSUB ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUBSUB history */ #define PUBSUB_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUBSUB tips */ #define PUBSUB_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUBSUB key specs */ #define PUBSUB_Keyspecs NULL #endif /********** PUNSUBSCRIBE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PUNSUBSCRIBE history */ #define PUNSUBSCRIBE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PUNSUBSCRIBE tips */ #define PUNSUBSCRIBE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PUNSUBSCRIBE key specs */ #define PUNSUBSCRIBE_Keyspecs NULL #endif /* PUNSUBSCRIBE argument table */ struct COMMAND_ARG PUNSUBSCRIBE_Args[] = { {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SPUBLISH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SPUBLISH history */ #define SPUBLISH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SPUBLISH tips */ #define SPUBLISH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SPUBLISH key specs */ keySpec SPUBLISH_Keyspecs[1] = { {NULL,CMD_KEY_NOT_KEY,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SPUBLISH argument table */ struct COMMAND_ARG SPUBLISH_Args[] = { {MAKE_ARG("shardchannel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("message",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SSUBSCRIBE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SSUBSCRIBE history */ #define SSUBSCRIBE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SSUBSCRIBE tips */ #define SSUBSCRIBE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SSUBSCRIBE key specs */ keySpec SSUBSCRIBE_Keyspecs[1] = { {NULL,CMD_KEY_NOT_KEY,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SSUBSCRIBE argument table */ struct COMMAND_ARG SSUBSCRIBE_Args[] = { {MAKE_ARG("shardchannel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SUBSCRIBE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SUBSCRIBE history */ #define SUBSCRIBE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SUBSCRIBE tips */ #define SUBSCRIBE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SUBSCRIBE key specs */ #define SUBSCRIBE_Keyspecs NULL #endif /* SUBSCRIBE argument table */ struct COMMAND_ARG SUBSCRIBE_Args[] = { {MAKE_ARG("channel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SUNSUBSCRIBE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SUNSUBSCRIBE history */ #define SUNSUBSCRIBE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SUNSUBSCRIBE tips */ #define SUNSUBSCRIBE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SUNSUBSCRIBE key specs */ keySpec SUNSUBSCRIBE_Keyspecs[1] = { {NULL,CMD_KEY_NOT_KEY,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SUNSUBSCRIBE argument table */ struct COMMAND_ARG SUNSUBSCRIBE_Args[] = { {MAKE_ARG("shardchannel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** UNSUBSCRIBE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* UNSUBSCRIBE history */ #define UNSUBSCRIBE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* UNSUBSCRIBE tips */ #define UNSUBSCRIBE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* UNSUBSCRIBE key specs */ #define UNSUBSCRIBE_Keyspecs NULL #endif /* UNSUBSCRIBE argument table */ struct COMMAND_ARG UNSUBSCRIBE_Args[] = { {MAKE_ARG("channel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** EVAL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EVAL history */ #define EVAL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* EVAL tips */ #define EVAL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EVAL key specs */ keySpec EVAL_Keyspecs[1] = { {"We cannot tell how the keys will be used so we assume the worst, RW and UPDATE",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* EVAL argument table */ struct COMMAND_ARG EVAL_Args[] = { {MAKE_ARG("script",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** EVALSHA ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EVALSHA history */ #define EVALSHA_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* EVALSHA tips */ #define EVALSHA_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EVALSHA key specs */ keySpec EVALSHA_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* EVALSHA argument table */ struct COMMAND_ARG EVALSHA_Args[] = { {MAKE_ARG("sha1",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** EVALSHA_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EVALSHA_RO history */ #define EVALSHA_RO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* EVALSHA_RO tips */ #define EVALSHA_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EVALSHA_RO key specs */ keySpec EVALSHA_RO_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* EVALSHA_RO argument table */ struct COMMAND_ARG EVALSHA_RO_Args[] = { {MAKE_ARG("sha1",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** EVAL_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EVAL_RO history */ #define EVAL_RO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* EVAL_RO tips */ #define EVAL_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EVAL_RO key specs */ keySpec EVAL_RO_Keyspecs[1] = { {"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* EVAL_RO argument table */ struct COMMAND_ARG EVAL_RO_Args[] = { {MAKE_ARG("script",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** FCALL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FCALL history */ #define FCALL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FCALL tips */ #define FCALL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FCALL key specs */ keySpec FCALL_Keyspecs[1] = { {"We cannot tell how the keys will be used so we assume the worst, RW and UPDATE",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* FCALL argument table */ struct COMMAND_ARG FCALL_Args[] = { {MAKE_ARG("function",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** FCALL_RO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FCALL_RO history */ #define FCALL_RO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FCALL_RO tips */ #define FCALL_RO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FCALL_RO key specs */ keySpec FCALL_RO_Keyspecs[1] = { {"We cannot tell how the keys will be used so we assume the worst, RO and ACCESS",CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* FCALL_RO argument table */ struct COMMAND_ARG FCALL_RO_Args[] = { {MAKE_ARG("function",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** FUNCTION DELETE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION DELETE history */ #define FUNCTION_DELETE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION DELETE tips */ const char *FUNCTION_DELETE_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION DELETE key specs */ #define FUNCTION_DELETE_Keyspecs NULL #endif /* FUNCTION DELETE argument table */ struct COMMAND_ARG FUNCTION_DELETE_Args[] = { {MAKE_ARG("library-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** FUNCTION DUMP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION DUMP history */ #define FUNCTION_DUMP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION DUMP tips */ #define FUNCTION_DUMP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION DUMP key specs */ #define FUNCTION_DUMP_Keyspecs NULL #endif /********** FUNCTION FLUSH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION FLUSH history */ #define FUNCTION_FLUSH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION FLUSH tips */ const char *FUNCTION_FLUSH_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION FLUSH key specs */ #define FUNCTION_FLUSH_Keyspecs NULL #endif /* FUNCTION FLUSH flush_type argument table */ struct COMMAND_ARG FUNCTION_FLUSH_flush_type_Subargs[] = { {MAKE_ARG("async",ARG_TYPE_PURE_TOKEN,-1,"ASYNC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sync",ARG_TYPE_PURE_TOKEN,-1,"SYNC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* FUNCTION FLUSH argument table */ struct COMMAND_ARG FUNCTION_FLUSH_Args[] = { {MAKE_ARG("flush-type",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=FUNCTION_FLUSH_flush_type_Subargs}, }; /********** FUNCTION HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION HELP history */ #define FUNCTION_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION HELP tips */ #define FUNCTION_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION HELP key specs */ #define FUNCTION_HELP_Keyspecs NULL #endif /********** FUNCTION KILL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION KILL history */ #define FUNCTION_KILL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION KILL tips */ const char *FUNCTION_KILL_Tips[] = { "request_policy:all_shards", "response_policy:one_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION KILL key specs */ #define FUNCTION_KILL_Keyspecs NULL #endif /********** FUNCTION LIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION LIST history */ #define FUNCTION_LIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION LIST tips */ const char *FUNCTION_LIST_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION LIST key specs */ #define FUNCTION_LIST_Keyspecs NULL #endif /* FUNCTION LIST argument table */ struct COMMAND_ARG FUNCTION_LIST_Args[] = { {MAKE_ARG("library-name-pattern",ARG_TYPE_STRING,-1,"LIBRARYNAME",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withcode",ARG_TYPE_PURE_TOKEN,-1,"WITHCODE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** FUNCTION LOAD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION LOAD history */ #define FUNCTION_LOAD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION LOAD tips */ const char *FUNCTION_LOAD_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION LOAD key specs */ #define FUNCTION_LOAD_Keyspecs NULL #endif /* FUNCTION LOAD argument table */ struct COMMAND_ARG FUNCTION_LOAD_Args[] = { {MAKE_ARG("replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("function-code",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** FUNCTION RESTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION RESTORE history */ #define FUNCTION_RESTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION RESTORE tips */ const char *FUNCTION_RESTORE_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION RESTORE key specs */ #define FUNCTION_RESTORE_Keyspecs NULL #endif /* FUNCTION RESTORE policy argument table */ struct COMMAND_ARG FUNCTION_RESTORE_policy_Subargs[] = { {MAKE_ARG("flush",ARG_TYPE_PURE_TOKEN,-1,"FLUSH",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("append",ARG_TYPE_PURE_TOKEN,-1,"APPEND",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* FUNCTION RESTORE argument table */ struct COMMAND_ARG FUNCTION_RESTORE_Args[] = { {MAKE_ARG("serialized-value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("policy",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=FUNCTION_RESTORE_policy_Subargs}, }; /********** FUNCTION STATS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION STATS history */ #define FUNCTION_STATS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION STATS tips */ const char *FUNCTION_STATS_Tips[] = { "nondeterministic_output", "request_policy:all_shards", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION STATS key specs */ #define FUNCTION_STATS_Keyspecs NULL #endif /* FUNCTION command table */ struct COMMAND_STRUCT FUNCTION_Subcommands[] = { {MAKE_CMD("delete","Deletes a library and its functions.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_DELETE_History,0,FUNCTION_DELETE_Tips,2,functionDeleteCommand,3,CMD_NOSCRIPT|CMD_WRITE,ACL_CATEGORY_SCRIPTING,FUNCTION_DELETE_Keyspecs,0,NULL,1),.args=FUNCTION_DELETE_Args}, {MAKE_CMD("dump","Dumps all libraries into a serialized binary payload.","O(N) where N is the number of functions","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_DUMP_History,0,FUNCTION_DUMP_Tips,0,functionDumpCommand,2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,FUNCTION_DUMP_Keyspecs,0,NULL,0)}, {MAKE_CMD("flush","Deletes all libraries and functions.","O(N) where N is the number of functions deleted","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_FLUSH_History,0,FUNCTION_FLUSH_Tips,2,functionFlushCommand,-2,CMD_NOSCRIPT|CMD_WRITE,ACL_CATEGORY_SCRIPTING,FUNCTION_FLUSH_Keyspecs,0,NULL,1),.args=FUNCTION_FLUSH_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_HELP_History,0,FUNCTION_HELP_Tips,0,functionHelpCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SCRIPTING,FUNCTION_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("kill","Terminates a function during execution.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_KILL_History,0,FUNCTION_KILL_Tips,2,functionKillCommand,2,CMD_NOSCRIPT|CMD_ALLOW_BUSY,ACL_CATEGORY_SCRIPTING,FUNCTION_KILL_Keyspecs,0,NULL,0)}, {MAKE_CMD("list","Returns information about all libraries.","O(N) where N is the number of functions","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_LIST_History,0,FUNCTION_LIST_Tips,1,functionListCommand,-2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,FUNCTION_LIST_Keyspecs,0,NULL,2),.args=FUNCTION_LIST_Args}, {MAKE_CMD("load","Creates a library.","O(1) (considering compilation time is redundant)","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_LOAD_History,0,FUNCTION_LOAD_Tips,2,functionLoadCommand,-3,CMD_NOSCRIPT|CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SCRIPTING,FUNCTION_LOAD_Keyspecs,0,NULL,2),.args=FUNCTION_LOAD_Args}, {MAKE_CMD("restore","Restores all libraries from a payload.","O(N) where N is the number of functions on the payload","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_RESTORE_History,0,FUNCTION_RESTORE_Tips,2,functionRestoreCommand,-3,CMD_NOSCRIPT|CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SCRIPTING,FUNCTION_RESTORE_Keyspecs,0,NULL,2),.args=FUNCTION_RESTORE_Args}, {MAKE_CMD("stats","Returns information about a function during execution.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_STATS_History,0,FUNCTION_STATS_Tips,3,functionStatsCommand,2,CMD_NOSCRIPT|CMD_ALLOW_BUSY,ACL_CATEGORY_SCRIPTING,FUNCTION_STATS_Keyspecs,0,NULL,0)}, {0} }; /********** FUNCTION ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FUNCTION history */ #define FUNCTION_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FUNCTION tips */ #define FUNCTION_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FUNCTION key specs */ #define FUNCTION_Keyspecs NULL #endif /********** SCRIPT DEBUG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT DEBUG history */ #define SCRIPT_DEBUG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT DEBUG tips */ #define SCRIPT_DEBUG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT DEBUG key specs */ #define SCRIPT_DEBUG_Keyspecs NULL #endif /* SCRIPT DEBUG mode argument table */ struct COMMAND_ARG SCRIPT_DEBUG_mode_Subargs[] = { {MAKE_ARG("yes",ARG_TYPE_PURE_TOKEN,-1,"YES",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sync",ARG_TYPE_PURE_TOKEN,-1,"SYNC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("no",ARG_TYPE_PURE_TOKEN,-1,"NO",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SCRIPT DEBUG argument table */ struct COMMAND_ARG SCRIPT_DEBUG_Args[] = { {MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=SCRIPT_DEBUG_mode_Subargs}, }; /********** SCRIPT EXISTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT EXISTS history */ #define SCRIPT_EXISTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT EXISTS tips */ const char *SCRIPT_EXISTS_Tips[] = { "request_policy:all_shards", "response_policy:agg_logical_and", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT EXISTS key specs */ #define SCRIPT_EXISTS_Keyspecs NULL #endif /* SCRIPT EXISTS argument table */ struct COMMAND_ARG SCRIPT_EXISTS_Args[] = { {MAKE_ARG("sha1",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SCRIPT FLUSH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT FLUSH history */ commandHistory SCRIPT_FLUSH_History[] = { {"6.2.0","Added the `ASYNC` and `SYNC` flushing mode modifiers."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT FLUSH tips */ const char *SCRIPT_FLUSH_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT FLUSH key specs */ #define SCRIPT_FLUSH_Keyspecs NULL #endif /* SCRIPT FLUSH flush_type argument table */ struct COMMAND_ARG SCRIPT_FLUSH_flush_type_Subargs[] = { {MAKE_ARG("async",ARG_TYPE_PURE_TOKEN,-1,"ASYNC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sync",ARG_TYPE_PURE_TOKEN,-1,"SYNC",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SCRIPT FLUSH argument table */ struct COMMAND_ARG SCRIPT_FLUSH_Args[] = { {MAKE_ARG("flush-type",ARG_TYPE_ONEOF,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=SCRIPT_FLUSH_flush_type_Subargs}, }; /********** SCRIPT HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT HELP history */ #define SCRIPT_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT HELP tips */ #define SCRIPT_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT HELP key specs */ #define SCRIPT_HELP_Keyspecs NULL #endif /********** SCRIPT KILL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT KILL history */ #define SCRIPT_KILL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT KILL tips */ const char *SCRIPT_KILL_Tips[] = { "request_policy:all_shards", "response_policy:one_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT KILL key specs */ #define SCRIPT_KILL_Keyspecs NULL #endif /********** SCRIPT LOAD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT LOAD history */ #define SCRIPT_LOAD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT LOAD tips */ const char *SCRIPT_LOAD_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT LOAD key specs */ #define SCRIPT_LOAD_Keyspecs NULL #endif /* SCRIPT LOAD argument table */ struct COMMAND_ARG SCRIPT_LOAD_Args[] = { {MAKE_ARG("script",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SCRIPT command table */ struct COMMAND_STRUCT SCRIPT_Subcommands[] = { {MAKE_CMD("debug","Sets the debug mode of server-side Lua scripts.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_DEBUG_History,0,SCRIPT_DEBUG_Tips,0,scriptCommand,3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,SCRIPT_DEBUG_Keyspecs,0,NULL,1),.args=SCRIPT_DEBUG_Args}, {MAKE_CMD("exists","Determines whether server-side Lua scripts exist in the script cache.","O(N) with N being the number of scripts to check (so checking a single script is an O(1) operation).","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_EXISTS_History,0,SCRIPT_EXISTS_Tips,2,scriptCommand,-3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,SCRIPT_EXISTS_Keyspecs,0,NULL,1),.args=SCRIPT_EXISTS_Args}, {MAKE_CMD("flush","Removes all server-side Lua scripts from the script cache.","O(N) with N being the number of scripts in cache","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_FLUSH_History,1,SCRIPT_FLUSH_Tips,2,scriptCommand,-2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,SCRIPT_FLUSH_Keyspecs,0,NULL,1),.args=SCRIPT_FLUSH_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_HELP_History,0,SCRIPT_HELP_Tips,0,scriptCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SCRIPTING,SCRIPT_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("kill","Terminates a server-side Lua script during execution.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_KILL_History,0,SCRIPT_KILL_Tips,2,scriptCommand,2,CMD_NOSCRIPT|CMD_ALLOW_BUSY,ACL_CATEGORY_SCRIPTING,SCRIPT_KILL_Keyspecs,0,NULL,0)}, {MAKE_CMD("load","Loads a server-side Lua script to the script cache.","O(N) with N being the length in bytes of the script body.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_LOAD_History,0,SCRIPT_LOAD_Tips,2,scriptCommand,3,CMD_NOSCRIPT|CMD_STALE,ACL_CATEGORY_SCRIPTING,SCRIPT_LOAD_Keyspecs,0,NULL,1),.args=SCRIPT_LOAD_Args}, {0} }; /********** SCRIPT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCRIPT history */ #define SCRIPT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCRIPT tips */ #define SCRIPT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCRIPT key specs */ #define SCRIPT_Keyspecs NULL #endif /********** SENTINEL CKQUORUM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL CKQUORUM history */ #define SENTINEL_CKQUORUM_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL CKQUORUM tips */ #define SENTINEL_CKQUORUM_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL CKQUORUM key specs */ #define SENTINEL_CKQUORUM_Keyspecs NULL #endif /* SENTINEL CKQUORUM argument table */ struct COMMAND_ARG SENTINEL_CKQUORUM_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL CONFIG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL CONFIG history */ commandHistory SENTINEL_CONFIG_History[] = { {"7.2.0","Added the ability to set and get multiple parameters in one call."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL CONFIG tips */ #define SENTINEL_CONFIG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL CONFIG key specs */ #define SENTINEL_CONFIG_Keyspecs NULL #endif /* SENTINEL CONFIG action set argument table */ struct COMMAND_ARG SENTINEL_CONFIG_action_set_Subargs[] = { {MAKE_ARG("parameter",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SENTINEL CONFIG action argument table */ struct COMMAND_ARG SENTINEL_CONFIG_action_Subargs[] = { {MAKE_ARG("set",ARG_TYPE_BLOCK,-1,"SET",NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=SENTINEL_CONFIG_action_set_Subargs}, {MAKE_ARG("parameter",ARG_TYPE_STRING,-1,"GET",NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* SENTINEL CONFIG argument table */ struct COMMAND_ARG SENTINEL_CONFIG_Args[] = { {MAKE_ARG("action",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=SENTINEL_CONFIG_action_Subargs}, }; /********** SENTINEL DEBUG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL DEBUG history */ #define SENTINEL_DEBUG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL DEBUG tips */ #define SENTINEL_DEBUG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL DEBUG key specs */ #define SENTINEL_DEBUG_Keyspecs NULL #endif /* SENTINEL DEBUG data argument table */ struct COMMAND_ARG SENTINEL_DEBUG_data_Subargs[] = { {MAKE_ARG("parameter",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SENTINEL DEBUG argument table */ struct COMMAND_ARG SENTINEL_DEBUG_Args[] = { {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,2,NULL),.subargs=SENTINEL_DEBUG_data_Subargs}, }; /********** SENTINEL FAILOVER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL FAILOVER history */ #define SENTINEL_FAILOVER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL FAILOVER tips */ #define SENTINEL_FAILOVER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL FAILOVER key specs */ #define SENTINEL_FAILOVER_Keyspecs NULL #endif /* SENTINEL FAILOVER argument table */ struct COMMAND_ARG SENTINEL_FAILOVER_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL FLUSHCONFIG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL FLUSHCONFIG history */ #define SENTINEL_FLUSHCONFIG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL FLUSHCONFIG tips */ #define SENTINEL_FLUSHCONFIG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL FLUSHCONFIG key specs */ #define SENTINEL_FLUSHCONFIG_Keyspecs NULL #endif /********** SENTINEL GET_MASTER_ADDR_BY_NAME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL GET_MASTER_ADDR_BY_NAME history */ #define SENTINEL_GET_MASTER_ADDR_BY_NAME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL GET_MASTER_ADDR_BY_NAME tips */ #define SENTINEL_GET_MASTER_ADDR_BY_NAME_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL GET_MASTER_ADDR_BY_NAME key specs */ #define SENTINEL_GET_MASTER_ADDR_BY_NAME_Keyspecs NULL #endif /* SENTINEL GET_MASTER_ADDR_BY_NAME argument table */ struct COMMAND_ARG SENTINEL_GET_MASTER_ADDR_BY_NAME_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL HELP history */ #define SENTINEL_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL HELP tips */ #define SENTINEL_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL HELP key specs */ #define SENTINEL_HELP_Keyspecs NULL #endif /********** SENTINEL INFO_CACHE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL INFO_CACHE history */ #define SENTINEL_INFO_CACHE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL INFO_CACHE tips */ #define SENTINEL_INFO_CACHE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL INFO_CACHE key specs */ #define SENTINEL_INFO_CACHE_Keyspecs NULL #endif /* SENTINEL INFO_CACHE argument table */ struct COMMAND_ARG SENTINEL_INFO_CACHE_Args[] = { {MAKE_ARG("nodename",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SENTINEL IS_MASTER_DOWN_BY_ADDR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL IS_MASTER_DOWN_BY_ADDR history */ #define SENTINEL_IS_MASTER_DOWN_BY_ADDR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL IS_MASTER_DOWN_BY_ADDR tips */ #define SENTINEL_IS_MASTER_DOWN_BY_ADDR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL IS_MASTER_DOWN_BY_ADDR key specs */ #define SENTINEL_IS_MASTER_DOWN_BY_ADDR_Keyspecs NULL #endif /* SENTINEL IS_MASTER_DOWN_BY_ADDR argument table */ struct COMMAND_ARG SENTINEL_IS_MASTER_DOWN_BY_ADDR_Args[] = { {MAKE_ARG("ip",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("current-epoch",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("runid",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL MASTER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL MASTER history */ #define SENTINEL_MASTER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL MASTER tips */ #define SENTINEL_MASTER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL MASTER key specs */ #define SENTINEL_MASTER_Keyspecs NULL #endif /* SENTINEL MASTER argument table */ struct COMMAND_ARG SENTINEL_MASTER_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL MASTERS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL MASTERS history */ #define SENTINEL_MASTERS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL MASTERS tips */ #define SENTINEL_MASTERS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL MASTERS key specs */ #define SENTINEL_MASTERS_Keyspecs NULL #endif /********** SENTINEL MONITOR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL MONITOR history */ #define SENTINEL_MONITOR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL MONITOR tips */ #define SENTINEL_MONITOR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL MONITOR key specs */ #define SENTINEL_MONITOR_Keyspecs NULL #endif /* SENTINEL MONITOR argument table */ struct COMMAND_ARG SENTINEL_MONITOR_Args[] = { {MAKE_ARG("name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ip",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("quorum",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL MYID ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL MYID history */ #define SENTINEL_MYID_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL MYID tips */ #define SENTINEL_MYID_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL MYID key specs */ #define SENTINEL_MYID_Keyspecs NULL #endif /********** SENTINEL PENDING_SCRIPTS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL PENDING_SCRIPTS history */ #define SENTINEL_PENDING_SCRIPTS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL PENDING_SCRIPTS tips */ #define SENTINEL_PENDING_SCRIPTS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL PENDING_SCRIPTS key specs */ #define SENTINEL_PENDING_SCRIPTS_Keyspecs NULL #endif /********** SENTINEL REMOVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL REMOVE history */ #define SENTINEL_REMOVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL REMOVE tips */ #define SENTINEL_REMOVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL REMOVE key specs */ #define SENTINEL_REMOVE_Keyspecs NULL #endif /* SENTINEL REMOVE argument table */ struct COMMAND_ARG SENTINEL_REMOVE_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL REPLICAS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL REPLICAS history */ #define SENTINEL_REPLICAS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL REPLICAS tips */ #define SENTINEL_REPLICAS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL REPLICAS key specs */ #define SENTINEL_REPLICAS_Keyspecs NULL #endif /* SENTINEL REPLICAS argument table */ struct COMMAND_ARG SENTINEL_REPLICAS_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL RESET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL RESET history */ #define SENTINEL_RESET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL RESET tips */ #define SENTINEL_RESET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL RESET key specs */ #define SENTINEL_RESET_Keyspecs NULL #endif /* SENTINEL RESET argument table */ struct COMMAND_ARG SENTINEL_RESET_Args[] = { {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL SENTINELS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL SENTINELS history */ #define SENTINEL_SENTINELS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL SENTINELS tips */ #define SENTINEL_SENTINELS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL SENTINELS key specs */ #define SENTINEL_SENTINELS_Keyspecs NULL #endif /* SENTINEL SENTINELS argument table */ struct COMMAND_ARG SENTINEL_SENTINELS_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SENTINEL SET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL SET history */ #define SENTINEL_SET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL SET tips */ #define SENTINEL_SET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL SET key specs */ #define SENTINEL_SET_Keyspecs NULL #endif /* SENTINEL SET data argument table */ struct COMMAND_ARG SENTINEL_SET_data_Subargs[] = { {MAKE_ARG("option",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SENTINEL SET argument table */ struct COMMAND_ARG SENTINEL_SET_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=SENTINEL_SET_data_Subargs}, }; /********** SENTINEL SIMULATE_FAILURE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL SIMULATE_FAILURE history */ #define SENTINEL_SIMULATE_FAILURE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL SIMULATE_FAILURE tips */ #define SENTINEL_SIMULATE_FAILURE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL SIMULATE_FAILURE key specs */ #define SENTINEL_SIMULATE_FAILURE_Keyspecs NULL #endif /* SENTINEL SIMULATE_FAILURE mode argument table */ struct COMMAND_ARG SENTINEL_SIMULATE_FAILURE_mode_Subargs[] = { {MAKE_ARG("crash-after-election",ARG_TYPE_PURE_TOKEN,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("crash-after-promotion",ARG_TYPE_PURE_TOKEN,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("help",ARG_TYPE_PURE_TOKEN,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SENTINEL SIMULATE_FAILURE argument table */ struct COMMAND_ARG SENTINEL_SIMULATE_FAILURE_Args[] = { {MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,3,NULL),.subargs=SENTINEL_SIMULATE_FAILURE_mode_Subargs}, }; /********** SENTINEL SLAVES ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL SLAVES history */ #define SENTINEL_SLAVES_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL SLAVES tips */ #define SENTINEL_SLAVES_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL SLAVES key specs */ #define SENTINEL_SLAVES_Keyspecs NULL #endif /* SENTINEL SLAVES argument table */ struct COMMAND_ARG SENTINEL_SLAVES_Args[] = { {MAKE_ARG("master-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SENTINEL command table */ struct COMMAND_STRUCT SENTINEL_Subcommands[] = { {MAKE_CMD("ckquorum","Checks for a Redis Sentinel quorum.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CKQUORUM_History,0,SENTINEL_CKQUORUM_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CKQUORUM_Keyspecs,0,NULL,1),.args=SENTINEL_CKQUORUM_Args}, {MAKE_CMD("config","Configures Redis Sentinel.","O(N) when N is the number of configuration parameters provided","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CONFIG_History,1,SENTINEL_CONFIG_Tips,0,sentinelCommand,-4,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CONFIG_Keyspecs,0,NULL,1),.args=SENTINEL_CONFIG_Args}, {MAKE_CMD("debug","Lists or updates the current configurable parameters of Redis Sentinel.","O(N) where N is the number of configurable parameters","7.0.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_DEBUG_History,0,SENTINEL_DEBUG_Tips,0,sentinelCommand,-2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_DEBUG_Keyspecs,0,NULL,1),.args=SENTINEL_DEBUG_Args}, {MAKE_CMD("failover","Forces a Redis Sentinel failover.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FAILOVER_History,0,SENTINEL_FAILOVER_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FAILOVER_Keyspecs,0,NULL,1),.args=SENTINEL_FAILOVER_Args}, {MAKE_CMD("flushconfig","Rewrites the Redis Sentinel configuration file.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FLUSHCONFIG_History,0,SENTINEL_FLUSHCONFIG_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FLUSHCONFIG_Keyspecs,0,NULL,0)}, {MAKE_CMD("get-master-addr-by-name","Returns the port and address of a master Redis instance.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_GET_MASTER_ADDR_BY_NAME_History,0,SENTINEL_GET_MASTER_ADDR_BY_NAME_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_GET_MASTER_ADDR_BY_NAME_Keyspecs,0,NULL,1),.args=SENTINEL_GET_MASTER_ADDR_BY_NAME_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_HELP_History,0,SENTINEL_HELP_Tips,0,sentinelCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("info-cache","Returns the cached `INFO` replies from the deployment's instances.","O(N) where N is the number of instances","3.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_INFO_CACHE_History,0,SENTINEL_INFO_CACHE_Tips,0,sentinelCommand,-3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_INFO_CACHE_Keyspecs,0,NULL,1),.args=SENTINEL_INFO_CACHE_Args}, {MAKE_CMD("is-master-down-by-addr","Determines whether a master Redis instance is down.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_IS_MASTER_DOWN_BY_ADDR_History,0,SENTINEL_IS_MASTER_DOWN_BY_ADDR_Tips,0,sentinelCommand,6,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_IS_MASTER_DOWN_BY_ADDR_Keyspecs,0,NULL,4),.args=SENTINEL_IS_MASTER_DOWN_BY_ADDR_Args}, {MAKE_CMD("master","Returns the state of a master Redis instance.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_MASTER_History,0,SENTINEL_MASTER_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_MASTER_Keyspecs,0,NULL,1),.args=SENTINEL_MASTER_Args}, {MAKE_CMD("masters","Returns a list of monitored Redis masters.","O(N) where N is the number of masters","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_MASTERS_History,0,SENTINEL_MASTERS_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_MASTERS_Keyspecs,0,NULL,0)}, {MAKE_CMD("monitor","Starts monitoring.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_MONITOR_History,0,SENTINEL_MONITOR_Tips,0,sentinelCommand,6,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_MONITOR_Keyspecs,0,NULL,4),.args=SENTINEL_MONITOR_Args}, {MAKE_CMD("myid","Returns the Redis Sentinel instance ID.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_MYID_History,0,SENTINEL_MYID_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_MYID_Keyspecs,0,NULL,0)}, {MAKE_CMD("pending-scripts","Returns information about pending scripts for Redis Sentinel.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_PENDING_SCRIPTS_History,0,SENTINEL_PENDING_SCRIPTS_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_PENDING_SCRIPTS_Keyspecs,0,NULL,0)}, {MAKE_CMD("remove","Stops monitoring.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_REMOVE_History,0,SENTINEL_REMOVE_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_REMOVE_Keyspecs,0,NULL,1),.args=SENTINEL_REMOVE_Args}, {MAKE_CMD("replicas","Returns a list of the monitored Redis replicas.","O(N) where N is the number of replicas","5.0.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_REPLICAS_History,0,SENTINEL_REPLICAS_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_REPLICAS_Keyspecs,0,NULL,1),.args=SENTINEL_REPLICAS_Args}, {MAKE_CMD("reset","Resets Redis masters by name matching a pattern.","O(N) where N is the number of monitored masters","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_RESET_History,0,SENTINEL_RESET_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_RESET_Keyspecs,0,NULL,1),.args=SENTINEL_RESET_Args}, {MAKE_CMD("sentinels","Returns a list of Sentinel instances.","O(N) where N is the number of Sentinels","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_SENTINELS_History,0,SENTINEL_SENTINELS_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_SENTINELS_Keyspecs,0,NULL,1),.args=SENTINEL_SENTINELS_Args}, {MAKE_CMD("set","Changes the configuration of a monitored Redis master.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_SET_History,0,SENTINEL_SET_Tips,0,sentinelCommand,-5,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_SET_Keyspecs,0,NULL,2),.args=SENTINEL_SET_Args}, {MAKE_CMD("simulate-failure","Simulates failover scenarios.",NULL,"3.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_SIMULATE_FAILURE_History,0,SENTINEL_SIMULATE_FAILURE_Tips,0,sentinelCommand,-3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_SIMULATE_FAILURE_Keyspecs,0,NULL,1),.args=SENTINEL_SIMULATE_FAILURE_Args}, {MAKE_CMD("slaves","Returns a list of the monitored replicas.","O(N) where N is the number of replicas.","2.8.0",CMD_DOC_DEPRECATED,"`SENTINEL REPLICAS`","5.0.0","sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_SLAVES_History,0,SENTINEL_SLAVES_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_SLAVES_Keyspecs,0,NULL,1),.args=SENTINEL_SLAVES_Args}, {0} }; /********** SENTINEL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SENTINEL history */ #define SENTINEL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SENTINEL tips */ #define SENTINEL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SENTINEL key specs */ #define SENTINEL_Keyspecs NULL #endif /********** ACL CAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL CAT history */ #define ACL_CAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL CAT tips */ #define ACL_CAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL CAT key specs */ #define ACL_CAT_Keyspecs NULL #endif /* ACL CAT argument table */ struct COMMAND_ARG ACL_CAT_Args[] = { {MAKE_ARG("category",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ACL DELUSER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL DELUSER history */ #define ACL_DELUSER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL DELUSER tips */ const char *ACL_DELUSER_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL DELUSER key specs */ #define ACL_DELUSER_Keyspecs NULL #endif /* ACL DELUSER argument table */ struct COMMAND_ARG ACL_DELUSER_Args[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** ACL DRYRUN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL DRYRUN history */ #define ACL_DRYRUN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL DRYRUN tips */ #define ACL_DRYRUN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL DRYRUN key specs */ #define ACL_DRYRUN_Keyspecs NULL #endif /* ACL DRYRUN argument table */ struct COMMAND_ARG ACL_DRYRUN_Args[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("command",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** ACL GENPASS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL GENPASS history */ #define ACL_GENPASS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL GENPASS tips */ #define ACL_GENPASS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL GENPASS key specs */ #define ACL_GENPASS_Keyspecs NULL #endif /* ACL GENPASS argument table */ struct COMMAND_ARG ACL_GENPASS_Args[] = { {MAKE_ARG("bits",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ACL GETUSER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL GETUSER history */ commandHistory ACL_GETUSER_History[] = { {"6.2.0","Added Pub/Sub channel patterns."}, {"7.0.0","Added selectors and changed the format of key and channel patterns from a list to their rule representation."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL GETUSER tips */ #define ACL_GETUSER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL GETUSER key specs */ #define ACL_GETUSER_Keyspecs NULL #endif /* ACL GETUSER argument table */ struct COMMAND_ARG ACL_GETUSER_Args[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ACL HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL HELP history */ #define ACL_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL HELP tips */ #define ACL_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL HELP key specs */ #define ACL_HELP_Keyspecs NULL #endif /********** ACL LIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL LIST history */ #define ACL_LIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL LIST tips */ #define ACL_LIST_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL LIST key specs */ #define ACL_LIST_Keyspecs NULL #endif /********** ACL LOAD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL LOAD history */ #define ACL_LOAD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL LOAD tips */ #define ACL_LOAD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL LOAD key specs */ #define ACL_LOAD_Keyspecs NULL #endif /********** ACL LOG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL LOG history */ commandHistory ACL_LOG_History[] = { {"7.2.0","Added entry ID, timestamp created, and timestamp last updated."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL LOG tips */ #define ACL_LOG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL LOG key specs */ #define ACL_LOG_Keyspecs NULL #endif /* ACL LOG operation argument table */ struct COMMAND_ARG ACL_LOG_operation_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("reset",ARG_TYPE_PURE_TOKEN,-1,"RESET",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ACL LOG argument table */ struct COMMAND_ARG ACL_LOG_Args[] = { {MAKE_ARG("operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ACL_LOG_operation_Subargs}, }; /********** ACL SAVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL SAVE history */ #define ACL_SAVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL SAVE tips */ const char *ACL_SAVE_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL SAVE key specs */ #define ACL_SAVE_Keyspecs NULL #endif /********** ACL SETUSER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL SETUSER history */ commandHistory ACL_SETUSER_History[] = { {"6.2.0","Added Pub/Sub channel patterns."}, {"7.0.0","Added selectors and key based permissions."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL SETUSER tips */ const char *ACL_SETUSER_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL SETUSER key specs */ #define ACL_SETUSER_Keyspecs NULL #endif /* ACL SETUSER argument table */ struct COMMAND_ARG ACL_SETUSER_Args[] = { {MAKE_ARG("username",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("rule",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** ACL USERS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL USERS history */ #define ACL_USERS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL USERS tips */ #define ACL_USERS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL USERS key specs */ #define ACL_USERS_Keyspecs NULL #endif /********** ACL WHOAMI ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL WHOAMI history */ #define ACL_WHOAMI_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL WHOAMI tips */ #define ACL_WHOAMI_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL WHOAMI key specs */ #define ACL_WHOAMI_Keyspecs NULL #endif /* ACL command table */ struct COMMAND_STRUCT ACL_Subcommands[] = { {MAKE_CMD("cat","Lists the ACL categories, or the commands inside a category.","O(1) since the categories and commands are a fixed set.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_CAT_History,0,ACL_CAT_Tips,0,aclCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_CAT_Keyspecs,0,NULL,1),.args=ACL_CAT_Args}, {MAKE_CMD("deluser","Deletes ACL users, and terminates their connections.","O(1) amortized time considering the typical user.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_DELUSER_History,0,ACL_DELUSER_Tips,2,aclCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_DELUSER_Keyspecs,0,NULL,1),.args=ACL_DELUSER_Args}, {MAKE_CMD("dryrun","Simulates the execution of a command by a user, without executing the command.","O(1).","7.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_DRYRUN_History,0,ACL_DRYRUN_Tips,0,aclCommand,-4,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_DRYRUN_Keyspecs,0,NULL,3),.args=ACL_DRYRUN_Args}, {MAKE_CMD("genpass","Generates a pseudorandom, secure password that can be used to identify ACL users.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_GENPASS_History,0,ACL_GENPASS_Tips,0,aclCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_GENPASS_Keyspecs,0,NULL,1),.args=ACL_GENPASS_Args}, {MAKE_CMD("getuser","Lists the ACL rules of a user.","O(N). Where N is the number of password, command and pattern rules that the user has.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_GETUSER_History,2,ACL_GETUSER_Tips,0,aclCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_GETUSER_Keyspecs,0,NULL,1),.args=ACL_GETUSER_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_HELP_History,0,ACL_HELP_Tips,0,aclCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("list","Dumps the effective rules in ACL file format.","O(N). Where N is the number of configured users.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_LIST_History,0,ACL_LIST_Tips,0,aclCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_LIST_Keyspecs,0,NULL,0)}, {MAKE_CMD("load","Reloads the rules from the configured ACL file.","O(N). Where N is the number of configured users.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_LOAD_History,0,ACL_LOAD_Tips,0,aclCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_LOAD_Keyspecs,0,NULL,0)}, {MAKE_CMD("log","Lists recent security events generated due to ACL rules.","O(N) with N being the number of entries shown.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_LOG_History,1,ACL_LOG_Tips,0,aclCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_LOG_Keyspecs,0,NULL,1),.args=ACL_LOG_Args}, {MAKE_CMD("save","Saves the effective ACL rules in the configured ACL file.","O(N). Where N is the number of configured users.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_SAVE_History,0,ACL_SAVE_Tips,2,aclCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_SAVE_Keyspecs,0,NULL,0)}, {MAKE_CMD("setuser","Creates and modifies an ACL user and its rules.","O(N). Where N is the number of rules provided.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_SETUSER_History,2,ACL_SETUSER_Tips,2,aclCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_SETUSER_Keyspecs,0,NULL,2),.args=ACL_SETUSER_Args}, {MAKE_CMD("users","Lists all ACL users.","O(N). Where N is the number of configured users.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_USERS_History,0,ACL_USERS_Tips,0,aclCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_USERS_Keyspecs,0,NULL,0)}, {MAKE_CMD("whoami","Returns the authenticated username of the current connection.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_WHOAMI_History,0,ACL_WHOAMI_Tips,0,aclCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,ACL_WHOAMI_Keyspecs,0,NULL,0)}, {0} }; /********** ACL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ACL history */ #define ACL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ACL tips */ #define ACL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ACL key specs */ #define ACL_Keyspecs NULL #endif /********** BGREWRITEAOF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BGREWRITEAOF history */ #define BGREWRITEAOF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BGREWRITEAOF tips */ #define BGREWRITEAOF_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BGREWRITEAOF key specs */ #define BGREWRITEAOF_Keyspecs NULL #endif /********** BGSAVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BGSAVE history */ commandHistory BGSAVE_History[] = { {"3.2.2","Added the `SCHEDULE` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BGSAVE tips */ #define BGSAVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BGSAVE key specs */ #define BGSAVE_Keyspecs NULL #endif /* BGSAVE argument table */ struct COMMAND_ARG BGSAVE_Args[] = { {MAKE_ARG("schedule",ARG_TYPE_PURE_TOKEN,-1,"SCHEDULE",NULL,"3.2.2",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** COMMAND COUNT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND COUNT history */ #define COMMAND_COUNT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND COUNT tips */ #define COMMAND_COUNT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND COUNT key specs */ #define COMMAND_COUNT_Keyspecs NULL #endif /********** COMMAND DOCS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND DOCS history */ #define COMMAND_DOCS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND DOCS tips */ const char *COMMAND_DOCS_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND DOCS key specs */ #define COMMAND_DOCS_Keyspecs NULL #endif /* COMMAND DOCS argument table */ struct COMMAND_ARG COMMAND_DOCS_Args[] = { {MAKE_ARG("command-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** COMMAND GETKEYS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND GETKEYS history */ #define COMMAND_GETKEYS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND GETKEYS tips */ #define COMMAND_GETKEYS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND GETKEYS key specs */ #define COMMAND_GETKEYS_Keyspecs NULL #endif /* COMMAND GETKEYS argument table */ struct COMMAND_ARG COMMAND_GETKEYS_Args[] = { {MAKE_ARG("command",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** COMMAND GETKEYSANDFLAGS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND GETKEYSANDFLAGS history */ #define COMMAND_GETKEYSANDFLAGS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND GETKEYSANDFLAGS tips */ #define COMMAND_GETKEYSANDFLAGS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND GETKEYSANDFLAGS key specs */ #define COMMAND_GETKEYSANDFLAGS_Keyspecs NULL #endif /* COMMAND GETKEYSANDFLAGS argument table */ struct COMMAND_ARG COMMAND_GETKEYSANDFLAGS_Args[] = { {MAKE_ARG("command",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** COMMAND HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND HELP history */ #define COMMAND_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND HELP tips */ #define COMMAND_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND HELP key specs */ #define COMMAND_HELP_Keyspecs NULL #endif /********** COMMAND INFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND INFO history */ commandHistory COMMAND_INFO_History[] = { {"7.0.0","Allowed to be called with no argument to get info on all commands."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND INFO tips */ const char *COMMAND_INFO_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND INFO key specs */ #define COMMAND_INFO_Keyspecs NULL #endif /* COMMAND INFO argument table */ struct COMMAND_ARG COMMAND_INFO_Args[] = { {MAKE_ARG("command-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** COMMAND LIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND LIST history */ #define COMMAND_LIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND LIST tips */ const char *COMMAND_LIST_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND LIST key specs */ #define COMMAND_LIST_Keyspecs NULL #endif /* COMMAND LIST filterby argument table */ struct COMMAND_ARG COMMAND_LIST_filterby_Subargs[] = { {MAKE_ARG("module-name",ARG_TYPE_STRING,-1,"MODULE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("category",ARG_TYPE_STRING,-1,"ACLCAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,"PATTERN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* COMMAND LIST argument table */ struct COMMAND_ARG COMMAND_LIST_Args[] = { {MAKE_ARG("filterby",ARG_TYPE_ONEOF,-1,"FILTERBY",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=COMMAND_LIST_filterby_Subargs}, }; /* COMMAND command table */ struct COMMAND_STRUCT COMMAND_Subcommands[] = { {MAKE_CMD("count","Returns a count of commands.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_COUNT_History,0,COMMAND_COUNT_Tips,0,commandCountCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_COUNT_Keyspecs,0,NULL,0)}, {MAKE_CMD("docs","Returns documentary information about one, multiple or all commands.","O(N) where N is the number of commands to look up","7.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_DOCS_History,0,COMMAND_DOCS_Tips,1,commandDocsCommand,-2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_DOCS_Keyspecs,0,NULL,1),.args=COMMAND_DOCS_Args}, {MAKE_CMD("getkeys","Extracts the key names from an arbitrary command.","O(N) where N is the number of arguments to the command","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_GETKEYS_History,0,COMMAND_GETKEYS_Tips,0,commandGetKeysCommand,-3,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_GETKEYS_Keyspecs,0,NULL,2),.args=COMMAND_GETKEYS_Args}, {MAKE_CMD("getkeysandflags","Extracts the key names and access flags for an arbitrary command.","O(N) where N is the number of arguments to the command","7.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_GETKEYSANDFLAGS_History,0,COMMAND_GETKEYSANDFLAGS_Tips,0,commandGetKeysAndFlagsCommand,-3,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_GETKEYSANDFLAGS_Keyspecs,0,NULL,2),.args=COMMAND_GETKEYSANDFLAGS_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_HELP_History,0,COMMAND_HELP_Tips,0,commandHelpCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("info","Returns information about one, multiple or all commands.","O(N) where N is the number of commands to look up","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_INFO_History,1,COMMAND_INFO_Tips,1,commandInfoCommand,-2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_INFO_Keyspecs,0,NULL,1),.args=COMMAND_INFO_Args}, {MAKE_CMD("list","Returns a list of command names.","O(N) where N is the total number of Redis commands","7.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_LIST_History,0,COMMAND_LIST_Tips,1,commandListCommand,-2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_LIST_Keyspecs,0,NULL,1),.args=COMMAND_LIST_Args}, {0} }; /********** COMMAND ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* COMMAND history */ #define COMMAND_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* COMMAND tips */ const char *COMMAND_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* COMMAND key specs */ #define COMMAND_Keyspecs NULL #endif /********** CONFIG GET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CONFIG GET history */ commandHistory CONFIG_GET_History[] = { {"7.0.0","Added the ability to pass multiple pattern parameters in one call"}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CONFIG GET tips */ #define CONFIG_GET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CONFIG GET key specs */ #define CONFIG_GET_Keyspecs NULL #endif /* CONFIG GET argument table */ struct COMMAND_ARG CONFIG_GET_Args[] = { {MAKE_ARG("parameter",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** CONFIG HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CONFIG HELP history */ #define CONFIG_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CONFIG HELP tips */ #define CONFIG_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CONFIG HELP key specs */ #define CONFIG_HELP_Keyspecs NULL #endif /********** CONFIG RESETSTAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CONFIG RESETSTAT history */ #define CONFIG_RESETSTAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CONFIG RESETSTAT tips */ const char *CONFIG_RESETSTAT_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CONFIG RESETSTAT key specs */ #define CONFIG_RESETSTAT_Keyspecs NULL #endif /********** CONFIG REWRITE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CONFIG REWRITE history */ #define CONFIG_REWRITE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CONFIG REWRITE tips */ const char *CONFIG_REWRITE_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CONFIG REWRITE key specs */ #define CONFIG_REWRITE_Keyspecs NULL #endif /********** CONFIG SET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CONFIG SET history */ commandHistory CONFIG_SET_History[] = { {"7.0.0","Added the ability to set multiple parameters in one call."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* CONFIG SET tips */ const char *CONFIG_SET_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CONFIG SET key specs */ #define CONFIG_SET_Keyspecs NULL #endif /* CONFIG SET data argument table */ struct COMMAND_ARG CONFIG_SET_data_Subargs[] = { {MAKE_ARG("parameter",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* CONFIG SET argument table */ struct COMMAND_ARG CONFIG_SET_Args[] = { {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=CONFIG_SET_data_Subargs}, }; /* CONFIG command table */ struct COMMAND_STRUCT CONFIG_Subcommands[] = { {MAKE_CMD("get","Returns the effective values of configuration parameters.","O(N) when N is the number of configuration parameters provided","2.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_GET_History,1,CONFIG_GET_Tips,0,configGetCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,CONFIG_GET_Keyspecs,0,NULL,1),.args=CONFIG_GET_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_HELP_History,0,CONFIG_HELP_Tips,0,configHelpCommand,2,CMD_LOADING|CMD_STALE,0,CONFIG_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("resetstat","Resets the server's statistics.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_RESETSTAT_History,0,CONFIG_RESETSTAT_Tips,2,configResetStatCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,CONFIG_RESETSTAT_Keyspecs,0,NULL,0)}, {MAKE_CMD("rewrite","Persists the effective configuration to file.","O(1)","2.8.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_REWRITE_History,0,CONFIG_REWRITE_Tips,2,configRewriteCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,CONFIG_REWRITE_Keyspecs,0,NULL,0)}, {MAKE_CMD("set","Sets configuration parameters in-flight.","O(N) when N is the number of configuration parameters provided","2.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_SET_History,1,CONFIG_SET_Tips,2,configSetCommand,-4,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,CONFIG_SET_Keyspecs,0,NULL,1),.args=CONFIG_SET_Args}, {0} }; /********** CONFIG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* CONFIG history */ #define CONFIG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* CONFIG tips */ #define CONFIG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* CONFIG key specs */ #define CONFIG_Keyspecs NULL #endif /********** DBSIZE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DBSIZE history */ #define DBSIZE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DBSIZE tips */ const char *DBSIZE_Tips[] = { "request_policy:all_shards", "response_policy:agg_sum", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DBSIZE key specs */ #define DBSIZE_Keyspecs NULL #endif /********** DEBUG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DEBUG history */ #define DEBUG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DEBUG tips */ #define DEBUG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DEBUG key specs */ #define DEBUG_Keyspecs NULL #endif /********** FAILOVER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FAILOVER history */ #define FAILOVER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* FAILOVER tips */ #define FAILOVER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FAILOVER key specs */ #define FAILOVER_Keyspecs NULL #endif /* FAILOVER target argument table */ struct COMMAND_ARG FAILOVER_target_Subargs[] = { {MAKE_ARG("host",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("force",ARG_TYPE_PURE_TOKEN,-1,"FORCE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* FAILOVER argument table */ struct COMMAND_ARG FAILOVER_Args[] = { {MAKE_ARG("target",ARG_TYPE_BLOCK,-1,"TO",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=FAILOVER_target_Subargs}, {MAKE_ARG("abort",ARG_TYPE_PURE_TOKEN,-1,"ABORT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"TIMEOUT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** FLUSHALL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FLUSHALL history */ commandHistory FLUSHALL_History[] = { {"4.0.0","Added the `ASYNC` flushing mode modifier."}, {"6.2.0","Added the `SYNC` flushing mode modifier."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* FLUSHALL tips */ const char *FLUSHALL_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FLUSHALL key specs */ #define FLUSHALL_Keyspecs NULL #endif /* FLUSHALL flush_type argument table */ struct COMMAND_ARG FLUSHALL_flush_type_Subargs[] = { {MAKE_ARG("async",ARG_TYPE_PURE_TOKEN,-1,"ASYNC",NULL,"4.0.0",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sync",ARG_TYPE_PURE_TOKEN,-1,"SYNC",NULL,"6.2.0",CMD_ARG_NONE,0,NULL)}, }; /* FLUSHALL argument table */ struct COMMAND_ARG FLUSHALL_Args[] = { {MAKE_ARG("flush-type",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=FLUSHALL_flush_type_Subargs}, }; /********** FLUSHDB ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* FLUSHDB history */ commandHistory FLUSHDB_History[] = { {"4.0.0","Added the `ASYNC` flushing mode modifier."}, {"6.2.0","Added the `SYNC` flushing mode modifier."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* FLUSHDB tips */ const char *FLUSHDB_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* FLUSHDB key specs */ #define FLUSHDB_Keyspecs NULL #endif /* FLUSHDB flush_type argument table */ struct COMMAND_ARG FLUSHDB_flush_type_Subargs[] = { {MAKE_ARG("async",ARG_TYPE_PURE_TOKEN,-1,"ASYNC",NULL,"4.0.0",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sync",ARG_TYPE_PURE_TOKEN,-1,"SYNC",NULL,"6.2.0",CMD_ARG_NONE,0,NULL)}, }; /* FLUSHDB argument table */ struct COMMAND_ARG FLUSHDB_Args[] = { {MAKE_ARG("flush-type",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=FLUSHDB_flush_type_Subargs}, }; /********** INFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* INFO history */ commandHistory INFO_History[] = { {"7.0.0","Added support for taking multiple section arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* INFO tips */ const char *INFO_Tips[] = { "nondeterministic_output", "request_policy:all_shards", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* INFO key specs */ #define INFO_Keyspecs NULL #endif /* INFO argument table */ struct COMMAND_ARG INFO_Args[] = { {MAKE_ARG("section",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** LASTSAVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LASTSAVE history */ #define LASTSAVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LASTSAVE tips */ const char *LASTSAVE_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LASTSAVE key specs */ #define LASTSAVE_Keyspecs NULL #endif /********** LATENCY DOCTOR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY DOCTOR history */ #define LATENCY_DOCTOR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY DOCTOR tips */ const char *LATENCY_DOCTOR_Tips[] = { "nondeterministic_output", "request_policy:all_nodes", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY DOCTOR key specs */ #define LATENCY_DOCTOR_Keyspecs NULL #endif /********** LATENCY GRAPH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY GRAPH history */ #define LATENCY_GRAPH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY GRAPH tips */ const char *LATENCY_GRAPH_Tips[] = { "nondeterministic_output", "request_policy:all_nodes", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY GRAPH key specs */ #define LATENCY_GRAPH_Keyspecs NULL #endif /* LATENCY GRAPH argument table */ struct COMMAND_ARG LATENCY_GRAPH_Args[] = { {MAKE_ARG("event",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LATENCY HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY HELP history */ #define LATENCY_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY HELP tips */ #define LATENCY_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY HELP key specs */ #define LATENCY_HELP_Keyspecs NULL #endif /********** LATENCY HISTOGRAM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY HISTOGRAM history */ #define LATENCY_HISTOGRAM_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY HISTOGRAM tips */ const char *LATENCY_HISTOGRAM_Tips[] = { "nondeterministic_output", "request_policy:all_nodes", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY HISTOGRAM key specs */ #define LATENCY_HISTOGRAM_Keyspecs NULL #endif /* LATENCY HISTOGRAM argument table */ struct COMMAND_ARG LATENCY_HISTOGRAM_Args[] = { {MAKE_ARG("command",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** LATENCY HISTORY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY HISTORY history */ #define LATENCY_HISTORY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY HISTORY tips */ const char *LATENCY_HISTORY_Tips[] = { "nondeterministic_output", "request_policy:all_nodes", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY HISTORY key specs */ #define LATENCY_HISTORY_Keyspecs NULL #endif /* LATENCY HISTORY argument table */ struct COMMAND_ARG LATENCY_HISTORY_Args[] = { {MAKE_ARG("event",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LATENCY LATEST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY LATEST history */ #define LATENCY_LATEST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY LATEST tips */ const char *LATENCY_LATEST_Tips[] = { "nondeterministic_output", "request_policy:all_nodes", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY LATEST key specs */ #define LATENCY_LATEST_Keyspecs NULL #endif /********** LATENCY RESET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY RESET history */ #define LATENCY_RESET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY RESET tips */ const char *LATENCY_RESET_Tips[] = { "request_policy:all_nodes", "response_policy:agg_sum", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY RESET key specs */ #define LATENCY_RESET_Keyspecs NULL #endif /* LATENCY RESET argument table */ struct COMMAND_ARG LATENCY_RESET_Args[] = { {MAKE_ARG("event",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /* LATENCY command table */ struct COMMAND_STRUCT LATENCY_Subcommands[] = { {MAKE_CMD("doctor","Returns a human-readable latency analysis report.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_DOCTOR_History,0,LATENCY_DOCTOR_Tips,3,latencyCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,LATENCY_DOCTOR_Keyspecs,0,NULL,0)}, {MAKE_CMD("graph","Returns a latency graph for an event.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_GRAPH_History,0,LATENCY_GRAPH_Tips,3,latencyCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,LATENCY_GRAPH_Keyspecs,0,NULL,1),.args=LATENCY_GRAPH_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_HELP_History,0,LATENCY_HELP_Tips,0,latencyCommand,2,CMD_LOADING|CMD_STALE,0,LATENCY_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("histogram","Returns the cumulative distribution of latencies of a subset or all commands.","O(N) where N is the number of commands with latency information being retrieved.","7.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_HISTOGRAM_History,0,LATENCY_HISTOGRAM_Tips,3,latencyCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,LATENCY_HISTOGRAM_Keyspecs,0,NULL,1),.args=LATENCY_HISTOGRAM_Args}, {MAKE_CMD("history","Returns timestamp-latency samples for an event.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_HISTORY_History,0,LATENCY_HISTORY_Tips,3,latencyCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,LATENCY_HISTORY_Keyspecs,0,NULL,1),.args=LATENCY_HISTORY_Args}, {MAKE_CMD("latest","Returns the latest latency samples for all events.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_LATEST_History,0,LATENCY_LATEST_Tips,3,latencyCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,LATENCY_LATEST_Keyspecs,0,NULL,0)}, {MAKE_CMD("reset","Resets the latency data for one or more events.","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_RESET_History,0,LATENCY_RESET_Tips,2,latencyCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,LATENCY_RESET_Keyspecs,0,NULL,1),.args=LATENCY_RESET_Args}, {0} }; /********** LATENCY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LATENCY history */ #define LATENCY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LATENCY tips */ #define LATENCY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LATENCY key specs */ #define LATENCY_Keyspecs NULL #endif /********** LOLWUT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LOLWUT history */ #define LOLWUT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LOLWUT tips */ #define LOLWUT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LOLWUT key specs */ #define LOLWUT_Keyspecs NULL #endif /* LOLWUT argument table */ struct COMMAND_ARG LOLWUT_Args[] = { {MAKE_ARG("version",ARG_TYPE_INTEGER,-1,"VERSION",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** MEMORY DOCTOR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY DOCTOR history */ #define MEMORY_DOCTOR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY DOCTOR tips */ const char *MEMORY_DOCTOR_Tips[] = { "nondeterministic_output", "request_policy:all_shards", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY DOCTOR key specs */ #define MEMORY_DOCTOR_Keyspecs NULL #endif /********** MEMORY HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY HELP history */ #define MEMORY_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY HELP tips */ #define MEMORY_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY HELP key specs */ #define MEMORY_HELP_Keyspecs NULL #endif /********** MEMORY MALLOC_STATS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY MALLOC_STATS history */ #define MEMORY_MALLOC_STATS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY MALLOC_STATS tips */ const char *MEMORY_MALLOC_STATS_Tips[] = { "nondeterministic_output", "request_policy:all_shards", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY MALLOC_STATS key specs */ #define MEMORY_MALLOC_STATS_Keyspecs NULL #endif /********** MEMORY PURGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY PURGE history */ #define MEMORY_PURGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY PURGE tips */ const char *MEMORY_PURGE_Tips[] = { "request_policy:all_shards", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY PURGE key specs */ #define MEMORY_PURGE_Keyspecs NULL #endif /********** MEMORY STATS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY STATS history */ #define MEMORY_STATS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY STATS tips */ const char *MEMORY_STATS_Tips[] = { "nondeterministic_output", "request_policy:all_shards", "response_policy:special", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY STATS key specs */ #define MEMORY_STATS_Keyspecs NULL #endif /********** MEMORY USAGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY USAGE history */ #define MEMORY_USAGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY USAGE tips */ #define MEMORY_USAGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY USAGE key specs */ keySpec MEMORY_USAGE_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* MEMORY USAGE argument table */ struct COMMAND_ARG MEMORY_USAGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"SAMPLES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* MEMORY command table */ struct COMMAND_STRUCT MEMORY_Subcommands[] = { {MAKE_CMD("doctor","Outputs a memory problems report.","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_DOCTOR_History,0,MEMORY_DOCTOR_Tips,3,memoryCommand,2,0,0,MEMORY_DOCTOR_Keyspecs,0,NULL,0)}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_HELP_History,0,MEMORY_HELP_Tips,0,memoryCommand,2,CMD_LOADING|CMD_STALE,0,MEMORY_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("malloc-stats","Returns the allocator statistics.","Depends on how much memory is allocated, could be slow","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_MALLOC_STATS_History,0,MEMORY_MALLOC_STATS_Tips,3,memoryCommand,2,0,0,MEMORY_MALLOC_STATS_Keyspecs,0,NULL,0)}, {MAKE_CMD("purge","Asks the allocator to release memory.","Depends on how much memory is allocated, could be slow","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_PURGE_History,0,MEMORY_PURGE_Tips,2,memoryCommand,2,0,0,MEMORY_PURGE_Keyspecs,0,NULL,0)}, {MAKE_CMD("stats","Returns details about memory usage.","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_STATS_History,0,MEMORY_STATS_Tips,3,memoryCommand,2,0,0,MEMORY_STATS_Keyspecs,0,NULL,0)}, {MAKE_CMD("usage","Estimates the memory usage of a key.","O(N) where N is the number of samples.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_USAGE_History,0,MEMORY_USAGE_Tips,0,memoryCommand,-3,CMD_READONLY,0,MEMORY_USAGE_Keyspecs,1,NULL,2),.args=MEMORY_USAGE_Args}, {0} }; /********** MEMORY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MEMORY history */ #define MEMORY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MEMORY tips */ #define MEMORY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MEMORY key specs */ #define MEMORY_Keyspecs NULL #endif /********** MODULE HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MODULE HELP history */ #define MODULE_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MODULE HELP tips */ #define MODULE_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MODULE HELP key specs */ #define MODULE_HELP_Keyspecs NULL #endif /********** MODULE LIST ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MODULE LIST history */ #define MODULE_LIST_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MODULE LIST tips */ const char *MODULE_LIST_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MODULE LIST key specs */ #define MODULE_LIST_Keyspecs NULL #endif /********** MODULE LOAD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MODULE LOAD history */ #define MODULE_LOAD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MODULE LOAD tips */ #define MODULE_LOAD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MODULE LOAD key specs */ #define MODULE_LOAD_Keyspecs NULL #endif /* MODULE LOAD argument table */ struct COMMAND_ARG MODULE_LOAD_Args[] = { {MAKE_ARG("path",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** MODULE LOADEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MODULE LOADEX history */ #define MODULE_LOADEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MODULE LOADEX tips */ #define MODULE_LOADEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MODULE LOADEX key specs */ #define MODULE_LOADEX_Keyspecs NULL #endif /* MODULE LOADEX configs argument table */ struct COMMAND_ARG MODULE_LOADEX_configs_Subargs[] = { {MAKE_ARG("name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* MODULE LOADEX argument table */ struct COMMAND_ARG MODULE_LOADEX_Args[] = { {MAKE_ARG("path",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("configs",ARG_TYPE_BLOCK,-1,"CONFIG",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE|CMD_ARG_MULTIPLE_TOKEN,2,NULL),.subargs=MODULE_LOADEX_configs_Subargs}, {MAKE_ARG("args",ARG_TYPE_STRING,-1,"ARGS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, }; /********** MODULE UNLOAD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MODULE UNLOAD history */ #define MODULE_UNLOAD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MODULE UNLOAD tips */ #define MODULE_UNLOAD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MODULE UNLOAD key specs */ #define MODULE_UNLOAD_Keyspecs NULL #endif /* MODULE UNLOAD argument table */ struct COMMAND_ARG MODULE_UNLOAD_Args[] = { {MAKE_ARG("name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* MODULE command table */ struct COMMAND_STRUCT MODULE_Subcommands[] = { {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_HELP_History,0,MODULE_HELP_Tips,0,moduleCommand,2,CMD_LOADING|CMD_STALE,0,MODULE_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("list","Returns all loaded modules.","O(N) where N is the number of loaded modules.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_LIST_History,0,MODULE_LIST_Tips,1,moduleCommand,2,CMD_ADMIN|CMD_NOSCRIPT,0,MODULE_LIST_Keyspecs,0,NULL,0)}, {MAKE_CMD("load","Loads a module.","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_LOAD_History,0,MODULE_LOAD_Tips,0,moduleCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,MODULE_LOAD_Keyspecs,0,NULL,2),.args=MODULE_LOAD_Args}, {MAKE_CMD("loadex","Loads a module using extended parameters.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_LOADEX_History,0,MODULE_LOADEX_Tips,0,moduleCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,MODULE_LOADEX_Keyspecs,0,NULL,3),.args=MODULE_LOADEX_Args}, {MAKE_CMD("unload","Unloads a module.","O(1)","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_UNLOAD_History,0,MODULE_UNLOAD_Tips,0,moduleCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_PROTECTED,0,MODULE_UNLOAD_Keyspecs,0,NULL,1),.args=MODULE_UNLOAD_Args}, {0} }; /********** MODULE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MODULE history */ #define MODULE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MODULE tips */ #define MODULE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MODULE key specs */ #define MODULE_Keyspecs NULL #endif /********** MONITOR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MONITOR history */ #define MONITOR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MONITOR tips */ #define MONITOR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MONITOR key specs */ #define MONITOR_Keyspecs NULL #endif /********** PSYNC ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PSYNC history */ #define PSYNC_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PSYNC tips */ #define PSYNC_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PSYNC key specs */ #define PSYNC_Keyspecs NULL #endif /* PSYNC argument table */ struct COMMAND_ARG PSYNC_Args[] = { {MAKE_ARG("replicationid",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** REPLCONF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* REPLCONF history */ #define REPLCONF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* REPLCONF tips */ #define REPLCONF_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* REPLCONF key specs */ #define REPLCONF_Keyspecs NULL #endif /********** REPLICAOF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* REPLICAOF history */ #define REPLICAOF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* REPLICAOF tips */ #define REPLICAOF_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* REPLICAOF key specs */ #define REPLICAOF_Keyspecs NULL #endif /* REPLICAOF args host_port argument table */ struct COMMAND_ARG REPLICAOF_args_host_port_Subargs[] = { {MAKE_ARG("host",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* REPLICAOF args no_one argument table */ struct COMMAND_ARG REPLICAOF_args_no_one_Subargs[] = { {MAKE_ARG("no",ARG_TYPE_PURE_TOKEN,-1,"NO",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("one",ARG_TYPE_PURE_TOKEN,-1,"ONE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* REPLICAOF args argument table */ struct COMMAND_ARG REPLICAOF_args_Subargs[] = { {MAKE_ARG("host-port",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=REPLICAOF_args_host_port_Subargs}, {MAKE_ARG("no-one",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=REPLICAOF_args_no_one_Subargs}, }; /* REPLICAOF argument table */ struct COMMAND_ARG REPLICAOF_Args[] = { {MAKE_ARG("args",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=REPLICAOF_args_Subargs}, }; /********** RESTORE_ASKING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* RESTORE_ASKING history */ commandHistory RESTORE_ASKING_History[] = { {"3.0.0","Added the `REPLACE` modifier."}, {"5.0.0","Added the `ABSTTL` modifier."}, {"5.0.0","Added the `IDLETIME` and `FREQ` options."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* RESTORE_ASKING tips */ #define RESTORE_ASKING_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* RESTORE_ASKING key specs */ keySpec RESTORE_ASKING_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* RESTORE_ASKING argument table */ struct COMMAND_ARG RESTORE_ASKING_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("ttl",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("serialized-value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("replace",ARG_TYPE_PURE_TOKEN,-1,"REPLACE",NULL,"3.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("absttl",ARG_TYPE_PURE_TOKEN,-1,"ABSTTL",NULL,"5.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"IDLETIME",NULL,"5.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("frequency",ARG_TYPE_INTEGER,-1,"FREQ",NULL,"5.0.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ROLE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ROLE history */ #define ROLE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ROLE tips */ #define ROLE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ROLE key specs */ #define ROLE_Keyspecs NULL #endif /********** SAVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SAVE history */ #define SAVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SAVE tips */ #define SAVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SAVE key specs */ #define SAVE_Keyspecs NULL #endif /********** SHUTDOWN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SHUTDOWN history */ commandHistory SHUTDOWN_History[] = { {"7.0.0","Added the `NOW`, `FORCE` and `ABORT` modifiers."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SHUTDOWN tips */ #define SHUTDOWN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SHUTDOWN key specs */ #define SHUTDOWN_Keyspecs NULL #endif /* SHUTDOWN save_selector argument table */ struct COMMAND_ARG SHUTDOWN_save_selector_Subargs[] = { {MAKE_ARG("nosave",ARG_TYPE_PURE_TOKEN,-1,"NOSAVE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("save",ARG_TYPE_PURE_TOKEN,-1,"SAVE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SHUTDOWN argument table */ struct COMMAND_ARG SHUTDOWN_Args[] = { {MAKE_ARG("save-selector",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=SHUTDOWN_save_selector_Subargs}, {MAKE_ARG("now",ARG_TYPE_PURE_TOKEN,-1,"NOW",NULL,"7.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("force",ARG_TYPE_PURE_TOKEN,-1,"FORCE",NULL,"7.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("abort",ARG_TYPE_PURE_TOKEN,-1,"ABORT",NULL,"7.0.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SLAVEOF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SLAVEOF history */ #define SLAVEOF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SLAVEOF tips */ #define SLAVEOF_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SLAVEOF key specs */ #define SLAVEOF_Keyspecs NULL #endif /* SLAVEOF args host_port argument table */ struct COMMAND_ARG SLAVEOF_args_host_port_Subargs[] = { {MAKE_ARG("host",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SLAVEOF args no_one argument table */ struct COMMAND_ARG SLAVEOF_args_no_one_Subargs[] = { {MAKE_ARG("no",ARG_TYPE_PURE_TOKEN,-1,"NO",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("one",ARG_TYPE_PURE_TOKEN,-1,"ONE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SLAVEOF args argument table */ struct COMMAND_ARG SLAVEOF_args_Subargs[] = { {MAKE_ARG("host-port",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=SLAVEOF_args_host_port_Subargs}, {MAKE_ARG("no-one",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=SLAVEOF_args_no_one_Subargs}, }; /* SLAVEOF argument table */ struct COMMAND_ARG SLAVEOF_Args[] = { {MAKE_ARG("args",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=SLAVEOF_args_Subargs}, }; /********** SLOWLOG GET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SLOWLOG GET history */ commandHistory SLOWLOG_GET_History[] = { {"4.0.0","Added client IP address, port and name to the reply."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SLOWLOG GET tips */ const char *SLOWLOG_GET_Tips[] = { "request_policy:all_nodes", "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SLOWLOG GET key specs */ #define SLOWLOG_GET_Keyspecs NULL #endif /* SLOWLOG GET argument table */ struct COMMAND_ARG SLOWLOG_GET_Args[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SLOWLOG HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SLOWLOG HELP history */ #define SLOWLOG_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SLOWLOG HELP tips */ #define SLOWLOG_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SLOWLOG HELP key specs */ #define SLOWLOG_HELP_Keyspecs NULL #endif /********** SLOWLOG LEN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SLOWLOG LEN history */ #define SLOWLOG_LEN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SLOWLOG LEN tips */ const char *SLOWLOG_LEN_Tips[] = { "request_policy:all_nodes", "response_policy:agg_sum", "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SLOWLOG LEN key specs */ #define SLOWLOG_LEN_Keyspecs NULL #endif /********** SLOWLOG RESET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SLOWLOG RESET history */ #define SLOWLOG_RESET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SLOWLOG RESET tips */ const char *SLOWLOG_RESET_Tips[] = { "request_policy:all_nodes", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SLOWLOG RESET key specs */ #define SLOWLOG_RESET_Keyspecs NULL #endif /* SLOWLOG command table */ struct COMMAND_STRUCT SLOWLOG_Subcommands[] = { {MAKE_CMD("get","Returns the slow log's entries.","O(N) where N is the number of entries returned","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_GET_History,1,SLOWLOG_GET_Tips,2,slowlogCommand,-2,CMD_ADMIN|CMD_LOADING|CMD_STALE,0,SLOWLOG_GET_Keyspecs,0,NULL,1),.args=SLOWLOG_GET_Args}, {MAKE_CMD("help","Show helpful text about the different subcommands","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_HELP_History,0,SLOWLOG_HELP_Tips,0,slowlogCommand,2,CMD_LOADING|CMD_STALE,0,SLOWLOG_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("len","Returns the number of entries in the slow log.","O(1)","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_LEN_History,0,SLOWLOG_LEN_Tips,3,slowlogCommand,2,CMD_ADMIN|CMD_LOADING|CMD_STALE,0,SLOWLOG_LEN_Keyspecs,0,NULL,0)}, {MAKE_CMD("reset","Clears all entries from the slow log.","O(N) where N is the number of entries in the slowlog","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_RESET_History,0,SLOWLOG_RESET_Tips,2,slowlogCommand,2,CMD_ADMIN|CMD_LOADING|CMD_STALE,0,SLOWLOG_RESET_Keyspecs,0,NULL,0)}, {0} }; /********** SLOWLOG ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SLOWLOG history */ #define SLOWLOG_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SLOWLOG tips */ #define SLOWLOG_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SLOWLOG key specs */ #define SLOWLOG_Keyspecs NULL #endif /********** SWAPDB ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SWAPDB history */ #define SWAPDB_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SWAPDB tips */ #define SWAPDB_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SWAPDB key specs */ #define SWAPDB_Keyspecs NULL #endif /* SWAPDB argument table */ struct COMMAND_ARG SWAPDB_Args[] = { {MAKE_ARG("index1",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("index2",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SYNC ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SYNC history */ #define SYNC_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SYNC tips */ #define SYNC_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SYNC key specs */ #define SYNC_Keyspecs NULL #endif /********** TIME ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* TIME history */ #define TIME_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* TIME tips */ const char *TIME_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* TIME key specs */ #define TIME_Keyspecs NULL #endif /********** SADD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SADD history */ commandHistory SADD_History[] = { {"2.4.0","Accepts multiple `member` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SADD tips */ #define SADD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SADD key specs */ keySpec SADD_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SADD argument table */ struct COMMAND_ARG SADD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SCARD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SCARD history */ #define SCARD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SCARD tips */ #define SCARD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SCARD key specs */ keySpec SCARD_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SCARD argument table */ struct COMMAND_ARG SCARD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SDIFF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SDIFF history */ #define SDIFF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SDIFF tips */ const char *SDIFF_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SDIFF key specs */ keySpec SDIFF_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SDIFF argument table */ struct COMMAND_ARG SDIFF_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SDIFFSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SDIFFSTORE history */ #define SDIFFSTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SDIFFSTORE tips */ #define SDIFFSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SDIFFSTORE key specs */ keySpec SDIFFSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SDIFFSTORE argument table */ struct COMMAND_ARG SDIFFSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SINTER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SINTER history */ #define SINTER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SINTER tips */ const char *SINTER_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SINTER key specs */ keySpec SINTER_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SINTER argument table */ struct COMMAND_ARG SINTER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SINTERCARD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SINTERCARD history */ #define SINTERCARD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SINTERCARD tips */ #define SINTERCARD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SINTERCARD key specs */ keySpec SINTERCARD_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* SINTERCARD argument table */ struct COMMAND_ARG SINTERCARD_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SINTERSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SINTERSTORE history */ #define SINTERSTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SINTERSTORE tips */ #define SINTERSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SINTERSTORE key specs */ keySpec SINTERSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SINTERSTORE argument table */ struct COMMAND_ARG SINTERSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SISMEMBER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SISMEMBER history */ #define SISMEMBER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SISMEMBER tips */ #define SISMEMBER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SISMEMBER key specs */ keySpec SISMEMBER_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SISMEMBER argument table */ struct COMMAND_ARG SISMEMBER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SMEMBERS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SMEMBERS history */ #define SMEMBERS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SMEMBERS tips */ const char *SMEMBERS_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SMEMBERS key specs */ keySpec SMEMBERS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SMEMBERS argument table */ struct COMMAND_ARG SMEMBERS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SMISMEMBER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SMISMEMBER history */ #define SMISMEMBER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SMISMEMBER tips */ #define SMISMEMBER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SMISMEMBER key specs */ keySpec SMISMEMBER_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SMISMEMBER argument table */ struct COMMAND_ARG SMISMEMBER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SMOVE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SMOVE history */ #define SMOVE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SMOVE tips */ #define SMOVE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SMOVE key specs */ keySpec SMOVE_Keyspecs[2] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SMOVE argument table */ struct COMMAND_ARG SMOVE_Args[] = { {MAKE_ARG("source",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("destination",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SPOP history */ commandHistory SPOP_History[] = { {"3.2.0","Added the `count` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SPOP tips */ const char *SPOP_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SPOP key specs */ keySpec SPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SPOP argument table */ struct COMMAND_ARG SPOP_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,"3.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SRANDMEMBER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SRANDMEMBER history */ commandHistory SRANDMEMBER_History[] = { {"2.6.0","Added the optional `count` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SRANDMEMBER tips */ const char *SRANDMEMBER_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SRANDMEMBER key specs */ keySpec SRANDMEMBER_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SRANDMEMBER argument table */ struct COMMAND_ARG SRANDMEMBER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,"2.6.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SREM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SREM history */ commandHistory SREM_History[] = { {"2.4.0","Accepts multiple `member` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SREM tips */ #define SREM_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SREM key specs */ keySpec SREM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SREM argument table */ struct COMMAND_ARG SREM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SSCAN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SSCAN history */ #define SSCAN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SSCAN tips */ const char *SSCAN_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SSCAN key specs */ keySpec SSCAN_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SSCAN argument table */ struct COMMAND_ARG SSCAN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("cursor",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,"MATCH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** SUNION ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SUNION history */ #define SUNION_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SUNION tips */ const char *SUNION_Tips[] = { "nondeterministic_output_order", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SUNION key specs */ keySpec SUNION_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SUNION argument table */ struct COMMAND_ARG SUNION_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** SUNIONSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SUNIONSTORE history */ #define SUNIONSTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SUNIONSTORE tips */ #define SUNIONSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SUNIONSTORE key specs */ keySpec SUNIONSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* SUNIONSTORE argument table */ struct COMMAND_ARG SUNIONSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** BZMPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BZMPOP history */ #define BZMPOP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* BZMPOP tips */ #define BZMPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BZMPOP key specs */ keySpec BZMPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* BZMPOP where argument table */ struct COMMAND_ARG BZMPOP_where_Subargs[] = { {MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* BZMPOP argument table */ struct COMMAND_ARG BZMPOP_Args[] = { {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("where",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=BZMPOP_where_Subargs}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** BZPOPMAX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BZPOPMAX history */ commandHistory BZPOPMAX_History[] = { {"6.0.0","`timeout` is interpreted as a double instead of an integer."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BZPOPMAX tips */ #define BZPOPMAX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BZPOPMAX key specs */ keySpec BZPOPMAX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-2,1,0}} }; #endif /* BZPOPMAX argument table */ struct COMMAND_ARG BZPOPMAX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** BZPOPMIN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* BZPOPMIN history */ commandHistory BZPOPMIN_History[] = { {"6.0.0","`timeout` is interpreted as a double instead of an integer."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* BZPOPMIN tips */ #define BZPOPMIN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* BZPOPMIN key specs */ keySpec BZPOPMIN_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-2,1,0}} }; #endif /* BZPOPMIN argument table */ struct COMMAND_ARG BZPOPMIN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("timeout",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZADD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZADD history */ commandHistory ZADD_History[] = { {"2.4.0","Accepts multiple elements."}, {"3.0.2","Added the `XX`, `NX`, `CH` and `INCR` options."}, {"6.2.0","Added the `GT` and `LT` options."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZADD tips */ #define ZADD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZADD key specs */ keySpec ZADD_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZADD condition argument table */ struct COMMAND_ARG ZADD_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZADD comparison argument table */ struct COMMAND_ARG ZADD_comparison_Subargs[] = { {MAKE_ARG("gt",ARG_TYPE_PURE_TOKEN,-1,"GT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("lt",ARG_TYPE_PURE_TOKEN,-1,"LT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZADD data argument table */ struct COMMAND_ARG ZADD_data_Subargs[] = { {MAKE_ARG("score",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZADD argument table */ struct COMMAND_ARG ZADD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"3.0.2",CMD_ARG_OPTIONAL,2,NULL),.subargs=ZADD_condition_Subargs}, {MAKE_ARG("comparison",ARG_TYPE_ONEOF,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=ZADD_comparison_Subargs}, {MAKE_ARG("change",ARG_TYPE_PURE_TOKEN,-1,"CH",NULL,"3.0.2",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_PURE_TOKEN,-1,"INCR",NULL,"3.0.2",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=ZADD_data_Subargs}, }; /********** ZCARD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZCARD history */ #define ZCARD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZCARD tips */ #define ZCARD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZCARD key specs */ keySpec ZCARD_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZCARD argument table */ struct COMMAND_ARG ZCARD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZCOUNT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZCOUNT history */ #define ZCOUNT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZCOUNT tips */ #define ZCOUNT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZCOUNT key specs */ keySpec ZCOUNT_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZCOUNT argument table */ struct COMMAND_ARG ZCOUNT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZDIFF ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZDIFF history */ #define ZDIFF_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZDIFF tips */ #define ZDIFF_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZDIFF key specs */ keySpec ZDIFF_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZDIFF argument table */ struct COMMAND_ARG ZDIFF_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZDIFFSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZDIFFSTORE history */ #define ZDIFFSTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZDIFFSTORE tips */ #define ZDIFFSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZDIFFSTORE key specs */ keySpec ZDIFFSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZDIFFSTORE argument table */ struct COMMAND_ARG ZDIFFSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** ZINCRBY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZINCRBY history */ #define ZINCRBY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZINCRBY tips */ #define ZINCRBY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZINCRBY key specs */ keySpec ZINCRBY_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZINCRBY argument table */ struct COMMAND_ARG ZINCRBY_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZINTER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZINTER history */ #define ZINTER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZINTER tips */ #define ZINTER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZINTER key specs */ keySpec ZINTER_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZINTER aggregate argument table */ struct COMMAND_ARG ZINTER_aggregate_Subargs[] = { {MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZINTER argument table */ struct COMMAND_ARG ZINTER_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZINTER_aggregate_Subargs}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZINTERCARD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZINTERCARD history */ #define ZINTERCARD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZINTERCARD tips */ #define ZINTERCARD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZINTERCARD key specs */ keySpec ZINTERCARD_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZINTERCARD argument table */ struct COMMAND_ARG ZINTERCARD_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZINTERSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZINTERSTORE history */ #define ZINTERSTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZINTERSTORE tips */ #define ZINTERSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZINTERSTORE key specs */ keySpec ZINTERSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZINTERSTORE aggregate argument table */ struct COMMAND_ARG ZINTERSTORE_aggregate_Subargs[] = { {MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZINTERSTORE argument table */ struct COMMAND_ARG ZINTERSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZINTERSTORE_aggregate_Subargs}, }; /********** ZLEXCOUNT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZLEXCOUNT history */ #define ZLEXCOUNT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZLEXCOUNT tips */ #define ZLEXCOUNT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZLEXCOUNT key specs */ keySpec ZLEXCOUNT_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZLEXCOUNT argument table */ struct COMMAND_ARG ZLEXCOUNT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZMPOP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZMPOP history */ #define ZMPOP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZMPOP tips */ #define ZMPOP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZMPOP key specs */ keySpec ZMPOP_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZMPOP where argument table */ struct COMMAND_ARG ZMPOP_where_Subargs[] = { {MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZMPOP argument table */ struct COMMAND_ARG ZMPOP_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("where",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ZMPOP_where_Subargs}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZMSCORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZMSCORE history */ #define ZMSCORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZMSCORE tips */ #define ZMSCORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZMSCORE key specs */ keySpec ZMSCORE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZMSCORE argument table */ struct COMMAND_ARG ZMSCORE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** ZPOPMAX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZPOPMAX history */ #define ZPOPMAX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZPOPMAX tips */ #define ZPOPMAX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZPOPMAX key specs */ keySpec ZPOPMAX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZPOPMAX argument table */ struct COMMAND_ARG ZPOPMAX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZPOPMIN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZPOPMIN history */ #define ZPOPMIN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZPOPMIN tips */ #define ZPOPMIN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZPOPMIN key specs */ keySpec ZPOPMIN_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZPOPMIN argument table */ struct COMMAND_ARG ZPOPMIN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZRANDMEMBER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZRANDMEMBER history */ #define ZRANDMEMBER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZRANDMEMBER tips */ const char *ZRANDMEMBER_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZRANDMEMBER key specs */ keySpec ZRANDMEMBER_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZRANDMEMBER options argument table */ struct COMMAND_ARG ZRANDMEMBER_options_Subargs[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* ZRANDMEMBER argument table */ struct COMMAND_ARG ZRANDMEMBER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("options",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANDMEMBER_options_Subargs}, }; /********** ZRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZRANGE history */ commandHistory ZRANGE_History[] = { {"6.2.0","Added the `REV`, `BYSCORE`, `BYLEX` and `LIMIT` options."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZRANGE tips */ #define ZRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZRANGE key specs */ keySpec ZRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZRANGE sortby argument table */ struct COMMAND_ARG ZRANGE_sortby_Subargs[] = { {MAKE_ARG("byscore",ARG_TYPE_PURE_TOKEN,-1,"BYSCORE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("bylex",ARG_TYPE_PURE_TOKEN,-1,"BYLEX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZRANGE limit argument table */ struct COMMAND_ARG ZRANGE_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZRANGE argument table */ struct COMMAND_ARG ZRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("stop",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sortby",ARG_TYPE_ONEOF,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANGE_sortby_Subargs}, {MAKE_ARG("rev",ARG_TYPE_PURE_TOKEN,-1,"REV",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,"6.2.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANGE_limit_Subargs}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZRANGEBYLEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZRANGEBYLEX history */ #define ZRANGEBYLEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZRANGEBYLEX tips */ #define ZRANGEBYLEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZRANGEBYLEX key specs */ keySpec ZRANGEBYLEX_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZRANGEBYLEX limit argument table */ struct COMMAND_ARG ZRANGEBYLEX_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZRANGEBYLEX argument table */ struct COMMAND_ARG ZRANGEBYLEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANGEBYLEX_limit_Subargs}, }; /********** ZRANGEBYSCORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZRANGEBYSCORE history */ commandHistory ZRANGEBYSCORE_History[] = { {"2.0.0","Added the `WITHSCORES` modifier."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZRANGEBYSCORE tips */ #define ZRANGEBYSCORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZRANGEBYSCORE key specs */ keySpec ZRANGEBYSCORE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZRANGEBYSCORE limit argument table */ struct COMMAND_ARG ZRANGEBYSCORE_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZRANGEBYSCORE argument table */ struct COMMAND_ARG ZRANGEBYSCORE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,"2.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANGEBYSCORE_limit_Subargs}, }; /********** ZRANGESTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZRANGESTORE history */ #define ZRANGESTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZRANGESTORE tips */ #define ZRANGESTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZRANGESTORE key specs */ keySpec ZRANGESTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZRANGESTORE sortby argument table */ struct COMMAND_ARG ZRANGESTORE_sortby_Subargs[] = { {MAKE_ARG("byscore",ARG_TYPE_PURE_TOKEN,-1,"BYSCORE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("bylex",ARG_TYPE_PURE_TOKEN,-1,"BYLEX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZRANGESTORE limit argument table */ struct COMMAND_ARG ZRANGESTORE_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZRANGESTORE argument table */ struct COMMAND_ARG ZRANGESTORE_Args[] = { {MAKE_ARG("dst",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("src",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("sortby",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANGESTORE_sortby_Subargs}, {MAKE_ARG("rev",ARG_TYPE_PURE_TOKEN,-1,"REV",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZRANGESTORE_limit_Subargs}, }; /********** ZRANK ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZRANK history */ commandHistory ZRANK_History[] = { {"7.2.0","Added the optional `WITHSCORE` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZRANK tips */ #define ZRANK_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZRANK key specs */ keySpec ZRANK_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZRANK argument table */ struct COMMAND_ARG ZRANK_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withscore",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZREM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREM history */ commandHistory ZREM_History[] = { {"2.4.0","Accepts multiple elements."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREM tips */ #define ZREM_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREM key specs */ keySpec ZREM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREM argument table */ struct COMMAND_ARG ZREM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** ZREMRANGEBYLEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREMRANGEBYLEX history */ #define ZREMRANGEBYLEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREMRANGEBYLEX tips */ #define ZREMRANGEBYLEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREMRANGEBYLEX key specs */ keySpec ZREMRANGEBYLEX_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREMRANGEBYLEX argument table */ struct COMMAND_ARG ZREMRANGEBYLEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZREMRANGEBYRANK ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREMRANGEBYRANK history */ #define ZREMRANGEBYRANK_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREMRANGEBYRANK tips */ #define ZREMRANGEBYRANK_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREMRANGEBYRANK key specs */ keySpec ZREMRANGEBYRANK_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREMRANGEBYRANK argument table */ struct COMMAND_ARG ZREMRANGEBYRANK_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("stop",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZREMRANGEBYSCORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREMRANGEBYSCORE history */ #define ZREMRANGEBYSCORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREMRANGEBYSCORE tips */ #define ZREMRANGEBYSCORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREMRANGEBYSCORE key specs */ keySpec ZREMRANGEBYSCORE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREMRANGEBYSCORE argument table */ struct COMMAND_ARG ZREMRANGEBYSCORE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZREVRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREVRANGE history */ #define ZREVRANGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREVRANGE tips */ #define ZREVRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREVRANGE key specs */ keySpec ZREVRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREVRANGE argument table */ struct COMMAND_ARG ZREVRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("stop",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZREVRANGEBYLEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREVRANGEBYLEX history */ #define ZREVRANGEBYLEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREVRANGEBYLEX tips */ #define ZREVRANGEBYLEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREVRANGEBYLEX key specs */ keySpec ZREVRANGEBYLEX_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREVRANGEBYLEX limit argument table */ struct COMMAND_ARG ZREVRANGEBYLEX_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZREVRANGEBYLEX argument table */ struct COMMAND_ARG ZREVRANGEBYLEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZREVRANGEBYLEX_limit_Subargs}, }; /********** ZREVRANGEBYSCORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREVRANGEBYSCORE history */ commandHistory ZREVRANGEBYSCORE_History[] = { {"2.1.6","`min` and `max` can be exclusive."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREVRANGEBYSCORE tips */ #define ZREVRANGEBYSCORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREVRANGEBYSCORE key specs */ keySpec ZREVRANGEBYSCORE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREVRANGEBYSCORE limit argument table */ struct COMMAND_ARG ZREVRANGEBYSCORE_limit_Subargs[] = { {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZREVRANGEBYSCORE argument table */ struct COMMAND_ARG ZREVRANGEBYSCORE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("limit",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=ZREVRANGEBYSCORE_limit_Subargs}, }; /********** ZREVRANK ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZREVRANK history */ commandHistory ZREVRANK_History[] = { {"7.2.0","Added the optional `WITHSCORE` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZREVRANK tips */ #define ZREVRANK_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZREVRANK key specs */ keySpec ZREVRANK_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZREVRANK argument table */ struct COMMAND_ARG ZREVRANK_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("withscore",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZSCAN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZSCAN history */ #define ZSCAN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZSCAN tips */ const char *ZSCAN_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZSCAN key specs */ keySpec ZSCAN_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZSCAN argument table */ struct COMMAND_ARG ZSCAN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("cursor",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("pattern",ARG_TYPE_PATTERN,-1,"MATCH",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZSCORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZSCORE history */ #define ZSCORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZSCORE tips */ #define ZSCORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZSCORE key specs */ keySpec ZSCORE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* ZSCORE argument table */ struct COMMAND_ARG ZSCORE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** ZUNION ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZUNION history */ #define ZUNION_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZUNION tips */ #define ZUNION_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZUNION key specs */ keySpec ZUNION_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZUNION aggregate argument table */ struct COMMAND_ARG ZUNION_aggregate_Subargs[] = { {MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZUNION argument table */ struct COMMAND_ARG ZUNION_Args[] = { {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZUNION_aggregate_Subargs}, {MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** ZUNIONSTORE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* ZUNIONSTORE history */ #define ZUNIONSTORE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* ZUNIONSTORE tips */ #define ZUNIONSTORE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* ZUNIONSTORE key specs */ keySpec ZUNIONSTORE_Keyspecs[2] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}} }; #endif /* ZUNIONSTORE aggregate argument table */ struct COMMAND_ARG ZUNIONSTORE_aggregate_Subargs[] = { {MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* ZUNIONSTORE argument table */ struct COMMAND_ARG ZUNIONSTORE_Args[] = { {MAKE_ARG("destination",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZUNIONSTORE_aggregate_Subargs}, }; /********** XACK ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XACK history */ #define XACK_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XACK tips */ #define XACK_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XACK key specs */ keySpec XACK_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XACK argument table */ struct COMMAND_ARG XACK_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** XADD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XADD history */ commandHistory XADD_History[] = { {"6.2.0","Added the `NOMKSTREAM` option, `MINID` trimming strategy and the `LIMIT` option."}, {"7.0.0","Added support for the `-*` explicit ID form."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XADD tips */ const char *XADD_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XADD key specs */ keySpec XADD_Keyspecs[1] = { {"UPDATE instead of INSERT because of the optional trimming feature",CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XADD trim strategy argument table */ struct COMMAND_ARG XADD_trim_strategy_Subargs[] = { {MAKE_ARG("maxlen",ARG_TYPE_PURE_TOKEN,-1,"MAXLEN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("minid",ARG_TYPE_PURE_TOKEN,-1,"MINID",NULL,"6.2.0",CMD_ARG_NONE,0,NULL)}, }; /* XADD trim operator argument table */ struct COMMAND_ARG XADD_trim_operator_Subargs[] = { {MAKE_ARG("equal",ARG_TYPE_PURE_TOKEN,-1,"=",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("approximately",ARG_TYPE_PURE_TOKEN,-1,"~",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XADD trim argument table */ struct COMMAND_ARG XADD_trim_Subargs[] = { {MAKE_ARG("strategy",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XADD_trim_strategy_Subargs}, {MAKE_ARG("operator",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=XADD_trim_operator_Subargs}, {MAKE_ARG("threshold",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /* XADD id_selector argument table */ struct COMMAND_ARG XADD_id_selector_Subargs[] = { {MAKE_ARG("auto-id",ARG_TYPE_PURE_TOKEN,-1,"*",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XADD data argument table */ struct COMMAND_ARG XADD_data_Subargs[] = { {MAKE_ARG("field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XADD argument table */ struct COMMAND_ARG XADD_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("nomkstream",ARG_TYPE_PURE_TOKEN,-1,"NOMKSTREAM",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("trim",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=XADD_trim_Subargs}, {MAKE_ARG("id-selector",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XADD_id_selector_Subargs}, {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=XADD_data_Subargs}, }; /********** XAUTOCLAIM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XAUTOCLAIM history */ commandHistory XAUTOCLAIM_History[] = { {"7.0.0","Added an element to the reply array, containing deleted entries the command cleared from the PEL"}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XAUTOCLAIM tips */ const char *XAUTOCLAIM_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XAUTOCLAIM key specs */ keySpec XAUTOCLAIM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XAUTOCLAIM argument table */ struct COMMAND_ARG XAUTOCLAIM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("consumer",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min-idle-time",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("justid",ARG_TYPE_PURE_TOKEN,-1,"JUSTID",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** XCLAIM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XCLAIM history */ #define XCLAIM_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XCLAIM tips */ const char *XCLAIM_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XCLAIM key specs */ keySpec XCLAIM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XCLAIM argument table */ struct COMMAND_ARG XCLAIM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("consumer",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("min-idle-time",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("ms",ARG_TYPE_INTEGER,-1,"IDLE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"TIME",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"RETRYCOUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("force",ARG_TYPE_PURE_TOKEN,-1,"FORCE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("justid",ARG_TYPE_PURE_TOKEN,-1,"JUSTID",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("lastid",ARG_TYPE_STRING,-1,"LASTID",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** XDEL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XDEL history */ #define XDEL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XDEL tips */ #define XDEL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XDEL key specs */ keySpec XDEL_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XDEL argument table */ struct COMMAND_ARG XDEL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** XGROUP CREATE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP CREATE history */ commandHistory XGROUP_CREATE_History[] = { {"7.0.0","Added the `entries_read` named argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP CREATE tips */ #define XGROUP_CREATE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP CREATE key specs */ keySpec XGROUP_CREATE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XGROUP CREATE id_selector argument table */ struct COMMAND_ARG XGROUP_CREATE_id_selector_Subargs[] = { {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("new-id",ARG_TYPE_PURE_TOKEN,-1,"$",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XGROUP CREATE argument table */ struct COMMAND_ARG XGROUP_CREATE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("id-selector",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XGROUP_CREATE_id_selector_Subargs}, {MAKE_ARG("mkstream",ARG_TYPE_PURE_TOKEN,-1,"MKSTREAM",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("entriesread",ARG_TYPE_INTEGER,-1,"ENTRIESREAD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="entries-read"}, }; /********** XGROUP CREATECONSUMER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP CREATECONSUMER history */ #define XGROUP_CREATECONSUMER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP CREATECONSUMER tips */ #define XGROUP_CREATECONSUMER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP CREATECONSUMER key specs */ keySpec XGROUP_CREATECONSUMER_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XGROUP CREATECONSUMER argument table */ struct COMMAND_ARG XGROUP_CREATECONSUMER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("consumer",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** XGROUP DELCONSUMER ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP DELCONSUMER history */ #define XGROUP_DELCONSUMER_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP DELCONSUMER tips */ #define XGROUP_DELCONSUMER_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP DELCONSUMER key specs */ keySpec XGROUP_DELCONSUMER_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XGROUP DELCONSUMER argument table */ struct COMMAND_ARG XGROUP_DELCONSUMER_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("consumer",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** XGROUP DESTROY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP DESTROY history */ #define XGROUP_DESTROY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP DESTROY tips */ #define XGROUP_DESTROY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP DESTROY key specs */ keySpec XGROUP_DESTROY_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XGROUP DESTROY argument table */ struct COMMAND_ARG XGROUP_DESTROY_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** XGROUP HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP HELP history */ #define XGROUP_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP HELP tips */ #define XGROUP_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP HELP key specs */ #define XGROUP_HELP_Keyspecs NULL #endif /********** XGROUP SETID ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP SETID history */ commandHistory XGROUP_SETID_History[] = { {"7.0.0","Added the optional `entries_read` argument."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP SETID tips */ #define XGROUP_SETID_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP SETID key specs */ keySpec XGROUP_SETID_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XGROUP SETID id_selector argument table */ struct COMMAND_ARG XGROUP_SETID_id_selector_Subargs[] = { {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("new-id",ARG_TYPE_PURE_TOKEN,-1,"$",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XGROUP SETID argument table */ struct COMMAND_ARG XGROUP_SETID_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("id-selector",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XGROUP_SETID_id_selector_Subargs}, {MAKE_ARG("entriesread",ARG_TYPE_INTEGER,-1,"ENTRIESREAD",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="entries-read"}, }; /* XGROUP command table */ struct COMMAND_STRUCT XGROUP_Subcommands[] = { {MAKE_CMD("create","Creates a consumer group.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_CREATE_History,1,XGROUP_CREATE_Tips,0,xgroupCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STREAM,XGROUP_CREATE_Keyspecs,1,NULL,5),.args=XGROUP_CREATE_Args}, {MAKE_CMD("createconsumer","Creates a consumer in a consumer group.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_CREATECONSUMER_History,0,XGROUP_CREATECONSUMER_Tips,0,xgroupCommand,5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STREAM,XGROUP_CREATECONSUMER_Keyspecs,1,NULL,3),.args=XGROUP_CREATECONSUMER_Args}, {MAKE_CMD("delconsumer","Deletes a consumer from a consumer group.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_DELCONSUMER_History,0,XGROUP_DELCONSUMER_Tips,0,xgroupCommand,5,CMD_WRITE,ACL_CATEGORY_STREAM,XGROUP_DELCONSUMER_Keyspecs,1,NULL,3),.args=XGROUP_DELCONSUMER_Args}, {MAKE_CMD("destroy","Destroys a consumer group.","O(N) where N is the number of entries in the group's pending entries list (PEL).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_DESTROY_History,0,XGROUP_DESTROY_Tips,0,xgroupCommand,4,CMD_WRITE,ACL_CATEGORY_STREAM,XGROUP_DESTROY_Keyspecs,1,NULL,2),.args=XGROUP_DESTROY_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_HELP_History,0,XGROUP_HELP_Tips,0,xgroupCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_STREAM,XGROUP_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("setid","Sets the last-delivered ID of a consumer group.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_SETID_History,1,XGROUP_SETID_Tips,0,xgroupCommand,-5,CMD_WRITE,ACL_CATEGORY_STREAM,XGROUP_SETID_Keyspecs,1,NULL,4),.args=XGROUP_SETID_Args}, {0} }; /********** XGROUP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XGROUP history */ #define XGROUP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XGROUP tips */ #define XGROUP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XGROUP key specs */ #define XGROUP_Keyspecs NULL #endif /********** XINFO CONSUMERS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XINFO CONSUMERS history */ commandHistory XINFO_CONSUMERS_History[] = { {"7.2.0","Added the `inactive` field, and changed the meaning of `idle`."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XINFO CONSUMERS tips */ const char *XINFO_CONSUMERS_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XINFO CONSUMERS key specs */ keySpec XINFO_CONSUMERS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XINFO CONSUMERS argument table */ struct COMMAND_ARG XINFO_CONSUMERS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** XINFO GROUPS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XINFO GROUPS history */ commandHistory XINFO_GROUPS_History[] = { {"7.0.0","Added the `entries-read` and `lag` fields"}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XINFO GROUPS tips */ #define XINFO_GROUPS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XINFO GROUPS key specs */ keySpec XINFO_GROUPS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XINFO GROUPS argument table */ struct COMMAND_ARG XINFO_GROUPS_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** XINFO HELP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XINFO HELP history */ #define XINFO_HELP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XINFO HELP tips */ #define XINFO_HELP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XINFO HELP key specs */ #define XINFO_HELP_Keyspecs NULL #endif /********** XINFO STREAM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XINFO STREAM history */ commandHistory XINFO_STREAM_History[] = { {"6.0.0","Added the `FULL` modifier."}, {"7.0.0","Added the `max-deleted-entry-id`, `entries-added`, `recorded-first-entry-id`, `entries-read` and `lag` fields"}, {"7.2.0","Added the `active-time` field, and changed the meaning of `seen-time`."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XINFO STREAM tips */ #define XINFO_STREAM_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XINFO STREAM key specs */ keySpec XINFO_STREAM_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XINFO STREAM full_block argument table */ struct COMMAND_ARG XINFO_STREAM_full_block_Subargs[] = { {MAKE_ARG("full",ARG_TYPE_PURE_TOKEN,-1,"FULL",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* XINFO STREAM argument table */ struct COMMAND_ARG XINFO_STREAM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("full-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=XINFO_STREAM_full_block_Subargs}, }; /* XINFO command table */ struct COMMAND_STRUCT XINFO_Subcommands[] = { {MAKE_CMD("consumers","Returns a list of the consumers in a consumer group.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_CONSUMERS_History,1,XINFO_CONSUMERS_Tips,1,xinfoCommand,4,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_CONSUMERS_Keyspecs,1,NULL,2),.args=XINFO_CONSUMERS_Args}, {MAKE_CMD("groups","Returns a list of the consumer groups of a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_GROUPS_History,1,XINFO_GROUPS_Tips,0,xinfoCommand,3,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_GROUPS_Keyspecs,1,NULL,1),.args=XINFO_GROUPS_Args}, {MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_HELP_History,0,XINFO_HELP_Tips,0,xinfoCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_STREAM,XINFO_HELP_Keyspecs,0,NULL,0)}, {MAKE_CMD("stream","Returns information about a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_STREAM_History,3,XINFO_STREAM_Tips,0,xinfoCommand,-3,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_STREAM_Keyspecs,1,NULL,2),.args=XINFO_STREAM_Args}, {0} }; /********** XINFO ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XINFO history */ #define XINFO_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XINFO tips */ #define XINFO_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XINFO key specs */ #define XINFO_Keyspecs NULL #endif /********** XLEN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XLEN history */ #define XLEN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XLEN tips */ #define XLEN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XLEN key specs */ keySpec XLEN_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XLEN argument table */ struct COMMAND_ARG XLEN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** XPENDING ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XPENDING history */ commandHistory XPENDING_History[] = { {"6.2.0","Added the `IDLE` option and exclusive range intervals."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XPENDING tips */ const char *XPENDING_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XPENDING key specs */ keySpec XPENDING_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XPENDING filters argument table */ struct COMMAND_ARG XPENDING_filters_Subargs[] = { {MAKE_ARG("min-idle-time",ARG_TYPE_INTEGER,-1,"IDLE",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("consumer",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /* XPENDING argument table */ struct COMMAND_ARG XPENDING_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("filters",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=XPENDING_filters_Subargs}, }; /********** XRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XRANGE history */ commandHistory XRANGE_History[] = { {"6.2.0","Added exclusive ranges."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XRANGE tips */ #define XRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XRANGE key specs */ keySpec XRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XRANGE argument table */ struct COMMAND_ARG XRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** XREAD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XREAD history */ #define XREAD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XREAD tips */ #define XREAD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XREAD key specs */ keySpec XREAD_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_KEYWORD,.bs.keyword={"STREAMS",1},KSPEC_FK_RANGE,.fk.range={-1,1,2}} }; #endif /* XREAD streams argument table */ struct COMMAND_ARG XREAD_streams_Subargs[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* XREAD argument table */ struct COMMAND_ARG XREAD_Args[] = { {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"BLOCK",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("streams",ARG_TYPE_BLOCK,-1,"STREAMS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XREAD_streams_Subargs}, }; /********** XREADGROUP ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XREADGROUP history */ #define XREADGROUP_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* XREADGROUP tips */ #define XREADGROUP_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XREADGROUP key specs */ keySpec XREADGROUP_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_KEYWORD,.bs.keyword={"STREAMS",4},KSPEC_FK_RANGE,.fk.range={-1,1,2}} }; #endif /* XREADGROUP group_block argument table */ struct COMMAND_ARG XREADGROUP_group_block_Subargs[] = { {MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("consumer",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XREADGROUP streams argument table */ struct COMMAND_ARG XREADGROUP_streams_Subargs[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, {MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* XREADGROUP argument table */ struct COMMAND_ARG XREADGROUP_Args[] = { {MAKE_ARG("group-block",ARG_TYPE_BLOCK,-1,"GROUP",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XREADGROUP_group_block_Subargs}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"BLOCK",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("noack",ARG_TYPE_PURE_TOKEN,-1,"NOACK",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("streams",ARG_TYPE_BLOCK,-1,"STREAMS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XREADGROUP_streams_Subargs}, }; /********** XREVRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XREVRANGE history */ commandHistory XREVRANGE_History[] = { {"6.2.0","Added exclusive ranges."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XREVRANGE tips */ #define XREVRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XREVRANGE key specs */ keySpec XREVRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XREVRANGE argument table */ struct COMMAND_ARG XREVRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"COUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** XSETID ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XSETID history */ commandHistory XSETID_History[] = { {"7.0.0","Added the `entries_added` and `max_deleted_entry_id` arguments."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XSETID tips */ #define XSETID_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XSETID key specs */ keySpec XSETID_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XSETID argument table */ struct COMMAND_ARG XSETID_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("last-id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("entries-added",ARG_TYPE_INTEGER,-1,"ENTRIESADDED",NULL,"7.0.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("max-deleted-id",ARG_TYPE_STRING,-1,"MAXDELETEDID",NULL,"7.0.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /********** XTRIM ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* XTRIM history */ commandHistory XTRIM_History[] = { {"6.2.0","Added the `MINID` trimming strategy and the `LIMIT` option."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* XTRIM tips */ const char *XTRIM_Tips[] = { "nondeterministic_output", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* XTRIM key specs */ keySpec XTRIM_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* XTRIM trim strategy argument table */ struct COMMAND_ARG XTRIM_trim_strategy_Subargs[] = { {MAKE_ARG("maxlen",ARG_TYPE_PURE_TOKEN,-1,"MAXLEN",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("minid",ARG_TYPE_PURE_TOKEN,-1,"MINID",NULL,"6.2.0",CMD_ARG_NONE,0,NULL)}, }; /* XTRIM trim operator argument table */ struct COMMAND_ARG XTRIM_trim_operator_Subargs[] = { {MAKE_ARG("equal",ARG_TYPE_PURE_TOKEN,-1,"=",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("approximately",ARG_TYPE_PURE_TOKEN,-1,"~",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* XTRIM trim argument table */ struct COMMAND_ARG XTRIM_trim_Subargs[] = { {MAKE_ARG("strategy",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XTRIM_trim_strategy_Subargs}, {MAKE_ARG("operator",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=XTRIM_trim_operator_Subargs}, {MAKE_ARG("threshold",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, }; /* XTRIM argument table */ struct COMMAND_ARG XTRIM_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("trim",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,4,NULL),.subargs=XTRIM_trim_Subargs}, }; /********** APPEND ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* APPEND history */ #define APPEND_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* APPEND tips */ #define APPEND_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* APPEND key specs */ keySpec APPEND_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* APPEND argument table */ struct COMMAND_ARG APPEND_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** DECR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DECR history */ #define DECR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DECR tips */ #define DECR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DECR key specs */ keySpec DECR_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* DECR argument table */ struct COMMAND_ARG DECR_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** DECRBY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DECRBY history */ #define DECRBY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DECRBY tips */ #define DECRBY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DECRBY key specs */ keySpec DECRBY_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* DECRBY argument table */ struct COMMAND_ARG DECRBY_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("decrement",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** GET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GET history */ #define GET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GET tips */ #define GET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GET key specs */ keySpec GET_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GET argument table */ struct COMMAND_ARG GET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** GETDEL ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GETDEL history */ #define GETDEL_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GETDEL tips */ #define GETDEL_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GETDEL key specs */ keySpec GETDEL_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GETDEL argument table */ struct COMMAND_ARG GETDEL_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** GETEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GETEX history */ #define GETEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GETEX tips */ #define GETEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GETEX key specs */ keySpec GETEX_Keyspecs[1] = { {"RW and UPDATE because it changes the TTL",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GETEX expiration argument table */ struct COMMAND_ARG GETEX_expiration_Subargs[] = { {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("persist",ARG_TYPE_PURE_TOKEN,-1,"PERSIST",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* GETEX argument table */ struct COMMAND_ARG GETEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=GETEX_expiration_Subargs}, }; /********** GETRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GETRANGE history */ #define GETRANGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GETRANGE tips */ #define GETRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GETRANGE key specs */ keySpec GETRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GETRANGE argument table */ struct COMMAND_ARG GETRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** GETSET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* GETSET history */ #define GETSET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* GETSET tips */ #define GETSET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* GETSET key specs */ keySpec GETSET_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* GETSET argument table */ struct COMMAND_ARG GETSET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** INCR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* INCR history */ #define INCR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* INCR tips */ #define INCR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* INCR key specs */ keySpec INCR_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* INCR argument table */ struct COMMAND_ARG INCR_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** INCRBY ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* INCRBY history */ #define INCRBY_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* INCRBY tips */ #define INCRBY_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* INCRBY key specs */ keySpec INCRBY_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* INCRBY argument table */ struct COMMAND_ARG INCRBY_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** INCRBYFLOAT ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* INCRBYFLOAT history */ #define INCRBYFLOAT_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* INCRBYFLOAT tips */ #define INCRBYFLOAT_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* INCRBYFLOAT key specs */ keySpec INCRBYFLOAT_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* INCRBYFLOAT argument table */ struct COMMAND_ARG INCRBYFLOAT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("increment",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** LCS ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* LCS history */ #define LCS_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* LCS tips */ #define LCS_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* LCS key specs */ keySpec LCS_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={1,1,0}} }; #endif /* LCS argument table */ struct COMMAND_ARG LCS_Args[] = { {MAKE_ARG("key1",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("key2",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("len",ARG_TYPE_PURE_TOKEN,-1,"LEN",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("idx",ARG_TYPE_PURE_TOKEN,-1,"IDX",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("min-match-len",ARG_TYPE_INTEGER,-1,"MINMATCHLEN",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("withmatchlen",ARG_TYPE_PURE_TOKEN,-1,"WITHMATCHLEN",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)}, }; /********** MGET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MGET history */ #define MGET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MGET tips */ const char *MGET_Tips[] = { "request_policy:multi_shard", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MGET key specs */ keySpec MGET_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* MGET argument table */ struct COMMAND_ARG MGET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /********** MSET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MSET history */ #define MSET_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MSET tips */ const char *MSET_Tips[] = { "request_policy:multi_shard", "response_policy:all_succeeded", }; #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MSET key specs */ keySpec MSET_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,2,0}} }; #endif /* MSET data argument table */ struct COMMAND_ARG MSET_data_Subargs[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* MSET argument table */ struct COMMAND_ARG MSET_Args[] = { {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=MSET_data_Subargs}, }; /********** MSETNX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MSETNX history */ #define MSETNX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MSETNX tips */ #define MSETNX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MSETNX key specs */ keySpec MSETNX_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,2,0}} }; #endif /* MSETNX data argument table */ struct COMMAND_ARG MSETNX_data_Subargs[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* MSETNX argument table */ struct COMMAND_ARG MSETNX_Args[] = { {MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=MSETNX_data_Subargs}, }; /********** PSETEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* PSETEX history */ #define PSETEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* PSETEX tips */ #define PSETEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* PSETEX key specs */ keySpec PSETEX_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* PSETEX argument table */ struct COMMAND_ARG PSETEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SET ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SET history */ commandHistory SET_History[] = { {"2.6.12","Added the `EX`, `PX`, `NX` and `XX` options."}, {"6.0.0","Added the `KEEPTTL` option."}, {"6.2.0","Added the `GET`, `EXAT` and `PXAT` option."}, {"7.0.0","Allowed the `NX` and `GET` options to be used together."}, }; #endif #ifndef SKIP_CMD_TIPS_TABLE /* SET tips */ #define SET_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SET key specs */ keySpec SET_Keyspecs[1] = { {"RW and ACCESS due to the optional `GET` argument",CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE|CMD_KEY_VARIABLE_FLAGS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SET condition argument table */ struct COMMAND_ARG SET_condition_Subargs[] = { {MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /* SET expiration argument table */ struct COMMAND_ARG SET_expiration_Subargs[] = { {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,"2.6.12",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,"2.6.12",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,"6.2.0",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,"6.2.0",CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("keepttl",ARG_TYPE_PURE_TOKEN,-1,"KEEPTTL",NULL,"6.0.0",CMD_ARG_NONE,0,NULL)}, }; /* SET argument table */ struct COMMAND_ARG SET_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"2.6.12",CMD_ARG_OPTIONAL,2,NULL),.subargs=SET_condition_Subargs}, {MAKE_ARG("get",ARG_TYPE_PURE_TOKEN,-1,"GET",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)}, {MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=SET_expiration_Subargs}, }; /********** SETEX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SETEX history */ #define SETEX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SETEX tips */ #define SETEX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SETEX key specs */ keySpec SETEX_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SETEX argument table */ struct COMMAND_ARG SETEX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SETNX ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SETNX history */ #define SETNX_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SETNX tips */ #define SETNX_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SETNX key specs */ keySpec SETNX_Keyspecs[1] = { {NULL,CMD_KEY_OW|CMD_KEY_INSERT,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SETNX argument table */ struct COMMAND_ARG SETNX_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SETRANGE ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SETRANGE history */ #define SETRANGE_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SETRANGE tips */ #define SETRANGE_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SETRANGE key specs */ keySpec SETRANGE_Keyspecs[1] = { {NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SETRANGE argument table */ struct COMMAND_ARG SETRANGE_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("offset",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** STRLEN ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* STRLEN history */ #define STRLEN_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* STRLEN tips */ #define STRLEN_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* STRLEN key specs */ keySpec STRLEN_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* STRLEN argument table */ struct COMMAND_ARG STRLEN_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** SUBSTR ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* SUBSTR history */ #define SUBSTR_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* SUBSTR tips */ #define SUBSTR_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* SUBSTR key specs */ keySpec SUBSTR_Keyspecs[1] = { {NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}} }; #endif /* SUBSTR argument table */ struct COMMAND_ARG SUBSTR_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; /********** DISCARD ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* DISCARD history */ #define DISCARD_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* DISCARD tips */ #define DISCARD_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* DISCARD key specs */ #define DISCARD_Keyspecs NULL #endif /********** EXEC ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* EXEC history */ #define EXEC_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* EXEC tips */ #define EXEC_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* EXEC key specs */ #define EXEC_Keyspecs NULL #endif /********** MULTI ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* MULTI history */ #define MULTI_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* MULTI tips */ #define MULTI_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* MULTI key specs */ #define MULTI_Keyspecs NULL #endif /********** UNWATCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* UNWATCH history */ #define UNWATCH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* UNWATCH tips */ #define UNWATCH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* UNWATCH key specs */ #define UNWATCH_Keyspecs NULL #endif /********** WATCH ********************/ #ifndef SKIP_CMD_HISTORY_TABLE /* WATCH history */ #define WATCH_History NULL #endif #ifndef SKIP_CMD_TIPS_TABLE /* WATCH tips */ #define WATCH_Tips NULL #endif #ifndef SKIP_CMD_KEY_SPECS_TABLE /* WATCH key specs */ keySpec WATCH_Keyspecs[1] = { {NULL,CMD_KEY_RO,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}} }; #endif /* WATCH argument table */ struct COMMAND_ARG WATCH_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)}, }; /* Main command table */ struct COMMAND_STRUCT redisCommandTable[] = { /* bitmap */ {MAKE_CMD("bitcount","Counts the number of set bits (population counting) in a string.","O(N)","2.6.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITCOUNT_History,1,BITCOUNT_Tips,0,bitcountCommand,-2,CMD_READONLY,ACL_CATEGORY_BITMAP,BITCOUNT_Keyspecs,1,NULL,2),.args=BITCOUNT_Args}, {MAKE_CMD("bitfield","Performs arbitrary bitfield integer operations on strings.","O(1) for each subcommand specified","3.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITFIELD_History,0,BITFIELD_Tips,0,bitfieldCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,BITFIELD_Keyspecs,1,bitfieldGetKeys,2),.args=BITFIELD_Args}, {MAKE_CMD("bitfield_ro","Performs arbitrary read-only bitfield integer operations on strings.","O(1) for each subcommand specified","6.0.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITFIELD_RO_History,0,BITFIELD_RO_Tips,0,bitfieldroCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_BITMAP,BITFIELD_RO_Keyspecs,1,NULL,2),.args=BITFIELD_RO_Args}, {MAKE_CMD("bitop","Performs bitwise operations on multiple strings, and stores the result.","O(N)","2.6.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITOP_History,0,BITOP_Tips,0,bitopCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,BITOP_Keyspecs,2,NULL,3),.args=BITOP_Args}, {MAKE_CMD("bitpos","Finds the first set (1) or clear (0) bit in a string.","O(N)","2.8.7",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITPOS_History,1,BITPOS_Tips,0,bitposCommand,-3,CMD_READONLY,ACL_CATEGORY_BITMAP,BITPOS_Keyspecs,1,NULL,3),.args=BITPOS_Args}, {MAKE_CMD("getbit","Returns a bit value by offset.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,GETBIT_History,0,GETBIT_Tips,0,getbitCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_BITMAP,GETBIT_Keyspecs,1,NULL,2),.args=GETBIT_Args}, {MAKE_CMD("setbit","Sets or clears the bit at offset of the string value. Creates the key if it doesn't exist.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,SETBIT_History,0,SETBIT_Tips,0,setbitCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,SETBIT_Keyspecs,1,NULL,3),.args=SETBIT_Args}, /* cluster */ {MAKE_CMD("asking","Signals that a cluster client is following an -ASK redirect.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,ASKING_History,0,ASKING_Tips,0,askingCommand,1,CMD_FAST,ACL_CATEGORY_CONNECTION,ASKING_Keyspecs,0,NULL,0)}, {MAKE_CMD("cluster","A container for Redis Cluster commands.","Depends on subcommand.","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_History,0,CLUSTER_Tips,0,NULL,-2,0,0,CLUSTER_Keyspecs,0,NULL,0),.subcommands=CLUSTER_Subcommands}, {MAKE_CMD("readonly","Enables read-only queries for a connection to a Redis Cluster replica node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,READONLY_History,0,READONLY_Tips,0,readonlyCommand,1,CMD_FAST|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,READONLY_Keyspecs,0,NULL,0)}, {MAKE_CMD("readwrite","Enables read-write queries for a connection to a Reids Cluster replica node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,READWRITE_History,0,READWRITE_Tips,0,readwriteCommand,1,CMD_FAST|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,READWRITE_Keyspecs,0,NULL,0)}, /* connection */ {MAKE_CMD("auth","Authenticates the connection.","O(N) where N is the number of passwords defined for the user","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,AUTH_History,1,AUTH_Tips,0,authCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_SENTINEL|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,AUTH_Keyspecs,0,NULL,2),.args=AUTH_Args}, {MAKE_CMD("client","A container for client connection commands.","Depends on subcommand.","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_History,0,CLIENT_Tips,0,NULL,-2,CMD_SENTINEL,0,CLIENT_Keyspecs,0,NULL,0),.subcommands=CLIENT_Subcommands}, {MAKE_CMD("echo","Returns the given string.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,ECHO_History,0,ECHO_Tips,0,echoCommand,2,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_CONNECTION,ECHO_Keyspecs,0,NULL,1),.args=ECHO_Args}, {MAKE_CMD("hello","Handshakes with the Redis server.","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,HELLO_History,1,HELLO_Tips,0,helloCommand,-1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_SENTINEL|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,HELLO_Keyspecs,0,NULL,1),.args=HELLO_Args}, {MAKE_CMD("ping","Returns the server's liveliness response.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,PING_History,0,PING_Tips,2,pingCommand,-1,CMD_FAST|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,PING_Keyspecs,0,NULL,1),.args=PING_Args}, {MAKE_CMD("quit","Closes the connection.","O(1)","1.0.0",CMD_DOC_DEPRECATED,"just closing the connection","7.2.0","connection",COMMAND_GROUP_CONNECTION,QUIT_History,0,QUIT_Tips,0,quitCommand,-1,CMD_ALLOW_BUSY|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH,ACL_CATEGORY_CONNECTION,QUIT_Keyspecs,0,NULL,0)}, {MAKE_CMD("reset","Resets the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,RESET_History,0,RESET_Tips,0,resetCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,RESET_Keyspecs,0,NULL,0)}, {MAKE_CMD("select","Changes the selected database.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,SELECT_History,0,SELECT_Tips,0,selectCommand,2,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_CONNECTION,SELECT_Keyspecs,0,NULL,1),.args=SELECT_Args}, /* generic */ {MAKE_CMD("copy","Copies the value of a key to a new key.","O(N) worst case for collections, where N is the number of nested items. O(1) for string values.","6.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,COPY_History,0,COPY_Tips,0,copyCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_KEYSPACE,COPY_Keyspecs,2,NULL,4),.args=COPY_Args}, {MAKE_CMD("del","Deletes one or more keys.","O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,DEL_History,0,DEL_Tips,2,delCommand,-2,CMD_WRITE,ACL_CATEGORY_KEYSPACE,DEL_Keyspecs,1,NULL,1),.args=DEL_Args}, {MAKE_CMD("dump","Returns a serialized representation of the value stored at a key.","O(1) to access the key and additional O(N*M) to serialize it, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1).","2.6.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,DUMP_History,0,DUMP_Tips,1,dumpCommand,2,CMD_READONLY,ACL_CATEGORY_KEYSPACE,DUMP_Keyspecs,1,NULL,1),.args=DUMP_Args}, {MAKE_CMD("exists","Determines whether one or more keys exist.","O(N) where N is the number of keys to check.","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,EXISTS_History,1,EXISTS_Tips,2,existsCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,EXISTS_Keyspecs,1,NULL,1),.args=EXISTS_Args}, {MAKE_CMD("expire","Sets the expiration time of a key in seconds.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,EXPIRE_History,1,EXPIRE_Tips,0,expireCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,EXPIRE_Keyspecs,1,NULL,3),.args=EXPIRE_Args}, {MAKE_CMD("expireat","Sets the expiration time of a key to a Unix timestamp.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,EXPIREAT_History,1,EXPIREAT_Tips,0,expireatCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,EXPIREAT_Keyspecs,1,NULL,3),.args=EXPIREAT_Args}, {MAKE_CMD("expiretime","Returns the expiration time of a key as a Unix timestamp.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,EXPIRETIME_History,0,EXPIRETIME_Tips,0,expiretimeCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,EXPIRETIME_Keyspecs,1,NULL,1),.args=EXPIRETIME_Args}, {MAKE_CMD("keys","Returns all key names that match a pattern.","O(N) with N being the number of keys in the database, under the assumption that the key names in the database and the given pattern have limited length.","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,KEYS_History,0,KEYS_Tips,2,keysCommand,2,CMD_READONLY,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,KEYS_Keyspecs,0,NULL,1),.args=KEYS_Args}, {MAKE_CMD("migrate","Atomically transfers a key from one Redis instance to another.","This command actually executes a DUMP+DEL in the source instance, and a RESTORE in the target instance. See the pages of these commands for time complexity. Also an O(N) data transfer between the two instances is performed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,MIGRATE_History,4,MIGRATE_Tips,1,migrateCommand,-6,CMD_WRITE,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,MIGRATE_Keyspecs,2,migrateGetKeys,9),.args=MIGRATE_Args}, {MAKE_CMD("move","Moves a key to another database.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,MOVE_History,0,MOVE_Tips,0,moveCommand,3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,MOVE_Keyspecs,1,NULL,2),.args=MOVE_Args}, {MAKE_CMD("object","A container for object introspection commands.","Depends on subcommand.","2.2.3",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,OBJECT_History,0,OBJECT_Tips,0,NULL,-2,0,0,OBJECT_Keyspecs,0,NULL,0),.subcommands=OBJECT_Subcommands}, {MAKE_CMD("persist","Removes the expiration time of a key.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,PERSIST_History,0,PERSIST_Tips,0,persistCommand,2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,PERSIST_Keyspecs,1,NULL,1),.args=PERSIST_Args}, {MAKE_CMD("pexpire","Sets the expiration time of a key in milliseconds.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,PEXPIRE_History,1,PEXPIRE_Tips,0,pexpireCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,PEXPIRE_Keyspecs,1,NULL,3),.args=PEXPIRE_Args}, {MAKE_CMD("pexpireat","Sets the expiration time of a key to a Unix milliseconds timestamp.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,PEXPIREAT_History,1,PEXPIREAT_Tips,0,pexpireatCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,PEXPIREAT_Keyspecs,1,NULL,3),.args=PEXPIREAT_Args}, {MAKE_CMD("pexpiretime","Returns the expiration time of a key as a Unix milliseconds timestamp.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,PEXPIRETIME_History,0,PEXPIRETIME_Tips,0,pexpiretimeCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,PEXPIRETIME_Keyspecs,1,NULL,1),.args=PEXPIRETIME_Args}, {MAKE_CMD("pttl","Returns the expiration time in milliseconds of a key.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,PTTL_History,1,PTTL_Tips,1,pttlCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,PTTL_Keyspecs,1,NULL,1),.args=PTTL_Args}, {MAKE_CMD("randomkey","Returns a random key name from the database.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,RANDOMKEY_History,0,RANDOMKEY_Tips,3,randomkeyCommand,1,CMD_READONLY|CMD_TOUCHES_ARBITRARY_KEYS,ACL_CATEGORY_KEYSPACE,RANDOMKEY_Keyspecs,0,NULL,0)}, {MAKE_CMD("rename","Renames a key and overwrites the destination.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,RENAME_History,0,RENAME_Tips,0,renameCommand,3,CMD_WRITE,ACL_CATEGORY_KEYSPACE,RENAME_Keyspecs,2,NULL,2),.args=RENAME_Args}, {MAKE_CMD("renamenx","Renames a key only when the target key name doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,RENAMENX_History,1,RENAMENX_Tips,0,renamenxCommand,3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,RENAMENX_Keyspecs,2,NULL,2),.args=RENAMENX_Args}, {MAKE_CMD("restore","Creates a key from the serialized representation of a value.","O(1) to create the new key and additional O(N*M) to reconstruct the serialized value, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1). However for sorted set values the complexity is O(N*M*log(N)) because inserting values into sorted sets is O(log(N)).","2.6.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,RESTORE_History,3,RESTORE_Tips,0,restoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,RESTORE_Keyspecs,1,NULL,7),.args=RESTORE_Args}, {MAKE_CMD("scan","Iterates over the key names in the database.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,SCAN_History,1,SCAN_Tips,3,scanCommand,-2,CMD_READONLY|CMD_TOUCHES_ARBITRARY_KEYS,ACL_CATEGORY_KEYSPACE,SCAN_Keyspecs,0,NULL,4),.args=SCAN_Args}, {MAKE_CMD("sort","Sorts the elements in a list, a set, or a sorted set, optionally storing the result.","O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is O(N).","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,SORT_History,0,SORT_Tips,0,sortCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_LIST|ACL_CATEGORY_DANGEROUS,SORT_Keyspecs,3,sortGetKeys,7),.args=SORT_Args}, {MAKE_CMD("sort_ro","Returns the sorted elements of a list, a set, or a sorted set.","O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is O(N).","7.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,SORT_RO_History,0,SORT_RO_Tips,0,sortroCommand,-2,CMD_READONLY,ACL_CATEGORY_SET|ACL_CATEGORY_SORTEDSET|ACL_CATEGORY_LIST|ACL_CATEGORY_DANGEROUS,SORT_RO_Keyspecs,2,sortROGetKeys,6),.args=SORT_RO_Args}, {MAKE_CMD("touch","Returns the number of existing keys out of those specified after updating the time they were last accessed.","O(N) where N is the number of keys that will be touched.","3.2.1",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,TOUCH_History,0,TOUCH_Tips,2,touchCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,TOUCH_Keyspecs,1,NULL,1),.args=TOUCH_Args}, {MAKE_CMD("ttl","Returns the expiration time in seconds of a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,TTL_History,1,TTL_Tips,1,ttlCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,TTL_Keyspecs,1,NULL,1),.args=TTL_Args}, {MAKE_CMD("type","Determines the type of value stored at a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,TYPE_History,0,TYPE_Tips,0,typeCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,TYPE_Keyspecs,1,NULL,1),.args=TYPE_Args}, {MAKE_CMD("unlink","Asynchronously deletes one or more keys.","O(1) for each key removed regardless of its size. Then the command does O(N) work in a different thread in order to reclaim memory, where N is the number of allocations the deleted objects where composed of.","4.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,UNLINK_History,0,UNLINK_Tips,2,unlinkCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE,UNLINK_Keyspecs,1,NULL,1),.args=UNLINK_Args}, {MAKE_CMD("wait","Blocks until the asynchronous replication of all preceding write commands sent by the connection is completed.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,WAIT_History,0,WAIT_Tips,2,waitCommand,3,CMD_BLOCKING,ACL_CATEGORY_CONNECTION,WAIT_Keyspecs,0,NULL,2),.args=WAIT_Args}, {MAKE_CMD("waitaof","Blocks until all of the preceding write commands sent by the connection are written to the append-only file of the master and/or replicas.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"generic",COMMAND_GROUP_GENERIC,WAITAOF_History,0,WAITAOF_Tips,2,waitaofCommand,4,CMD_BLOCKING,ACL_CATEGORY_CONNECTION,WAITAOF_Keyspecs,0,NULL,3),.args=WAITAOF_Args}, /* geo */ {MAKE_CMD("geoadd","Adds one or more members to a geospatial index. The key is created if it doesn't exist.","O(log(N)) for each item added, where N is the number of elements in the sorted set.","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOADD_History,1,GEOADD_Tips,0,geoaddCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEOADD_Keyspecs,1,NULL,4),.args=GEOADD_Args}, {MAKE_CMD("geodist","Returns the distance between two members of a geospatial index.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEODIST_History,0,GEODIST_Tips,0,geodistCommand,-4,CMD_READONLY,ACL_CATEGORY_GEO,GEODIST_Keyspecs,1,NULL,4),.args=GEODIST_Args}, {MAKE_CMD("geohash","Returns members from a geospatial index as geohash strings.","O(1) for each member requested.","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOHASH_History,0,GEOHASH_Tips,0,geohashCommand,-2,CMD_READONLY,ACL_CATEGORY_GEO,GEOHASH_Keyspecs,1,NULL,2),.args=GEOHASH_Args}, {MAKE_CMD("geopos","Returns the longitude and latitude of members from a geospatial index.","O(1) for each member requested.","3.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOPOS_History,0,GEOPOS_Tips,0,geoposCommand,-2,CMD_READONLY,ACL_CATEGORY_GEO,GEOPOS_Keyspecs,1,NULL,2),.args=GEOPOS_Args}, {MAKE_CMD("georadius","Queries a geospatial index for members within a distance from a coordinate, optionally stores the result.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.0",CMD_DOC_DEPRECATED,"`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` argument","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUS_History,2,GEORADIUS_Tips,0,georadiusCommand,-6,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEORADIUS_Keyspecs,3,georadiusGetKeys,11),.args=GEORADIUS_Args}, {MAKE_CMD("georadiusbymember","Queries a geospatial index for members within a distance from a member, optionally stores the result.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.0",CMD_DOC_DEPRECATED,"`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` and `FROMMEMBER` arguments","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUSBYMEMBER_History,2,GEORADIUSBYMEMBER_Tips,0,georadiusbymemberCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEORADIUSBYMEMBER_Keyspecs,3,georadiusGetKeys,10),.args=GEORADIUSBYMEMBER_Args}, {MAKE_CMD("georadiusbymember_ro","Returns members from a geospatial index that are within a distance from a member.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.10",CMD_DOC_DEPRECATED,"`GEOSEARCH` with the `BYRADIUS` and `FROMMEMBER` arguments","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUSBYMEMBER_RO_History,2,GEORADIUSBYMEMBER_RO_Tips,0,georadiusbymemberroCommand,-5,CMD_READONLY,ACL_CATEGORY_GEO,GEORADIUSBYMEMBER_RO_Keyspecs,1,NULL,9),.args=GEORADIUSBYMEMBER_RO_Args}, {MAKE_CMD("georadius_ro","Returns members from a geospatial index that are within a distance from a coordinate.","O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.","3.2.10",CMD_DOC_DEPRECATED,"`GEOSEARCH` with the `BYRADIUS` argument","6.2.0","geo",COMMAND_GROUP_GEO,GEORADIUS_RO_History,2,GEORADIUS_RO_Tips,0,georadiusroCommand,-6,CMD_READONLY,ACL_CATEGORY_GEO,GEORADIUS_RO_Keyspecs,1,NULL,10),.args=GEORADIUS_RO_Args}, {MAKE_CMD("geosearch","Queries a geospatial index for members inside an area of a box or a circle.","O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape","6.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOSEARCH_History,1,GEOSEARCH_Tips,0,geosearchCommand,-7,CMD_READONLY,ACL_CATEGORY_GEO,GEOSEARCH_Keyspecs,1,NULL,8),.args=GEOSEARCH_Args}, {MAKE_CMD("geosearchstore","Queries a geospatial index for members inside an area of a box or a circle, optionally stores the result.","O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape","6.2.0",CMD_DOC_NONE,NULL,NULL,"geo",COMMAND_GROUP_GEO,GEOSEARCHSTORE_History,1,GEOSEARCHSTORE_Tips,0,geosearchstoreCommand,-8,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_GEO,GEOSEARCHSTORE_Keyspecs,2,NULL,7),.args=GEOSEARCHSTORE_Args}, /* hash */ {MAKE_CMD("hdel","Deletes one or more fields and their values from a hash. Deletes the hash if no fields remain.","O(N) where N is the number of fields to be removed.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HDEL_History,1,HDEL_Tips,0,hdelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HDEL_Keyspecs,1,NULL,2),.args=HDEL_Args}, {MAKE_CMD("hexists","Determines whether a field exists in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXISTS_History,0,HEXISTS_Tips,0,hexistsCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HEXISTS_Keyspecs,1,NULL,2),.args=HEXISTS_Args}, {MAKE_CMD("hexpire","Set expiry for hash field using relative time to expire (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRE_History,0,HEXPIRE_Tips,0,hexpireCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRE_Keyspecs,1,NULL,4),.args=HEXPIRE_Args}, {MAKE_CMD("hexpireat","Set expiry for hash field using an absolute Unix timestamp (seconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIREAT_History,0,HEXPIREAT_Tips,0,hexpireatCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HEXPIREAT_Keyspecs,1,NULL,4),.args=HEXPIREAT_Args}, {MAKE_CMD("hexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in seconds.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HEXPIRETIME_History,0,HEXPIRETIME_Tips,0,hexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HEXPIRETIME_Keyspecs,1,NULL,2),.args=HEXPIRETIME_Args}, {MAKE_CMD("hget","Returns the value of a field in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGET_History,0,HGET_Tips,0,hgetCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HGET_Keyspecs,1,NULL,2),.args=HGET_Args}, {MAKE_CMD("hgetall","Returns all fields and values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETALL_History,0,HGETALL_Tips,1,hgetallCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HGETALL_Keyspecs,1,NULL,1),.args=HGETALL_Args}, {MAKE_CMD("hgetdel","Returns the value of a field and deletes it from the hash.","O(N) where N is the number of specified fields","8.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETDEL_History,0,HGETDEL_Tips,0,hgetdelCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HGETDEL_Keyspecs,1,NULL,2),.args=HGETDEL_Args}, {MAKE_CMD("hgetex","Get the value of one or more fields of a given hash key, and optionally set their expiration.","O(N) where N is the number of specified fields","8.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HGETEX_History,0,HGETEX_Tips,0,hgetexCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HGETEX_Keyspecs,1,NULL,3),.args=HGETEX_Args}, {MAKE_CMD("hincrby","Increments the integer value of a field in a hash by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBY_History,0,HINCRBY_Tips,0,hincrbyCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HINCRBY_Keyspecs,1,NULL,3),.args=HINCRBY_Args}, {MAKE_CMD("hincrbyfloat","Increments the floating point value of a field by a number. Uses 0 as initial value if the field doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HINCRBYFLOAT_History,0,HINCRBYFLOAT_Tips,0,hincrbyfloatCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HINCRBYFLOAT_Keyspecs,1,NULL,3),.args=HINCRBYFLOAT_Args}, {MAKE_CMD("hkeys","Returns all fields in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HKEYS_History,0,HKEYS_Tips,1,hkeysCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HKEYS_Keyspecs,1,NULL,1),.args=HKEYS_Args}, {MAKE_CMD("hlen","Returns the number of fields in a hash.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HLEN_History,0,HLEN_Tips,0,hlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HLEN_Keyspecs,1,NULL,1),.args=HLEN_Args}, {MAKE_CMD("hmget","Returns the values of all fields in a hash.","O(N) where N is the number of fields being requested.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HMGET_History,0,HMGET_Tips,0,hmgetCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HMGET_Keyspecs,1,NULL,2),.args=HMGET_Args}, {MAKE_CMD("hmset","Sets the values of multiple fields.","O(N) where N is the number of fields being set.","2.0.0",CMD_DOC_DEPRECATED,"`HSET` with multiple field-value pairs","4.0.0","hash",COMMAND_GROUP_HASH,HMSET_History,0,HMSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HMSET_Keyspecs,1,NULL,2),.args=HMSET_Args}, {MAKE_CMD("hpersist","Removes the expiration time for each specified field","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPERSIST_History,0,HPERSIST_Tips,0,hpersistCommand,-5,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HPERSIST_Keyspecs,1,NULL,2),.args=HPERSIST_Args}, {MAKE_CMD("hpexpire","Set expiry for hash field using relative time to expire (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRE_History,0,HPEXPIRE_Tips,0,hpexpireCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRE_Keyspecs,1,NULL,4),.args=HPEXPIRE_Args}, {MAKE_CMD("hpexpireat","Set expiry for hash field using an absolute Unix timestamp (milliseconds)","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIREAT_History,0,HPEXPIREAT_Tips,0,hpexpireatCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIREAT_Keyspecs,1,NULL,4),.args=HPEXPIREAT_Args}, {MAKE_CMD("hpexpiretime","Returns the expiration time of a hash field as a Unix timestamp, in msec.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPEXPIRETIME_History,0,HPEXPIRETIME_Tips,0,hpexpiretimeCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPEXPIRETIME_Keyspecs,1,NULL,2),.args=HPEXPIRETIME_Args}, {MAKE_CMD("hpttl","Returns the TTL in milliseconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HPTTL_History,0,HPTTL_Tips,1,hpttlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HPTTL_Keyspecs,1,NULL,2),.args=HPTTL_Args}, {MAKE_CMD("hrandfield","Returns one or more random fields from a hash.","O(N) where N is the number of fields returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HRANDFIELD_History,0,HRANDFIELD_Tips,1,hrandfieldCommand,-2,CMD_READONLY,ACL_CATEGORY_HASH,HRANDFIELD_Keyspecs,1,NULL,2),.args=HRANDFIELD_Args}, {MAKE_CMD("hscan","Iterates over fields and values of a hash.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSCAN_History,0,HSCAN_Tips,1,hscanCommand,-3,CMD_READONLY,ACL_CATEGORY_HASH,HSCAN_Keyspecs,1,NULL,5),.args=HSCAN_Args}, {MAKE_CMD("hset","Creates or modifies the value of a field in a hash.","O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSET_History,1,HSET_Tips,0,hsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSET_Keyspecs,1,NULL,2),.args=HSET_Args}, {MAKE_CMD("hsetex","Set the value of one or more fields of a given hash key, and optionally set their expiration.","O(N) where N is the number of fields being set.","8.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETEX_History,0,HSETEX_Tips,0,hsetexCommand,-6,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETEX_Keyspecs,1,NULL,4),.args=HSETEX_Args}, {MAKE_CMD("hsetnx","Sets the value of a field in a hash only when the field doesn't exist.","O(1)","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSETNX_History,0,HSETNX_Tips,0,hsetnxCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HASH,HSETNX_Keyspecs,1,NULL,3),.args=HSETNX_Args}, {MAKE_CMD("hstrlen","Returns the length of the value of a field.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HSTRLEN_History,0,HSTRLEN_Tips,0,hstrlenCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HSTRLEN_Keyspecs,1,NULL,2),.args=HSTRLEN_Args}, {MAKE_CMD("httl","Returns the TTL in seconds of a hash field.","O(N) where N is the number of specified fields","7.4.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HTTL_History,0,HTTL_Tips,1,httlCommand,-5,CMD_READONLY|CMD_FAST,ACL_CATEGORY_HASH,HTTL_Keyspecs,1,NULL,2),.args=HTTL_Args}, {MAKE_CMD("hvals","Returns all values in a hash.","O(N) where N is the size of the hash.","2.0.0",CMD_DOC_NONE,NULL,NULL,"hash",COMMAND_GROUP_HASH,HVALS_History,0,HVALS_Tips,1,hvalsCommand,2,CMD_READONLY,ACL_CATEGORY_HASH,HVALS_Keyspecs,1,NULL,1),.args=HVALS_Args}, /* hyperloglog */ {MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args}, {MAKE_CMD("pfcount","Returns the approximated cardinality of the set(s) observed by the HyperLogLog key(s).","O(1) with a very small average constant time when called with a single key. O(N) with N being the number of keys, and much bigger constant times, when called with multiple keys.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFCOUNT_History,0,PFCOUNT_Tips,0,pfcountCommand,-2,CMD_READONLY|CMD_MAY_REPLICATE,ACL_CATEGORY_HYPERLOGLOG,PFCOUNT_Keyspecs,1,NULL,1),.args=PFCOUNT_Args}, {MAKE_CMD("pfdebug","Internal commands for debugging HyperLogLog values.","N/A","2.8.9",CMD_DOC_SYSCMD,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFDEBUG_History,0,PFDEBUG_Tips,0,pfdebugCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_ADMIN,ACL_CATEGORY_HYPERLOGLOG,PFDEBUG_Keyspecs,1,NULL,2),.args=PFDEBUG_Args}, {MAKE_CMD("pfmerge","Merges one or more HyperLogLog values into a single key.","O(N) to merge N HyperLogLogs, but with high constant times.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFMERGE_History,0,PFMERGE_Tips,0,pfmergeCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_HYPERLOGLOG,PFMERGE_Keyspecs,2,NULL,2),.args=PFMERGE_Args}, {MAKE_CMD("pfselftest","An internal command for testing HyperLogLog values.","N/A","2.8.9",CMD_DOC_SYSCMD,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFSELFTEST_History,0,PFSELFTEST_Tips,0,pfselftestCommand,1,CMD_ADMIN,ACL_CATEGORY_HYPERLOGLOG,PFSELFTEST_Keyspecs,0,NULL,0)}, /* list */ {MAKE_CMD("blmove","Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was moved.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMOVE_History,0,BLMOVE_Tips,0,blmoveCommand,6,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_LIST,BLMOVE_Keyspecs,2,NULL,5),.args=BLMOVE_Args}, {MAKE_CMD("blmpop","Pops the first element from one of multiple lists. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N+M) where N is the number of provided keys and M is the number of elements returned.","7.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMPOP_History,0,BLMPOP_Tips,0,blmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_LIST,BLMPOP_Keyspecs,1,blmpopGetKeys,5),.args=BLMPOP_Args}, {MAKE_CMD("blpop","Removes and returns the first element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N) where N is the number of provided keys.","2.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLPOP_History,1,BLPOP_Tips,0,blpopCommand,-3,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_LIST,BLPOP_Keyspecs,1,NULL,2),.args=BLPOP_Args}, {MAKE_CMD("brpop","Removes and returns the last element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.","O(N) where N is the number of provided keys.","2.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BRPOP_History,1,BRPOP_Tips,0,brpopCommand,-3,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_LIST,BRPOP_Keyspecs,1,NULL,2),.args=BRPOP_Args}, {MAKE_CMD("brpoplpush","Pops an element from a list, pushes it to another list and returns it. Block until an element is available otherwise. Deletes the list if the last element was popped.","O(1)","2.2.0",CMD_DOC_DEPRECATED,"`BLMOVE` with the `RIGHT` and `LEFT` arguments","6.2.0","list",COMMAND_GROUP_LIST,BRPOPLPUSH_History,1,BRPOPLPUSH_Tips,0,brpoplpushCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_LIST,BRPOPLPUSH_Keyspecs,2,NULL,3),.args=BRPOPLPUSH_Args}, {MAKE_CMD("lindex","Returns an element from a list by its index.","O(N) where N is the number of elements to traverse to get to the element at index. This makes asking for the first or the last element of the list O(1).","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LINDEX_History,0,LINDEX_Tips,0,lindexCommand,3,CMD_READONLY,ACL_CATEGORY_LIST,LINDEX_Keyspecs,1,NULL,2),.args=LINDEX_Args}, {MAKE_CMD("linsert","Inserts an element before or after another element in a list.","O(N) where N is the number of elements to traverse before seeing the value pivot. This means that inserting somewhere on the left end on the list (head) can be considered O(1) and inserting somewhere on the right end (tail) is O(N).","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LINSERT_History,0,LINSERT_Tips,0,linsertCommand,5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,LINSERT_Keyspecs,1,NULL,4),.args=LINSERT_Args}, {MAKE_CMD("llen","Returns the length of a list.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LLEN_History,0,LLEN_Tips,0,llenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_LIST,LLEN_Keyspecs,1,NULL,1),.args=LLEN_Args}, {MAKE_CMD("lmove","Returns an element after popping it from one list and pushing it to another. Deletes the list if the last element was moved.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LMOVE_History,0,LMOVE_Tips,0,lmoveCommand,5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,LMOVE_Keyspecs,2,NULL,4),.args=LMOVE_Args}, {MAKE_CMD("lmpop","Returns multiple elements from a list after removing them. Deletes the list if the last element was popped.","O(N+M) where N is the number of provided keys and M is the number of elements returned.","7.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LMPOP_History,0,LMPOP_Tips,0,lmpopCommand,-4,CMD_WRITE,ACL_CATEGORY_LIST,LMPOP_Keyspecs,1,lmpopGetKeys,4),.args=LMPOP_Args}, {MAKE_CMD("lpop","Returns the first elements in a list after removing it. Deletes the list if the last element was popped.","O(N) where N is the number of elements returned","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LPOP_History,1,LPOP_Tips,0,lpopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_LIST,LPOP_Keyspecs,1,NULL,2),.args=LPOP_Args}, {MAKE_CMD("lpos","Returns the index of matching elements in a list.","O(N) where N is the number of elements in the list, for the average case. When searching for elements near the head or the tail of the list, or when the MAXLEN option is provided, the command may run in constant time.","6.0.6",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LPOS_History,0,LPOS_Tips,0,lposCommand,-3,CMD_READONLY,ACL_CATEGORY_LIST,LPOS_Keyspecs,1,NULL,5),.args=LPOS_Args}, {MAKE_CMD("lpush","Prepends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LPUSH_History,1,LPUSH_Tips,0,lpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,LPUSH_Keyspecs,1,NULL,2),.args=LPUSH_Args}, {MAKE_CMD("lpushx","Prepends one or more elements to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LPUSHX_History,1,LPUSHX_Tips,0,lpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,LPUSHX_Keyspecs,1,NULL,2),.args=LPUSHX_Args}, {MAKE_CMD("lrange","Returns a range of elements from a list.","O(S+N) where S is the distance of start offset from HEAD for small lists, from nearest end (HEAD or TAIL) for large lists; and N is the number of elements in the specified range.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LRANGE_History,0,LRANGE_Tips,0,lrangeCommand,4,CMD_READONLY,ACL_CATEGORY_LIST,LRANGE_Keyspecs,1,NULL,3),.args=LRANGE_Args}, {MAKE_CMD("lrem","Removes elements from a list. Deletes the list if the last element was removed.","O(N+M) where N is the length of the list and M is the number of elements removed.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LREM_History,0,LREM_Tips,0,lremCommand,4,CMD_WRITE,ACL_CATEGORY_LIST,LREM_Keyspecs,1,NULL,3),.args=LREM_Args}, {MAKE_CMD("lset","Sets the value of an element in a list by its index.","O(N) where N is the length of the list. Setting either the first or the last element of the list is O(1).","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LSET_History,0,LSET_Tips,0,lsetCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,LSET_Keyspecs,1,NULL,3),.args=LSET_Args}, {MAKE_CMD("ltrim","Removes elements from both ends a list. Deletes the list if all elements were trimmed.","O(N) where N is the number of elements to be removed by the operation.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,LTRIM_History,0,LTRIM_Tips,0,ltrimCommand,4,CMD_WRITE,ACL_CATEGORY_LIST,LTRIM_Keyspecs,1,NULL,3),.args=LTRIM_Args}, {MAKE_CMD("rpop","Returns and removes the last elements of a list. Deletes the list if the last element was popped.","O(N) where N is the number of elements returned","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPOP_History,1,RPOP_Tips,0,rpopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_LIST,RPOP_Keyspecs,1,NULL,2),.args=RPOP_Args}, {MAKE_CMD("rpoplpush","Returns the last element of a list after removing and pushing it to another list. Deletes the list if the last element was popped.","O(1)","1.2.0",CMD_DOC_DEPRECATED,"`LMOVE` with the `RIGHT` and `LEFT` arguments","6.2.0","list",COMMAND_GROUP_LIST,RPOPLPUSH_History,0,RPOPLPUSH_Tips,0,rpoplpushCommand,3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_LIST,RPOPLPUSH_Keyspecs,2,NULL,2),.args=RPOPLPUSH_Args}, {MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args}, {MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args}, /* pubsub */ {MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args}, {MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args}, {MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands}, {MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args}, {MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args}, {MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args}, {MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args}, {MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args}, {MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of channels to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args}, /* scripting */ {MAKE_CMD("eval","Executes a server-side Lua script.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_History,0,EVAL_Tips,0,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVAL_Keyspecs,1,evalGetKeys,4),.args=EVAL_Args}, {MAKE_CMD("evalsha","Executes a server-side Lua script by SHA1 digest.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_History,0,EVALSHA_Tips,0,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVALSHA_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_Args}, {MAKE_CMD("evalsha_ro","Executes a read-only server-side Lua script by SHA1 digest.","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_RO_History,0,EVALSHA_RO_Tips,0,evalShaRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE|CMD_READONLY,ACL_CATEGORY_SCRIPTING,EVALSHA_RO_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_RO_Args}, {MAKE_CMD("eval_ro","Executes a read-only server-side Lua script.","Depends on the script that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_RO_History,0,EVAL_RO_Tips,0,evalRoCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE|CMD_READONLY,ACL_CATEGORY_SCRIPTING,EVAL_RO_Keyspecs,1,evalGetKeys,4),.args=EVAL_RO_Args}, {MAKE_CMD("fcall","Invokes a function.","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FCALL_History,0,FCALL_Tips,0,fcallCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,FCALL_Keyspecs,1,functionGetKeys,4),.args=FCALL_Args}, {MAKE_CMD("fcall_ro","Invokes a read-only function.","Depends on the function that is executed.","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FCALL_RO_History,0,FCALL_RO_Tips,0,fcallroCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_NO_MANDATORY_KEYS|CMD_STALE|CMD_READONLY,ACL_CATEGORY_SCRIPTING,FCALL_RO_Keyspecs,1,functionGetKeys,4),.args=FCALL_RO_Args}, {MAKE_CMD("function","A container for function commands.","Depends on subcommand.","7.0.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,FUNCTION_History,0,FUNCTION_Tips,0,NULL,-2,0,0,FUNCTION_Keyspecs,0,NULL,0),.subcommands=FUNCTION_Subcommands}, {MAKE_CMD("script","A container for Lua scripts management commands.","Depends on subcommand.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,SCRIPT_History,0,SCRIPT_Tips,0,NULL,-2,0,0,SCRIPT_Keyspecs,0,NULL,0),.subcommands=SCRIPT_Subcommands}, /* sentinel */ {MAKE_CMD("sentinel","A container for Redis Sentinel commands.","Depends on subcommand.","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_History,0,SENTINEL_Tips,0,NULL,-2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_Keyspecs,0,NULL,0),.subcommands=SENTINEL_Subcommands}, /* server */ {MAKE_CMD("acl","A container for Access List Control commands.","Depends on subcommand.","6.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ACL_History,0,ACL_Tips,0,NULL,-2,CMD_SENTINEL,0,ACL_Keyspecs,0,NULL,0),.subcommands=ACL_Subcommands}, {MAKE_CMD("bgrewriteaof","Asynchronously rewrites the append-only file to disk.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,BGREWRITEAOF_History,0,BGREWRITEAOF_Tips,0,bgrewriteaofCommand,1,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT,0,BGREWRITEAOF_Keyspecs,0,NULL,0)}, {MAKE_CMD("bgsave","Asynchronously saves the database(s) to disk.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,BGSAVE_History,1,BGSAVE_Tips,0,bgsaveCommand,-1,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT,0,BGSAVE_Keyspecs,0,NULL,1),.args=BGSAVE_Args}, {MAKE_CMD("command","Returns detailed information about all commands.","O(N) where N is the total number of Redis commands","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,COMMAND_History,0,COMMAND_Tips,1,commandCommand,-1,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,COMMAND_Keyspecs,0,NULL,0),.subcommands=COMMAND_Subcommands}, {MAKE_CMD("config","A container for server configuration commands.","Depends on subcommand.","2.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,CONFIG_History,0,CONFIG_Tips,0,NULL,-2,0,0,CONFIG_Keyspecs,0,NULL,0),.subcommands=CONFIG_Subcommands}, {MAKE_CMD("dbsize","Returns the number of keys in the database.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,DBSIZE_History,0,DBSIZE_Tips,2,dbsizeCommand,1,CMD_READONLY|CMD_FAST,ACL_CATEGORY_KEYSPACE,DBSIZE_Keyspecs,0,NULL,0)}, {MAKE_CMD("debug","A container for debugging commands.","Depends on subcommand.","1.0.0",CMD_DOC_SYSCMD,NULL,NULL,"server",COMMAND_GROUP_SERVER,DEBUG_History,0,DEBUG_Tips,0,debugCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_PROTECTED,0,DEBUG_Keyspecs,0,NULL,0)}, {MAKE_CMD("failover","Starts a coordinated failover from a server to one of its replicas.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FAILOVER_History,0,FAILOVER_Tips,0,failoverCommand,-1,CMD_ADMIN|CMD_NOSCRIPT|CMD_STALE,0,FAILOVER_Keyspecs,0,NULL,3),.args=FAILOVER_Args}, {MAKE_CMD("flushall","Removes all keys from all databases.","O(N) where N is the total number of keys in all databases","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FLUSHALL_History,2,FLUSHALL_Tips,2,flushallCommand,-1,CMD_WRITE,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,FLUSHALL_Keyspecs,0,NULL,1),.args=FLUSHALL_Args}, {MAKE_CMD("flushdb","Remove all keys from the current database.","O(N) where N is the number of keys in the selected database","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,FLUSHDB_History,2,FLUSHDB_Tips,2,flushdbCommand,-1,CMD_WRITE,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,FLUSHDB_Keyspecs,0,NULL,1),.args=FLUSHDB_Args}, {MAKE_CMD("info","Returns information and statistics about the server.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,INFO_History,1,INFO_Tips,3,infoCommand,-1,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_DANGEROUS,INFO_Keyspecs,0,NULL,1),.args=INFO_Args}, {MAKE_CMD("lastsave","Returns the Unix timestamp of the last successful save to disk.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LASTSAVE_History,0,LASTSAVE_Tips,1,lastsaveCommand,1,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS,LASTSAVE_Keyspecs,0,NULL,0)}, {MAKE_CMD("latency","A container for latency diagnostics commands.","Depends on subcommand.","2.8.13",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LATENCY_History,0,LATENCY_Tips,0,NULL,-2,0,0,LATENCY_Keyspecs,0,NULL,0),.subcommands=LATENCY_Subcommands}, {MAKE_CMD("lolwut","Displays computer art and the Redis version",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,LOLWUT_History,0,LOLWUT_Tips,0,lolwutCommand,-1,CMD_READONLY|CMD_FAST,0,LOLWUT_Keyspecs,0,NULL,1),.args=LOLWUT_Args}, {MAKE_CMD("memory","A container for memory diagnostics commands.","Depends on subcommand.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MEMORY_History,0,MEMORY_Tips,0,NULL,-2,0,0,MEMORY_Keyspecs,0,NULL,0),.subcommands=MEMORY_Subcommands}, {MAKE_CMD("module","A container for module commands.","Depends on subcommand.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MODULE_History,0,MODULE_Tips,0,NULL,-2,0,0,MODULE_Keyspecs,0,NULL,0),.subcommands=MODULE_Subcommands}, {MAKE_CMD("monitor","Listens for all requests received by the server in real-time.",NULL,"1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,MONITOR_History,0,MONITOR_Tips,0,monitorCommand,1,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,MONITOR_Keyspecs,0,NULL,0)}, {MAKE_CMD("psync","An internal command used in replication.",NULL,"2.8.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,PSYNC_History,0,PSYNC_Tips,0,syncCommand,-3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NO_MULTI|CMD_NOSCRIPT,0,PSYNC_Keyspecs,0,NULL,2),.args=PSYNC_Args}, {MAKE_CMD("replconf","An internal command for configuring the replication stream.","O(1)","3.0.0",CMD_DOC_SYSCMD,NULL,NULL,"server",COMMAND_GROUP_SERVER,REPLCONF_History,0,REPLCONF_Tips,0,replconfCommand,-1,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_ALLOW_BUSY,0,REPLCONF_Keyspecs,0,NULL,0)}, {MAKE_CMD("replicaof","Configures a server as replica of another, or promotes it to a master.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,REPLICAOF_History,0,REPLICAOF_Tips,0,replicaofCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_STALE,0,REPLICAOF_Keyspecs,0,NULL,1),.args=REPLICAOF_Args}, {MAKE_CMD("restore-asking","An internal command for migrating keys in a cluster.","O(1) to create the new key and additional O(N*M) to reconstruct the serialized value, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1). However for sorted set values the complexity is O(N*M*log(N)) because inserting values into sorted sets is O(log(N)).","3.0.0",CMD_DOC_SYSCMD,NULL,NULL,"server",COMMAND_GROUP_SERVER,RESTORE_ASKING_History,3,RESTORE_ASKING_Tips,0,restoreCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_ASKING,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,RESTORE_ASKING_Keyspecs,1,NULL,7),.args=RESTORE_ASKING_Args}, {MAKE_CMD("role","Returns the replication role.","O(1)","2.8.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,ROLE_History,0,ROLE_Tips,0,roleCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_SENTINEL,ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS,ROLE_Keyspecs,0,NULL,0)}, {MAKE_CMD("save","Synchronously saves the database(s) to disk.","O(N) where N is the total number of keys in all databases","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SAVE_History,0,SAVE_Tips,0,saveCommand,1,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_NO_MULTI,0,SAVE_Keyspecs,0,NULL,0)}, {MAKE_CMD("shutdown","Synchronously saves the database(s) to disk and shuts down the Redis server.","O(N) when saving, where N is the total number of keys in all databases when saving data, otherwise O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SHUTDOWN_History,1,SHUTDOWN_Tips,0,shutdownCommand,-1,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_NO_MULTI|CMD_SENTINEL|CMD_ALLOW_BUSY,0,SHUTDOWN_Keyspecs,0,NULL,4),.args=SHUTDOWN_Args}, {MAKE_CMD("slaveof","Sets a Redis server as a replica of another, or promotes it to being a master.","O(1)","1.0.0",CMD_DOC_DEPRECATED,"`REPLICAOF`","5.0.0","server",COMMAND_GROUP_SERVER,SLAVEOF_History,0,SLAVEOF_Tips,0,replicaofCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NOSCRIPT|CMD_STALE,0,SLAVEOF_Keyspecs,0,NULL,1),.args=SLAVEOF_Args}, {MAKE_CMD("slowlog","A container for slow log commands.","Depends on subcommand.","2.2.12",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SLOWLOG_History,0,SLOWLOG_Tips,0,NULL,-2,0,0,SLOWLOG_Keyspecs,0,NULL,0),.subcommands=SLOWLOG_Subcommands}, {MAKE_CMD("swapdb","Swaps two Redis databases.","O(N) where N is the count of clients watching or blocking on keys from both databases.","4.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SWAPDB_History,0,SWAPDB_Tips,0,swapdbCommand,3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_KEYSPACE|ACL_CATEGORY_DANGEROUS,SWAPDB_Keyspecs,0,NULL,2),.args=SWAPDB_Args}, {MAKE_CMD("sync","An internal command used in replication.",NULL,"1.0.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,SYNC_History,0,SYNC_Tips,0,syncCommand,1,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_NO_MULTI|CMD_NOSCRIPT,0,SYNC_Keyspecs,0,NULL,0)}, {MAKE_CMD("time","Returns the server time.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"server",COMMAND_GROUP_SERVER,TIME_History,0,TIME_Tips,1,timeCommand,1,CMD_LOADING|CMD_STALE|CMD_FAST,0,TIME_Keyspecs,0,NULL,0)}, /* set */ {MAKE_CMD("sadd","Adds one or more members to a set. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SADD_History,1,SADD_Tips,0,saddCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_SET,SADD_Keyspecs,1,NULL,2),.args=SADD_Args}, {MAKE_CMD("scard","Returns the number of members in a set.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SCARD_History,0,SCARD_Tips,0,scardCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SCARD_Keyspecs,1,NULL,1),.args=SCARD_Args}, {MAKE_CMD("sdiff","Returns the difference of multiple sets.","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SDIFF_History,0,SDIFF_Tips,1,sdiffCommand,-2,CMD_READONLY,ACL_CATEGORY_SET,SDIFF_Keyspecs,1,NULL,1),.args=SDIFF_Args}, {MAKE_CMD("sdiffstore","Stores the difference of multiple sets in a key.","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SDIFFSTORE_History,0,SDIFFSTORE_Tips,0,sdiffstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,SDIFFSTORE_Keyspecs,2,NULL,2),.args=SDIFFSTORE_Args}, {MAKE_CMD("sinter","Returns the intersect of multiple sets.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTER_History,0,SINTER_Tips,1,sinterCommand,-2,CMD_READONLY,ACL_CATEGORY_SET,SINTER_Keyspecs,1,NULL,1),.args=SINTER_Args}, {MAKE_CMD("sintercard","Returns the number of members of the intersect of multiple sets.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERCARD_History,0,SINTERCARD_Tips,0,sinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SET,SINTERCARD_Keyspecs,1,sintercardGetKeys,3),.args=SINTERCARD_Args}, {MAKE_CMD("sinterstore","Stores the intersect of multiple sets in a key.","O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SINTERSTORE_History,0,SINTERSTORE_Tips,0,sinterstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,SINTERSTORE_Keyspecs,2,NULL,2),.args=SINTERSTORE_Args}, {MAKE_CMD("sismember","Determines whether a member belongs to a set.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SISMEMBER_History,0,SISMEMBER_Tips,0,sismemberCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SISMEMBER_Keyspecs,1,NULL,2),.args=SISMEMBER_Args}, {MAKE_CMD("smembers","Returns all members of a set.","O(N) where N is the set cardinality.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMEMBERS_History,0,SMEMBERS_Tips,1,smembersCommand,2,CMD_READONLY,ACL_CATEGORY_SET,SMEMBERS_Keyspecs,1,NULL,1),.args=SMEMBERS_Args}, {MAKE_CMD("smismember","Determines whether multiple members belong to a set.","O(N) where N is the number of elements being checked for membership","6.2.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMISMEMBER_History,0,SMISMEMBER_Tips,0,smismemberCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SET,SMISMEMBER_Keyspecs,1,NULL,2),.args=SMISMEMBER_Args}, {MAKE_CMD("smove","Moves a member from one set to another.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SMOVE_History,0,SMOVE_Tips,0,smoveCommand,4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SMOVE_Keyspecs,2,NULL,3),.args=SMOVE_Args}, {MAKE_CMD("spop","Returns one or more random members from a set after removing them. Deletes the set if the last member was popped.","Without the count argument O(1), otherwise O(N) where N is the value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SPOP_History,1,SPOP_Tips,1,spopCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SPOP_Keyspecs,1,NULL,2),.args=SPOP_Args}, {MAKE_CMD("srandmember","Get one or multiple random members from a set","Without the count argument O(1), otherwise O(N) where N is the absolute value of the passed count.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SRANDMEMBER_History,1,SRANDMEMBER_Tips,1,srandmemberCommand,-2,CMD_READONLY,ACL_CATEGORY_SET,SRANDMEMBER_Keyspecs,1,NULL,2),.args=SRANDMEMBER_Args}, {MAKE_CMD("srem","Removes one or more members from a set. Deletes the set if the last member was removed.","O(N) where N is the number of members to be removed.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SREM_History,1,SREM_Tips,0,sremCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SET,SREM_Keyspecs,1,NULL,2),.args=SREM_Args}, {MAKE_CMD("sscan","Iterates over members of a set.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SSCAN_History,0,SSCAN_Tips,1,sscanCommand,-3,CMD_READONLY,ACL_CATEGORY_SET,SSCAN_Keyspecs,1,NULL,4),.args=SSCAN_Args}, {MAKE_CMD("sunion","Returns the union of multiple sets.","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SUNION_History,0,SUNION_Tips,1,sunionCommand,-2,CMD_READONLY,ACL_CATEGORY_SET,SUNION_Keyspecs,1,NULL,1),.args=SUNION_Args}, {MAKE_CMD("sunionstore","Stores the union of multiple sets in a key.","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,"set",COMMAND_GROUP_SET,SUNIONSTORE_History,0,SUNIONSTORE_Tips,0,sunionstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,SUNIONSTORE_Keyspecs,2,NULL,2),.args=SUNIONSTORE_Args}, /* sorted_set */ {MAKE_CMD("bzmpop","Removes and returns a member by score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.","O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZMPOP_History,0,BZMPOP_Tips,0,bzmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,BZMPOP_Keyspecs,1,blmpopGetKeys,5),.args=BZMPOP_Args}, {MAKE_CMD("bzpopmax","Removes and returns the member with the highest score from one or more sorted sets. Blocks until a member available otherwise. Deletes the sorted set if the last element was popped.","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZPOPMAX_History,1,BZPOPMAX_Tips,0,bzpopmaxCommand,-3,CMD_WRITE|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,BZPOPMAX_Keyspecs,1,NULL,2),.args=BZPOPMAX_Args}, {MAKE_CMD("bzpopmin","Removes and returns the member with the lowest score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,BZPOPMIN_History,1,BZPOPMIN_Tips,0,bzpopminCommand,-3,CMD_WRITE|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,BZPOPMIN_Keyspecs,1,NULL,2),.args=BZPOPMIN_Args}, {MAKE_CMD("zadd","Adds one or more members to a sorted set, or updates their scores. Creates the key if it doesn't exist.","O(log(N)) for each item added, where N is the number of elements in the sorted set.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZADD_History,3,ZADD_Tips,0,zaddCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZADD_Keyspecs,1,NULL,6),.args=ZADD_Args}, {MAKE_CMD("zcard","Returns the number of members in a sorted set.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZCARD_History,0,ZCARD_Tips,0,zcardCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZCARD_Keyspecs,1,NULL,1),.args=ZCARD_Args}, {MAKE_CMD("zcount","Returns the count of members in a sorted set that have scores within a range.","O(log(N)) with N being the number of elements in the sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZCOUNT_History,0,ZCOUNT_Tips,0,zcountCommand,4,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZCOUNT_Keyspecs,1,NULL,3),.args=ZCOUNT_Args}, {MAKE_CMD("zdiff","Returns the difference between multiple sorted sets.","O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZDIFF_History,0,ZDIFF_Tips,0,zdiffCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZDIFF_Keyspecs,1,zunionInterDiffGetKeys,3),.args=ZDIFF_Args}, {MAKE_CMD("zdiffstore","Stores the difference of multiple sorted sets in a key.","O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZDIFFSTORE_History,0,ZDIFFSTORE_Tips,0,zdiffstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZDIFFSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,3),.args=ZDIFFSTORE_Args}, {MAKE_CMD("zincrby","Increments the score of a member in a sorted set.","O(log(N)) where N is the number of elements in the sorted set.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINCRBY_History,0,ZINCRBY_Tips,0,zincrbyCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZINCRBY_Keyspecs,1,NULL,3),.args=ZINCRBY_Args}, {MAKE_CMD("zinter","Returns the intersect of multiple sorted sets.","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTER_History,0,ZINTER_Tips,0,zinterCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZINTER_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZINTER_Args}, {MAKE_CMD("zintercard","Returns the number of members of the intersect of multiple sorted sets.","O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTERCARD_History,0,ZINTERCARD_Tips,0,zinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZINTERCARD_Keyspecs,1,zunionInterDiffGetKeys,3),.args=ZINTERCARD_Args}, {MAKE_CMD("zinterstore","Stores the intersect of multiple sorted sets in a key.","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTERSTORE_History,0,ZINTERSTORE_Tips,0,zinterstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZINTERSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZINTERSTORE_Args}, {MAKE_CMD("zlexcount","Returns the number of members in a sorted set within a lexicographical range.","O(log(N)) with N being the number of elements in the sorted set.","2.8.9",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZLEXCOUNT_History,0,ZLEXCOUNT_Tips,0,zlexcountCommand,4,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZLEXCOUNT_Keyspecs,1,NULL,3),.args=ZLEXCOUNT_Args}, {MAKE_CMD("zmpop","Returns the highest- or lowest-scoring members from one or more sorted sets after removing them. Deletes the sorted set if the last member was popped.","O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZMPOP_History,0,ZMPOP_Tips,0,zmpopCommand,-4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,ZMPOP_Keyspecs,1,zmpopGetKeys,4),.args=ZMPOP_Args}, {MAKE_CMD("zmscore","Returns the score of one or more members in a sorted set.","O(N) where N is the number of members being requested.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZMSCORE_History,0,ZMSCORE_Tips,0,zmscoreCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZMSCORE_Keyspecs,1,NULL,2),.args=ZMSCORE_Args}, {MAKE_CMD("zpopmax","Returns the highest-scoring members from a sorted set after removing them. Deletes the sorted set if the last member was popped.","O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZPOPMAX_History,0,ZPOPMAX_Tips,0,zpopmaxCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZPOPMAX_Keyspecs,1,NULL,2),.args=ZPOPMAX_Args}, {MAKE_CMD("zpopmin","Returns the lowest-scoring members from a sorted set after removing them. Deletes the sorted set if the last member was popped.","O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.","5.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZPOPMIN_History,0,ZPOPMIN_Tips,0,zpopminCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZPOPMIN_Keyspecs,1,NULL,2),.args=ZPOPMIN_Args}, {MAKE_CMD("zrandmember","Returns one or more random members from a sorted set.","O(N) where N is the number of members returned","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZRANDMEMBER_History,0,ZRANDMEMBER_Tips,1,zrandmemberCommand,-2,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZRANDMEMBER_Keyspecs,1,NULL,2),.args=ZRANDMEMBER_Args}, {MAKE_CMD("zrange","Returns members in a sorted set within a range of indexes.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZRANGE_History,1,ZRANGE_Tips,0,zrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZRANGE_Keyspecs,1,NULL,7),.args=ZRANGE_Args}, {MAKE_CMD("zrangebylex","Returns members in a sorted set within a lexicographical range.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.8.9",CMD_DOC_DEPRECATED,"`ZRANGE` with the `BYLEX` argument","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZRANGEBYLEX_History,0,ZRANGEBYLEX_Tips,0,zrangebylexCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZRANGEBYLEX_Keyspecs,1,NULL,4),.args=ZRANGEBYLEX_Args}, {MAKE_CMD("zrangebyscore","Returns members in a sorted set within a range of scores.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","1.0.5",CMD_DOC_DEPRECATED,"`ZRANGE` with the `BYSCORE` argument","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZRANGEBYSCORE_History,1,ZRANGEBYSCORE_Tips,0,zrangebyscoreCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZRANGEBYSCORE_Keyspecs,1,NULL,5),.args=ZRANGEBYSCORE_Args}, {MAKE_CMD("zrangestore","Stores a range of members from sorted set in a key.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements stored into the destination key.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZRANGESTORE_History,0,ZRANGESTORE_Tips,0,zrangestoreCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZRANGESTORE_Keyspecs,2,NULL,7),.args=ZRANGESTORE_Args}, {MAKE_CMD("zrank","Returns the index of a member in a sorted set ordered by ascending scores.","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZRANK_History,1,ZRANK_Tips,0,zrankCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZRANK_Keyspecs,1,NULL,3),.args=ZRANK_Args}, {MAKE_CMD("zrem","Removes one or more members from a sorted set. Deletes the sorted set if all members were removed.","O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREM_History,1,ZREM_Tips,0,zremCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZREM_Keyspecs,1,NULL,2),.args=ZREM_Args}, {MAKE_CMD("zremrangebylex","Removes members in a sorted set within a lexicographical range. Deletes the sorted set if all members were removed.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.","2.8.9",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREMRANGEBYLEX_History,0,ZREMRANGEBYLEX_Tips,0,zremrangebylexCommand,4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,ZREMRANGEBYLEX_Keyspecs,1,NULL,3),.args=ZREMRANGEBYLEX_Args}, {MAKE_CMD("zremrangebyrank","Removes members in a sorted set within a range of indexes. Deletes the sorted set if all members were removed.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREMRANGEBYRANK_History,0,ZREMRANGEBYRANK_Tips,0,zremrangebyrankCommand,4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,ZREMRANGEBYRANK_Keyspecs,1,NULL,3),.args=ZREMRANGEBYRANK_Args}, {MAKE_CMD("zremrangebyscore","Removes members in a sorted set within a range of scores. Deletes the sorted set if all members were removed.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREMRANGEBYSCORE_History,0,ZREMRANGEBYSCORE_Tips,0,zremrangebyscoreCommand,4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,ZREMRANGEBYSCORE_Keyspecs,1,NULL,3),.args=ZREMRANGEBYSCORE_Args}, {MAKE_CMD("zrevrange","Returns members in a sorted set within a range of indexes in reverse order.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.","1.2.0",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` argument","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANGE_History,0,ZREVRANGE_Tips,0,zrevrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZREVRANGE_Keyspecs,1,NULL,4),.args=ZREVRANGE_Args}, {MAKE_CMD("zrevrangebylex","Returns members in a sorted set within a lexicographical range in reverse order.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.8.9",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` and `BYLEX` arguments","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANGEBYLEX_History,0,ZREVRANGEBYLEX_Tips,0,zrevrangebylexCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZREVRANGEBYLEX_Keyspecs,1,NULL,4),.args=ZREVRANGEBYLEX_Args}, {MAKE_CMD("zrevrangebyscore","Returns members in a sorted set within a range of scores in reverse order.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.2.0",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` and `BYSCORE` arguments","6.2.0","sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANGEBYSCORE_History,1,ZREVRANGEBYSCORE_Tips,0,zrevrangebyscoreCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZREVRANGEBYSCORE_Keyspecs,1,NULL,5),.args=ZREVRANGEBYSCORE_Args}, {MAKE_CMD("zrevrank","Returns the index of a member in a sorted set ordered by descending scores.","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANK_History,1,ZREVRANK_Tips,0,zrevrankCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZREVRANK_Keyspecs,1,NULL,3),.args=ZREVRANK_Args}, {MAKE_CMD("zscan","Iterates over members and scores of a sorted set.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCAN_History,0,ZSCAN_Tips,1,zscanCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZSCAN_Keyspecs,1,NULL,4),.args=ZSCAN_Args}, {MAKE_CMD("zscore","Returns the score of a member in a sorted set.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCORE_History,0,ZSCORE_Tips,0,zscoreCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZSCORE_Keyspecs,1,NULL,2),.args=ZSCORE_Args}, {MAKE_CMD("zunion","Returns the union of multiple sorted sets.","O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNION_History,0,ZUNION_Tips,0,zunionCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZUNION_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZUNION_Args}, {MAKE_CMD("zunionstore","Stores the union of multiple sorted sets in a key.","O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNIONSTORE_History,0,ZUNIONSTORE_Tips,0,zunionstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZUNIONSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZUNIONSTORE_Args}, /* stream */ {MAKE_CMD("xack","Returns the number of messages that were successfully acknowledged by the consumer group member of a stream.","O(1) for each message ID processed.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XACK_History,0,XACK_Tips,0,xackCommand,-4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XACK_Keyspecs,1,NULL,3),.args=XACK_Args}, {MAKE_CMD("xadd","Appends a new message to a stream. Creates the key if it doesn't exist.","O(1) when adding a new entry, O(N) when trimming where N being the number of entries evicted.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XADD_History,2,XADD_Tips,1,xaddCommand,-5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STREAM,XADD_Keyspecs,1,NULL,5),.args=XADD_Args}, {MAKE_CMD("xautoclaim","Changes, or acquires, ownership of messages in a consumer group, as if the messages were delivered to as consumer group member.","O(1) if COUNT is small.","6.2.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XAUTOCLAIM_History,1,XAUTOCLAIM_Tips,1,xautoclaimCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XAUTOCLAIM_Keyspecs,1,NULL,7),.args=XAUTOCLAIM_Args}, {MAKE_CMD("xclaim","Changes, or acquires, ownership of a message in a consumer group, as if the message was delivered a consumer group member.","O(log N) with N being the number of messages in the PEL of the consumer group.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XCLAIM_History,0,XCLAIM_Tips,1,xclaimCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XCLAIM_Keyspecs,1,NULL,11),.args=XCLAIM_Args}, {MAKE_CMD("xdel","Returns the number of messages after removing them from a stream.","O(1) for each single item to delete in the stream, regardless of the stream size.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XDEL_History,0,XDEL_Tips,0,xdelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XDEL_Keyspecs,1,NULL,2),.args=XDEL_Args}, {MAKE_CMD("xgroup","A container for consumer groups commands.","Depends on subcommand.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XGROUP_History,0,XGROUP_Tips,0,NULL,-2,0,0,XGROUP_Keyspecs,0,NULL,0),.subcommands=XGROUP_Subcommands}, {MAKE_CMD("xinfo","A container for stream introspection commands.","Depends on subcommand.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_History,0,XINFO_Tips,0,NULL,-2,0,0,XINFO_Keyspecs,0,NULL,0),.subcommands=XINFO_Subcommands}, {MAKE_CMD("xlen","Return the number of messages in a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XLEN_History,0,XLEN_Tips,0,xlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STREAM,XLEN_Keyspecs,1,NULL,1),.args=XLEN_Args}, {MAKE_CMD("xpending","Returns the information and entries from a stream consumer group's pending entries list.","O(N) with N being the number of elements returned, so asking for a small fixed number of entries per call is O(1). O(M), where M is the total number of entries scanned when used with the IDLE filter. When the command returns just the summary and the list of consumers is small, it runs in O(1) time; otherwise, an additional O(N) time for iterating every consumer.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XPENDING_History,1,XPENDING_Tips,1,xpendingCommand,-3,CMD_READONLY,ACL_CATEGORY_STREAM,XPENDING_Keyspecs,1,NULL,3),.args=XPENDING_Args}, {MAKE_CMD("xrange","Returns the messages from a stream within a range of IDs.","O(N) with N being the number of elements being returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XRANGE_History,1,XRANGE_Tips,0,xrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_STREAM,XRANGE_Keyspecs,1,NULL,4),.args=XRANGE_Args}, {MAKE_CMD("xread","Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREAD_History,0,XREAD_Tips,0,xreadCommand,-4,CMD_BLOCKING|CMD_READONLY,ACL_CATEGORY_STREAM,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_Args}, {MAKE_CMD("xreadgroup","Returns new or historical messages from a stream for a consumer in a group. Blocks until a message is available otherwise.","For each stream mentioned: O(M) with M being the number of elements returned. If M is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1). On the other side when XREADGROUP blocks, XADD will pay the O(N) time in order to serve the N clients blocked on the stream getting new data.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREADGROUP_History,0,XREADGROUP_Tips,0,xreadCommand,-7,CMD_BLOCKING|CMD_WRITE,ACL_CATEGORY_STREAM,XREADGROUP_Keyspecs,1,xreadGetKeys,5),.args=XREADGROUP_Args}, {MAKE_CMD("xrevrange","Returns the messages from a stream within a range of IDs in reverse order.","O(N) with N being the number of elements returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREVRANGE_History,1,XREVRANGE_Tips,0,xrevrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_STREAM,XREVRANGE_Keyspecs,1,NULL,4),.args=XREVRANGE_Args}, {MAKE_CMD("xsetid","An internal command for replicating stream values.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XSETID_History,1,XSETID_Tips,0,xsetidCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STREAM,XSETID_Keyspecs,1,NULL,4),.args=XSETID_Args}, {MAKE_CMD("xtrim","Deletes messages from the beginning of a stream.","O(N), with N being the number of evicted entries. Constant times are very small however, since entries are organized in macro nodes containing multiple entries that can be released with a single deallocation.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XTRIM_History,1,XTRIM_Tips,1,xtrimCommand,-4,CMD_WRITE,ACL_CATEGORY_STREAM,XTRIM_Keyspecs,1,NULL,2),.args=XTRIM_Args}, /* string */ {MAKE_CMD("append","Appends a string to the value of a key. Creates the key if it doesn't exist.","O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation.","2.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,APPEND_History,0,APPEND_Tips,0,appendCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,APPEND_Keyspecs,1,NULL,2),.args=APPEND_Args}, {MAKE_CMD("decr","Decrements the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECR_History,0,DECR_Tips,0,decrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,DECR_Keyspecs,1,NULL,1),.args=DECR_Args}, {MAKE_CMD("decrby","Decrements a number from the integer value of a key. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECRBY_History,0,DECRBY_Tips,0,decrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,DECRBY_Keyspecs,1,NULL,2),.args=DECRBY_Args}, {MAKE_CMD("get","Returns the string value of a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GET_History,0,GET_Tips,0,getCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,GET_Keyspecs,1,NULL,1),.args=GET_Args}, {MAKE_CMD("getdel","Returns the string value of a key after deleting the key.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETDEL_History,0,GETDEL_Tips,0,getdelCommand,2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,GETDEL_Keyspecs,1,NULL,1),.args=GETDEL_Args}, {MAKE_CMD("getex","Returns the string value of a key after setting its expiration time.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETEX_History,0,GETEX_Tips,0,getexCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,GETEX_Keyspecs,1,NULL,2),.args=GETEX_Args}, {MAKE_CMD("getrange","Returns a substring of the string stored at a key.","O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.","2.4.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETRANGE_History,0,GETRANGE_Tips,0,getrangeCommand,4,CMD_READONLY,ACL_CATEGORY_STRING,GETRANGE_Keyspecs,1,NULL,3),.args=GETRANGE_Args}, {MAKE_CMD("getset","Returns the previous string value of a key after setting it to a new value.","O(1)","1.0.0",CMD_DOC_DEPRECATED,"`SET` with the `!GET` argument","6.2.0","string",COMMAND_GROUP_STRING,GETSET_History,0,GETSET_Tips,0,getsetCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,GETSET_Keyspecs,1,NULL,2),.args=GETSET_Args}, {MAKE_CMD("incr","Increments the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCR_History,0,INCR_Tips,0,incrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCR_Keyspecs,1,NULL,1),.args=INCR_Args}, {MAKE_CMD("incrby","Increments the integer value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBY_History,0,INCRBY_Tips,0,incrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCRBY_Keyspecs,1,NULL,2),.args=INCRBY_Args}, {MAKE_CMD("incrbyfloat","Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBYFLOAT_History,0,INCRBYFLOAT_Tips,0,incrbyfloatCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCRBYFLOAT_Keyspecs,1,NULL,2),.args=INCRBYFLOAT_Args}, {MAKE_CMD("lcs","Finds the longest common substring.","O(N*M) where N and M are the lengths of s1 and s2, respectively","7.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,LCS_History,0,LCS_Tips,0,lcsCommand,-3,CMD_READONLY,ACL_CATEGORY_STRING,LCS_Keyspecs,1,NULL,6),.args=LCS_Args}, {MAKE_CMD("mget","Atomically returns the string values of one or more keys.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGET_History,0,MGET_Tips,1,mgetCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,MGET_Keyspecs,1,NULL,1),.args=MGET_Args}, {MAKE_CMD("mset","Atomically creates or modifies the string values of one or more keys.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSET_History,0,MSET_Tips,2,msetCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSET_Keyspecs,1,NULL,1),.args=MSET_Args}, {MAKE_CMD("msetnx","Atomically modifies the string values of one or more keys only when all keys don't exist.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSETNX_History,0,MSETNX_Tips,0,msetnxCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSETNX_Keyspecs,1,NULL,1),.args=MSETNX_Args}, {MAKE_CMD("psetex","Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.","O(1)","2.6.0",CMD_DOC_DEPRECATED,"`SET` with the `PX` argument","2.6.12","string",COMMAND_GROUP_STRING,PSETEX_History,0,PSETEX_Tips,0,psetexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,PSETEX_Keyspecs,1,NULL,3),.args=PSETEX_Args}, {MAKE_CMD("set","Sets the string value of a key, ignoring its type. The key is created if it doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SET_History,4,SET_Tips,0,setCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SET_Keyspecs,1,setGetKeys,5),.args=SET_Args}, {MAKE_CMD("setex","Sets the string value and expiration time of a key. Creates the key if it doesn't exist.","O(1)","2.0.0",CMD_DOC_DEPRECATED,"`SET` with the `EX` argument","2.6.12","string",COMMAND_GROUP_STRING,SETEX_History,0,SETEX_Tips,0,setexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SETEX_Keyspecs,1,NULL,3),.args=SETEX_Args}, {MAKE_CMD("setnx","Set the string value of a key only when the key doesn't exist.","O(1)","1.0.0",CMD_DOC_DEPRECATED,"`SET` with the `NX` argument","2.6.12","string",COMMAND_GROUP_STRING,SETNX_History,0,SETNX_Tips,0,setnxCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,SETNX_Keyspecs,1,NULL,2),.args=SETNX_Args}, {MAKE_CMD("setrange","Overwrites a part of a string value with another by an offset. Creates the key if it doesn't exist.","O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.","2.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SETRANGE_History,0,SETRANGE_Tips,0,setrangeCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SETRANGE_Keyspecs,1,NULL,3),.args=SETRANGE_Args}, {MAKE_CMD("strlen","Returns the length of a string value.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,STRLEN_History,0,STRLEN_Tips,0,strlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,STRLEN_Keyspecs,1,NULL,1),.args=STRLEN_Args}, {MAKE_CMD("substr","Returns a substring from a string value.","O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.","1.0.0",CMD_DOC_DEPRECATED,"`GETRANGE`","2.0.0","string",COMMAND_GROUP_STRING,SUBSTR_History,0,SUBSTR_Tips,0,getrangeCommand,4,CMD_READONLY,ACL_CATEGORY_STRING,SUBSTR_Keyspecs,1,NULL,3),.args=SUBSTR_Args}, /* transactions */ {MAKE_CMD("discard","Discards a transaction.","O(N), when N is the number of queued commands","2.0.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,DISCARD_History,0,DISCARD_Tips,0,discardCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,DISCARD_Keyspecs,0,NULL,0)}, {MAKE_CMD("exec","Executes all commands in a transaction.","Depends on commands in the transaction","1.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,EXEC_History,0,EXEC_Tips,0,execCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SKIP_SLOWLOG,ACL_CATEGORY_TRANSACTION,EXEC_Keyspecs,0,NULL,0)}, {MAKE_CMD("multi","Starts a transaction.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,MULTI_History,0,MULTI_Tips,0,multiCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,MULTI_Keyspecs,0,NULL,0)}, {MAKE_CMD("unwatch","Forgets about watched keys of a transaction.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,UNWATCH_History,0,UNWATCH_Tips,0,unwatchCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,UNWATCH_Keyspecs,0,NULL,0)}, {MAKE_CMD("watch","Monitors changes to keys to determine the execution of a transaction.","O(1) for every key.","2.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,WATCH_History,0,WATCH_Tips,0,watchCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,WATCH_Keyspecs,1,NULL,1),.args=WATCH_Args}, {0} }; redis-8.0.2/src/commands.h000066400000000000000000000021151501533116600153700ustar00rootroot00000000000000#ifndef __REDIS_COMMANDS_H #define __REDIS_COMMANDS_H /* Must be synced with ARG_TYPE_STR and generate-command-code.py */ typedef enum { ARG_TYPE_STRING, ARG_TYPE_INTEGER, ARG_TYPE_DOUBLE, ARG_TYPE_KEY, /* A string, but represents a keyname */ ARG_TYPE_PATTERN, ARG_TYPE_UNIX_TIME, ARG_TYPE_PURE_TOKEN, ARG_TYPE_ONEOF, /* Has subargs */ ARG_TYPE_BLOCK /* Has subargs */ } redisCommandArgType; #define CMD_ARG_NONE (0) #define CMD_ARG_OPTIONAL (1<<0) #define CMD_ARG_MULTIPLE (1<<1) #define CMD_ARG_MULTIPLE_TOKEN (1<<2) /* Must be compatible with RedisModuleCommandArg. See moduleCopyCommandArgs. */ typedef struct redisCommandArg { const char *name; redisCommandArgType type; int key_spec_index; const char *token; const char *summary; const char *since; int flags; const char *deprecated_since; int num_args; struct redisCommandArg *subargs; const char *display_text; } redisCommandArg; /* Returns the command group name by group number. */ const char *commandGroupStr(int index); #endif redis-8.0.2/src/commands/000077500000000000000000000000001501533116600152205ustar00rootroot00000000000000redis-8.0.2/src/commands/README.md000066400000000000000000000021411501533116600164750ustar00rootroot00000000000000This directory contains JSON files, one for each of Redis commands. Each JSON contains all the information about the command itself, but these JSON files are not to be used directly! Any third party who needs access to command information must get it from `COMMAND INFO` and `COMMAND DOCS`. The output can be extracted in a JSON format by using `redis-cli --json`, in the same manner as in `utils/generate-commands-json.py`. The JSON files are used to generate commands.def (and https://github.com/redis/redis-doc/blob/master/commands.json) in Redis, and despite looking similar to the output of `COMMAND` there are some fields and flags that are implicitly populated, and that's the reason one shouldn't rely on the raw files. The structure of each JSON is somewhat documented in https://redis.io/commands/command-docs/ and https://redis.io/commands/command/ The `reply_schema` section is a standard JSON Schema (see https://json-schema.org/) that describes the reply of each command. It is designed to someday be used to auto-generate code in client libraries, but is not yet mature and is not exposed externally. redis-8.0.2/src/commands/acl-cat.json000066400000000000000000000023521501533116600174210ustar00rootroot00000000000000{ "CAT": { "summary": "Lists the ACL categories, or the commands inside a category.", "complexity": "O(1) since the categories and commands are a fixed set.", "group": "server", "since": "6.0.0", "arity": -2, "container": "ACL", "function": "aclCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "anyOf": [ { "type": "array", "description": "In case `category` was not given, a list of existing ACL categories", "items": { "type": "string" } }, { "type": "array", "description": "In case `category` was given, list of commands that fall under the provided ACL category", "items": { "type": "string" } } ] }, "arguments": [ { "name": "category", "type": "string", "optional": true } ] } } redis-8.0.2/src/commands/acl-deluser.json000066400000000000000000000015671501533116600203240ustar00rootroot00000000000000{ "DELUSER": { "summary": "Deletes ACL users, and terminates their connections.", "complexity": "O(1) amortized time considering the typical user.", "group": "server", "since": "6.0.0", "arity": -3, "container": "ACL", "function": "aclCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "type": "integer", "description": "The number of users that were deleted" }, "arguments": [ { "name": "username", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/acl-dryrun.json000066400000000000000000000024221501533116600201730ustar00rootroot00000000000000{ "DRYRUN": { "summary": "Simulates the execution of a command by a user, without executing the command.", "complexity": "O(1).", "group": "server", "since": "7.0.0", "arity": -4, "container": "ACL", "function": "aclCommand", "history": [], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "anyOf": [ { "const": "OK", "description": "The given user may successfully execute the given command." }, { "type": "string", "description": "The description of the problem, in case the user is not allowed to run the given command." } ] }, "arguments": [ { "name": "username", "type": "string" }, { "name": "command", "type": "string" }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/acl-genpass.json000066400000000000000000000015761501533116600203210ustar00rootroot00000000000000{ "GENPASS": { "summary": "Generates a pseudorandom, secure password that can be used to identify ACL users.", "complexity": "O(1)", "group": "server", "since": "6.0.0", "arity": -2, "container": "ACL", "function": "aclCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "type": "string", "description": "Pseudorandom data. By default it contains 64 bytes, representing 256 bits of data. If `bits` was given, the output string length is the number of specified bits (rounded to the next multiple of 4) divided by 4." }, "arguments": [ { "name": "bits", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/acl-getuser.json000066400000000000000000000061651501533116600203360ustar00rootroot00000000000000{ "GETUSER": { "summary": "Lists the ACL rules of a user.", "complexity": "O(N). Where N is the number of password, command and pattern rules that the user has.", "group": "server", "since": "6.0.0", "arity": 3, "container": "ACL", "function": "aclCommand", "history": [ [ "6.2.0", "Added Pub/Sub channel patterns." ], [ "7.0.0", "Added selectors and changed the format of key and channel patterns from a list to their rule representation." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "arguments": [ { "name": "username", "type": "string" } ], "reply_schema": { "oneOf": [ { "description": "a set of ACL rule definitions for the user", "type": "object", "additionalProperties": false, "properties": { "flags": { "type": "array", "items": { "type": "string" } }, "passwords": { "type": "array", "items": { "type": "string" } }, "commands": { "description": "root selector's commands", "type": "string" }, "keys": { "description": "root selector's keys", "type": "string" }, "channels": { "description": "root selector's channels", "type": "string" }, "selectors": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "commands": { "type": "string" }, "keys": { "type": "string" }, "channels": { "type": "string" } } } } } }, { "description": "If user does not exist", "type": "null" } ] } } } redis-8.0.2/src/commands/acl-help.json000066400000000000000000000011031501533116600175730ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "server", "since": "6.0.0", "arity": 2, "container": "ACL", "function": "aclCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "type": "array", "description": "A list of subcommands and their description", "items": { "type": "string" } } } } redis-8.0.2/src/commands/acl-list.json000066400000000000000000000012151501533116600176220ustar00rootroot00000000000000{ "LIST": { "summary": "Dumps the effective rules in ACL file format.", "complexity": "O(N). Where N is the number of configured users.", "group": "server", "since": "6.0.0", "arity": 2, "container": "ACL", "function": "aclCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "type": "array", "description": "A list of currently active ACL rules", "items": { "type": "string" } } } } redis-8.0.2/src/commands/acl-load.json000066400000000000000000000010031501533116600175610ustar00rootroot00000000000000{ "LOAD": { "summary": "Reloads the rules from the configured ACL file.", "complexity": "O(N). Where N is the number of configured users.", "group": "server", "since": "6.0.0", "arity": 2, "container": "ACL", "function": "aclCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/acl-log.json000066400000000000000000000057201501533116600174350ustar00rootroot00000000000000{ "LOG": { "summary": "Lists recent security events generated due to ACL rules.", "complexity": "O(N) with N being the number of entries shown.", "group": "server", "since": "6.0.0", "arity": -2, "container": "ACL", "function": "aclCommand", "history": [ [ "7.2.0", "Added entry ID, timestamp created, and timestamp last updated." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "oneOf": [ { "description": "In case `RESET` was not given, a list of recent ACL security events.", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "count": { "type": "integer" }, "reason": { "type": "string" }, "context": { "type": "string" }, "object": { "type": "string" }, "username": { "type": "string" }, "age-seconds": { "type": "number" }, "client-info": { "type": "string" }, "entry-id": { "type": "integer" }, "timestamp-created": { "type": "integer" }, "timestamp-last-updated": { "type": "integer" } } } }, { "const": "OK", "description": "In case `RESET` was given, OK indicates ACL log was cleared." } ] }, "arguments": [ { "name": "operation", "type": "oneof", "optional": true, "arguments": [ { "name": "count", "type": "integer" }, { "name": "reset", "type": "pure-token", "token": "RESET" } ] } ] } } redis-8.0.2/src/commands/acl-save.json000066400000000000000000000012021501533116600176010ustar00rootroot00000000000000{ "SAVE": { "summary": "Saves the effective ACL rules in the configured ACL file.", "complexity": "O(N). Where N is the number of configured users.", "group": "server", "since": "6.0.0", "arity": 2, "container": "ACL", "function": "aclCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/acl-setuser.json000066400000000000000000000022411501533116600203410ustar00rootroot00000000000000{ "SETUSER": { "summary": "Creates and modifies an ACL user and its rules.", "complexity": "O(N). Where N is the number of rules provided.", "group": "server", "since": "6.0.0", "arity": -3, "container": "ACL", "function": "aclCommand", "history": [ [ "6.2.0", "Added Pub/Sub channel patterns." ], [ "7.0.0", "Added selectors and key based permissions." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "username", "type": "string" }, { "name": "rule", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/acl-users.json000066400000000000000000000011531501533116600200110ustar00rootroot00000000000000{ "USERS": { "summary": "Lists all ACL users.", "complexity": "O(N). Where N is the number of configured users.", "group": "server", "since": "6.0.0", "arity": 2, "container": "ACL", "function": "aclCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "type": "array", "description": "List of existing ACL users", "items": { "type": "string" } } } } redis-8.0.2/src/commands/acl-whoami.json000066400000000000000000000010331501533116600201310ustar00rootroot00000000000000{ "WHOAMI": { "summary": "Returns the authenticated username of the current connection.", "complexity": "O(1)", "group": "server", "since": "6.0.0", "arity": 2, "container": "ACL", "function": "aclCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "reply_schema": { "type": "string", "description": "The username of the current connection." } } } redis-8.0.2/src/commands/acl.json000066400000000000000000000004211501533116600166470ustar00rootroot00000000000000{ "ACL": { "summary": "A container for Access List Control commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "6.0.0", "arity": -2, "command_flags": [ "SENTINEL" ] } } redis-8.0.2/src/commands/append.json000066400000000000000000000030431501533116600173620ustar00rootroot00000000000000{ "APPEND": { "summary": "Appends a string to the value of a key. Creates the key if it doesn't exist.", "complexity": "O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation.", "group": "string", "since": "2.0.0", "arity": 3, "function": "appendCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "The length of the string after the append operation." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/asking.json000066400000000000000000000006611501533116600173720ustar00rootroot00000000000000{ "ASKING": { "summary": "Signals that a cluster client is following an -ASK redirect.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 1, "function": "askingCommand", "command_flags": [ "FAST" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/auth.json000066400000000000000000000020201501533116600170460ustar00rootroot00000000000000{ "AUTH": { "summary": "Authenticates the connection.", "complexity": "O(N) where N is the number of passwords defined for the user", "group": "connection", "since": "1.0.0", "arity": -2, "function": "authCommand", "history": [ [ "6.0.0", "Added ACL style (username and password)." ] ], "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "NO_AUTH", "SENTINEL", "ALLOW_BUSY" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "username", "type": "string", "optional": true, "since": "6.0.0" }, { "name": "password", "type": "string" } ] } } redis-8.0.2/src/commands/bgrewriteaof.json000066400000000000000000000010451501533116600205730ustar00rootroot00000000000000{ "BGREWRITEAOF": { "summary": "Asynchronously rewrites the append-only file to disk.", "complexity": "O(1)", "group": "server", "since": "1.0.0", "arity": 1, "function": "bgrewriteaofCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT" ], "reply_schema": { "description": "A simple string reply indicating that the rewriting started or is about to start ASAP", "type": "string" } } } redis-8.0.2/src/commands/bgsave.json000066400000000000000000000017271501533116600173710ustar00rootroot00000000000000{ "BGSAVE": { "summary": "Asynchronously saves the database(s) to disk.", "complexity": "O(1)", "group": "server", "since": "1.0.0", "arity": -1, "function": "bgsaveCommand", "history": [ [ "3.2.2", "Added the `SCHEDULE` option." ] ], "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT" ], "arguments": [ { "name": "schedule", "token": "SCHEDULE", "type": "pure-token", "optional": true, "since": "3.2.2" } ], "reply_schema": { "oneOf": [ { "const": "Background saving started" }, { "const": "Background saving scheduled" } ] } } } redis-8.0.2/src/commands/bitcount.json000066400000000000000000000046331501533116600177500ustar00rootroot00000000000000{ "BITCOUNT": { "summary": "Counts the number of set bits (population counting) in a string.", "complexity": "O(N)", "group": "bitmap", "since": "2.6.0", "arity": -2, "function": "bitcountCommand", "history": [ [ "7.0.0", "Added the `BYTE|BIT` option." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "range", "type": "block", "optional": true, "arguments": [ { "name": "start", "type": "integer" }, { "name": "end", "type": "integer" }, { "name": "unit", "type": "oneof", "optional": true, "since": "7.0.0", "arguments": [ { "name": "byte", "type": "pure-token", "token": "BYTE" }, { "name": "bit", "type": "pure-token", "token": "BIT" } ] } ] } ], "reply_schema": { "description": "The number of bits set to 1.", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/bitfield.json000066400000000000000000000140541501533116600177010ustar00rootroot00000000000000{ "BITFIELD": { "summary": "Performs arbitrary bitfield integer operations on strings.", "complexity": "O(1) for each subcommand specified", "group": "bitmap", "since": "3.2.0", "arity": -2, "function": "bitfieldCommand", "get_keys_function": "bitfieldGetKeys", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "notes": "This command allows both access and modification of the key", "flags": [ "RW", "UPDATE", "ACCESS", "VARIABLE_FLAGS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "operation", "type": "oneof", "multiple": true, "optional": true, "arguments": [ { "token": "GET", "name": "get-block", "type": "block", "arguments": [ { "name": "encoding", "type": "string" }, { "name": "offset", "type": "integer" } ] }, { "name": "write", "type": "block", "arguments": [ { "token": "OVERFLOW", "name": "overflow-block", "type": "oneof", "optional": true, "arguments": [ { "name": "wrap", "type": "pure-token", "token": "WRAP" }, { "name": "sat", "type": "pure-token", "token": "SAT" }, { "name": "fail", "type": "pure-token", "token": "FAIL" } ] }, { "name": "write-operation", "type": "oneof", "arguments": [ { "token": "SET", "name": "set-block", "type": "block", "arguments": [ { "name": "encoding", "type": "string" }, { "name": "offset", "type": "integer" }, { "name": "value", "type": "integer" } ] }, { "token": "INCRBY", "name": "incrby-block", "type": "block", "arguments": [ { "name": "encoding", "type": "string" }, { "name": "offset", "type": "integer" }, { "name": "increment", "type": "integer" } ] } ] } ] } ] } ], "reply_schema": { "type": "array", "items": { "oneOf": [ { "description": "The result of the subcommand at the same position", "type": "integer" }, { "description": "In case OVERFLOW FAIL was given and overflows or underflows detected", "type": "null" } ] } } } } redis-8.0.2/src/commands/bitfield_ro.json000066400000000000000000000035241501533116600204010ustar00rootroot00000000000000{ "BITFIELD_RO": { "summary": "Performs arbitrary read-only bitfield integer operations on strings.", "complexity": "O(1) for each subcommand specified", "group": "bitmap", "since": "6.0.0", "arity": -2, "function": "bitfieldroCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "token": "GET", "name": "get-block", "type": "block", "optional": true, "multiple": true, "multiple_token": true, "arguments": [ { "name": "encoding", "type": "string" }, { "name": "offset", "type": "integer" } ] } ], "reply_schema": { "type": "array", "items": { "description": "The result of the subcommand at the same position", "type": "integer" } } } } redis-8.0.2/src/commands/bitop.json000066400000000000000000000053201501533116600172300ustar00rootroot00000000000000{ "BITOP": { "summary": "Performs bitwise operations on multiple strings, and stores the result.", "complexity": "O(N)", "group": "bitmap", "since": "2.6.0", "arity": -4, "function": "bitopCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 3 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "operation", "type": "oneof", "arguments": [ { "name": "and", "type": "pure-token", "token": "AND" }, { "name": "or", "type": "pure-token", "token": "OR" }, { "name": "xor", "type": "pure-token", "token": "XOR" }, { "name": "not", "type": "pure-token", "token": "NOT" } ] }, { "name": "destkey", "type": "key", "key_spec_index": 0 }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true } ], "reply_schema": { "description": "the size of the string stored in the destination key, that is equal to the size of the longest input string", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/bitpos.json000066400000000000000000000063271501533116600174230ustar00rootroot00000000000000{ "BITPOS": { "summary": "Finds the first set (1) or clear (0) bit in a string.", "complexity": "O(N)", "group": "bitmap", "since": "2.8.7", "arity": -3, "function": "bitposCommand", "history": [ [ "7.0.0", "Added the `BYTE|BIT` option." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "bit", "type": "integer" }, { "name": "range", "type": "block", "optional": true, "arguments": [ { "name": "start", "type": "integer" }, { "name": "end-unit-block", "type": "block", "optional": true, "arguments": [ { "name": "end", "type": "integer" }, { "name": "unit", "type": "oneof", "optional": true, "since": "7.0.0", "arguments": [ { "name": "byte", "type": "pure-token", "token": "BYTE" }, { "name": "bit", "type": "pure-token", "token": "BIT" } ] } ] } ] } ], "reply_schema": { "oneOf": [ { "description": "the position of the first bit set to 1 or 0 according to the request", "type": "integer", "minimum": 0 }, { "description": "In case the `bit` argument is 1 and the string is empty or composed of just zero bytes", "const": -1 } ] } } } redis-8.0.2/src/commands/blmove.json000066400000000000000000000062561501533116600174100ustar00rootroot00000000000000{ "BLMOVE": { "summary": "Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was moved.", "complexity": "O(1)", "group": "list", "since": "6.2.0", "arity": 6, "function": "blmoveCommand", "command_flags": [ "WRITE", "DENYOOM", "BLOCKING" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The popped element.", "type": "string" }, { "description": "Operation timed-out", "type": "null" } ] }, "arguments": [ { "name": "source", "type": "key", "key_spec_index": 0 }, { "name": "destination", "type": "key", "key_spec_index": 1 }, { "name": "wherefrom", "type": "oneof", "arguments": [ { "name": "left", "type": "pure-token", "token": "LEFT" }, { "name": "right", "type": "pure-token", "token": "RIGHT" } ] }, { "name": "whereto", "type": "oneof", "arguments": [ { "name": "left", "type": "pure-token", "token": "LEFT" }, { "name": "right", "type": "pure-token", "token": "RIGHT" } ] }, { "name": "timeout", "type": "double" } ] } } redis-8.0.2/src/commands/blmpop.json000066400000000000000000000062111501533116600174040ustar00rootroot00000000000000{ "BLMPOP": { "summary": "Pops the first element from one of multiple lists. Blocks until an element is available otherwise. Deletes the list if the last element was popped.", "complexity": "O(N+M) where N is the number of provided keys and M is the number of elements returned.", "group": "list", "since": "7.0.0", "arity": -5, "function": "blmpopCommand", "get_keys_function": "blmpopGetKeys", "command_flags": [ "WRITE", "BLOCKING" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "oneOf": [ { "description": "Operation timed-out", "type": "null" }, { "description": "The key from which elements were popped and the popped elements", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "List key from which elements were popped.", "type": "string" }, { "description": "Array of popped elements.", "type": "array", "minItems": 1, "items": { "type": "string" } } ] } ] }, "arguments": [ { "name": "timeout", "type": "double" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "where", "type": "oneof", "arguments": [ { "name": "left", "type": "pure-token", "token": "LEFT" }, { "name": "right", "type": "pure-token", "token": "RIGHT" } ] }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/blpop.json000066400000000000000000000045521501533116600172350ustar00rootroot00000000000000{ "BLPOP": { "summary": "Removes and returns the first element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.", "complexity": "O(N) where N is the number of provided keys.", "group": "list", "since": "2.0.0", "arity": -3, "function": "blpopCommand", "history": [ [ "6.0.0", "`timeout` is interpreted as a double instead of an integer." ] ], "command_flags": [ "WRITE", "BLOCKING" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -2, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "null", "description": "No element could be popped and timeout expired" }, { "description": "The key from which the element was popped and the value of the popped element", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "List key from which the element was popped.", "type": "string" }, { "description": "Value of the popped element.", "type": "string" } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "timeout", "type": "double" } ] } } redis-8.0.2/src/commands/brpop.json000066400000000000000000000044021501533116600172350ustar00rootroot00000000000000{ "BRPOP": { "summary": "Removes and returns the last element in a list. Blocks until an element is available otherwise. Deletes the list if the last element was popped.", "complexity": "O(N) where N is the number of provided keys.", "group": "list", "since": "2.0.0", "arity": -3, "function": "brpopCommand", "history": [ [ "6.0.0", "`timeout` is interpreted as a double instead of an integer." ] ], "command_flags": [ "WRITE", "BLOCKING" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -2, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "timeout", "type": "double" } ], "reply_schema": { "oneOf": [ { "description": "No element could be popped and the timeout expired.", "type": "null" }, { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "The name of the key where an element was popped ", "type": "string" }, { "description": "The value of the popped element", "type": "string" } ] } ] } } } redis-8.0.2/src/commands/brpoplpush.json000066400000000000000000000051321501533116600203120ustar00rootroot00000000000000{ "BRPOPLPUSH": { "summary": "Pops an element from a list, pushes it to another list and returns it. Block until an element is available otherwise. Deletes the list if the last element was popped.", "complexity": "O(1)", "group": "list", "since": "2.2.0", "arity": 4, "function": "brpoplpushCommand", "history": [ [ "6.0.0", "`timeout` is interpreted as a double instead of an integer." ] ], "deprecated_since": "6.2.0", "replaced_by": "`BLMOVE` with the `RIGHT` and `LEFT` arguments", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM", "BLOCKING" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "string", "description": "The element being popped from source and pushed to destination." }, { "type": "null", "description": "Timeout is reached." } ] }, "arguments": [ { "name": "source", "type": "key", "key_spec_index": 0 }, { "name": "destination", "type": "key", "key_spec_index": 1 }, { "name": "timeout", "type": "double" } ] } } redis-8.0.2/src/commands/bzmpop.json000066400000000000000000000074021501533116600174250ustar00rootroot00000000000000{ "BZMPOP": { "summary": "Removes and returns a member by score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.", "complexity": "O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.", "group": "sorted_set", "since": "7.0.0", "arity": -5, "function": "bzmpopCommand", "get_keys_function": "blmpopGetKeys", "command_flags": [ "WRITE", "BLOCKING" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "oneOf": [ { "description": "Timeout reached and no elements were popped.", "type": "null" }, { "description": "The keyname and the popped members.", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Keyname", "type": "string" }, { "description": "Popped members and their scores.", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Member", "type": "string" }, { "description": "Score", "type": "number" } ] } } ] } ] }, "arguments": [ { "name": "timeout", "type": "double" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "where", "type": "oneof", "arguments": [ { "name": "min", "type": "pure-token", "token": "MIN" }, { "name": "max", "type": "pure-token", "token": "MAX" } ] }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/bzpopmax.json000066400000000000000000000047771501533116600177720ustar00rootroot00000000000000{ "BZPOPMAX": { "summary": "Removes and returns the member with the highest score from one or more sorted sets. Blocks until a member available otherwise. Deletes the sorted set if the last element was popped.", "complexity": "O(log(N)) with N being the number of elements in the sorted set.", "group": "sorted_set", "since": "5.0.0", "arity": -3, "function": "bzpopmaxCommand", "history": [ [ "6.0.0", "`timeout` is interpreted as a double instead of an integer." ] ], "command_flags": [ "WRITE", "FAST", "BLOCKING" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -2, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Timeout reached and no elements were popped.", "type": "null" }, { "description": "The keyname, popped member, and its score.", "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "description": "Keyname", "type": "string" }, { "description": "Member", "type": "string" }, { "description": "Score", "type": "number" } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "timeout", "type": "double" } ] } } redis-8.0.2/src/commands/bzpopmin.json000066400000000000000000000050001501533116600177440ustar00rootroot00000000000000{ "BZPOPMIN": { "summary": "Removes and returns the member with the lowest score from one or more sorted sets. Blocks until a member is available otherwise. Deletes the sorted set if the last element was popped.", "complexity": "O(log(N)) with N being the number of elements in the sorted set.", "group": "sorted_set", "since": "5.0.0", "arity": -3, "function": "bzpopminCommand", "history": [ [ "6.0.0", "`timeout` is interpreted as a double instead of an integer." ] ], "command_flags": [ "WRITE", "FAST", "BLOCKING" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -2, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Timeout reached and no elements were popped.", "type": "null" }, { "description": "The keyname, popped member, and its score.", "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "description": "Keyname", "type": "string" }, { "description": "Member", "type": "string" }, { "description": "Score", "type": "number" } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "timeout", "type": "double" } ] } } redis-8.0.2/src/commands/client-caching.json000066400000000000000000000020421501533116600207610ustar00rootroot00000000000000{ "CACHING": { "summary": "Instructs the server whether to track the keys in the next request.", "complexity": "O(1)", "group": "connection", "since": "6.0.0", "arity": 3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "mode", "type": "oneof", "arguments": [ { "name": "yes", "type": "pure-token", "token": "YES" }, { "name": "no", "type": "pure-token", "token": "NO" } ] } ] } } redis-8.0.2/src/commands/client-getname.json000066400000000000000000000014651501533116600210150ustar00rootroot00000000000000{ "GETNAME": { "summary": "Returns the name of the connection.", "complexity": "O(1)", "group": "connection", "since": "2.6.9", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "oneOf": [ { "type": "string", "description": "The connection name of the current connection" }, { "type": "null", "description": "Connection name was not set" } ] } } } redis-8.0.2/src/commands/client-getredir.json000066400000000000000000000020601501533116600211720ustar00rootroot00000000000000{ "GETREDIR": { "summary": "Returns the client ID to which the connection's tracking notifications are redirected.", "complexity": "O(1)", "group": "connection", "since": "6.0.0", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "oneOf": [ { "const": 0, "description": "Not redirecting notifications to any client." }, { "const": -1, "description": "Client tracking is not enabled." }, { "type": "integer", "description": "ID of the client we are redirecting the notifications to.", "minimum": 1 } ] } } } redis-8.0.2/src/commands/client-help.json000066400000000000000000000012011501533116600203110ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "connection", "since": "5.0.0", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/client-id.json000066400000000000000000000011011501533116600177540ustar00rootroot00000000000000{ "ID": { "summary": "Returns the unique client ID of the connection.", "complexity": "O(1)", "group": "connection", "since": "5.0.0", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "type": "integer", "description": "The id of the client" } } } redis-8.0.2/src/commands/client-info.json000066400000000000000000000013001501533116600203140ustar00rootroot00000000000000{ "INFO": { "summary": "Returns information about the connection.", "complexity": "O(1)", "group": "connection", "since": "6.2.0", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "a unique string, as described at the CLIENT LIST page, for the current client", "type": "string" } } } redis-8.0.2/src/commands/client-kill.json000066400000000000000000000144421501533116600203270ustar00rootroot00000000000000{ "KILL": { "summary": "Terminates open connections.", "complexity": "O(N) where N is the number of client connections", "group": "connection", "since": "2.4.0", "arity": -3, "container": "CLIENT", "function": "clientCommand", "history": [ [ "2.8.12", "Added new filter format." ], [ "2.8.12", "`ID` option." ], [ "3.2.0", "Added `master` type in for `TYPE` option." ], [ "5.0.0", "Replaced `slave` `TYPE` with `replica`. `slave` still supported for backward compatibility." ], [ "6.2.0", "`LADDR` option." ], [ "7.4.0", "`MAXAGE` option." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "arguments": [ { "name": "filter", "type": "oneof", "arguments": [ { "name": "old-format", "display": "ip:port", "type": "string", "deprecated_since": "2.8.12" }, { "name": "new-format", "type": "oneof", "multiple": true, "arguments": [ { "token": "ID", "name": "client-id", "type": "integer", "optional": true, "since": "2.8.12" }, { "token": "TYPE", "name": "client-type", "type": "oneof", "optional": true, "since": "2.8.12", "arguments": [ { "name": "normal", "type": "pure-token", "token": "normal" }, { "name": "master", "type": "pure-token", "token": "master", "since": "3.2.0" }, { "name": "slave", "type": "pure-token", "token": "slave" }, { "name": "replica", "type": "pure-token", "token": "replica", "since": "5.0.0" }, { "name": "pubsub", "type": "pure-token", "token": "pubsub" } ] }, { "token": "USER", "name": "username", "type": "string", "optional": true }, { "token": "ADDR", "name": "addr", "display": "ip:port", "type": "string", "optional": true }, { "token": "LADDR", "name": "laddr", "display": "ip:port", "type": "string", "optional": true, "since": "6.2.0" }, { "token": "SKIPME", "name": "skipme", "type": "oneof", "optional": true, "arguments": [ { "name": "yes", "type": "pure-token", "token": "YES" }, { "name": "no", "type": "pure-token", "token": "NO" } ] }, { "token": "MAXAGE", "name": "maxage", "type": "integer", "optional": true, "since": "7.4.0" } ] } ] } ], "reply_schema": { "oneOf": [ { "description": "when called in 3 argument format", "const": "OK" }, { "description": "when called in filter/value format, the number of clients killed", "type": "integer", "minimum": 0 } ] } } } redis-8.0.2/src/commands/client-list.json000066400000000000000000000056041501533116600203470ustar00rootroot00000000000000{ "LIST": { "summary": "Lists open connections.", "complexity": "O(N) where N is the number of client connections", "group": "connection", "since": "2.4.0", "arity": -2, "container": "CLIENT", "function": "clientCommand", "history": [ [ "2.8.12", "Added unique client `id` field." ], [ "5.0.0", "Added optional `TYPE` filter." ], [ "6.0.0", "Added `user` field." ], [ "6.2.0", "Added `argv-mem`, `tot-mem`, `laddr` and `redir` fields and the optional `ID` filter." ], [ "7.0.0", "Added `resp`, `multi-mem`, `rbs` and `rbp` fields." ], [ "7.0.3", "Added `ssub` field." ], [ "7.2.0", "Added `lib-name` and `lib-ver` fields." ], [ "7.4.0", "Added `watch` field." ], [ "8.0.0", "Added `io-thread` field." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "type": "string", "description": "Information and statistics about client connections" }, "arguments": [ { "token": "TYPE", "name": "client-type", "type": "oneof", "optional": true, "since": "5.0.0", "arguments": [ { "name": "normal", "type": "pure-token", "token": "normal" }, { "name": "master", "type": "pure-token", "token": "master" }, { "name": "replica", "type": "pure-token", "token": "replica" }, { "name": "pubsub", "type": "pure-token", "token": "pubsub" } ] }, { "name": "client-id", "token": "ID", "type": "integer", "optional": true, "multiple": true, "since": "6.2.0" } ] } } redis-8.0.2/src/commands/client-no-evict.json000066400000000000000000000020501501533116600211100ustar00rootroot00000000000000{ "NO-EVICT": { "summary": "Sets the client eviction mode of the connection.", "complexity": "O(1)", "group": "connection", "since": "7.0.0", "arity": 3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "arguments": [ { "name": "enabled", "type": "oneof", "arguments": [ { "name": "on", "type": "pure-token", "token": "ON" }, { "name": "off", "type": "pure-token", "token": "OFF" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/client-no-touch.json000066400000000000000000000020341501533116600211220ustar00rootroot00000000000000{ "NO-TOUCH": { "summary": "Controls whether commands sent by the client affect the LRU/LFU of accessed keys.", "complexity": "O(1)", "group": "connection", "since": "7.2.0", "arity": 3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "enabled", "type": "oneof", "arguments": [ { "name": "on", "type": "pure-token", "token": "ON" }, { "name": "off", "type": "pure-token", "token": "OFF" } ] } ] } } redis-8.0.2/src/commands/client-pause.json000066400000000000000000000025411501533116600205060ustar00rootroot00000000000000{ "PAUSE": { "summary": "Suspends commands processing.", "complexity": "O(1)", "group": "connection", "since": "3.0.0", "arity": -3, "container": "CLIENT", "function": "clientCommand", "history": [ [ "6.2.0", "`CLIENT PAUSE WRITE` mode added along with the `mode` option." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "arguments": [ { "name": "timeout", "type": "integer" }, { "name": "mode", "type": "oneof", "optional": true, "since": "6.2.0", "arguments": [ { "name": "write", "type": "pure-token", "token": "WRITE" }, { "name": "all", "type": "pure-token", "token": "ALL" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/client-reply.json000066400000000000000000000024771501533116600205340ustar00rootroot00000000000000{ "REPLY": { "summary": "Instructs the server whether to reply to commands.", "complexity": "O(1)", "group": "connection", "since": "3.2.0", "arity": 3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK", "description": "When called with either OFF or SKIP subcommands, no reply is made. When called with ON, reply is OK." }, "arguments": [ { "name": "action", "type": "oneof", "arguments": [ { "name": "on", "type": "pure-token", "token": "ON" }, { "name": "off", "type": "pure-token", "token": "OFF" }, { "name": "skip", "type": "pure-token", "token": "SKIP" } ] } ] } } redis-8.0.2/src/commands/client-setinfo.json000066400000000000000000000022431501533116600210370ustar00rootroot00000000000000{ "SETINFO": { "summary": "Sets information specific to the client or connection.", "complexity": "O(1)", "group": "connection", "since": "7.2.0", "arity": 4, "container": "CLIENT", "function": "clientSetinfoCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "attr", "type": "oneof", "arguments": [ { "token": "lib-name", "name": "libname", "type": "string" }, { "token": "lib-ver", "name": "libver", "type": "string" } ] } ] } } redis-8.0.2/src/commands/client-setname.json000066400000000000000000000014001501533116600210160ustar00rootroot00000000000000{ "SETNAME": { "summary": "Sets the connection name.", "complexity": "O(1)", "group": "connection", "since": "2.6.9", "arity": 3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "acl_categories": [ "CONNECTION" ], "arguments": [ { "name": "connection-name", "type": "string" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/client-tracking.json000066400000000000000000000043641501533116600212000ustar00rootroot00000000000000{ "TRACKING": { "summary": "Controls server-assisted client-side caching for the connection.", "complexity": "O(1). Some options may introduce additional complexity.", "group": "connection", "since": "6.0.0", "arity": -3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "arguments": [ { "name": "status", "type": "oneof", "arguments": [ { "name": "on", "type": "pure-token", "token": "ON" }, { "name": "off", "type": "pure-token", "token": "OFF" } ] }, { "token": "REDIRECT", "name": "client-id", "type": "integer", "optional": true }, { "token": "PREFIX", "name": "prefix", "type": "string", "optional": true, "multiple": true, "multiple_token": true }, { "name": "BCAST", "token": "BCAST", "type": "pure-token", "optional": true }, { "name": "OPTIN", "token": "OPTIN", "type": "pure-token", "optional": true }, { "name": "OPTOUT", "token": "OPTOUT", "type": "pure-token", "optional": true }, { "name": "NOLOOP", "token": "NOLOOP", "type": "pure-token", "optional": true } ], "reply_schema": { "description": "if the client was successfully put into or taken out of tracking mode", "const": "OK" } } } redis-8.0.2/src/commands/client-trackinginfo.json000066400000000000000000000063221501533116600220500ustar00rootroot00000000000000{ "TRACKINGINFO": { "summary": "Returns information about server-assisted client-side caching for the connection.", "complexity": "O(1)", "group": "connection", "since": "6.2.0", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "type": "object", "additionalProperties": false, "properties": { "flags": { "type": "array", "items": { "oneOf": [ { "const": "off", "description": "The connection isn't using server assisted client side caching." }, { "const": "on", "description": "Server assisted client side caching is enabled for the connection." }, { "const": "bcast", "description": "The client uses broadcasting mode." }, { "const": "optin", "description": "The client does not cache keys by default." }, { "const": "optout", "description": "The client caches keys by default." }, { "const": "caching-yes", "description": "The next command will cache keys (exists only together with optin)." }, { "const": "caching-no", "description": "The next command won't cache keys (exists only together with optout)." }, { "const": "noloop", "description": "The client isn't notified about keys modified by itself." }, { "const": "broken_redirect", "description": "The client ID used for redirection isn't valid anymore." } ] } }, "redirect": { "type": "integer", "description": "The client ID used for notifications redirection, or -1 when none." }, "prefixes": { "type": "array", "description": "List of key prefixes for which notifications are sent to the client.", "items": { "type": "string" } } } } } } redis-8.0.2/src/commands/client-unblock.json000066400000000000000000000030631501533116600210260ustar00rootroot00000000000000{ "UNBLOCK": { "summary": "Unblocks a client blocked by a blocking command from a different connection.", "complexity": "O(log N) where N is the number of client connections", "group": "connection", "since": "5.0.0", "arity": -3, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "oneOf": [ { "const": 0, "description": "if the client was unblocked successfully" }, { "const": 1, "description": "if the client wasn't unblocked" } ] }, "arguments": [ { "name": "client-id", "type": "integer" }, { "name": "unblock-type", "type": "oneof", "optional": true, "arguments": [ { "name": "timeout", "type": "pure-token", "token": "TIMEOUT" }, { "name": "error", "type": "pure-token", "token": "ERROR" } ] } ] } } redis-8.0.2/src/commands/client-unpause.json000066400000000000000000000011151501533116600210450ustar00rootroot00000000000000{ "UNPAUSE": { "summary": "Resumes processing commands from paused clients.", "complexity": "O(N) Where N is the number of paused clients", "group": "connection", "since": "6.2.0", "arity": 2, "container": "CLIENT", "function": "clientCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/client.json000066400000000000000000000004261501533116600173730ustar00rootroot00000000000000{ "CLIENT": { "summary": "A container for client connection commands.", "complexity": "Depends on subcommand.", "group": "connection", "since": "2.4.0", "arity": -2, "command_flags": [ "SENTINEL" ] } } redis-8.0.2/src/commands/cluster-addslots.json000066400000000000000000000012051501533116600214050ustar00rootroot00000000000000{ "ADDSLOTS": { "summary": "Assigns new hash slots to a node.", "complexity": "O(N) where N is the total number of hash slot arguments", "group": "cluster", "since": "3.0.0", "arity": -3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "slot", "type": "integer", "multiple": true } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-addslotsrange.json000066400000000000000000000017551501533116600224340ustar00rootroot00000000000000{ "ADDSLOTSRANGE": { "summary": "Assigns new hash slot ranges to a node.", "complexity": "O(N) where N is the total number of the slots between the start slot and end slot arguments.", "group": "cluster", "since": "7.0.0", "arity": -4, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "range", "type": "block", "multiple": true, "arguments": [ { "name": "start-slot", "type": "integer" }, { "name": "end-slot", "type": "integer" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-bumpepoch.json000066400000000000000000000016471501533116600215640ustar00rootroot00000000000000{ "BUMPEPOCH": { "summary": "Advances the cluster config epoch.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "oneOf": [ { "description": "if the epoch was incremented", "type": "string", "pattern": "^BUMPED [0-9]*$" }, { "description": "if the node already has the greatest config epoch in the cluster", "type": "string", "pattern": "^STILL [0-9]*$" } ] } } } redis-8.0.2/src/commands/cluster-count-failure-reports.json000066400000000000000000000014361501533116600240470ustar00rootroot00000000000000{ "COUNT-FAILURE-REPORTS": { "summary": "Returns the number of active failure reports active for a node.", "complexity": "O(N) where N is the number of failure reports", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "ADMIN", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "arguments": [ { "name": "node-id", "type": "string" } ], "reply_schema": { "description": "the number of active failure reports for the node", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/cluster-countkeysinslot.json000066400000000000000000000011641501533116600230510ustar00rootroot00000000000000{ "COUNTKEYSINSLOT": { "summary": "Returns the number of keys in a hash slot.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "arguments": [ { "name": "slot", "type": "integer" } ], "reply_schema": { "description": "The number of keys in the specified hash slot", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/cluster-delslots.json000066400000000000000000000012121501533116600214170ustar00rootroot00000000000000{ "DELSLOTS": { "summary": "Sets hash slots as unbound for a node.", "complexity": "O(N) where N is the total number of hash slot arguments", "group": "cluster", "since": "3.0.0", "arity": -3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "slot", "type": "integer", "multiple": true } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-delslotsrange.json000066400000000000000000000017621501533116600224460ustar00rootroot00000000000000{ "DELSLOTSRANGE": { "summary": "Sets hash slot ranges as unbound for a node.", "complexity": "O(N) where N is the total number of the slots between the start slot and end slot arguments.", "group": "cluster", "since": "7.0.0", "arity": -4, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "range", "type": "block", "multiple": true, "arguments": [ { "name": "start-slot", "type": "integer" }, { "name": "end-slot", "type": "integer" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-failover.json000066400000000000000000000017771501533116600214150ustar00rootroot00000000000000{ "FAILOVER": { "summary": "Forces a replica to perform a manual failover of its master.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": -2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "options", "type": "oneof", "optional": true, "arguments": [ { "name": "force", "type": "pure-token", "token": "FORCE" }, { "name": "takeover", "type": "pure-token", "token": "TAKEOVER" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-flushslots.json000066400000000000000000000006721501533116600220050ustar00rootroot00000000000000{ "FLUSHSLOTS": { "summary": "Deletes all slots information from a node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-forget.json000066400000000000000000000010621501533116600210570ustar00rootroot00000000000000{ "FORGET": { "summary": "Removes a node from the nodes table.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "node-id", "type": "string" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-getkeysinslot.json000066400000000000000000000016131501533116600224770ustar00rootroot00000000000000{ "GETKEYSINSLOT": { "summary": "Returns the key names in a hash slot.", "complexity": "O(N) where N is the number of requested keys", "group": "cluster", "since": "3.0.0", "arity": 4, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "arguments": [ { "name": "slot", "type": "integer" }, { "name": "count", "type": "integer" } ], "reply_schema": { "description": "an array with up to count elements", "type": "array", "items": { "description": "key name", "type": "string" } } } } redis-8.0.2/src/commands/cluster-help.json000066400000000000000000000010501501533116600205160ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "cluster", "since": "5.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/cluster-info.json000066400000000000000000000011571501533116600205310ustar00rootroot00000000000000{ "INFO": { "summary": "Returns information about the state of a node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "A map between named fields and values in the form of : lines separated by newlines composed by the two bytes CRLF", "type": "string" } } } redis-8.0.2/src/commands/cluster-keyslot.json000066400000000000000000000012071501533116600212640ustar00rootroot00000000000000{ "KEYSLOT": { "summary": "Returns the hash slot for a key.", "complexity": "O(N) where N is the number of bytes in the key", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "arguments": [ { "name": "key", "type": "string" } ], "reply_schema": { "description": "The hash slot number for the specified key", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/cluster-links.json000066400000000000000000000050541501533116600207160ustar00rootroot00000000000000{ "LINKS": { "summary": "Returns a list of all TCP links to and from peer nodes.", "complexity": "O(N) where N is the total number of Cluster nodes", "group": "cluster", "since": "7.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "an array of cluster links and their attributes", "type": "array", "items": { "type": "object", "properties": { "direction": { "description": "This link is established by the local node _to_ the peer, or accepted by the local node _from_ the peer.", "oneOf": [ { "description": "connection initiated from peer", "const": "from" }, { "description": "connection initiated to peer", "const": "to" } ] }, "node": { "description": "the node id of the peer", "type": "string" }, "create-time": { "description": "unix time creation time of the link. (In the case of a _to_ link, this is the time when the TCP link is created by the local node, not the time when it is actually established.)", "type": "integer" }, "events": { "description": "events currently registered for the link. r means readable event, w means writable event", "type": "string" }, "send-buffer-allocated": { "description": "allocated size of the link's send buffer, which is used to buffer outgoing messages toward the peer", "type": "integer" }, "send-buffer-used": { "description": "size of the portion of the link's send buffer that is currently holding data(messages)", "type": "integer" } }, "additionalProperties": false } } } } redis-8.0.2/src/commands/cluster-meet.json000066400000000000000000000017321501533116600205270ustar00rootroot00000000000000{ "MEET": { "summary": "Forces a node to handshake with another node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": -4, "container": "CLUSTER", "function": "clusterCommand", "history": [ [ "4.0.0", "Added the optional `cluster_bus_port` argument." ] ], "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "ip", "type": "string" }, { "name": "port", "type": "integer" }, { "name": "cluster-bus-port", "type": "integer", "optional": true, "since": "4.0.0" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-myid.json000066400000000000000000000006331501533116600205360ustar00rootroot00000000000000{ "MYID": { "summary": "Returns the ID of a node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "reply_schema": { "description": "the node id", "type": "string" } } } redis-8.0.2/src/commands/cluster-myshardid.json000066400000000000000000000010201501533116600215470ustar00rootroot00000000000000{ "MYSHARDID": { "summary": "Returns the shard ID of a node.", "complexity": "O(1)", "group": "cluster", "since": "7.2.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "history": [], "command_flags": [ "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "the node's shard id", "type": "string" } } } redis-8.0.2/src/commands/cluster-nodes.json000066400000000000000000000011011501533116600206730ustar00rootroot00000000000000{ "NODES": { "summary": "Returns the cluster configuration for a node.", "complexity": "O(N) where N is the total number of Cluster nodes", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "the serialized cluster configuration", "type": "string" } } } redis-8.0.2/src/commands/cluster-replicas.json000066400000000000000000000016521501533116600214000ustar00rootroot00000000000000{ "REPLICAS": { "summary": "Lists the replica nodes of a master node.", "complexity": "O(N) where N is the number of replicas.", "group": "cluster", "since": "5.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "ADMIN", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "arguments": [ { "name": "node-id", "type": "string" } ], "reply_schema": { "description": "a list of replica nodes replicating from the specified master node provided in the same format used by CLUSTER NODES", "type": "array", "items": { "type": "string", "description": "the serialized cluster configuration" } } } } redis-8.0.2/src/commands/cluster-replicate.json000066400000000000000000000010761501533116600215460ustar00rootroot00000000000000{ "REPLICATE": { "summary": "Configure a node as replica of a master node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "node-id", "type": "string" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-reset.json000066400000000000000000000020321501533116600207110ustar00rootroot00000000000000{ "RESET": { "summary": "Resets a node.", "complexity": "O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.", "group": "cluster", "since": "3.0.0", "arity": -2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "ADMIN", "STALE", "NOSCRIPT" ], "arguments": [ { "name": "reset-type", "type": "oneof", "optional": true, "arguments": [ { "name": "hard", "type": "pure-token", "token": "HARD" }, { "name": "soft", "type": "pure-token", "token": "SOFT" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-saveconfig.json000066400000000000000000000007101501533116600217140ustar00rootroot00000000000000{ "SAVECONFIG": { "summary": "Forces a node to save the cluster configuration to disk.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-set-config-epoch.json000066400000000000000000000011121501533116600227170ustar00rootroot00000000000000{ "SET-CONFIG-EPOCH": { "summary": "Sets the configuration epoch for a new node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "config-epoch", "type": "integer" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-setslot.json000066400000000000000000000030001501533116600212600ustar00rootroot00000000000000{ "SETSLOT": { "summary": "Binds a hash slot to a node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": -4, "container": "CLUSTER", "function": "clusterCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "STALE" ], "arguments": [ { "name": "slot", "type": "integer" }, { "name": "subcommand", "type": "oneof", "arguments": [ { "name": "importing", "display": "node-id", "type": "string", "token": "IMPORTING" }, { "name": "migrating", "display": "node-id", "type": "string", "token": "MIGRATING" }, { "name": "node", "display": "node-id", "type": "string", "token": "NODE" }, { "name": "stable", "type": "pure-token", "token": "STABLE" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/cluster-shards.json000066400000000000000000000070141501533116600210600ustar00rootroot00000000000000{ "SHARDS": { "summary": "Returns the mapping of cluster slots to shards.", "complexity": "O(N) where N is the total number of cluster nodes", "group": "cluster", "since": "7.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "history": [], "command_flags": [ "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "a nested list of a map of hash ranges and shard nodes describing individual shards", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "slots": { "description": "an even number element array specifying the start and end slot numbers for slot ranges owned by this shard", "type": "array", "items": { "type": "integer" } }, "nodes": { "description": "nodes that handle these slot ranges", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string" }, "port": { "type": "integer" }, "tls-port": { "type": "integer" }, "ip": { "type": "string" }, "endpoint": { "type": "string" }, "hostname": { "type": "string" }, "role": { "oneOf": [ { "const": "master" }, { "const": "replica" } ] }, "replication-offset": { "type": "integer" }, "health": { "oneOf": [ { "const": "fail" }, { "const": "loading" }, { "const": "online" } ] } } } } } } } } } redis-8.0.2/src/commands/cluster-slaves.json000066400000000000000000000020651501533116600210720ustar00rootroot00000000000000{ "SLAVES": { "summary": "Lists the replica nodes of a master node.", "complexity": "O(N) where N is the number of replicas.", "group": "cluster", "since": "3.0.0", "arity": 3, "container": "CLUSTER", "function": "clusterCommand", "deprecated_since": "5.0.0", "replaced_by": "`CLUSTER REPLICAS`", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "ADMIN", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "arguments": [ { "name": "node-id", "type": "string" } ], "reply_schema": { "description": "a list of replica nodes replicating from the specified master node provided in the same format used by CLUSTER NODES", "type": "array", "items": { "type": "string", "description": "the serialized cluster configuration" } } } } redis-8.0.2/src/commands/cluster-slots.json000066400000000000000000000117041501533116600207410ustar00rootroot00000000000000{ "SLOTS": { "summary": "Returns the mapping of cluster slots to nodes.", "complexity": "O(N) where N is the total number of Cluster nodes", "group": "cluster", "since": "3.0.0", "arity": 2, "container": "CLUSTER", "function": "clusterCommand", "deprecated_since": "7.0.0", "replaced_by": "`CLUSTER SHARDS`", "doc_flags": [ "DEPRECATED" ], "history": [ [ "4.0.0", "Added node IDs." ], [ "7.0.0", "Added additional networking metadata field." ] ], "command_flags": [ "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "description": "nested list of slot ranges with networking information", "type": "array", "items": { "type": "array", "minItems": 3, "maxItems": 4294967295, "items": [ { "description": "start slot number", "type": "integer" }, { "description": "end slot number", "type": "integer" }, { "type": "array", "description": "Master node for the slot range", "minItems": 4, "maxItems": 4, "items": [ { "description": "endpoint description", "oneOf": [ { "description": "hostname or ip", "type": "string" }, { "description": "unknown type", "type": "null" } ] }, { "description": "port", "type": "integer" }, { "description": "node name", "type": "string" }, { "description": "array of node descriptions", "type": "object", "additionalProperties": false, "properties": { "hostname": { "type": "string" }, "ip": { "type": "string" } } } ] } ], "additionalItems": { "type": "array", "description": "Replica node for the slot range", "minItems": 4, "maxItems": 4, "items": [ { "description": "endpoint description", "oneOf": [ { "description": "hostname or ip", "type": "string" }, { "description": "unknown type", "type": "null" } ] }, { "description": "port", "type": "integer" }, { "description": "node name", "type": "string" }, { "description": "array of node descriptions", "type": "object", "additionalProperties": false, "properties": { "hostname": { "type": "string" }, "ip": { "type": "string" } } } ] } } } } } redis-8.0.2/src/commands/cluster.json000066400000000000000000000003231501533116600175720ustar00rootroot00000000000000{ "CLUSTER": { "summary": "A container for Redis Cluster commands.", "complexity": "Depends on subcommand.", "group": "cluster", "since": "3.0.0", "arity": -2 } } redis-8.0.2/src/commands/command-count.json000066400000000000000000000010671501533116600206630ustar00rootroot00000000000000{ "COUNT": { "summary": "Returns a count of commands.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": 2, "container": "COMMAND", "function": "commandCountCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "description": "Number of total commands in this Redis server.", "type": "integer" } } } redis-8.0.2/src/commands/command-docs.json000066400000000000000000000220041501533116600204550ustar00rootroot00000000000000{ "DOCS": { "summary": "Returns documentary information about one, multiple or all commands.", "complexity": "O(N) where N is the number of commands to look up", "group": "server", "since": "7.0.0", "arity": -2, "container": "COMMAND", "function": "commandDocsCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "reply_schema": { "description": "A map where each key is a command name, and each value is the documentary information", "type": "object", "additionalProperties": false, "patternProperties": { "^.*$": { "type": "object", "additionalProperties": false, "properties": { "summary": { "description": "short command description", "type": "string" }, "since": { "description": "the Redis version that added the command (or for module commands, the module version).", "type": "string" }, "group": { "description": "the functional group to which the command belongs", "oneOf": [ { "const": "bitmap" }, { "const": "cluster" }, { "const": "connection" }, { "const": "generic" }, { "const": "geo" }, { "const": "hash" }, { "const": "hyperloglog" }, { "const": "list" }, { "const": "module" }, { "const": "pubsub" }, { "const": "scripting" }, { "const": "sentinel" }, { "const": "server" }, { "const": "set" }, { "const": "sorted-set" }, { "const": "stream" }, { "const": "string" }, { "const": "transactions" } ] }, "complexity": { "description": "a short explanation about the command's time complexity.", "type": "string" }, "module": { "type": "string" }, "doc_flags": { "description": "an array of documentation flags", "type": "array", "items": { "oneOf": [ { "description": "the command is deprecated.", "const": "deprecated" }, { "description": "a system command that isn't meant to be called by users.", "const": "syscmd" } ] } }, "deprecated_since": { "description": "the Redis version that deprecated the command (or for module commands, the module version)", "type": "string" }, "replaced_by": { "description": "the alternative for a deprecated command.", "type": "string" }, "history": { "description": "an array of historical notes describing changes to the command's behavior or arguments.", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "The Redis version that the entry applies to." }, { "type": "string", "description": "The description of the change." } ] } }, "arguments": { "description": "an array of maps that describe the command's arguments.", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string" }, "type": { "type": "string" }, "display_text": { "type": "string" }, "key_spec_index": { "type": "integer" }, "token": { "type": "string" }, "summary": { "type": "string" }, "since": { "type": "string" }, "deprecated_since": { "type": "string" }, "flags": { "type": "array", "items": { "type": "string" } }, "arguments": { "type": "array" } } } }, "reply_schema": { "description": "command reply schema", "type": "object" }, "subcommands": { "description": "A map where each key is a subcommand, and each value is the documentary information", "$ref": "#" } } } } }, "arguments": [ { "name": "command-name", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/command-getkeys.json000066400000000000000000000020101501533116600211730ustar00rootroot00000000000000{ "GETKEYS": { "summary": "Extracts the key names from an arbitrary command.", "complexity": "O(N) where N is the number of arguments to the command", "group": "server", "since": "2.8.13", "arity": -3, "container": "COMMAND", "function": "commandGetKeysCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "description": "List of keys from the given Redis command.", "type": "array", "items": { "type": "string" }, "uniqueItems": true }, "arguments": [ { "name": "command", "type": "string" }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/command-getkeysandflags.json000066400000000000000000000031221501533116600227000ustar00rootroot00000000000000{ "GETKEYSANDFLAGS": { "summary": "Extracts the key names and access flags for an arbitrary command.", "complexity": "O(N) where N is the number of arguments to the command", "group": "server", "since": "7.0.0", "arity": -3, "container": "COMMAND", "function": "commandGetKeysAndFlagsCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "description": "List of keys from the given Redis command and their usage flags.", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Key name", "type": "string" }, { "description": "Set of key flags", "type": "array", "minItems": 1, "items": { "type": "string" } } ] } }, "arguments": [ { "name": "command", "type": "string" }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/command-help.json000066400000000000000000000012031501533116600204530ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "server", "since": "5.0.0", "arity": 2, "container": "COMMAND", "function": "commandHelpCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/command-info.json000066400000000000000000000254451501533116600204740ustar00rootroot00000000000000{ "INFO": { "summary": "Returns information about one, multiple or all commands.", "complexity": "O(N) where N is the number of commands to look up", "group": "server", "since": "2.8.13", "arity": -2, "container": "COMMAND", "function": "commandInfoCommand", "history": [ [ "7.0.0", "Allowed to be called with no argument to get info on all commands." ] ], "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "arguments": [ { "name": "command-name", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "type": "array", "items": { "oneOf": [ { "description": "command does not exist", "type": "null" }, { "description": "command info array output", "type": "array", "minItems": 10, "maxItems": 10, "items": [ { "description": "command name", "type": "string" }, { "description": "command arity", "type": "integer" }, { "description": "command flags", "type": "array", "items": { "description": "command flag", "type": "string" } }, { "description": "command first key index", "type": "integer" }, { "description": "command last key index", "type": "integer" }, { "description": "command key step index", "type": "integer" }, { "description": "command categories", "type": "array", "items": { "description": "command category", "type": "string" } }, { "description": "command tips", "type": "array", "items": { "description": "command tip", "type": "string" } }, { "description": "command key specs", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "notes": { "type": "string" }, "flags": { "type": "array", "items": { "type": "string" } }, "begin_search": { "type": "object", "additionalProperties": false, "properties": { "type": { "type": "string" }, "spec": { "anyOf": [ { "description": "unknown type, empty map", "type": "object", "additionalProperties": false }, { "description": "index type", "type": "object", "additionalProperties": false, "properties": { "index": { "type": "integer" } } }, { "description": "keyword type", "type": "object", "additionalProperties": false, "properties": { "keyword": { "type": "string" }, "startfrom": { "type": "integer" } } } ] } } }, "find_keys": { "type": "object", "additionalProperties": false, "properties": { "type": { "type": "string" }, "spec": { "anyOf": [ { "description": "unknown type", "type": "object", "additionalProperties": false }, { "description": "range type", "type": "object", "additionalProperties": false, "properties": { "lastkey": { "type": "integer" }, "keystep": { "type": "integer" }, "limit": { "type": "integer" } } }, { "description": "keynum type", "type": "object", "additionalProperties": false, "properties": { "keynumidx": { "type": "integer" }, "firstkey": { "type": "integer" }, "keystep": { "type": "integer" } } } ] } } } } } }, { "type": "array", "description": "subcommands" } ] } ] } } } } redis-8.0.2/src/commands/command-list.json000066400000000000000000000030051501533116600205000ustar00rootroot00000000000000{ "LIST": { "summary": "Returns a list of command names.", "complexity": "O(N) where N is the total number of Redis commands", "group": "server", "since": "7.0.0", "arity": -2, "container": "COMMAND", "function": "commandListCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "arguments": [ { "name": "filterby", "token": "FILTERBY", "type": "oneof", "optional": true, "arguments": [ { "name": "module-name", "type": "string", "token": "MODULE" }, { "name": "category", "type": "string", "token": "ACLCAT" }, { "name": "pattern", "type": "pattern", "token": "PATTERN" } ] } ], "reply_schema": { "type": "array", "items": { "description": "command name", "type": "string" }, "uniqueItems": true } } } redis-8.0.2/src/commands/command.json000066400000000000000000000010301501533116600175230ustar00rootroot00000000000000{ "COMMAND": { "summary": "Returns detailed information about all commands.", "complexity": "O(N) where N is the total number of Redis commands", "group": "server", "since": "2.8.13", "arity": -1, "function": "commandCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ] } } redis-8.0.2/src/commands/config-get.json000066400000000000000000000016641501533116600201440ustar00rootroot00000000000000{ "GET": { "summary": "Returns the effective values of configuration parameters.", "complexity": "O(N) when N is the number of configuration parameters provided", "group": "server", "since": "2.0.0", "arity": -3, "container": "CONFIG", "function": "configGetCommand", "history": [ [ "7.0.0", "Added the ability to pass multiple pattern parameters in one call" ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "reply_schema": { "type": "object", "additionalProperties": { "type": "string" } }, "arguments": [ { "name": "parameter", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/config-help.json000066400000000000000000000010511501533116600203030ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "server", "since": "5.0.0", "arity": 2, "container": "CONFIG", "function": "configHelpCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/config-resetstat.json000066400000000000000000000010701501533116600213720ustar00rootroot00000000000000{ "RESETSTAT": { "summary": "Resets the server's statistics.", "complexity": "O(1)", "group": "server", "since": "2.0.0", "arity": 2, "container": "CONFIG", "function": "configResetStatCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/config-rewrite.json000066400000000000000000000011021501533116600210310ustar00rootroot00000000000000{ "REWRITE": { "summary": "Persists the effective configuration to file.", "complexity": "O(1)", "group": "server", "since": "2.8.0", "arity": 2, "container": "CONFIG", "function": "configRewriteCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/config-set.json000066400000000000000000000023501501533116600201510ustar00rootroot00000000000000{ "SET": { "summary": "Sets configuration parameters in-flight.", "complexity": "O(N) when N is the number of configuration parameters provided", "group": "server", "since": "2.0.0", "arity": -4, "container": "CONFIG", "function": "configSetCommand", "history": [ [ "7.0.0", "Added the ability to set multiple parameters in one call." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "parameter", "type": "string" }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/config.json000066400000000000000000000003301501533116600173540ustar00rootroot00000000000000{ "CONFIG": { "summary": "A container for server configuration commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "2.0.0", "arity": -2 } } redis-8.0.2/src/commands/copy.json000066400000000000000000000045561501533116600170770ustar00rootroot00000000000000{ "COPY": { "summary": "Copies the value of a key to a new key.", "complexity": "O(N) worst case for collections, where N is the number of nested items. O(1) for string values.", "group": "generic", "since": "6.2.0", "arity": -3, "function": "copyCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "source", "type": "key", "key_spec_index": 0 }, { "name": "destination", "type": "key", "key_spec_index": 1 }, { "token": "DB", "name": "destination-db", "type": "integer", "optional": true }, { "name": "replace", "token": "REPLACE", "type": "pure-token", "optional": true } ], "reply_schema": { "oneOf": [ { "description": "source was copied", "const": 1 }, { "description": "source was not copied", "const": 0 } ] } } } redis-8.0.2/src/commands/dbsize.json000066400000000000000000000012021501533116600173660ustar00rootroot00000000000000{ "DBSIZE": { "summary": "Returns the number of keys in the database.", "complexity": "O(1)", "group": "server", "since": "1.0.0", "arity": 1, "function": "dbsizeCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:AGG_SUM" ], "reply_schema": { "type": "integer", "description": "The number of keys in the currently-selected database." } } } redis-8.0.2/src/commands/debug.json000066400000000000000000000006761501533116600172120ustar00rootroot00000000000000{ "DEBUG": { "summary": "A container for debugging commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "1.0.0", "arity": -2, "function": "debugCommand", "doc_flags": [ "SYSCMD" ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "PROTECTED" ] } } redis-8.0.2/src/commands/decr.json000066400000000000000000000024031501533116600170270ustar00rootroot00000000000000{ "DECR": { "summary": "Decrements the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 2, "function": "decrCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "The value of the key after decrementing it." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/decrby.json000066400000000000000000000025621501533116600173700ustar00rootroot00000000000000{ "DECRBY": { "summary": "Decrements a number from the integer value of a key. Uses 0 as initial value if the key doesn't exist.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 3, "function": "decrbyCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "The value of the key after decrementing it." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "decrement", "type": "integer" } ] } } redis-8.0.2/src/commands/del.json000066400000000000000000000030751501533116600166640ustar00rootroot00000000000000{ "DEL": { "summary": "Deletes one or more keys.", "complexity": "O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).", "group": "generic", "since": "1.0.0", "arity": -2, "function": "delCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "REQUEST_POLICY:MULTI_SHARD", "RESPONSE_POLICY:AGG_SUM" ], "key_specs": [ { "flags": [ "RM", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "the number of keys that were removed", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/discard.json000066400000000000000000000010331501533116600175210ustar00rootroot00000000000000{ "DISCARD": { "summary": "Discards a transaction.", "complexity": "O(N), when N is the number of queued commands", "group": "transactions", "since": "2.0.0", "arity": 1, "function": "discardCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "ALLOW_BUSY" ], "acl_categories": [ "TRANSACTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/dump.json000066400000000000000000000032411501533116600170600ustar00rootroot00000000000000{ "DUMP": { "summary": "Returns a serialized representation of the value stored at a key.", "complexity": "O(1) to access the key and additional O(N*M) to serialize it, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1).", "group": "generic", "since": "2.6.0", "arity": 2, "function": "dumpCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The serialized value.", "type": "string" }, { "description": "Key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/echo.json000066400000000000000000000011551501533116600170330ustar00rootroot00000000000000{ "ECHO": { "summary": "Returns the given string.", "complexity": "O(1)", "group": "connection", "since": "1.0.0", "arity": 2, "function": "echoCommand", "command_flags": [ "LOADING", "STALE", "FAST" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "description": "The given string", "type": "string" }, "arguments": [ { "name": "message", "type": "string" } ] } } redis-8.0.2/src/commands/eval.json000066400000000000000000000035441501533116600170500ustar00rootroot00000000000000{ "EVAL": { "summary": "Executes a server-side Lua script.", "complexity": "Depends on the script that is executed.", "group": "scripting", "since": "2.6.0", "arity": -3, "function": "evalCommand", "get_keys_function": "evalGetKeys", "command_flags": [ "NOSCRIPT", "SKIP_MONITOR", "MAY_REPLICATE", "NO_MANDATORY_KEYS", "STALE" ], "acl_categories": [ "SCRIPTING" ], "key_specs": [ { "notes": "We cannot tell how the keys will be used so we assume the worst, RW and UPDATE", "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "arguments": [ { "name": "script", "type": "string" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "optional": true, "multiple": true }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "description": "Return value depends on the script that is executed" } } } redis-8.0.2/src/commands/eval_ro.json000066400000000000000000000035161501533116600175470ustar00rootroot00000000000000{ "EVAL_RO": { "summary": "Executes a read-only server-side Lua script.", "complexity": "Depends on the script that is executed.", "group": "scripting", "since": "7.0.0", "arity": -3, "function": "evalRoCommand", "get_keys_function": "evalGetKeys", "command_flags": [ "NOSCRIPT", "SKIP_MONITOR", "NO_MANDATORY_KEYS", "STALE", "READONLY" ], "acl_categories": [ "SCRIPTING" ], "key_specs": [ { "notes": "We cannot tell how the keys will be used so we assume the worst, RO and ACCESS", "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "arguments": [ { "name": "script", "type": "string" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "optional":true, "multiple": true }, { "name": "arg", "type": "string", "optional":true, "multiple": true } ], "reply_schema": { "description": "Return value depends on the script that is executed" } } } redis-8.0.2/src/commands/evalsha.json000066400000000000000000000034141501533116600175400ustar00rootroot00000000000000{ "EVALSHA": { "summary": "Executes a server-side Lua script by SHA1 digest.", "complexity": "Depends on the script that is executed.", "group": "scripting", "since": "2.6.0", "arity": -3, "function": "evalShaCommand", "get_keys_function": "evalGetKeys", "command_flags": [ "NOSCRIPT", "SKIP_MONITOR", "MAY_REPLICATE", "NO_MANDATORY_KEYS", "STALE" ], "acl_categories": [ "SCRIPTING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "arguments": [ { "name": "sha1", "type": "string" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "optional": true, "multiple": true }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "description": "Return value depends on the script that is executed" } } } redis-8.0.2/src/commands/evalsha_ro.json000066400000000000000000000033661501533116600202460ustar00rootroot00000000000000{ "EVALSHA_RO": { "summary": "Executes a read-only server-side Lua script by SHA1 digest.", "complexity": "Depends on the script that is executed.", "group": "scripting", "since": "7.0.0", "arity": -3, "function": "evalShaRoCommand", "get_keys_function": "evalGetKeys", "command_flags": [ "NOSCRIPT", "SKIP_MONITOR", "NO_MANDATORY_KEYS", "STALE", "READONLY" ], "acl_categories": [ "SCRIPTING" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "arguments": [ { "name": "sha1", "type": "string" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "optional":true, "multiple": true }, { "name": "arg", "type": "string", "optional":true, "multiple": true } ], "reply_schema": { "description": "Return value depends on the script that is executed" } } } redis-8.0.2/src/commands/exec.json000066400000000000000000000016031501533116600170370ustar00rootroot00000000000000{ "EXEC": { "summary": "Executes all commands in a transaction.", "complexity": "Depends on commands in the transaction", "group": "transactions", "since": "1.2.0", "arity": 1, "function": "execCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "SKIP_SLOWLOG" ], "acl_categories": [ "TRANSACTION" ], "reply_schema": { "oneOf": [ { "description": "Each element being the reply to each of the commands in the atomic transaction.", "type": "array" }, { "description": "The transaction was aborted because a `WATCH`ed key was touched", "type": "null" } ] } } } redis-8.0.2/src/commands/exists.json000066400000000000000000000027401501533116600174350ustar00rootroot00000000000000{ "EXISTS": { "summary": "Determines whether one or more keys exist.", "complexity": "O(N) where N is the number of keys to check.", "group": "generic", "since": "1.0.0", "arity": -2, "function": "existsCommand", "history": [ [ "3.0.3", "Accepts multiple `key` arguments." ] ], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "REQUEST_POLICY:MULTI_SHARD", "RESPONSE_POLICY:AGG_SUM" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Number of keys that exist from those specified as arguments.", "type": "integer" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/expire.json000066400000000000000000000050241501533116600174100ustar00rootroot00000000000000{ "EXPIRE": { "summary": "Sets the expiration time of a key in seconds.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": -3, "function": "expireCommand", "history": [ [ "7.0.0", "Added options: `NX`, `XX`, `GT` and `LT`." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The timeout was not set. e.g. key doesn't exist, or operation skipped due to the provided arguments.", "const": 0 }, { "description": "The timeout was set.", "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "seconds", "type": "integer" }, { "name": "condition", "type": "oneof", "optional": true, "since": "7.0.0", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] } ] } } redis-8.0.2/src/commands/expireat.json000066400000000000000000000050551501533116600177410ustar00rootroot00000000000000{ "EXPIREAT": { "summary": "Sets the expiration time of a key to a Unix timestamp.", "complexity": "O(1)", "group": "generic", "since": "1.2.0", "arity": -3, "function": "expireatCommand", "history": [ [ "7.0.0", "Added options: `NX`, `XX`, `GT` and `LT`." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": 1, "description": "The timeout was set." }, { "const": 0, "description": "The timeout was not set. e.g. key doesn't exist, or operation skipped due to the provided arguments." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "unix-time-seconds", "type": "unix-time" }, { "name": "condition", "type": "oneof", "optional": true, "since": "7.0.0", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] } ] } } redis-8.0.2/src/commands/expiretime.json000066400000000000000000000031231501533116600202650ustar00rootroot00000000000000{ "EXPIRETIME": { "summary": "Returns the expiration time of a key as a Unix timestamp.", "complexity": "O(1)", "group": "generic", "since": "7.0.0", "arity": 2, "function": "expiretimeCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "integer", "description": "Expiration Unix timestamp in seconds.", "minimum": 0 }, { "const": -1, "description": "The key exists but has no associated expiration time." }, { "const": -2, "description": "The key does not exist." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/failover.json000066400000000000000000000027041501533116600177250ustar00rootroot00000000000000{ "FAILOVER": { "summary": "Starts a coordinated failover from a server to one of its replicas.", "complexity": "O(1)", "group": "server", "since": "6.2.0", "arity": -1, "function": "failoverCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "STALE" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "target", "token": "TO", "type": "block", "optional": true, "arguments": [ { "name": "host", "type": "string" }, { "name": "port", "type": "integer" }, { "token": "FORCE", "name": "force", "type": "pure-token", "optional": true } ] }, { "token": "ABORT", "name": "abort", "type": "pure-token", "optional": true }, { "token": "TIMEOUT", "name": "milliseconds", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/fcall.json000066400000000000000000000035411501533116600171770ustar00rootroot00000000000000{ "FCALL": { "summary": "Invokes a function.", "complexity": "Depends on the function that is executed.", "group": "scripting", "since": "7.0.0", "arity": -3, "function": "fcallCommand", "get_keys_function": "functionGetKeys", "command_flags": [ "NOSCRIPT", "SKIP_MONITOR", "MAY_REPLICATE", "NO_MANDATORY_KEYS", "STALE" ], "acl_categories": [ "SCRIPTING" ], "key_specs": [ { "notes": "We cannot tell how the keys will be used so we assume the worst, RW and UPDATE", "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "arguments": [ { "name": "function", "type": "string" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "optional": true, "multiple": true }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "description": "Return value depends on the function that is executed" } } } redis-8.0.2/src/commands/fcall_ro.json000066400000000000000000000035151501533116600177000ustar00rootroot00000000000000{ "FCALL_RO": { "summary": "Invokes a read-only function.", "complexity": "Depends on the function that is executed.", "group": "scripting", "since": "7.0.0", "arity": -3, "function": "fcallroCommand", "get_keys_function": "functionGetKeys", "command_flags": [ "NOSCRIPT", "SKIP_MONITOR", "NO_MANDATORY_KEYS", "STALE", "READONLY" ], "acl_categories": [ "SCRIPTING" ], "key_specs": [ { "notes": "We cannot tell how the keys will be used so we assume the worst, RO and ACCESS", "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "arguments": [ { "name": "function", "type": "string" }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "optional": true, "multiple": true }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "description": "Return value depends on the function that is executed" } } } redis-8.0.2/src/commands/flushall.json000066400000000000000000000027511501533116600177320ustar00rootroot00000000000000{ "FLUSHALL": { "summary": "Removes all keys from all databases.", "complexity": "O(N) where N is the total number of keys in all databases", "group": "server", "since": "1.0.0", "arity": -1, "function": "flushallCommand", "history": [ [ "4.0.0", "Added the `ASYNC` flushing mode modifier." ], [ "6.2.0", "Added the `SYNC` flushing mode modifier." ] ], "command_flags": [ "WRITE" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "flush-type", "type": "oneof", "optional": true, "arguments": [ { "name": "async", "type": "pure-token", "token": "ASYNC", "since": "4.0.0" }, { "name": "sync", "type": "pure-token", "token": "SYNC", "since": "6.2.0" } ] } ] } } redis-8.0.2/src/commands/flushdb.json000066400000000000000000000027571501533116600175550ustar00rootroot00000000000000{ "FLUSHDB": { "summary": "Remove all keys from the current database.", "complexity": "O(N) where N is the number of keys in the selected database", "group": "server", "since": "1.0.0", "arity": -1, "function": "flushdbCommand", "history": [ [ "4.0.0", "Added the `ASYNC` flushing mode modifier." ], [ "6.2.0", "Added the `SYNC` flushing mode modifier." ] ], "command_flags": [ "WRITE" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "flush-type", "type": "oneof", "optional": true, "arguments": [ { "name": "async", "type": "pure-token", "token": "ASYNC", "since": "4.0.0" }, { "name": "sync", "type": "pure-token", "token": "SYNC", "since": "6.2.0" } ] } ] } } redis-8.0.2/src/commands/function-delete.json000066400000000000000000000013351501533116600212020ustar00rootroot00000000000000{ "DELETE": { "summary": "Deletes a library and its functions.", "complexity": "O(1)", "group": "scripting", "since": "7.0.0", "arity": 3, "container": "FUNCTION", "function": "functionDeleteCommand", "command_flags": [ "NOSCRIPT", "WRITE" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ { "name": "library-name", "type": "string" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/function-dump.json000066400000000000000000000010571501533116600207060ustar00rootroot00000000000000{ "DUMP": { "summary": "Dumps all libraries into a serialized binary payload.", "complexity": "O(N) where N is the number of functions", "group": "scripting", "since": "7.0.0", "arity": 2, "container": "FUNCTION", "function": "functionDumpCommand", "command_flags": [ "NOSCRIPT" ], "acl_categories": [ "SCRIPTING" ], "reply_schema": { "description": "the serialized payload", "type": "string" } } } redis-8.0.2/src/commands/function-flush.json000066400000000000000000000022571501533116600210650ustar00rootroot00000000000000{ "FLUSH": { "summary": "Deletes all libraries and functions.", "complexity": "O(N) where N is the number of functions deleted", "group": "scripting", "since": "7.0.0", "arity": -2, "container": "FUNCTION", "function": "functionFlushCommand", "command_flags": [ "NOSCRIPT", "WRITE" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ { "name": "flush-type", "type": "oneof", "optional": true, "arguments": [ { "name": "async", "type": "pure-token", "token": "ASYNC" }, { "name": "sync", "type": "pure-token", "token": "SYNC" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/function-help.json000066400000000000000000000011571501533116600206720ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "scripting", "since": "7.0.0", "arity": 2, "container": "FUNCTION", "function": "functionHelpCommand", "command_flags": [ "LOADING", "STALE" ], "acl_categories": [ "SCRIPTING" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/function-kill.json000066400000000000000000000011321501533116600206660ustar00rootroot00000000000000{ "KILL": { "summary": "Terminates a function during execution.", "complexity": "O(1)", "group": "scripting", "since": "7.0.0", "arity": 2, "container": "FUNCTION", "function": "functionKillCommand", "command_flags": [ "NOSCRIPT", "ALLOW_BUSY" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ONE_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/function-list.json000066400000000000000000000061651501533116600207210ustar00rootroot00000000000000{ "LIST": { "summary": "Returns information about all libraries.", "complexity": "O(N) where N is the number of functions", "group": "scripting", "since": "7.0.0", "arity": -2, "container": "FUNCTION", "function": "functionListCommand", "command_flags": [ "NOSCRIPT" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "acl_categories": [ "SCRIPTING" ], "reply_schema": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "library_name": { "description": " the name of the library", "type": "string" }, "engine": { "description": "the engine of the library", "type": "string" }, "functions": { "description": "the list of functions in the library", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "description": "the name of the function", "type": "string" }, "description": { "description": "the function's description", "oneOf": [ { "type": "null" }, { "type": "string" } ] }, "flags": { "description": "an array of function flags", "type": "array", "items": { "type": "string" } } } } }, "library_code": { "description": "the library's source code (when given the WITHCODE modifier)", "type": "string" } } } }, "arguments": [ { "name": "library-name-pattern", "type": "string", "token": "LIBRARYNAME", "optional": true }, { "name": "withcode", "type": "pure-token", "token": "WITHCODE", "optional": true } ] } } redis-8.0.2/src/commands/function-load.json000066400000000000000000000017711501533116600206630ustar00rootroot00000000000000{ "LOAD": { "summary": "Creates a library.", "complexity": "O(1) (considering compilation time is redundant)", "group": "scripting", "since": "7.0.0", "arity": -3, "container": "FUNCTION", "function": "functionLoadCommand", "command_flags": [ "NOSCRIPT", "WRITE", "DENYOOM" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ { "name": "replace", "type": "pure-token", "token": "REPLACE", "optional": true }, { "name": "function-code", "type": "string" } ], "reply_schema": { "description": "The library name that was loaded", "type": "string" } } } redis-8.0.2/src/commands/function-restore.json000066400000000000000000000027561501533116600214330ustar00rootroot00000000000000{ "RESTORE": { "summary": "Restores all libraries from a payload.", "complexity": "O(N) where N is the number of functions on the payload", "group": "scripting", "since": "7.0.0", "arity": -3, "container": "FUNCTION", "function": "functionRestoreCommand", "command_flags": [ "NOSCRIPT", "WRITE", "DENYOOM" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ { "name": "serialized-value", "type": "string" }, { "name": "policy", "type": "oneof", "optional": true, "arguments": [ { "name": "flush", "type": "pure-token", "token": "FLUSH" }, { "name": "append", "type": "pure-token", "token": "APPEND" }, { "name": "replace", "type": "pure-token", "token": "REPLACE" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/function-stats.json000066400000000000000000000063161501533116600211020ustar00rootroot00000000000000{ "STATS": { "summary": "Returns information about a function during execution.", "complexity": "O(1)", "group": "scripting", "since": "7.0.0", "arity": 2, "container": "FUNCTION", "function": "functionStatsCommand", "command_flags": [ "NOSCRIPT", "ALLOW_BUSY" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "type": "object", "additionalProperties": false, "properties": { "running_script": { "description": "information about the running script.", "oneOf": [ { "description": "If there's no in-flight function", "type": "null" }, { "description": "a map with the information about the running script", "type": "object", "additionalProperties": false, "properties": { "name": { "description": "the name of the function.", "type": "string" }, "command": { "description": "the command and arguments used for invoking the function.", "type": "array", "items": { "type": "string" } }, "duration_ms": { "description": "the function's runtime duration in milliseconds.", "type": "integer" } } } ] }, "engines": { "description": "A map when each entry in the map represent a single engine.", "type": "object", "patternProperties": { "^.*$": { "description": "Engine map contains statistics about the engine", "type": "object", "additionalProperties": false, "properties": { "libraries_count": { "description": "number of libraries", "type": "integer" }, "functions_count": { "description": "number of functions", "type": "integer" } } } } } } } } } redis-8.0.2/src/commands/function.json000066400000000000000000000003211501533116600177340ustar00rootroot00000000000000{ "FUNCTION": { "summary": "A container for function commands.", "complexity": "Depends on subcommand.", "group": "scripting", "since": "7.0.0", "arity": -2 } } redis-8.0.2/src/commands/geoadd.json000066400000000000000000000055471501533116600173510ustar00rootroot00000000000000{ "GEOADD": { "summary": "Adds one or more members to a geospatial index. The key is created if it doesn't exist.", "complexity": "O(log(N)) for each item added, where N is the number of elements in the sorted set.", "group": "geo", "since": "3.2.0", "arity": -5, "function": "geoaddCommand", "history": [ [ "6.2.0", "Added the `CH`, `NX` and `XX` options." ] ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "condition", "type": "oneof", "optional": true, "since": "6.2.0", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" } ] }, { "name": "change", "token": "CH", "type": "pure-token", "optional": true, "since": "6.2.0" }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "longitude", "type": "double" }, { "name": "latitude", "type": "double" }, { "name": "member", "type": "string" } ] } ], "reply_schema": { "description": "When used without optional arguments, the number of elements added to the sorted set (excluding score updates). If the CH option is specified, the number of elements that were changed (added or updated).", "type": "integer" } } } redis-8.0.2/src/commands/geodist.json000066400000000000000000000047431501533116600175610ustar00rootroot00000000000000{ "GEODIST": { "summary": "Returns the distance between two members of a geospatial index.", "complexity": "O(1)", "group": "geo", "since": "3.2.0", "arity": -4, "function": "geodistCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member1", "type": "string" }, { "name": "member2", "type": "string" }, { "name": "unit", "type": "oneof", "optional": true, "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] } ], "reply_schema": { "oneOf": [ { "description": "one or both of elements are missing", "type": "null" }, { "description": "distance as a double (represented as a string) in the specified units", "type": "string", "pattern": "^[0-9]*(.[0-9]*)?$" } ] } } } redis-8.0.2/src/commands/geohash.json000066400000000000000000000027421501533116600175360ustar00rootroot00000000000000{ "GEOHASH": { "summary": "Returns members from a geospatial index as geohash strings.", "complexity": "O(1) for each member requested.", "group": "geo", "since": "3.2.0", "arity": -2, "function": "geohashCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true, "optional": true } ], "reply_schema": { "description": "An array where each element is the Geohash corresponding to each member name passed as argument to the command.", "type": "array", "items": { "type": "string" } } } } redis-8.0.2/src/commands/geopos.json000066400000000000000000000043521501533116600174130ustar00rootroot00000000000000{ "GEOPOS": { "summary": "Returns the longitude and latitude of members from a geospatial index.", "complexity": "O(1) for each member requested.", "group": "geo", "since": "3.2.0", "arity": -2, "function": "geoposCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true, "optional": true } ], "reply_schema": { "description": "An array where each element is a two elements array representing longitude and latitude (x,y) of each member name passed as argument to the command", "type": "array", "items": { "oneOf": [ { "description": "Element does not exist", "type": "null" }, { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Latitude (x)", "type": "number" }, { "description": "Longitude (y)", "type": "number" } ] } ] } } } } redis-8.0.2/src/commands/georadius.json000066400000000000000000000213371501533116600201030ustar00rootroot00000000000000{ "GEORADIUS": { "summary": "Queries a geospatial index for members within a distance from a coordinate, optionally stores the result.", "complexity": "O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "group": "geo", "since": "3.2.0", "arity": -6, "function": "georadiusCommand", "get_keys_function": "georadiusGetKeys", "history": [ [ "6.2.0", "Added the `ANY` option for `COUNT`." ], [ "7.0.0", "Added support for uppercase unit names." ] ], "deprecated_since": "6.2.0", "replaced_by": "`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "UPDATE" ], "begin_search": { "keyword": { "keyword": "STORE", "startfrom": 6 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "UPDATE" ], "begin_search": { "keyword": { "keyword": "STOREDIST", "startfrom": 6 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "longitude", "type": "double" }, { "name": "latitude", "type": "double" }, { "name": "radius", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] }, { "name": "withcoord", "token": "WITHCOORD", "type": "pure-token", "optional": true }, { "name": "withdist", "token": "WITHDIST", "type": "pure-token", "optional": true }, { "name": "withhash", "token": "WITHHASH", "type": "pure-token", "optional": true }, { "name": "count-block", "type": "block", "optional": true, "arguments": [ { "token": "COUNT", "name": "count", "type": "integer" }, { "name": "any", "token": "ANY", "type": "pure-token", "optional": true, "since": "6.2.0" } ] }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] }, { "name": "store", "type": "oneof", "optional": true, "arguments": [ { "token": "STORE", "name": "storekey", "display": "key", "type": "key", "key_spec_index": 1 }, { "token": "STOREDIST", "name": "storedistkey", "display": "key", "type": "key", "key_spec_index": 2 } ] } ], "reply_schema": { "description": "Array of matched members information", "anyOf": [ { "description": "If no WITH* option is specified, array of matched members names", "type": "array", "items": { "description": "name", "type": "string" } }, { "type": "array", "items": { "type": "array", "minItems": 1, "maxItems": 4, "items": [ { "description": "Matched member name", "type": "string" } ], "additionalItems": { "oneOf": [ { "description": "If WITHDIST option is specified, the distance from the center as a floating point number, in the same unit specified in the radius", "type": "string" }, { "description": "If WITHHASH option is specified, the geohash integer", "type": "integer" }, { "description": "If WITHCOORD option is specified, the coordinates as a two items x,y array (longitude,latitude)", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "latitude (x)", "type": "number" }, { "description": "longitude (y)", "type": "number" } ] } ] } } }, { "description": "number of items stored in key", "type": "integer" } ] } } } redis-8.0.2/src/commands/georadius_ro.json000066400000000000000000000154051501533116600206020ustar00rootroot00000000000000{ "GEORADIUS_RO": { "summary": "Returns members from a geospatial index that are within a distance from a coordinate.", "complexity": "O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "group": "geo", "since": "3.2.10", "arity": -6, "function": "georadiusroCommand", "history": [ [ "6.2.0", "Added the `ANY` option for `COUNT`." ], [ "7.0.0", "Added support for uppercase unit names." ] ], "deprecated_since": "6.2.0", "replaced_by": "`GEOSEARCH` with the `BYRADIUS` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "longitude", "type": "double" }, { "name": "latitude", "type": "double" }, { "name": "radius", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] }, { "name": "withcoord", "token": "WITHCOORD", "type": "pure-token", "optional": true }, { "name": "withdist", "token": "WITHDIST", "type": "pure-token", "optional": true }, { "name": "withhash", "token": "WITHHASH", "type": "pure-token", "optional": true }, { "name": "count-block", "type": "block", "optional": true, "arguments": [ { "token": "COUNT", "name": "count", "type": "integer" }, { "name": "any", "token": "ANY", "type": "pure-token", "optional": true, "since": "6.2.0" } ] }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] } ], "reply_schema": { "description": "Array of matched members information", "anyOf": [ { "description": "If no WITH* option is specified, array of matched members names", "type": "array", "items": { "description": "name", "type": "string" } }, { "type": "array", "items": { "type": "array", "minItems": 1, "maxItems": 4, "items": [ { "description": "Matched member name", "type": "string" } ], "additionalItems": { "oneOf": [ { "description": "If WITHDIST option is specified, the distance from the center as a floating point number, in the same unit specified in the radius", "type": "string" }, { "description": "If WITHHASH option is specified, the geohash integer", "type": "integer" }, { "description": "If WITHCOORD option is specified, the coordinates as a two items x,y array (longitude,latitude)", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "latitude (x)", "type": "number" }, { "description": "longitude (y)", "type": "number" } ] } ] } } } ] } } } redis-8.0.2/src/commands/georadiusbymember.json000066400000000000000000000211561501533116600216250ustar00rootroot00000000000000{ "GEORADIUSBYMEMBER": { "summary": "Queries a geospatial index for members within a distance from a member, optionally stores the result.", "complexity": "O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "group": "geo", "since": "3.2.0", "arity": -5, "function": "georadiusbymemberCommand", "get_keys_function": "georadiusGetKeys", "history": [ [ "6.2.0", "Added the `ANY` option for `COUNT`." ], [ "7.0.0", "Added support for uppercase unit names." ] ], "deprecated_since": "6.2.0", "replaced_by": "`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` and `FROMMEMBER` arguments", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "UPDATE" ], "begin_search": { "keyword": { "keyword": "STORE", "startfrom": 5 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "UPDATE" ], "begin_search": { "keyword": { "keyword": "STOREDIST", "startfrom": 5 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string" }, { "name": "radius", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] }, { "name": "withcoord", "token": "WITHCOORD", "type": "pure-token", "optional": true }, { "name": "withdist", "token": "WITHDIST", "type": "pure-token", "optional": true }, { "name": "withhash", "token": "WITHHASH", "type": "pure-token", "optional": true }, { "name": "count-block", "type": "block", "optional": true, "arguments": [ { "token": "COUNT", "name": "count", "type": "integer" }, { "name": "any", "token": "ANY", "type": "pure-token", "optional": true } ] }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] }, { "name": "store", "type": "oneof", "optional": true, "arguments": [ { "token": "STORE", "name": "storekey", "display": "key", "type": "key", "key_spec_index": 1 }, { "token": "STOREDIST", "name": "storedistkey", "display": "key", "type": "key", "key_spec_index": 2 } ] } ], "reply_schema": { "description": "Array of matched members information", "anyOf": [ { "description": "If no WITH* option is specified, array of matched members names", "type": "array", "items": { "description": "name", "type": "string" } }, { "type": "array", "items": { "type": "array", "minItems": 1, "maxItems": 4, "items": [ { "description": "Matched member name", "type": "string" } ], "additionalItems": { "oneOf": [ { "description": "If WITHDIST option is specified, the distance from the center as a floating point number, in the same unit specified in the radius", "type": "string" }, { "description": "If WITHHASH option is specified, the geohash integer", "type": "integer" }, { "description": "If WITHCOORD option is specified, the coordinates as a two items x,y array (longitude,latitude)", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "latitude (x)", "type": "number" }, { "description": "longitude (y)", "type": "number" } ] } ] } } }, { "description": "number of items stored in key", "type": "integer" } ] } } } redis-8.0.2/src/commands/georadiusbymember_ro.json000066400000000000000000000152251501533116600223250ustar00rootroot00000000000000{ "GEORADIUSBYMEMBER_RO": { "summary": "Returns members from a geospatial index that are within a distance from a member.", "complexity": "O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.", "group": "geo", "since": "3.2.10", "arity": -5, "function": "georadiusbymemberroCommand", "history": [ [ "6.2.0", "Added the `ANY` option for `COUNT`." ], [ "7.0.0", "Added support for uppercase unit names." ] ], "deprecated_since": "6.2.0", "replaced_by": "`GEOSEARCH` with the `BYRADIUS` and `FROMMEMBER` arguments", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string" }, { "name": "radius", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] }, { "name": "withcoord", "token": "WITHCOORD", "type": "pure-token", "optional": true }, { "name": "withdist", "token": "WITHDIST", "type": "pure-token", "optional": true }, { "name": "withhash", "token": "WITHHASH", "type": "pure-token", "optional": true }, { "name": "count-block", "type": "block", "optional": true, "arguments": [ { "token": "COUNT", "name": "count", "type": "integer" }, { "name": "any", "token": "ANY", "type": "pure-token", "optional": true } ] }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] } ], "reply_schema": { "description": "Array of matched members information", "anyOf": [ { "description": "If no WITH* option is specified, array of matched members names", "type": "array", "items": { "description": "name", "type": "string" } }, { "type": "array", "items": { "type": "array", "minItems": 1, "maxItems": 4, "items": [ { "description": "Matched member name", "type": "string" } ], "additionalItems": { "oneOf": [ { "description": "If WITHDIST option is specified, the distance from the center as a floating point number, in the same unit specified in the radius", "type": "string" }, { "description": "If WITHHASH option is specified, the geohash integer", "type": "integer" }, { "description": "If WITHCOORD option is specified, the coordinates as a two items x,y array (longitude,latitude)", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "latitude (x)", "type": "number" }, { "description": "longitude (y)", "type": "number" } ] } ] } } } ] } } } redis-8.0.2/src/commands/geosearch.json000066400000000000000000000233041501533116600200550ustar00rootroot00000000000000{ "GEOSEARCH": { "summary": "Queries a geospatial index for members inside an area of a box or a circle.", "complexity": "O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape", "group": "geo", "since": "6.2.0", "arity": -7, "function": "geosearchCommand", "history": [ [ "7.0.0", "Added support for uppercase unit names." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "from", "type": "oneof", "arguments": [ { "token": "FROMMEMBER", "name": "member", "type": "string" }, { "token": "FROMLONLAT", "name": "fromlonlat", "type": "block", "arguments": [ { "name": "longitude", "type": "double" }, { "name": "latitude", "type": "double" } ] } ] }, { "name": "by", "type": "oneof", "arguments": [ { "name": "circle", "type": "block", "arguments": [ { "token": "BYRADIUS", "name": "radius", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] } ] }, { "name": "box", "type": "block", "arguments": [ { "token": "BYBOX", "name": "width", "type": "double" }, { "name": "height", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] } ] } ] }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] }, { "name": "count-block", "type": "block", "optional": true, "arguments": [ { "token": "COUNT", "name": "count", "type": "integer" }, { "name": "any", "token": "ANY", "type": "pure-token", "optional": true } ] }, { "name": "withcoord", "token": "WITHCOORD", "type": "pure-token", "optional": true }, { "name": "withdist", "token": "WITHDIST", "type": "pure-token", "optional": true }, { "name": "withhash", "token": "WITHHASH", "type": "pure-token", "optional": true } ], "reply_schema": { "description": "Array of matched members information", "anyOf": [ { "description": "If no WITH* option is specified, array of matched members names", "type": "array", "items": { "description": "name", "type": "string" } }, { "type": "array", "items": { "type": "array", "minItems": 1, "maxItems": 4, "items": [ { "description": "Matched member name", "type": "string" } ], "additionalItems": { "oneOf": [ { "description": "If WITHDIST option is specified, the distance from the center as a floating point number, in the same unit specified in the radius", "type": "string" }, { "description": "If WITHHASH option is specified, the geohash integer", "type": "integer" }, { "description": "If WITHCOORD option is specified, the coordinates as a two items x,y array (longitude,latitude)", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "latitude (x)", "type": "number" }, { "description": "longitude (y)", "type": "number" } ] } ] } } } ] } } } redis-8.0.2/src/commands/geosearchstore.json000066400000000000000000000173031501533116600211340ustar00rootroot00000000000000{ "GEOSEARCHSTORE": { "summary": "Queries a geospatial index for members inside an area of a box or a circle, optionally stores the result.", "complexity": "O(N+log(M)) where N is the number of elements in the grid-aligned bounding box area around the shape provided as the filter and M is the number of items inside the shape", "group": "geo", "since": "6.2.0", "arity": -8, "function": "geosearchstoreCommand", "history": [ [ "7.0.0", "Added support for uppercase unit names." ] ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "GEO" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "source", "type": "key", "key_spec_index": 1 }, { "name": "from", "type": "oneof", "arguments": [ { "token": "FROMMEMBER", "name": "member", "type": "string" }, { "token": "FROMLONLAT", "name": "fromlonlat", "type": "block", "arguments": [ { "name": "longitude", "type": "double" }, { "name": "latitude", "type": "double" } ] } ] }, { "name": "by", "type": "oneof", "arguments": [ { "name": "circle", "type": "block", "arguments": [ { "token": "BYRADIUS", "name": "radius", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] } ] }, { "name": "box", "type": "block", "arguments": [ { "token": "BYBOX", "name": "width", "type": "double" }, { "name": "height", "type": "double" }, { "name": "unit", "type": "oneof", "arguments": [ { "name": "m", "type": "pure-token", "token": "m" }, { "name": "km", "type": "pure-token", "token": "km" }, { "name": "ft", "type": "pure-token", "token": "ft" }, { "name": "mi", "type": "pure-token", "token": "mi" } ] } ] } ] }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] }, { "name": "count-block", "type": "block", "optional": true, "arguments": [ { "token": "COUNT", "name": "count", "type": "integer" }, { "name": "any", "token": "ANY", "type": "pure-token", "optional": true } ] }, { "name": "storedist", "token": "STOREDIST", "type": "pure-token", "optional": true } ], "reply_schema": { "description": "the number of elements in the resulting set", "type": "integer" } } } redis-8.0.2/src/commands/get.json000066400000000000000000000025261501533116600166770ustar00rootroot00000000000000{ "GET": { "summary": "Returns the string value of a key.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 2, "function": "getCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The value of the key.", "type": "string" }, { "description": "Key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/getbit.json000066400000000000000000000025671501533116600174030ustar00rootroot00000000000000{ "GETBIT": { "summary": "Returns a bit value by offset.", "complexity": "O(1)", "group": "bitmap", "since": "2.2.0", "arity": 3, "function": "getbitCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The bit value stored at offset.", "oneOf": [ { "const": 0 }, { "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "offset", "type": "integer" } ] } } redis-8.0.2/src/commands/getdel.json000066400000000000000000000026221501533116600173610ustar00rootroot00000000000000{ "GETDEL": { "summary": "Returns the string value of a key after deleting the key.", "complexity": "O(1)", "group": "string", "since": "6.2.0", "arity": 2, "function": "getdelCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The value of the key.", "type": "string" }, { "description": "The key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/getex.json000066400000000000000000000050161501533116600172310ustar00rootroot00000000000000{ "GETEX": { "summary": "Returns the string value of a key after setting its expiration time.", "complexity": "O(1)", "group": "string", "since": "6.2.0", "arity": -2, "function": "getexCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "notes": "RW and UPDATE because it changes the TTL", "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The value of the key.", "type": "string" }, { "description": "Key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "expiration", "type": "oneof", "optional": true, "arguments": [ { "name": "seconds", "type": "integer", "token": "EX" }, { "name": "milliseconds", "type": "integer", "token": "PX" }, { "name": "unix-time-seconds", "type": "unix-time", "token": "EXAT" }, { "name": "unix-time-milliseconds", "type": "unix-time", "token": "PXAT" }, { "name": "persist", "type": "pure-token", "token": "PERSIST" } ] } ] } } redis-8.0.2/src/commands/getrange.json000066400000000000000000000031731501533116600177130ustar00rootroot00000000000000{ "GETRANGE": { "summary": "Returns a substring of the string stored at a key.", "complexity": "O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.", "group": "string", "since": "2.4.0", "arity": 4, "function": "getrangeCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "string", "description": "The substring of the string value stored at key, determined by the offsets start and end (both are inclusive)." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "integer" }, { "name": "end", "type": "integer" } ] } } redis-8.0.2/src/commands/getset.json000066400000000000000000000032761501533116600174160ustar00rootroot00000000000000{ "GETSET": { "summary": "Returns the previous string value of a key after setting it to a new value.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 3, "function": "getsetCommand", "deprecated_since": "6.2.0", "replaced_by": "`SET` with the `!GET` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The old value stored at the key.", "type": "string" }, { "description": "The key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/hdel.json000066400000000000000000000030171501533116600170300ustar00rootroot00000000000000{ "HDEL": { "summary": "Deletes one or more fields and their values from a hash. Deletes the hash if no fields remain.", "complexity": "O(N) where N is the number of fields to be removed.", "group": "hash", "since": "2.0.0", "arity": -3, "function": "hdelCommand", "history": [ [ "2.4.0", "Accepts multiple `field` arguments." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "The number of fields that were removed from the hash." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/hello.json000066400000000000000000000065131501533116600172230ustar00rootroot00000000000000{ "HELLO": { "summary": "Handshakes with the Redis server.", "complexity": "O(1)", "group": "connection", "since": "6.0.0", "arity": -1, "function": "helloCommand", "history": [ [ "6.2.0", "`protover` made optional; when called without arguments the command reports the current connection's context." ] ], "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "NO_AUTH", "SENTINEL", "ALLOW_BUSY" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "type": "object", "additionalProperties": false, "properties": { "server": { "type": "string" }, "version": { "type": "string" }, "proto": { "const": 3 }, "id": { "type": "integer" }, "mode": { "type": "string" }, "role": { "type": "string" }, "modules": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string" }, "ver": { "type": "integer" }, "path": { "type": "string" }, "args": { "type": "array", "items": { "type": "string" } } } } } } }, "arguments": [ { "name": "arguments", "type": "block", "optional": true, "arguments": [ { "name": "protover", "type": "integer" }, { "token": "AUTH", "name": "auth", "type": "block", "optional": true, "arguments": [ { "name": "username", "type": "string" }, { "name": "password", "type": "string" } ] }, { "token": "SETNAME", "name": "clientname", "type": "string", "optional": true } ] } ] } } redis-8.0.2/src/commands/hexists.json000066400000000000000000000027121501533116600176040ustar00rootroot00000000000000{ "HEXISTS": { "summary": "Determines whether a field exists in a hash.", "complexity": "O(1)", "group": "hash", "since": "2.0.0", "arity": 3, "function": "hexistsCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The hash does not contain the field, or key does not exist.", "const": 0 }, { "description": "The hash contains the field.", "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string" } ] } } redis-8.0.2/src/commands/hexpire.json000066400000000000000000000067041501533116600175660ustar00rootroot00000000000000{ "HEXPIRE": { "summary": "Set expiry for hash field using relative time to expire (seconds)", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -6, "function": "hexpireCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "Specified NX | XX | GT | LT condition not met", "const": 0 }, { "description": "Expiration time was set or updated.", "const": 1 }, { "description": "Field deleted because the specified expiration time is in the past.", "const": 2 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "seconds", "type": "integer" }, { "name": "condition", "type": "oneof", "optional": true, "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hexpireat.json000066400000000000000000000067261501533116600201170ustar00rootroot00000000000000{ "HEXPIREAT": { "summary": "Set expiry for hash field using an absolute Unix timestamp (seconds)", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -6, "function": "hexpireatCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "Specified NX | XX | GT | LT condition not met", "const": 0 }, { "description": "Expiration time was set or updated.", "const": 1 }, { "description": "Field deleted because the specified expiration time is in the past.", "const": 2 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "unix-time-seconds", "type": "unix-time" }, { "name": "condition", "type": "oneof", "optional": true, "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } }redis-8.0.2/src/commands/hexpiretime.json000066400000000000000000000046321501533116600204430ustar00rootroot00000000000000{ "HEXPIRETIME": { "summary": "Returns the expiration time of a hash field as a Unix timestamp, in seconds.", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -5, "function": "hexpiretimeCommand", "history": [], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "The field exists but has no associated expire.", "const": -1 }, { "description": "Expiration Unix timestamp in seconds.", "type": "integer", "minimum": 1 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hget.json000066400000000000000000000027621501533116600170510ustar00rootroot00000000000000{ "HGET": { "summary": "Returns the value of a field in a hash.", "complexity": "O(1)", "group": "hash", "since": "2.0.0", "arity": 3, "function": "hgetCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The value associated with the field.", "type": "string" }, { "description": "If the field is not present in the hash or key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string" } ] } } redis-8.0.2/src/commands/hgetall.json000066400000000000000000000026531501533116600175410ustar00rootroot00000000000000{ "HGETALL": { "summary": "Returns all fields and values in a hash.", "complexity": "O(N) where N is the size of the hash.", "group": "hash", "since": "2.0.0", "arity": 2, "function": "hgetallCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "object", "description": "Map of fields and their values stored in the hash, or an empty list when key does not exist. In RESP2 this is returned as a flat array.", "additionalProperties": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/hgetdel.json000066400000000000000000000040571501533116600175350ustar00rootroot00000000000000{ "HGETDEL": { "summary": "Returns the value of a field and deletes it from the hash.", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "8.0.0", "arity": -5, "function": "hgetdelCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "List of values associated with the given fields, in the same order as they are requested.", "type": "array", "minItems": 1, "items": { "oneOf": [ { "type": "string" }, { "type": "null" } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hgetex.json000066400000000000000000000063061501533116600174040ustar00rootroot00000000000000{ "HGETEX": { "summary": "Get the value of one or more fields of a given hash key, and optionally set their expiration.", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "8.0.0", "arity": -5, "function": "hgetexCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "notes": "RW and UPDATE because it changes the TTL", "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "List of values associated with the given fields, in the same order as they are requested.", "type": "array", "minItems": 1, "items": { "oneOf": [ { "type": "string" }, { "type": "null" } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "expiration", "type": "oneof", "optional": true, "arguments": [ { "name": "seconds", "type": "integer", "token": "EX" }, { "name": "milliseconds", "type": "integer", "token": "PX" }, { "name": "unix-time-seconds", "type": "unix-time", "token": "EXAT" }, { "name": "unix-time-milliseconds", "type": "unix-time", "token": "PXAT" }, { "name": "persist", "type": "pure-token", "token": "PERSIST" } ] }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hincrby.json000066400000000000000000000027451501533116600175610ustar00rootroot00000000000000{ "HINCRBY": { "summary": "Increments the integer value of a field in a hash by a number. Uses 0 as initial value if the field doesn't exist.", "complexity": "O(1)", "group": "hash", "since": "2.0.0", "arity": 4, "function": "hincrbyCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "The value of the field after the increment operation." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string" }, { "name": "increment", "type": "integer" } ] } } redis-8.0.2/src/commands/hincrbyfloat.json000066400000000000000000000027521501533116600206050ustar00rootroot00000000000000{ "HINCRBYFLOAT": { "summary": "Increments the floating point value of a field by a number. Uses 0 as initial value if the field doesn't exist.", "complexity": "O(1)", "group": "hash", "since": "2.6.0", "arity": 4, "function": "hincrbyfloatCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "string", "description": "The value of the field after the increment operation." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string" }, { "name": "increment", "type": "double" } ] } } redis-8.0.2/src/commands/hkeys.json000066400000000000000000000025571501533116600172470ustar00rootroot00000000000000{ "HKEYS": { "summary": "Returns all fields in a hash.", "complexity": "O(N) where N is the size of the hash.", "group": "hash", "since": "2.0.0", "arity": 2, "function": "hkeysCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List of fields in the hash, or an empty list when the key does not exist.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/hlen.json000066400000000000000000000022171501533116600170430ustar00rootroot00000000000000{ "HLEN": { "summary": "Returns the number of fields in a hash.", "complexity": "O(1)", "group": "hash", "since": "2.0.0", "arity": 2, "function": "hlenCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Number of the fields in the hash, or 0 when the key does not exist." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/hmget.json000066400000000000000000000032211501533116600172150ustar00rootroot00000000000000{ "HMGET": { "summary": "Returns the values of all fields in a hash.", "complexity": "O(N) where N is the number of fields being requested.", "group": "hash", "since": "2.0.0", "arity": -3, "function": "hmgetCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "List of values associated with the given fields, in the same order as they are requested.", "type": "array", "minItems": 1, "items": { "oneOf": [ { "type": "string" }, { "type": "null" } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/hmset.json000066400000000000000000000033231501533116600172340ustar00rootroot00000000000000{ "HMSET": { "summary": "Sets the values of multiple fields.", "complexity": "O(N) where N is the number of fields being set.", "group": "hash", "since": "2.0.0", "arity": -4, "function": "hsetCommand", "deprecated_since": "4.0.0", "replaced_by": "`HSET` with multiple field-value pairs", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "field", "type": "string" }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/hpersist.json000066400000000000000000000045021501533116600177550ustar00rootroot00000000000000{ "HPERSIST": { "summary": "Removes the expiration time for each specified field", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -5, "function": "hpersistCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "The field exists but has no associated expire.", "const": -1 }, { "description": "Expiration time was removed", "const": 1 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hpexpire.json000066400000000000000000000067171501533116600177520ustar00rootroot00000000000000{ "HPEXPIRE": { "summary": "Set expiry for hash field using relative time to expire (milliseconds)", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -6, "function": "hpexpireCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "Specified NX | XX | GT | LT condition not met", "const": 0 }, { "description": "Expiration time was set or updated.", "const": 1 }, { "description": "Field deleted because the specified expiration time is in the past.", "const": 2 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "milliseconds", "type": "integer" }, { "name": "condition", "type": "oneof", "optional": true, "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } }redis-8.0.2/src/commands/hpexpireat.json000066400000000000000000000067421501533116600202750ustar00rootroot00000000000000{ "HPEXPIREAT": { "summary": "Set expiry for hash field using an absolute Unix timestamp (milliseconds)", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -6, "function": "hpexpireatCommand", "history": [], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "Specified NX | XX | GT | LT condition not met", "const": 0 }, { "description": "Expiration time was set or updated.", "const": 1 }, { "description": "Field deleted because the specified expiration time is in the past.", "const": 2 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "unix-time-milliseconds", "type": "unix-time" }, { "name": "condition", "type": "oneof", "optional": true, "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } }redis-8.0.2/src/commands/hpexpiretime.json000066400000000000000000000046361501533116600206270ustar00rootroot00000000000000{ "HPEXPIRETIME": { "summary": "Returns the expiration time of a hash field as a Unix timestamp, in msec.", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -5, "function": "hpexpiretimeCommand", "history": [], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "The field exists but has no associated expire.", "const": -1 }, { "description": "Expiration Unix timestamp in milliseconds.", "type": "integer", "minimum": 1 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hpttl.json000066400000000000000000000046541501533116600172570ustar00rootroot00000000000000{ "HPTTL": { "summary": "Returns the TTL in milliseconds of a hash field.", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -5, "function": "hpttlCommand", "history": [], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "The field exists but has no associated expire.", "const": -1 }, { "description": "TTL in milliseconds.", "type": "integer", "minimum": 1 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hrandfield.json000066400000000000000000000060471501533116600202220ustar00rootroot00000000000000{ "HRANDFIELD": { "summary": "Returns one or more random fields from a hash.", "complexity": "O(N) where N is the number of fields returned", "group": "hash", "since": "6.2.0", "arity": -2, "function": "hrandfieldCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "description": "Key doesn't exist", "type": "null" }, { "description": "A single random field. Returned in case `COUNT` was not used.", "type": "string" }, { "description": "A list of fields. Returned in case `COUNT` was used.", "type": "array", "items": { "type": "string" } }, { "description": "Fields and their values. Returned in case `COUNT` and `WITHVALUES` were used. In RESP2 this is returned as a flat array.", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Field", "type": "string" }, { "description": "Value", "type": "string" } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "options", "type": "block", "optional": true, "arguments": [ { "name": "count", "type": "integer" }, { "name": "withvalues", "token": "WITHVALUES", "type": "pure-token", "optional": true } ] } ] } } redis-8.0.2/src/commands/hscan.json000066400000000000000000000050211501533116600172050ustar00rootroot00000000000000{ "HSCAN": { "summary": "Iterates over fields and values of a hash.", "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", "group": "hash", "since": "2.8.0", "arity": -3, "function": "hscanCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "cursor", "type": "integer" }, { "token": "MATCH", "name": "pattern", "type": "pattern", "optional": true }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true }, { "token": "NOVALUES", "name": "novalues", "type": "pure-token", "optional": true } ], "reply_schema": { "description": "cursor and scan response in array form", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "cursor", "type": "string" }, { "description": "list of key/value pairs from the hash where each even element is the key, and each odd element is the value, or when novalues option is on, a list of keys from the hash", "type": "array", "items": { "type": "string" } } ] } } } redis-8.0.2/src/commands/hset.json000066400000000000000000000035641501533116600170660ustar00rootroot00000000000000{ "HSET": { "summary": "Creates or modifies the value of a field in a hash.", "complexity": "O(1) for each field/value pair added, so O(N) to add N field/value pairs when the command is called with multiple field/value pairs.", "group": "hash", "since": "2.0.0", "arity": -4, "function": "hsetCommand", "history": [ [ "4.0.0", "Accepts multiple `field` and `value` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The number of fields that were added", "type": "integer" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "field", "type": "string" }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/hsetex.json000066400000000000000000000075711501533116600174250ustar00rootroot00000000000000{ "HSETEX": { "summary": "Set the value of one or more fields of a given hash key, and optionally set their expiration.", "complexity": "O(N) where N is the number of fields being set.", "group": "hash", "since": "8.0.0", "arity": -6, "function": "hsetexCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "No field was set (due to FXX or FNX flags).", "const": 0 }, { "description": "All the fields were set.", "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "condition", "type": "oneof", "optional": true, "arguments": [ { "name": "fnx", "type": "pure-token", "token": "FNX" }, { "name": "fxx", "type": "pure-token", "token": "FXX" } ] }, { "name": "expiration", "type": "oneof", "optional": true, "arguments": [ { "name": "seconds", "type": "integer", "token": "EX" }, { "name": "milliseconds", "type": "integer", "token": "PX" }, { "name": "unix-time-seconds", "type": "unix-time", "token": "EXAT" }, { "name": "unix-time-milliseconds", "type": "unix-time", "token": "PXAT" }, { "name": "keepttl", "type": "pure-token", "token": "KEEPTTL" } ] }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "field", "type": "string" }, { "name": "value", "type": "string" } ] } ] } ] } } redis-8.0.2/src/commands/hsetnx.json000066400000000000000000000032271501533116600174300ustar00rootroot00000000000000{ "HSETNX": { "summary": "Sets the value of a field in a hash only when the field doesn't exist.", "complexity": "O(1)", "group": "hash", "since": "2.0.0", "arity": 4, "function": "hsetnxCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The field is a new field in the hash and value was set.", "const": 0 }, { "description": "The field already exists in the hash and no operation was performed.", "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string" }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/hstrlen.json000066400000000000000000000025241501533116600175750ustar00rootroot00000000000000{ "HSTRLEN": { "summary": "Returns the length of the value of a field.", "complexity": "O(1)", "group": "hash", "since": "3.2.0", "arity": 3, "function": "hstrlenCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "String length of the value associated with the field, or zero when the field is not present in the hash or key does not exist at all.", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "field", "type": "string" } ] } } redis-8.0.2/src/commands/httl.json000066400000000000000000000046401501533116600170720ustar00rootroot00000000000000{ "HTTL": { "summary": "Returns the TTL in seconds of a hash field.", "complexity": "O(N) where N is the number of specified fields", "group": "hash", "since": "7.4.0", "arity": -5, "function": "httlCommand", "history": [], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Array of results. Returns empty array if the key does not exist.", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "oneOf": [ { "description": "The field does not exist.", "const": -2 }, { "description": "The field exists but has no associated expire.", "const": -1 }, { "description": "TTL in seconds.", "type": "integer", "minimum": 1 } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "fields", "token": "FIELDS", "type": "block", "arguments": [ { "name": "numfields", "type": "integer" }, { "name": "field", "type": "string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/hvals.json000066400000000000000000000025161501533116600172340ustar00rootroot00000000000000{ "HVALS": { "summary": "Returns all values in a hash.", "complexity": "O(N) where N is the size of the hash.", "group": "hash", "since": "2.0.0", "arity": 2, "function": "hvalsCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "HASH" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List of values in the hash, or an empty list when the key does not exist.", "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/incr.json000066400000000000000000000023741501533116600170540ustar00rootroot00000000000000{ "INCR": { "summary": "Increments the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 2, "function": "incrCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ], "reply_schema": { "description": "The value of key after the increment", "type": "integer" } } } redis-8.0.2/src/commands/incrby.json000066400000000000000000000025601501533116600174040ustar00rootroot00000000000000{ "INCRBY": { "summary": "Increments the integer value of a key by a number. Uses 0 as initial value if the key doesn't exist.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 3, "function": "incrbyCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "The value of the key after incrementing it." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "increment", "type": "integer" } ] } } redis-8.0.2/src/commands/incrbyfloat.json000066400000000000000000000025761501533116600204410ustar00rootroot00000000000000{ "INCRBYFLOAT": { "summary": "Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.", "complexity": "O(1)", "group": "string", "since": "2.6.0", "arity": 3, "function": "incrbyfloatCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "string", "description": "The value of the key after incrementing it." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "increment", "type": "double" } ] } } redis-8.0.2/src/commands/info.json000066400000000000000000000022771501533116600170560ustar00rootroot00000000000000{ "INFO": { "summary": "Returns information and statistics about the server.", "complexity": "O(1)", "group": "server", "since": "1.0.0", "arity": -1, "function": "infoCommand", "history": [ [ "7.0.0", "Added support for taking multiple section arguments." ] ], "command_flags": [ "LOADING", "STALE", "SENTINEL" ], "acl_categories": [ "DANGEROUS" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "description": "A map of info fields, one field per line in the form of : where the value can be a comma separated map like =. Also contains section header lines starting with `#` and blank lines.", "type": "string" }, "arguments": [ { "name": "section", "type": "string", "multiple": true, "optional": true } ] } } redis-8.0.2/src/commands/keys.json000066400000000000000000000017151501533116600170720ustar00rootroot00000000000000{ "KEYS": { "summary": "Returns all key names that match a pattern.", "complexity": "O(N) with N being the number of keys in the database, under the assumption that the key names in the database and the given pattern have limited length.", "group": "generic", "since": "1.0.0", "arity": 2, "function": "keysCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "NONDETERMINISTIC_OUTPUT_ORDER" ], "arguments": [ { "name": "pattern", "type": "pattern" } ], "reply_schema": { "description": "list of keys matching pattern", "type": "array", "items": { "type": "string" } } } } redis-8.0.2/src/commands/lastsave.json000066400000000000000000000012311501533116600177320ustar00rootroot00000000000000{ "LASTSAVE": { "summary": "Returns the Unix timestamp of the last successful save to disk.", "complexity": "O(1)", "group": "server", "since": "1.0.0", "arity": 1, "function": "lastsaveCommand", "command_flags": [ "LOADING", "STALE", "FAST" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "acl_categories": [ "ADMIN", "DANGEROUS" ], "reply_schema": { "type": "integer", "description": "UNIX TIME of the last DB save executed with success." } } } redis-8.0.2/src/commands/latency-doctor.json000066400000000000000000000012611501533116600210420ustar00rootroot00000000000000{ "DOCTOR": { "summary": "Returns a human-readable latency analysis report.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": 2, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "type": "string", "description": "A human readable latency analysis report." } } } redis-8.0.2/src/commands/latency-graph.json000066400000000000000000000014101501533116600206450ustar00rootroot00000000000000{ "GRAPH": { "summary": "Returns a latency graph for an event.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": 3, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:SPECIAL" ], "arguments": [ { "name": "event", "type": "string" } ], "reply_schema": { "type": "string", "description": "Latency graph" } } } redis-8.0.2/src/commands/latency-help.json000066400000000000000000000010501501533116600204740ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": 2, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/latency-histogram.json000066400000000000000000000035401501533116600215470ustar00rootroot00000000000000{ "HISTOGRAM": { "summary": "Returns the cumulative distribution of latencies of a subset or all commands.", "complexity": "O(N) where N is the number of commands with latency information being retrieved.", "group": "server", "since": "7.0.0", "arity": -2, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "type": "object", "description": "A map where each key is a command name, and each value is a map with the total calls, and an inner map of the histogram time buckets.", "patternProperties": { "^.*$": { "type": "object", "additionalProperties": false, "properties": { "calls": { "description": "The total calls for the command.", "type": "integer", "minimum": 0 }, "histogram_usec": { "description": "Histogram map, bucket id to latency", "type": "object", "additionalProperties": { "type": "integer" } } } } } }, "arguments": [ { "name": "COMMAND", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/latency-history.json000066400000000000000000000026351501533116600212570ustar00rootroot00000000000000{ "HISTORY": { "summary": "Returns timestamp-latency samples for an event.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": 3, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "type": "array", "description": "An array where each element is a two elements array representing the timestamp and the latency of the event.", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "timestamp of the event", "type": "integer", "minimum": 0 }, { "description": "latency of the event", "type": "integer", "minimum": 0 } ] } }, "arguments": [ { "name": "event", "type": "string" } ] } } redis-8.0.2/src/commands/latency-latest.json000066400000000000000000000030301501533116600210400ustar00rootroot00000000000000{ "LATEST": { "summary": "Returns the latest latency samples for all events.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": 2, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "type": "array", "description": "An array where each element is a four elements array representing the event's name, timestamp, latest and all-time latency measurements.", "items": { "type": "array", "minItems": 4, "maxItems": 4, "items": [ { "type": "string", "description": "Event name." }, { "type": "integer", "description": "Timestamp." }, { "type": "integer", "description": "Latest latency in milliseconds." }, { "type": "integer", "description": "Max latency in milliseconds." } ] } } } } redis-8.0.2/src/commands/latency-reset.json000066400000000000000000000015201501533116600206700ustar00rootroot00000000000000{ "RESET": { "summary": "Resets the latency data for one or more events.", "complexity": "O(1)", "group": "server", "since": "2.8.13", "arity": -2, "container": "LATENCY", "function": "latencyCommand", "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:AGG_SUM" ], "reply_schema": { "type": "integer", "description": "Number of event time series that were reset." }, "arguments": [ { "name": "event", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/latency.json000066400000000000000000000003311501533116600175470ustar00rootroot00000000000000{ "LATENCY": { "summary": "A container for latency diagnostics commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "2.8.13", "arity": -2 } } redis-8.0.2/src/commands/lcs.json000066400000000000000000000105471501533116600167030ustar00rootroot00000000000000{ "LCS": { "summary": "Finds the longest common substring.", "complexity": "O(N*M) where N and M are the lengths of s1 and s2, respectively", "group": "string", "since": "7.0.0", "arity": -3, "function": "lcsCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 1, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "string", "description": "The longest common subsequence." }, { "type": "integer", "description": "The length of the longest common subsequence when 'LEN' is given." }, { "type": "object", "description": "Array with the LCS length and all the ranges in both the strings when 'IDX' is given. In RESP2 this is returned as a flat array", "additionalProperties": false, "properties": { "matches": { "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 3, "items": [ { "type": "array", "description": "Matched range in the first string.", "minItems": 2, "maxItems": 2, "items": { "type": "integer" } }, { "type": "array", "description": "Matched range in the second string.", "minItems": 2, "maxItems": 2, "items": { "type": "integer" } } ], "additionalItems": { "type": "integer", "description": "The length of the match when 'WITHMATCHLEN' is given." } } }, "len": { "type": "integer", "description": "Length of the longest common subsequence." } } } ] }, "arguments": [ { "name": "key1", "type": "key", "key_spec_index": 0 }, { "name": "key2", "type": "key", "key_spec_index": 0 }, { "name": "len", "token": "LEN", "type": "pure-token", "optional": true }, { "name": "idx", "token": "IDX", "type": "pure-token", "optional": true }, { "token": "MINMATCHLEN", "name": "min-match-len", "type": "integer", "optional": true }, { "name": "withmatchlen", "token": "WITHMATCHLEN", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/lindex.json000066400000000000000000000031041501533116600173740ustar00rootroot00000000000000{ "LINDEX": { "summary": "Returns an element from a list by its index.", "complexity": "O(N) where N is the number of elements to traverse to get to the element at index. This makes asking for the first or the last element of the list O(1).", "group": "list", "since": "1.0.0", "arity": 3, "function": "lindexCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "null", "description": "Index is out of range" }, { "description": "The requested element", "type": "string" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "index", "type": "integer" } ] } } redis-8.0.2/src/commands/linsert.json000066400000000000000000000047151501533116600176020ustar00rootroot00000000000000{ "LINSERT": { "summary": "Inserts an element before or after another element in a list.", "complexity": "O(N) where N is the number of elements to traverse before seeing the value pivot. This means that inserting somewhere on the left end on the list (head) can be considered O(1) and inserting somewhere on the right end (tail) is O(N).", "group": "list", "since": "2.2.0", "arity": 5, "function": "linsertCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "List length after a successful insert operation.", "type": "integer", "minimum": 1 }, { "description": "in case key doesn't exist.", "const": 0 }, { "description": "when the pivot wasn't found.", "const": -1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "where", "type": "oneof", "arguments": [ { "name": "before", "type": "pure-token", "token": "BEFORE" }, { "name": "after", "type": "pure-token", "token": "AFTER" } ] }, { "name": "pivot", "type": "string" }, { "name": "element", "type": "string" } ] } } redis-8.0.2/src/commands/llen.json000066400000000000000000000021501501533116600170430ustar00rootroot00000000000000{ "LLEN": { "summary": "Returns the length of a list.", "complexity": "O(1)", "group": "list", "since": "1.0.0", "arity": 2, "function": "llenCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "List length.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/lmove.json000066400000000000000000000054771501533116600172520ustar00rootroot00000000000000{ "LMOVE": { "summary": "Returns an element after popping it from one list and pushing it to another. Deletes the list if the last element was moved.", "complexity": "O(1)", "group": "list", "since": "6.2.0", "arity": 5, "function": "lmoveCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The element being popped and pushed.", "type": "string" }, "arguments": [ { "name": "source", "type": "key", "key_spec_index": 0 }, { "name": "destination", "type": "key", "key_spec_index": 1 }, { "name": "wherefrom", "type": "oneof", "arguments": [ { "name": "left", "type": "pure-token", "token": "LEFT" }, { "name": "right", "type": "pure-token", "token": "RIGHT" } ] }, { "name": "whereto", "type": "oneof", "arguments": [ { "name": "left", "type": "pure-token", "token": "LEFT" }, { "name": "right", "type": "pure-token", "token": "RIGHT" } ] } ] } } redis-8.0.2/src/commands/lmpop.json000066400000000000000000000057411501533116600172510ustar00rootroot00000000000000{ "LMPOP": { "summary": "Returns multiple elements from a list after removing them. Deletes the list if the last element was popped.", "complexity": "O(N+M) where N is the number of provided keys and M is the number of elements returned.", "group": "list", "since": "7.0.0", "arity": -4, "function": "lmpopCommand", "get_keys_function": "lmpopGetKeys", "command_flags": [ "WRITE" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "anyOf": [ { "description": "If no element could be popped.", "type": "null" }, { "description": "List key from which elements were popped.", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Name of the key from which elements were popped.", "type": "string" }, { "description": "Array of popped elements.", "type": "array", "minItems": 1, "items": { "type": "string" } } ] } ] }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "where", "type": "oneof", "arguments": [ { "name": "left", "type": "pure-token", "token": "LEFT" }, { "name": "right", "type": "pure-token", "token": "RIGHT" } ] }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/lolwut.json000066400000000000000000000012261501533116600174420ustar00rootroot00000000000000{ "LOLWUT": { "summary": "Displays computer art and the Redis version", "group": "server", "since": "5.0.0", "arity": -1, "function": "lolwutCommand", "command_flags": [ "READONLY", "FAST" ], "reply_schema": { "type": "string", "description": "String containing the generative computer art, and a text with the Redis version." }, "arguments": [ { "token": "VERSION", "name": "version", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/lpop.json000066400000000000000000000041071501533116600170670ustar00rootroot00000000000000{ "LPOP": { "summary": "Returns the first elements in a list after removing it. Deletes the list if the last element was popped.", "complexity": "O(N) where N is the number of elements returned", "group": "list", "since": "1.0.0", "arity": -2, "function": "lpopCommand", "history": [ [ "6.2.0", "Added the `count` argument." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Key does not exist.", "type": "null" }, { "description": "In case `count` argument was not given, the value of the first element.", "type": "string" }, { "description": "In case `count` argument was given, a list of popped elements", "type": "array", "items": { "type": "string" } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer", "optional": true, "since": "6.2.0" } ] } } redis-8.0.2/src/commands/lpos.json000066400000000000000000000050141501533116600170700ustar00rootroot00000000000000{ "LPOS": { "summary": "Returns the index of matching elements in a list.", "complexity": "O(N) where N is the number of elements in the list, for the average case. When searching for elements near the head or the tail of the list, or when the MAXLEN option is provided, the command may run in constant time.", "group": "list", "since": "6.0.6", "arity": -3, "function": "lposCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "description": "In case there is no matching element", "type": "null" }, { "description": "An integer representing the matching element", "type": "integer" }, { "description": "If the COUNT option is given, an array of integers representing the matching elements (empty if there are no matches)", "type": "array", "uniqueItems": true, "items": { "type": "integer" } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "element", "type": "string" }, { "token": "RANK", "name": "rank", "type": "integer", "optional": true }, { "token": "COUNT", "name": "num-matches", "type": "integer", "optional": true }, { "token": "MAXLEN", "name": "len", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/lpush.json000066400000000000000000000031121501533116600172430ustar00rootroot00000000000000{ "LPUSH": { "summary": "Prepends one or more elements to a list. Creates the key if it doesn't exist.", "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", "group": "list", "since": "1.0.0", "arity": -3, "function": "lpushCommand", "history": [ [ "2.4.0", "Accepts multiple `element` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Length of the list after the push operations.", "type": "integer" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "element", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/lpushx.json000066400000000000000000000031351501533116600174400ustar00rootroot00000000000000{ "LPUSHX": { "summary": "Prepends one or more elements to a list only when the list exists.", "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", "group": "list", "since": "2.2.0", "arity": -3, "function": "lpushxCommand", "history": [ [ "4.0.0", "Accepts multiple `element` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "the length of the list after the push operation", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "element", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/lrange.json000066400000000000000000000030641501533116600173660ustar00rootroot00000000000000{ "LRANGE": { "summary": "Returns a range of elements from a list.", "complexity": "O(S+N) where S is the distance of start offset from HEAD for small lists, from nearest end (HEAD or TAIL) for large lists; and N is the number of elements in the specified range.", "group": "list", "since": "1.0.0", "arity": 4, "function": "lrangeCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "integer" }, { "name": "stop", "type": "integer" } ], "reply_schema": { "description": "List of elements in the specified range", "type": "array", "items": { "type": "string" } } } } redis-8.0.2/src/commands/lrem.json000066400000000000000000000027021501533116600170530ustar00rootroot00000000000000{ "LREM": { "summary": "Removes elements from a list. Deletes the list if the last element was removed.", "complexity": "O(N+M) where N is the length of the list and M is the number of elements removed.", "group": "list", "since": "1.0.0", "arity": 4, "function": "lremCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The number of removed elements.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer" }, { "name": "element", "type": "string" } ] } } redis-8.0.2/src/commands/lset.json000066400000000000000000000025721501533116600170700ustar00rootroot00000000000000{ "LSET": { "summary": "Sets the value of an element in a list by its index.", "complexity": "O(N) where N is the length of the list. Setting either the first or the last element of the list is O(1).", "group": "list", "since": "1.0.0", "arity": 4, "function": "lsetCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "index", "type": "integer" }, { "name": "element", "type": "string" } ] } } redis-8.0.2/src/commands/ltrim.json000066400000000000000000000025421501533116600172450ustar00rootroot00000000000000{ "LTRIM": { "summary": "Removes elements from both ends a list. Deletes the list if all elements were trimmed.", "complexity": "O(N) where N is the number of elements to be removed by the operation.", "group": "list", "since": "1.0.0", "arity": 4, "function": "ltrimCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "integer" }, { "name": "stop", "type": "integer" } ] } } redis-8.0.2/src/commands/memory-doctor.json000066400000000000000000000010161501533116600207110ustar00rootroot00000000000000{ "DOCTOR": { "summary": "Outputs a memory problems report.", "complexity": "O(1)", "group": "server", "since": "4.0.0", "arity": 2, "container": "MEMORY", "function": "memoryCommand", "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "description": "memory problems report", "type": "string" } } } redis-8.0.2/src/commands/memory-help.json000066400000000000000000000010451501533116600203510ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "server", "since": "4.0.0", "arity": 2, "container": "MEMORY", "function": "memoryCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/memory-malloc-stats.json000066400000000000000000000011421501533116600220220ustar00rootroot00000000000000{ "MALLOC-STATS": { "summary": "Returns the allocator statistics.", "complexity": "Depends on how much memory is allocated, could be slow", "group": "server", "since": "4.0.0", "arity": 2, "container": "MEMORY", "function": "memoryCommand", "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "type": "string", "description": "The memory allocator's internal statistics report." } } } redis-8.0.2/src/commands/memory-purge.json000066400000000000000000000007521501533116600205470ustar00rootroot00000000000000{ "PURGE": { "summary": "Asks the allocator to release memory.", "complexity": "Depends on how much memory is allocated, could be slow", "group": "server", "since": "4.0.0", "arity": 2, "container": "MEMORY", "function": "memoryCommand", "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/memory-stats.json000066400000000000000000000102771501533116600205660ustar00rootroot00000000000000{ "STATS": { "summary": "Returns details about memory usage.", "complexity": "O(1)", "group": "server", "since": "4.0.0", "arity": 2, "container": "MEMORY", "function": "memoryCommand", "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:SPECIAL" ], "reply_schema": { "description": "memory usage details", "type": "object", "additionalProperties": false, "properties": { "peak.allocated": { "type": "integer" }, "total.allocated": { "type": "integer" }, "startup.allocated": { "type": "integer" }, "replication.backlog": { "type": "integer" }, "replica.fullsync.buffer": { "type": "integer" }, "clients.slaves": { "type": "integer" }, "clients.normal": { "type": "integer" }, "cluster.links": { "type": "integer" }, "aof.buffer": { "type": "integer" }, "lua.caches": { "type": "integer" }, "script.VMs": { "type": "integer" }, "functions.caches": { "type": "integer" }, "overhead.db.hashtable.lut": { "type": "integer" }, "overhead.db.hashtable.rehashing": { "type": "integer" }, "overhead.total": { "type": "integer" }, "db.dict.rehashing.count": { "type": "integer" }, "keys.count": { "type": "integer" }, "keys.bytes-per-key": { "type": "integer" }, "dataset.bytes": { "type": "integer" }, "dataset.percentage": { "type": "number" }, "peak.percentage": { "type": "number" }, "allocator.allocated": { "type": "integer" }, "allocator.active": { "type": "integer" }, "allocator.resident": { "type": "integer" }, "allocator.muzzy": { "type": "integer" }, "allocator-fragmentation.ratio": { "type": "number" }, "allocator-fragmentation.bytes": { "type": "integer" }, "allocator-rss.ratio": { "type": "number" }, "allocator-rss.bytes": { "type": "integer" }, "rss-overhead.ratio": { "type": "number" }, "rss-overhead.bytes": { "type": "integer" }, "fragmentation": { "type": "number" }, "fragmentation.bytes": { "type": "integer" } }, "patternProperties": { "^db\\.\\d+$": { "type": "object", "properties": { "overhead.hashtable.main": { "type": "integer" }, "overhead.hashtable.expires": { "type": "integer" } }, "additionalProperties": false } } } } } redis-8.0.2/src/commands/memory-usage.json000066400000000000000000000030101501533116600205170ustar00rootroot00000000000000{ "USAGE": { "summary": "Estimates the memory usage of a key.", "complexity": "O(N) where N is the number of samples.", "group": "server", "since": "4.0.0", "arity": -3, "container": "MEMORY", "function": "memoryCommand", "command_flags": [ "READONLY" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Number of bytes that a key and its value require to be stored in RAM.", "type": "integer" }, { "description": "Key does not exist.", "type": "null" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "token": "SAMPLES", "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/memory.json000066400000000000000000000003261501533116600174240ustar00rootroot00000000000000{ "MEMORY": { "summary": "A container for memory diagnostics commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "4.0.0", "arity": -2 } } redis-8.0.2/src/commands/mget.json000066400000000000000000000031271501533116600170520ustar00rootroot00000000000000{ "MGET": { "summary": "Atomically returns the string values of one or more keys.", "complexity": "O(N) where N is the number of keys to retrieve.", "group": "string", "since": "1.0.0", "arity": -2, "function": "mgetCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "STRING" ], "command_tips": [ "REQUEST_POLICY:MULTI_SHARD" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "List of values at the specified keys.", "type": "array", "minItems": 1, "items": { "oneOf": [ { "type": "string" }, { "type": "null" } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/migrate.json000066400000000000000000000123121501533116600175420ustar00rootroot00000000000000{ "MIGRATE": { "summary": "Atomically transfers a key from one Redis instance to another.", "complexity": "This command actually executes a DUMP+DEL in the source instance, and a RESTORE in the target instance. See the pages of these commands for time complexity. Also an O(N) data transfer between the two instances is performed.", "group": "generic", "since": "2.6.0", "arity": -6, "function": "migrateCommand", "get_keys_function": "migrateGetKeys", "history": [ [ "3.0.0", "Added the `COPY` and `REPLACE` options." ], [ "3.0.6", "Added the `KEYS` option." ], [ "4.0.7", "Added the `AUTH` option." ], [ "6.0.0", "Added the `AUTH2` option." ] ], "command_flags": [ "WRITE" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 3 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RW", "ACCESS", "DELETE", "INCOMPLETE" ], "begin_search": { "keyword": { "keyword": "KEYS", "startfrom": -2 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": "OK", "description": "Success." }, { "const": "NOKEY", "description": "No keys were found in the source instance." } ] }, "arguments": [ { "name": "host", "type": "string" }, { "name": "port", "type": "integer" }, { "name": "key-selector", "type": "oneof", "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "empty-string", "type": "pure-token", "token": "\"\"" } ] }, { "name": "destination-db", "type": "integer" }, { "name": "timeout", "type": "integer" }, { "name": "copy", "token": "COPY", "type": "pure-token", "optional": true, "since": "3.0.0" }, { "name": "replace", "token": "REPLACE", "type": "pure-token", "optional": true, "since": "3.0.0" }, { "name": "authentication", "type": "oneof", "optional": true, "arguments": [ { "token": "AUTH", "name": "auth", "display": "password", "type": "string", "since": "4.0.7" }, { "token": "AUTH2", "name": "auth2", "type": "block", "since": "6.0.0", "arguments": [ { "name": "username", "type": "string" }, { "name": "password", "type": "string" } ] } ] }, { "token": "KEYS", "name": "keys", "display": "key", "type": "key", "key_spec_index": 1, "optional": true, "multiple": true, "since": "3.0.6" } ] } } redis-8.0.2/src/commands/module-help.json000066400000000000000000000010451501533116600203260ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "server", "since": "5.0.0", "arity": 2, "container": "MODULE", "function": "moduleCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/module-list.json000066400000000000000000000027271501533116600203610ustar00rootroot00000000000000{ "LIST": { "summary": "Returns all loaded modules.", "complexity": "O(N) where N is the number of loaded modules.", "group": "server", "since": "4.0.0", "arity": 2, "container": "MODULE", "function": "moduleCommand", "command_flags": [ "ADMIN", "NOSCRIPT" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "reply_schema": { "type": "array", "description": "Returns information about the modules loaded to the server.", "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "description": "Name of the module." }, "ver": { "type": "integer", "description": "Version of the module." }, "path": { "type": "string", "description": "Module path." }, "args": { "type": "array", "description": "Module arguments.", "items": { "type": "string" } } } } } } } redis-8.0.2/src/commands/module-load.json000066400000000000000000000013231501533116600203140ustar00rootroot00000000000000{ "LOAD": { "summary": "Loads a module.", "complexity": "O(1)", "group": "server", "since": "4.0.0", "arity": -3, "container": "MODULE", "function": "moduleCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT", "PROTECTED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "path", "type": "string" }, { "name": "arg", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/module-loadex.json000066400000000000000000000024561501533116600206610ustar00rootroot00000000000000{ "LOADEX": { "summary": "Loads a module using extended parameters.", "complexity": "O(1)", "group": "server", "since": "7.0.0", "arity": -3, "container": "MODULE", "function": "moduleCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT", "PROTECTED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "path", "type": "string" }, { "name": "configs", "token": "CONFIG", "type": "block", "multiple": true, "multiple_token": true, "optional": true, "arguments": [ { "name": "name", "type": "string" }, { "name": "value", "type": "string" } ] }, { "name": "args", "token": "ARGS", "type": "string", "multiple": true, "optional": true } ] } } redis-8.0.2/src/commands/module-unload.json000066400000000000000000000010651501533116600206620ustar00rootroot00000000000000{ "UNLOAD": { "summary": "Unloads a module.", "complexity": "O(1)", "group": "server", "since": "4.0.0", "arity": 3, "container": "MODULE", "function": "moduleCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT", "PROTECTED" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "name", "type": "string" } ] } } redis-8.0.2/src/commands/module.json000066400000000000000000000003121501533116600173740ustar00rootroot00000000000000{ "MODULE": { "summary": "A container for module commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "4.0.0", "arity": -2 } } redis-8.0.2/src/commands/monitor.json000066400000000000000000000005621501533116600176050ustar00rootroot00000000000000{ "MONITOR": { "summary": "Listens for all requests received by the server in real-time.", "group": "server", "since": "1.0.0", "arity": 1, "function": "monitorCommand", "history": [], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE" ] } } redis-8.0.2/src/commands/move.json000066400000000000000000000026741501533116600170720ustar00rootroot00000000000000{ "MOVE": { "summary": "Moves a key to another database.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": 3, "function": "moveCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "db", "type": "integer" } ], "reply_schema": { "oneOf": [ { "description": "key was moved", "const": 1 }, { "description": "key wasn't moved", "const": 0 } ] } } } redis-8.0.2/src/commands/mset.json000066400000000000000000000031431501533116600170640ustar00rootroot00000000000000{ "MSET": { "summary": "Atomically creates or modifies the string values of one or more keys.", "complexity": "O(N) where N is the number of keys to set.", "group": "string", "since": "1.0.1", "arity": -3, "function": "msetCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STRING" ], "command_tips": [ "REQUEST_POLICY:MULTI_SHARD", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 2, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/msetnx.json000066400000000000000000000034501501533116600174330ustar00rootroot00000000000000{ "MSETNX": { "summary": "Atomically modifies the string values of one or more keys only when all keys don't exist.", "complexity": "O(N) where N is the number of keys to set.", "group": "string", "since": "1.0.1", "arity": -3, "function": "msetnxCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "OW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 2, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "No key was set (at least one key already existed).", "const": 0 }, { "description": "All the keys were set.", "const": 1 } ] }, "arguments": [ { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/multi.json000066400000000000000000000007541501533116600172530ustar00rootroot00000000000000{ "MULTI": { "summary": "Starts a transaction.", "complexity": "O(1)", "group": "transactions", "since": "1.2.0", "arity": 1, "function": "multiCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "ALLOW_BUSY" ], "acl_categories": [ "TRANSACTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/object-encoding.json000066400000000000000000000026461501533116600211550ustar00rootroot00000000000000{ "ENCODING": { "summary": "Returns the internal encoding of a Redis object.", "complexity": "O(1)", "group": "generic", "since": "2.2.3", "arity": 3, "container": "OBJECT", "function": "objectCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ], "reply_schema": { "oneOf": [ { "description": "key doesn't exist", "type": "null" }, { "description": "encoding of the object", "type": "string" } ] } } } redis-8.0.2/src/commands/object-freq.json000066400000000000000000000023321501533116600203140ustar00rootroot00000000000000{ "FREQ": { "summary": "Returns the logarithmic access frequency counter of a Redis object.", "complexity": "O(1)", "group": "generic", "since": "4.0.0", "arity": 3, "container": "OBJECT", "function": "objectCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ], "reply_schema": { "description": "the counter's value", "type": "integer" } } } redis-8.0.2/src/commands/object-help.json000066400000000000000000000011441501533116600203070ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "generic", "since": "6.2.0", "arity": 2, "container": "OBJECT", "function": "objectCommand", "command_flags": [ "LOADING", "STALE" ], "acl_categories": [ "KEYSPACE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/object-idletime.json000066400000000000000000000023311501533116600211520ustar00rootroot00000000000000{ "IDLETIME": { "summary": "Returns the time since the last access to a Redis object.", "complexity": "O(1)", "group": "generic", "since": "2.2.3", "arity": 3, "container": "OBJECT", "function": "objectCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ], "reply_schema": { "description": "the idle time in seconds", "type": "integer" } } } redis-8.0.2/src/commands/object-refcount.json000066400000000000000000000023201501533116600212010ustar00rootroot00000000000000{ "REFCOUNT": { "summary": "Returns the reference count of a value of a key.", "complexity": "O(1)", "group": "generic", "since": "2.2.3", "arity": 3, "container": "OBJECT", "function": "objectCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ], "reply_schema": { "description": "the number of references", "type": "integer" } } } redis-8.0.2/src/commands/object.json000066400000000000000000000003311501533116600173560ustar00rootroot00000000000000{ "OBJECT": { "summary": "A container for object introspection commands.", "complexity": "Depends on subcommand.", "group": "generic", "since": "2.2.3", "arity": -2 } } redis-8.0.2/src/commands/persist.json000066400000000000000000000026061501533116600176100ustar00rootroot00000000000000{ "PERSIST": { "summary": "Removes the expiration time of a key.", "complexity": "O(1)", "group": "generic", "since": "2.2.0", "arity": 2, "function": "persistCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": 0, "description": "Key does not exist or does not have an associated timeout." }, { "const": 1, "description": "The timeout has been removed." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/pexpire.json000066400000000000000000000050401501533116600175660ustar00rootroot00000000000000{ "PEXPIRE": { "summary": "Sets the expiration time of a key in milliseconds.", "complexity": "O(1)", "group": "generic", "since": "2.6.0", "arity": -3, "function": "pexpireCommand", "history": [ [ "7.0.0", "Added options: `NX`, `XX`, `GT` and `LT`." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": 0, "description": "The timeout was not set. e.g. key doesn't exist, or operation skipped due to the provided arguments." }, { "const": 1, "description": "The timeout was set." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "milliseconds", "type": "integer" }, { "name": "condition", "type": "oneof", "optional": true, "since": "7.0.0", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] } ] } } redis-8.0.2/src/commands/pexpireat.json000066400000000000000000000051011501533116600201110ustar00rootroot00000000000000{ "PEXPIREAT": { "summary": "Sets the expiration time of a key to a Unix milliseconds timestamp.", "complexity": "O(1)", "group": "generic", "since": "2.6.0", "arity": -3, "function": "pexpireatCommand", "history": [ [ "7.0.0", "Added options: `NX`, `XX`, `GT` and `LT`." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": 1, "description": "The timeout was set." }, { "const": 0, "description": "The timeout was not set. e.g. key doesn't exist, or operation skipped due to the provided arguments." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "unix-time-milliseconds", "type": "unix-time" }, { "name": "condition", "type": "oneof", "optional": true, "since": "7.0.0", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" }, { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] } ] } } redis-8.0.2/src/commands/pexpiretime.json000066400000000000000000000031471501533116600204530ustar00rootroot00000000000000{ "PEXPIRETIME": { "summary": "Returns the expiration time of a key as a Unix milliseconds timestamp.", "complexity": "O(1)", "group": "generic", "since": "7.0.0", "arity": 2, "function": "pexpiretimeCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "integer", "description": "Expiration Unix timestamp in milliseconds.", "minimum": 0 }, { "const": -1, "description": "The key exists but has no associated expiration time." }, { "const": -2, "description": "The key does not exist." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/pfadd.json000066400000000000000000000032171501533116600171740ustar00rootroot00000000000000{ "PFADD": { "summary": "Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.", "complexity": "O(1) to add every element.", "group": "hyperloglog", "since": "2.8.9", "arity": -2, "function": "pfaddCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "HYPERLOGLOG" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "element", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "oneOf": [ { "description": "if at least 1 HyperLogLog internal register was altered", "const": 1 }, { "description": "if no HyperLogLog internal register were altered", "const": 0 } ] } } } redis-8.0.2/src/commands/pfcount.json000066400000000000000000000030731501533116600175740ustar00rootroot00000000000000{ "PFCOUNT": { "summary": "Returns the approximated cardinality of the set(s) observed by the HyperLogLog key(s).", "complexity": "O(1) with a very small average constant time when called with a single key. O(N) with N being the number of keys, and much bigger constant times, when called with multiple keys.", "group": "hyperloglog", "since": "2.8.9", "arity": -2, "function": "pfcountCommand", "command_flags": [ "READONLY", "MAY_REPLICATE" ], "acl_categories": [ "HYPERLOGLOG" ], "key_specs": [ { "notes": "RW because it may change the internal representation of the key, and propagate to replicas", "flags": [ "RW", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ], "reply_schema": { "description": "The approximated number of unique elements observed via PFADD", "type": "integer" } } } redis-8.0.2/src/commands/pfdebug.json000066400000000000000000000023271501533116600175330ustar00rootroot00000000000000{ "PFDEBUG": { "summary": "Internal commands for debugging HyperLogLog values.", "complexity": "N/A", "group": "hyperloglog", "since": "2.8.9", "arity": 3, "function": "pfdebugCommand", "doc_flags": [ "SYSCMD" ], "command_flags": [ "WRITE", "DENYOOM", "ADMIN" ], "acl_categories": [ "HYPERLOGLOG" ], "key_specs": [ { "flags": [ "RW", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "subcommand", "type": "string" }, { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/pfmerge.json000066400000000000000000000035571501533116600175520ustar00rootroot00000000000000{ "PFMERGE": { "summary": "Merges one or more HyperLogLog values into a single key.", "complexity": "O(N) to merge N HyperLogLogs, but with high constant times.", "group": "hyperloglog", "since": "2.8.9", "arity": -2, "function": "pfmergeCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "HYPERLOGLOG" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "destkey", "type": "key", "key_spec_index": 0 }, { "name": "sourcekey", "type": "key", "key_spec_index": 1, "optional": true, "multiple": true } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/pfselftest.json000066400000000000000000000007541501533116600203000ustar00rootroot00000000000000{ "PFSELFTEST": { "summary": "An internal command for testing HyperLogLog values.", "complexity": "N/A", "group": "hyperloglog", "since": "2.8.9", "arity": 1, "function": "pfselftestCommand", "doc_flags": [ "SYSCMD" ], "command_flags": [ "ADMIN" ], "acl_categories": [ "HYPERLOGLOG" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/ping.json000066400000000000000000000017461501533116600170600ustar00rootroot00000000000000{ "PING": { "summary": "Returns the server's liveliness response.", "complexity": "O(1)", "group": "connection", "since": "1.0.0", "arity": -1, "function": "pingCommand", "command_flags": [ "FAST", "SENTINEL" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "anyOf": [ { "const": "PONG", "description": "Default reply." }, { "type": "string", "description": "Relay of given `message`." } ] }, "arguments": [ { "name": "message", "type": "string", "optional": true } ] } } redis-8.0.2/src/commands/psetex.json000066400000000000000000000027621501533116600174320ustar00rootroot00000000000000{ "PSETEX": { "summary": "Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.", "complexity": "O(1)", "group": "string", "since": "2.6.0", "arity": 4, "function": "psetexCommand", "deprecated_since": "2.6.12", "replaced_by": "`SET` with the `PX` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "milliseconds", "type": "integer" }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/psubscribe.json000066400000000000000000000011771501533116600202620ustar00rootroot00000000000000{ "PSUBSCRIBE": { "summary": "Listens for messages published to channels that match one or more patterns.", "complexity": "O(N) where N is the number of patterns to subscribe to.", "group": "pubsub", "since": "2.0.0", "arity": -2, "function": "psubscribeCommand", "command_flags": [ "PUBSUB", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "arguments": [ { "name": "pattern", "type": "pattern", "multiple": true } ] } } redis-8.0.2/src/commands/psync.json000066400000000000000000000010671501533116600172530ustar00rootroot00000000000000{ "PSYNC": { "summary": "An internal command used in replication.", "group": "server", "since": "2.8.0", "arity": -3, "function": "syncCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NO_MULTI", "NOSCRIPT" ], "arguments": [ { "name": "replicationid", "type": "string" }, { "name": "offset", "type": "integer" } ] } } redis-8.0.2/src/commands/pttl.json000066400000000000000000000033571501533116600171060ustar00rootroot00000000000000{ "PTTL": { "summary": "Returns the expiration time in milliseconds of a key.", "complexity": "O(1)", "group": "generic", "since": "2.6.0", "arity": 2, "function": "pttlCommand", "history": [ [ "2.8.0", "Added the -2 reply." ] ], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "TTL in milliseconds.", "type": "integer", "minimum": 0 }, { "description": "The key exists but has no associated expire.", "const": -1 }, { "description": "The key does not exist.", "const": -2 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/publish.json000066400000000000000000000020451501533116600175620ustar00rootroot00000000000000{ "PUBLISH": { "summary": "Posts a message to a channel.", "complexity": "O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).", "group": "pubsub", "since": "2.0.0", "arity": 3, "function": "publishCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE", "FAST", "MAY_REPLICATE", "SENTINEL" ], "arguments": [ { "name": "channel", "type": "string" }, { "name": "message", "type": "string" } ], "reply_schema": { "description": "the number of clients that received the message. Note that in a Redis Cluster, only clients that are connected to the same node as the publishing client are included in the count", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/pubsub-channels.json000066400000000000000000000016211501533116600212040ustar00rootroot00000000000000{ "CHANNELS": { "summary": "Returns the active channels.", "complexity": "O(N) where N is the number of active channels, and assuming constant time pattern matching (relatively short channels and patterns)", "group": "pubsub", "since": "2.8.0", "arity": -2, "container": "PUBSUB", "function": "pubsubCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE" ], "arguments": [ { "name": "pattern", "type": "pattern", "optional": true } ], "reply_schema": { "description": "a list of active channels, optionally matching the specified pattern", "type": "array", "uniqueItems": true, "items": { "type": "string" } } } } redis-8.0.2/src/commands/pubsub-help.json000066400000000000000000000010451501533116600203410ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "pubsub", "since": "6.2.0", "arity": 2, "container": "PUBSUB", "function": "pubsubCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/pubsub-numpat.json000066400000000000000000000010461501533116600207160ustar00rootroot00000000000000{ "NUMPAT": { "summary": "Returns a count of unique pattern subscriptions.", "complexity": "O(1)", "group": "pubsub", "since": "2.8.0", "arity": 2, "container": "PUBSUB", "function": "pubsubCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE" ], "reply_schema": { "description": "the number of patterns all the clients are subscribed to", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/pubsub-numsub.json000066400000000000000000000015421501533116600207240ustar00rootroot00000000000000{ "NUMSUB": { "summary": "Returns a count of subscribers to channels.", "complexity": "O(N) for the NUMSUB subcommand, where N is the number of requested channels", "group": "pubsub", "since": "2.8.0", "arity": -2, "container": "PUBSUB", "function": "pubsubCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE" ], "arguments": [ { "name": "channel", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "description": "the number of subscribers per channel, each even element (including 0th) is channel name, each odd element is the number of subscribers", "type": "array" } } } redis-8.0.2/src/commands/pubsub-shardchannels.json000066400000000000000000000016341501533116600222320ustar00rootroot00000000000000{ "SHARDCHANNELS": { "summary": "Returns the active shard channels.", "complexity": "O(N) where N is the number of active shard channels, and assuming constant time pattern matching (relatively short shard channels).", "group": "pubsub", "since": "7.0.0", "arity": -2, "container": "PUBSUB", "function": "pubsubCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE" ], "arguments": [ { "name": "pattern", "type": "pattern", "optional": true } ], "reply_schema": { "description": "a list of active channels, optionally matching the specified pattern", "type": "array", "items": { "type": "string" }, "uniqueItems": true } } } redis-8.0.2/src/commands/pubsub-shardnumsub.json000066400000000000000000000016051501533116600217460ustar00rootroot00000000000000{ "SHARDNUMSUB": { "summary": "Returns the count of subscribers of shard channels.", "complexity": "O(N) for the SHARDNUMSUB subcommand, where N is the number of requested shard channels", "group": "pubsub", "since": "7.0.0", "arity": -2, "container": "PUBSUB", "function": "pubsubCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE" ], "arguments": [ { "name": "shardchannel", "type": "string", "optional": true, "multiple": true } ], "reply_schema": { "description": "the number of subscribers per shard channel, each even element (including 0th) is channel name, each odd element is the number of subscribers", "type": "array" } } } redis-8.0.2/src/commands/pubsub.json000066400000000000000000000003131501533116600174100ustar00rootroot00000000000000{ "PUBSUB": { "summary": "A container for Pub/Sub commands.", "complexity": "Depends on subcommand.", "group": "pubsub", "since": "2.8.0", "arity": -2 } } redis-8.0.2/src/commands/punsubscribe.json000066400000000000000000000012531501533116600206200ustar00rootroot00000000000000{ "PUNSUBSCRIBE": { "summary": "Stops listening to messages published to channels that match one or more patterns.", "complexity": "O(N) where N is the number of patterns to unsubscribe.", "group": "pubsub", "since": "2.0.0", "arity": -1, "function": "punsubscribeCommand", "command_flags": [ "PUBSUB", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "arguments": [ { "name": "pattern", "type": "pattern", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/quit.json000066400000000000000000000012261501533116600170760ustar00rootroot00000000000000{ "QUIT": { "summary": "Closes the connection.", "complexity": "O(1)", "group": "connection", "since": "1.0.0", "arity": -1, "function": "quitCommand", "deprecated_since": "7.2.0", "replaced_by": "just closing the connection", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "ALLOW_BUSY", "NOSCRIPT", "LOADING", "STALE", "FAST", "NO_AUTH" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/randomkey.json000066400000000000000000000015761501533116600201150ustar00rootroot00000000000000{ "RANDOMKEY": { "summary": "Returns a random key name from the database.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": 1, "function": "randomkeyCommand", "command_flags": [ "READONLY", "TOUCHES_ARBITRARY_KEYS" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:SPECIAL", "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "oneOf": [ { "description": "when the database is empty", "type": "null" }, { "description": "random key in db", "type": "string" } ] } } } redis-8.0.2/src/commands/readonly.json000066400000000000000000000007601501533116600177330ustar00rootroot00000000000000{ "READONLY": { "summary": "Enables read-only queries for a connection to a Redis Cluster replica node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 1, "function": "readonlyCommand", "command_flags": [ "FAST", "LOADING", "STALE" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/readwrite.json000066400000000000000000000007631501533116600201070ustar00rootroot00000000000000{ "READWRITE": { "summary": "Enables read-write queries for a connection to a Reids Cluster replica node.", "complexity": "O(1)", "group": "cluster", "since": "3.0.0", "arity": 1, "function": "readwriteCommand", "command_flags": [ "FAST", "LOADING", "STALE" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/rename.json000066400000000000000000000033301501533116600173610ustar00rootroot00000000000000{ "RENAME": { "summary": "Renames a key and overwrites the destination.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": 3, "function": "renameCommand", "history": [], "command_flags": [ "WRITE" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "newkey", "type": "key", "key_spec_index": 1 } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/renamenx.json000066400000000000000000000042621501533116600177340ustar00rootroot00000000000000{ "RENAMENX": { "summary": "Renames a key only when the target key name doesn't exist.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": 3, "function": "renamenxCommand", "history": [ [ "3.2.0", "The command no longer returns an error when source and destination names are the same." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "OW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "newkey", "type": "key", "key_spec_index": 1 } ], "reply_schema": { "oneOf": [ { "description": "key was renamed to newkey", "const": 1 }, { "description": "new key already exists", "const": 0 } ] } } } redis-8.0.2/src/commands/replconf.json000066400000000000000000000010121501533116600177150ustar00rootroot00000000000000{ "REPLCONF": { "summary": "An internal command for configuring the replication stream.", "complexity": "O(1)", "group": "server", "since": "3.0.0", "arity": -1, "function": "replconfCommand", "doc_flags": [ "SYSCMD" ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "ALLOW_BUSY" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/replicaof.json000066400000000000000000000034551501533116600200660ustar00rootroot00000000000000{ "REPLICAOF": { "summary": "Configures a server as replica of another, or promotes it to a master.", "complexity": "O(1)", "group": "server", "since": "5.0.0", "arity": 3, "function": "replicaofCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT", "STALE" ], "arguments": [ { "name": "args", "type": "oneof", "arguments": [ { "name": "host-port", "type": "block", "arguments": [ { "name": "host", "type": "string" }, { "name": "port", "type": "integer" } ] }, { "name": "no-one", "type": "block", "arguments": [ { "name": "no", "type": "pure-token", "token": "NO" }, { "name": "one", "type": "pure-token", "token": "ONE" } ] } ] } ], "reply_schema": { "description": "replicaOf status", "type": "string", "pattern": "OK*" } } } redis-8.0.2/src/commands/reset.json000066400000000000000000000010041501533116600172300ustar00rootroot00000000000000{ "RESET": { "summary": "Resets the connection.", "complexity": "O(1)", "group": "connection", "since": "6.2.0", "arity": 1, "function": "resetCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "NO_AUTH", "ALLOW_BUSY" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "RESET" } } } redis-8.0.2/src/commands/restore-asking.json000066400000000000000000000056331501533116600210570ustar00rootroot00000000000000{ "RESTORE-ASKING": { "summary": "An internal command for migrating keys in a cluster.", "complexity": "O(1) to create the new key and additional O(N*M) to reconstruct the serialized value, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1). However for sorted set values the complexity is O(N*M*log(N)) because inserting values into sorted sets is O(log(N)).", "group": "server", "since": "3.0.0", "arity": -4, "function": "restoreCommand", "history": [ [ "3.0.0", "Added the `REPLACE` modifier." ], [ "5.0.0", "Added the `ABSTTL` modifier." ], [ "5.0.0", "Added the `IDLETIME` and `FREQ` options." ] ], "doc_flags": [ "SYSCMD" ], "command_flags": [ "WRITE", "DENYOOM", "ASKING" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "ttl", "type": "integer" }, { "name": "serialized-value", "type": "string" }, { "name": "replace", "token": "REPLACE", "type": "pure-token", "optional": true, "since": "3.0.0" }, { "name": "absttl", "token": "ABSTTL", "type": "pure-token", "optional": true, "since": "5.0.0" }, { "token": "IDLETIME", "name": "seconds", "type": "integer", "optional": true, "since": "5.0.0" }, { "token": "FREQ", "name": "frequency", "type": "integer", "optional": true, "since": "5.0.0" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/restore.json000066400000000000000000000055201501533116600176000ustar00rootroot00000000000000{ "RESTORE": { "summary": "Creates a key from the serialized representation of a value.", "complexity": "O(1) to create the new key and additional O(N*M) to reconstruct the serialized value, where N is the number of Redis objects composing the value and M their average size. For small string values the time complexity is thus O(1)+O(1*M) where M is small, so simply O(1). However for sorted set values the complexity is O(N*M*log(N)) because inserting values into sorted sets is O(log(N)).", "group": "generic", "since": "2.6.0", "arity": -4, "function": "restoreCommand", "history": [ [ "3.0.0", "Added the `REPLACE` modifier." ], [ "5.0.0", "Added the `ABSTTL` modifier." ], [ "5.0.0", "Added the `IDLETIME` and `FREQ` options." ] ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "ttl", "type": "integer" }, { "name": "serialized-value", "type": "string" }, { "name": "replace", "token": "REPLACE", "type": "pure-token", "optional": true, "since": "3.0.0" }, { "name": "absttl", "token": "ABSTTL", "type": "pure-token", "optional": true, "since": "5.0.0" }, { "token": "IDLETIME", "name": "seconds", "type": "integer", "optional": true, "since": "5.0.0" }, { "token": "FREQ", "name": "frequency", "type": "integer", "optional": true, "since": "5.0.0" } ] } } redis-8.0.2/src/commands/role.json000066400000000000000000000122651501533116600170620ustar00rootroot00000000000000{ "ROLE": { "summary": "Returns the replication role.", "complexity": "O(1)", "group": "server", "since": "2.8.12", "arity": 1, "function": "roleCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "SENTINEL" ], "acl_categories": [ "ADMIN", "DANGEROUS" ], "reply_schema": { "oneOf": [ { "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "const": "master" }, { "description": "current replication master offset", "type": "integer" }, { "description": "connected replicas", "type": "array", "items": { "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "description": "replica ip", "type": "string" }, { "description": "replica port", "type": "string" }, { "description": "last acknowledged replication offset", "type": "string" } ] } } ] }, { "type": "array", "minItems": 5, "maxItems": 5, "items": [ { "const": "slave" }, { "description": "ip of master", "type": "string" }, { "description": "port number of master", "type": "integer" }, { "description": "state of the replication from the point of view of the master", "oneOf": [ { "description": "the instance is in handshake with its master", "const": "handshake" }, { "description": "the instance in not active", "const": "none" }, { "description": "the instance needs to connect to its master", "const": "connect" }, { "description": "the master-replica connection is in progress", "const": "connecting" }, { "description": "the master and replica are trying to perform the synchronization", "const": "sync" }, { "description": "the replica is online", "const": "connected" }, { "description": "instance state is unknown", "const": "unknown" } ] }, { "description": "the amount of data received from the replica so far in terms of master replication offset", "type": "integer" } ] }, { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "const": "sentinel" }, { "description": "list of master names monitored by this sentinel instance", "type": "array", "items": { "type": "string" } } ] } ] } } } redis-8.0.2/src/commands/rpop.json000066400000000000000000000040451501533116600170760ustar00rootroot00000000000000{ "RPOP": { "summary": "Returns and removes the last elements of a list. Deletes the list if the last element was popped.", "complexity": "O(N) where N is the number of elements returned", "group": "list", "since": "1.0.0", "arity": -2, "function": "rpopCommand", "history": [ [ "6.2.0", "Added the `count` argument." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "null", "description": "Key does not exist." }, { "type": "string", "description": "When 'COUNT' was not given, the value of the last element." }, { "type": "array", "description": "When 'COUNT' was given, list of popped elements.", "items": { "type": "string" } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer", "optional": true, "since": "6.2.0" } ] } } redis-8.0.2/src/commands/rpoplpush.json000066400000000000000000000043761501533116600201610ustar00rootroot00000000000000{ "RPOPLPUSH": { "summary": "Returns the last element of a list after removing and pushing it to another list. Deletes the list if the last element was popped.", "complexity": "O(1)", "group": "list", "since": "1.2.0", "arity": 3, "function": "rpoplpushCommand", "deprecated_since": "6.2.0", "replaced_by": "`LMOVE` with the `RIGHT` and `LEFT` arguments", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "string", "description": "The element being popped and pushed." }, { "type": "null", "description": "Source list is empty." } ] }, "arguments": [ { "name": "source", "type": "key", "key_spec_index": 0 }, { "name": "destination", "type": "key", "key_spec_index": 1 } ] } } redis-8.0.2/src/commands/rpush.json000066400000000000000000000031431501533116600172550ustar00rootroot00000000000000{ "RPUSH": { "summary": "Appends one or more elements to a list. Creates the key if it doesn't exist.", "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", "group": "list", "since": "1.0.0", "arity": -3, "function": "rpushCommand", "history": [ [ "2.4.0", "Accepts multiple `element` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Length of the list after the push operations.", "type": "integer", "minimum": 1 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "element", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/rpushx.json000066400000000000000000000031171501533116600174460ustar00rootroot00000000000000{ "RPUSHX": { "summary": "Appends an element to a list only when the list exists.", "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", "group": "list", "since": "2.2.0", "arity": -3, "function": "rpushxCommand", "history": [ [ "4.0.0", "Accepts multiple `element` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "LIST" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Length of the list after the push operation.", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "element", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/sadd.json000066400000000000000000000031721501533116600170310ustar00rootroot00000000000000{ "SADD": { "summary": "Adds one or more members to a set. Creates the key if it doesn't exist.", "complexity": "O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.", "group": "set", "since": "1.0.0", "arity": -3, "function": "saddCommand", "history": [ [ "2.4.0", "Accepts multiple `member` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Number of elements that were added to the set, not including all the elements already present in the set.", "type": "integer" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/save.json000066400000000000000000000007421501533116600170540ustar00rootroot00000000000000{ "SAVE": { "summary": "Synchronously saves the database(s) to disk.", "complexity": "O(N) where N is the total number of keys in all databases", "group": "server", "since": "1.0.0", "arity": 1, "function": "saveCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT", "NO_MULTI" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/scan.json000066400000000000000000000040121501533116600170340ustar00rootroot00000000000000{ "SCAN": { "summary": "Iterates over the key names in the database.", "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", "group": "generic", "since": "2.8.0", "arity": -2, "function": "scanCommand", "history": [ [ "6.0.0", "Added the `TYPE` subcommand." ] ], "command_flags": [ "READONLY", "TOUCHES_ARBITRARY_KEYS" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT", "REQUEST_POLICY:SPECIAL", "RESPONSE_POLICY:SPECIAL" ], "arguments": [ { "name": "cursor", "type": "integer" }, { "token": "MATCH", "name": "pattern", "type": "pattern", "optional": true }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true }, { "token": "TYPE", "name": "type", "type": "string", "optional": true, "since": "6.0.0" } ], "reply_schema": { "description": "cursor and scan response in array form", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "cursor", "type": "string" }, { "description": "list of keys", "type": "array", "items": { "type": "string" } } ] } } } redis-8.0.2/src/commands/scard.json000066400000000000000000000022621501533116600172110ustar00rootroot00000000000000{ "SCARD": { "summary": "Returns the number of members in a set.", "complexity": "O(1)", "group": "set", "since": "1.0.0", "arity": 2, "function": "scardCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The cardinality (number of elements) of the set, or 0 if key does not exist.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/script-debug.json000066400000000000000000000021611501533116600205030ustar00rootroot00000000000000{ "DEBUG": { "summary": "Sets the debug mode of server-side Lua scripts.", "complexity": "O(1)", "group": "scripting", "since": "3.2.0", "arity": 3, "container": "SCRIPT", "function": "scriptCommand", "command_flags": [ "NOSCRIPT" ], "acl_categories": [ "SCRIPTING" ], "arguments": [ { "name": "mode", "type": "oneof", "arguments": [ { "name": "yes", "type": "pure-token", "token": "YES" }, { "name": "sync", "type": "pure-token", "token": "SYNC" }, { "name": "no", "type": "pure-token", "token": "NO" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/script-exists.json000066400000000000000000000025311501533116600207350ustar00rootroot00000000000000{ "EXISTS": { "summary": "Determines whether server-side Lua scripts exist in the script cache.", "complexity": "O(N) with N being the number of scripts to check (so checking a single script is an O(1) operation).", "group": "scripting", "since": "2.6.0", "arity": -3, "container": "SCRIPT", "function": "scriptCommand", "command_flags": [ "NOSCRIPT" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:AGG_LOGICAL_AND" ], "arguments": [ { "name": "sha1", "type": "string", "multiple": true } ], "reply_schema": { "description": "An array of integers that correspond to the specified SHA1 digest arguments.", "type": "array", "items": { "oneOf": [ { "description": "sha1 hash exists in script cache", "const": 1 }, { "description": "sha1 hash does not exist in script cache", "const": 0 } ] } } } } redis-8.0.2/src/commands/script-flush.json000066400000000000000000000025461501533116600205450ustar00rootroot00000000000000{ "FLUSH": { "summary": "Removes all server-side Lua scripts from the script cache.", "complexity": "O(N) with N being the number of scripts in cache", "group": "scripting", "since": "2.6.0", "arity": -2, "container": "SCRIPT", "function": "scriptCommand", "history": [ [ "6.2.0", "Added the `ASYNC` and `SYNC` flushing mode modifiers." ] ], "command_flags": [ "NOSCRIPT" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ { "name": "flush-type", "type": "oneof", "optional": true, "since": "6.2.0", "arguments": [ { "name": "async", "type": "pure-token", "token": "ASYNC" }, { "name": "sync", "type": "pure-token", "token": "SYNC" } ] } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/script-help.json000066400000000000000000000011471501533116600203500ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "scripting", "since": "5.0.0", "arity": 2, "container": "SCRIPT", "function": "scriptCommand", "command_flags": [ "LOADING", "STALE" ], "acl_categories": [ "SCRIPTING" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/script-kill.json000066400000000000000000000011401501533116600203440ustar00rootroot00000000000000{ "KILL": { "summary": "Terminates a server-side Lua script during execution.", "complexity": "O(1)", "group": "scripting", "since": "2.6.0", "arity": 2, "container": "SCRIPT", "function": "scriptCommand", "command_flags": [ "NOSCRIPT", "ALLOW_BUSY" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:ONE_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/script-load.json000066400000000000000000000015511501533116600203360ustar00rootroot00000000000000{ "LOAD": { "summary": "Loads a server-side Lua script to the script cache.", "complexity": "O(N) with N being the length in bytes of the script body.", "group": "scripting", "since": "2.6.0", "arity": 3, "container": "SCRIPT", "function": "scriptCommand", "command_flags": [ "NOSCRIPT", "STALE" ], "acl_categories": [ "SCRIPTING" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "arguments": [ { "name": "script", "type": "string" } ], "reply_schema": { "description": "The SHA1 digest of the script added into the script cache", "type": "string" } } } redis-8.0.2/src/commands/script.json000066400000000000000000000003351501533116600174200ustar00rootroot00000000000000{ "SCRIPT": { "summary": "A container for Lua scripts management commands.", "complexity": "Depends on subcommand.", "group": "scripting", "since": "2.6.0", "arity": -2 } } redis-8.0.2/src/commands/sdiff.json000066400000000000000000000026301501533116600172070ustar00rootroot00000000000000{ "SDIFF": { "summary": "Returns the difference of multiple sets.", "complexity": "O(N) where N is the total number of elements in all given sets.", "group": "set", "since": "1.0.0", "arity": -2, "function": "sdiffCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List with the members of the resulting set.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/sdiffstore.json000066400000000000000000000036101501533116600202630ustar00rootroot00000000000000{ "SDIFFSTORE": { "summary": "Stores the difference of multiple sets in a key.", "complexity": "O(N) where N is the total number of elements in all given sets.", "group": "set", "since": "1.0.0", "arity": -3, "function": "sdiffstoreCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Number of the elements in the resulting set.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true } ] } } redis-8.0.2/src/commands/select.json000066400000000000000000000011031501533116600173650ustar00rootroot00000000000000{ "SELECT": { "summary": "Changes the selected database.", "complexity": "O(1)", "group": "connection", "since": "1.0.0", "arity": 2, "function": "selectCommand", "command_flags": [ "LOADING", "STALE", "FAST" ], "acl_categories": [ "CONNECTION" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "index", "type": "integer" } ] } } redis-8.0.2/src/commands/sentinel-ckquorum.json000066400000000000000000000013671501533116600216070ustar00rootroot00000000000000{ "CKQUORUM": { "summary": "Checks for a Redis Sentinel quorum.", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "string", "description": "Returns OK if the current Sentinel configuration is able to reach the quorum needed to failover a master, and the majority needed to authorize the failover.", "pattern": "OK" }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-config.json000066400000000000000000000101131501533116600211730ustar00rootroot00000000000000{ "CONFIG": { "summary": "Configures Redis Sentinel.", "complexity": "O(N) when N is the number of configuration parameters provided", "group": "sentinel", "since": "6.2.0", "arity": -4, "container": "SENTINEL", "function": "sentinelCommand", "history": [ [ "7.2.0", "Added the ability to set and get multiple parameters in one call." ] ], "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "oneOf": [ { "type": "object", "description": "When 'SENTINEL-CONFIG GET' is called, returns a map.", "properties": { "resolve-hostnames": { "oneOf": [ { "const": "yes" }, { "const": "no" } ] }, "announce-hostnames": { "oneOf": [ { "const": "yes" }, { "const": "no" } ] }, "announce-ip": { "type": "string" }, "announce-port": { "type": "string" }, "sentinel-user": { "type": "string" }, "sentinel-pass": { "type": "string" }, "loglevel": { "oneOf": [ { "const": "debug" }, { "const": "verbose" }, { "const": "notice" }, { "const": "warning" }, { "const": "nothing" }, { "const": "unknown" } ] } }, "additionalProperties": false }, { "const": "OK", "description": "When 'SENTINEL-CONFIG SET' is called, returns OK on success." } ] }, "arguments": [ { "name":"action", "type":"oneof", "arguments":[ { "name":"set", "token":"SET", "type":"block", "multiple": true, "arguments":[ { "name":"parameter", "type":"string" }, { "name":"value", "type":"string" } ] }, { "token":"GET", "name":"parameter", "type":"string", "multiple": true } ] } ] } } redis-8.0.2/src/commands/sentinel-debug.json000066400000000000000000000027001501533116600210170ustar00rootroot00000000000000{ "DEBUG": { "summary": "Lists or updates the current configurable parameters of Redis Sentinel.", "complexity": "O(N) where N is the number of configurable parameters", "group": "sentinel", "since": "7.0.0", "arity": -2, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "oneOf": [ { "description": "The configuration update was successful.", "const": "OK" }, { "description": "List of configurable time parameters and their values (milliseconds).", "type": "object", "additionalProperties": { "type": "string" } } ] }, "arguments": [ { "name": "data", "type": "block", "optional": true, "multiple": true, "arguments": [ { "name": "parameter", "type": "string" }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/sentinel-failover.json000066400000000000000000000012431501533116600215410ustar00rootroot00000000000000{ "FAILOVER": { "summary": "Forces a Redis Sentinel failover.", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "const": "OK", "description": "Force a fail over as if the master was not reachable, and without asking for agreement to other Sentinels." }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-flushconfig.json000066400000000000000000000010741501533116600222430ustar00rootroot00000000000000{ "FLUSHCONFIG": { "summary": "Rewrites the Redis Sentinel configuration file.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": 2, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "const": "OK", "description": "Force Sentinel to rewrite its configuration on disk, including the current Sentinel state." } } } redis-8.0.2/src/commands/sentinel-get-master-addr-by-name.json000066400000000000000000000017371501533116600242500ustar00rootroot00000000000000{ "GET-MASTER-ADDR-BY-NAME": { "summary": "Returns the port and address of a master Redis instance.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "IP addr or hostname." }, { "type": "string", "description": "Port.", "pattern": "[0-9]+" } ] }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-help.json000066400000000000000000000011401501533116600206560ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "sentinel", "since": "6.2.0", "arity": 2, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "LOADING", "STALE", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/sentinel-info-cache.json000066400000000000000000000050661501533116600217350ustar00rootroot00000000000000{ "INFO-CACHE": { "summary": "Returns the cached `INFO` replies from the deployment's instances.", "complexity": "O(N) where N is the number of instances", "group": "sentinel", "since": "3.2.0", "arity": -3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "This is actually a map, the odd entries are a master name, and the even entries are the last cached INFO output from that master and all its replicas.", "minItems": 0, "maxItems": 4294967295, "items": [ { "oneOf": [ { "type": "string", "description": "The master name." }, { "type": "array", "description": "This is an array of pairs, the odd entries are the INFO age, and the even entries are the cached INFO string. The first pair belong to the master and the rest are its replicas.", "minItems": 2, "maxItems": 2, "items": [ { "description": "The number of milliseconds since when the INFO was cached.", "type": "integer" }, { "description": "The cached INFO string or null.", "oneOf": [ { "description": "The cached INFO string.", "type": "string" }, { "description": "No cached INFO string.", "type": "null" } ] } ] } ] } ] }, "arguments": [ { "name": "nodename", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/sentinel-is-master-down-by-addr.json000066400000000000000000000031261501533116600241250ustar00rootroot00000000000000{ "IS-MASTER-DOWN-BY-ADDR": { "summary": "Determines whether a master Redis instance is down.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": 6, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "oneOf": [ { "const": 0, "description": "Master is up." }, { "const": 1, "description": "Master is down." } ] }, { "type": "string", "description": "Sentinel address." }, { "type": "integer", "description": "Port." } ] }, "arguments": [ { "name": "ip", "type": "string" }, { "name": "port", "type": "integer" }, { "name": "current-epoch", "type": "integer" }, { "name": "runid", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-master.json000066400000000000000000000013451501533116600212300ustar00rootroot00000000000000{ "MASTER": { "summary": "Returns the state of a master Redis instance.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "object", "description": "The state and info of the specified master.", "additionalProperties": { "type": "string" } }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-masters.json000066400000000000000000000013261501533116600214120ustar00rootroot00000000000000{ "MASTERS": { "summary": "Returns a list of monitored Redis masters.", "complexity": "O(N) where N is the number of masters", "group": "sentinel", "since": "2.8.4", "arity": 2, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "List of monitored Redis masters, and their state.", "items": { "type": "object", "additionalProperties": { "type": "string" } } } } } redis-8.0.2/src/commands/sentinel-monitor.json000066400000000000000000000014751501533116600214300ustar00rootroot00000000000000{ "MONITOR": { "summary": "Starts monitoring.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": 6, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "name", "type": "string" }, { "name": "ip", "type": "string" }, { "name": "port", "type": "integer" }, { "name": "quorum", "type": "integer" } ] } } redis-8.0.2/src/commands/sentinel-myid.json000066400000000000000000000007671501533116600207060ustar00rootroot00000000000000{ "MYID": { "summary": "Returns the Redis Sentinel instance ID.", "complexity": "O(1)", "group": "sentinel", "since": "6.2.0", "arity": 2, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "description": "Node ID of the sentinel instance.", "type": "string" } } } redis-8.0.2/src/commands/sentinel-pending-scripts.json000066400000000000000000000032771501533116600230540ustar00rootroot00000000000000{ "PENDING-SCRIPTS": { "summary": "Returns information about pending scripts for Redis Sentinel.", "group": "sentinel", "since": "2.8.4", "arity": 2, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "List of pending scripts.", "items": { "type": "object", "additionalProperties": false, "properties": { "argv": { "type": "array", "description": "Script arguments.", "items": { "type": "string" } }, "flags": { "type": "string", "description": "Script flags." }, "pid": { "type": "string", "description": "Script pid." }, "run-time": { "type": "string", "description": "Script run-time." }, "run-delay": { "type": "string", "description": "Script run-delay." }, "retry-num": { "type": "string", "description": "Number of times we tried to execute the script." } } } } } } redis-8.0.2/src/commands/sentinel-remove.json000066400000000000000000000010461501533116600212300ustar00rootroot00000000000000{ "REMOVE": { "summary": "Stops monitoring.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-replicas.json000066400000000000000000000015441501533116600215400ustar00rootroot00000000000000{ "REPLICAS": { "summary": "Returns a list of the monitored Redis replicas.", "complexity": "O(N) where N is the number of replicas", "group": "sentinel", "since": "5.0.0", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "List of replicas for this master, and their state.", "items": { "type": "object", "additionalProperties": { "type": "string" } } }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-reset.json000066400000000000000000000012651501533116600210600ustar00rootroot00000000000000{ "RESET": { "summary": "Resets Redis masters by name matching a pattern.", "complexity": "O(N) where N is the number of monitored masters", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "integer", "description": "The number of masters that were reset." }, "arguments": [ { "name": "pattern", "type": "pattern" } ] } } redis-8.0.2/src/commands/sentinel-sentinels.json000066400000000000000000000015261501533116600217420ustar00rootroot00000000000000{ "SENTINELS": { "summary": "Returns a list of Sentinel instances.", "complexity": "O(N) where N is the number of Sentinels", "group": "sentinel", "since": "2.8.4", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "List of sentinel instances, and their state.", "items": { "type": "object", "additionalProperties": { "type": "string" } } }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel-set.json000066400000000000000000000017701501533116600205320ustar00rootroot00000000000000{ "SET": { "summary": "Changes the configuration of a monitored Redis master.", "complexity": "O(1)", "group": "sentinel", "since": "2.8.4", "arity": -5, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "master-name", "type": "string" }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "option", "type": "string" }, { "name": "value", "type": "string" } ] } ] } } redis-8.0.2/src/commands/sentinel-simulate-failure.json000066400000000000000000000027331501533116600232070ustar00rootroot00000000000000{ "SIMULATE-FAILURE": { "summary": "Simulates failover scenarios.", "group": "sentinel", "since": "3.2.0", "arity": -3, "container": "SENTINEL", "function": "sentinelCommand", "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "oneOf": [ { "description": "The simulated flag was set.", "const": "OK" }, { "description": "Supported simulates flags. Returned in case `HELP` was used.", "type": "array", "items": { "type": "string" } } ] }, "arguments": [ { "name": "mode", "type": "oneof", "optional":true, "multiple":true, "arguments": [ { "name": "crash-after-election", "type": "pure-token" }, { "name": "crash-after-promotion", "type": "pure-token" }, { "name": "help", "type": "pure-token" } ] } ] } } redis-8.0.2/src/commands/sentinel-slaves.json000066400000000000000000000017451501533116600212360ustar00rootroot00000000000000{ "SLAVES": { "summary": "Returns a list of the monitored replicas.", "complexity": "O(N) where N is the number of replicas.", "group": "sentinel", "since": "2.8.0", "arity": 3, "container": "SENTINEL", "function": "sentinelCommand", "deprecated_since": "5.0.0", "replaced_by": "`SENTINEL REPLICAS`", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ], "reply_schema": { "type": "array", "description": "List of monitored replicas, and their state.", "items": { "type": "object", "additionalProperties": { "type": "string" } } }, "arguments": [ { "name": "master-name", "type": "string" } ] } } redis-8.0.2/src/commands/sentinel.json000066400000000000000000000005051501533116600177340ustar00rootroot00000000000000{ "SENTINEL": { "summary": "A container for Redis Sentinel commands.", "complexity": "Depends on subcommand.", "group": "sentinel", "since": "2.8.4", "arity": -2, "command_flags": [ "ADMIN", "SENTINEL", "ONLY_SENTINEL" ] } } redis-8.0.2/src/commands/set.json000066400000000000000000000110131501533116600167020ustar00rootroot00000000000000{ "SET": { "summary": "Sets the string value of a key, ignoring its type. The key is created if it doesn't exist.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": -3, "function": "setCommand", "get_keys_function": "setGetKeys", "history": [ [ "2.6.12", "Added the `EX`, `PX`, `NX` and `XX` options." ], [ "6.0.0", "Added the `KEEPTTL` option." ], [ "6.2.0", "Added the `GET`, `EXAT` and `PXAT` option." ], [ "7.0.0", "Allowed the `NX` and `GET` options to be used together." ] ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STRING" ], "key_specs": [ { "notes": "RW and ACCESS due to the optional `GET` argument", "flags": [ "RW", "ACCESS", "UPDATE", "VARIABLE_FLAGS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf":[ { "description": "`GET` not given: Operation was aborted (conflict with one of the `XX`/`NX` options).", "type": "null" }, { "description": "`GET` not given: The key was set.", "const": "OK" }, { "description": "`GET` given: The key didn't exist before the `SET`", "type": "null" }, { "description": "`GET` given: The previous value of the key", "type": "string" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "value", "type": "string" }, { "name": "condition", "type": "oneof", "optional": true, "since": "2.6.12", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" } ] }, { "name": "get", "token": "GET", "type": "pure-token", "optional": true, "since": "6.2.0" }, { "name": "expiration", "type": "oneof", "optional": true, "arguments": [ { "name": "seconds", "type": "integer", "token": "EX", "since": "2.6.12" }, { "name": "milliseconds", "type": "integer", "token": "PX", "since": "2.6.12" }, { "name": "unix-time-seconds", "type": "unix-time", "token": "EXAT", "since": "6.2.0" }, { "name": "unix-time-milliseconds", "type": "unix-time", "token": "PXAT", "since": "6.2.0" }, { "name": "keepttl", "type": "pure-token", "token": "KEEPTTL", "since": "6.0.0" } ] } ] } } redis-8.0.2/src/commands/setbit.json000066400000000000000000000030721501533116600174070ustar00rootroot00000000000000{ "SETBIT": { "summary": "Sets or clears the bit at offset of the string value. Creates the key if it doesn't exist.", "complexity": "O(1)", "group": "bitmap", "since": "2.2.0", "arity": 4, "function": "setbitCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "BITMAP" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The original bit value stored at offset.", "oneOf": [ { "const": 0 }, { "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "offset", "type": "integer" }, { "name": "value", "type": "integer" } ] } } redis-8.0.2/src/commands/setex.json000066400000000000000000000027271501533116600172530ustar00rootroot00000000000000{ "SETEX": { "summary": "Sets the string value and expiration time of a key. Creates the key if it doesn't exist.", "complexity": "O(1)", "group": "string", "since": "2.0.0", "arity": 4, "function": "setexCommand", "deprecated_since": "2.6.12", "replaced_by": "`SET` with the `EX` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "seconds", "type": "integer" }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/setnx.json000066400000000000000000000031631501533116600172570ustar00rootroot00000000000000{ "SETNX": { "summary": "Set the string value of a key only when the key doesn't exist.", "complexity": "O(1)", "group": "string", "since": "1.0.0", "arity": 3, "function": "setnxCommand", "deprecated_since": "2.6.12", "replaced_by": "`SET` with the `NX` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "OW", "INSERT" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "The key was set.", "const": 0 }, { "description": "The key was not set.", "const": 1 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/setrange.json000066400000000000000000000032311501533116600177220ustar00rootroot00000000000000{ "SETRANGE": { "summary": "Overwrites a part of a string value with another by an offset. Creates the key if it doesn't exist.", "complexity": "O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.", "group": "string", "since": "2.2.0", "arity": 4, "function": "setrangeCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Length of the string after it was modified by the command.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "offset", "type": "integer" }, { "name": "value", "type": "string" } ] } } redis-8.0.2/src/commands/sflush.json000066400000000000000000000041541501533116600174230ustar00rootroot00000000000000{ "SFLUSH": { "summary": "Remove all keys from selected range of slots.", "complexity": "O(N)+O(k) where N is the number of keys and k is the number of slots.", "group": "server", "since": "8.0.0", "arity": -3, "function": "sflushCommand", "command_flags": [ "WRITE", "EXPERIMENTAL" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "command_tips": [ ], "reply_schema": { "description": "List of slot ranges", "type": "array", "minItems": 0, "maxItems": 4294967295, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "start slot number", "type": "integer" }, { "description": "end slot number", "type": "integer" } ] } }, "arguments": [ { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "slot-start", "type": "integer" }, { "name": "slot-last", "type": "integer" } ] }, { "name": "flush-type", "type": "oneof", "optional": true, "arguments": [ { "name": "async", "type": "pure-token", "token": "ASYNC" }, { "name": "sync", "type": "pure-token", "token": "SYNC" } ] } ] } } redis-8.0.2/src/commands/shutdown.json000066400000000000000000000041561501533116600177740ustar00rootroot00000000000000{ "SHUTDOWN": { "summary": "Synchronously saves the database(s) to disk and shuts down the Redis server.", "complexity": "O(N) when saving, where N is the total number of keys in all databases when saving data, otherwise O(1)", "group": "server", "since": "1.0.0", "arity": -1, "function": "shutdownCommand", "history": [ [ "7.0.0", "Added the `NOW`, `FORCE` and `ABORT` modifiers." ] ], "command_flags": [ "ADMIN", "NOSCRIPT", "LOADING", "STALE", "NO_MULTI", "SENTINEL", "ALLOW_BUSY" ], "arguments": [ { "name": "save-selector", "type": "oneof", "optional": true, "arguments": [ { "name": "nosave", "type": "pure-token", "token": "NOSAVE" }, { "name": "save", "type": "pure-token", "token": "SAVE" } ] }, { "name": "now", "type": "pure-token", "token": "NOW", "optional": true, "since": "7.0.0" }, { "name": "force", "type": "pure-token", "token": "FORCE", "optional": true, "since": "7.0.0" }, { "name": "abort", "type": "pure-token", "token": "ABORT", "optional": true, "since": "7.0.0" } ], "reply_schema": { "description": "OK if ABORT was specified and shutdown was aborted. On successful shutdown, nothing is returned since the server quits and the connection is closed. On failure, an error is returned.", "const": "OK" } } } redis-8.0.2/src/commands/sinter.json000066400000000000000000000026671501533116600174320ustar00rootroot00000000000000{ "SINTER": { "summary": "Returns the intersect of multiple sets.", "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", "group": "set", "since": "1.0.0", "arity": -2, "function": "sinterCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List with the members of the resulting set.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/sintercard.json000066400000000000000000000032021501533116600202460ustar00rootroot00000000000000{ "SINTERCARD": { "summary": "Returns the number of members of the intersect of multiple sets.", "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", "group": "set", "since": "7.0.0", "arity": -3, "function": "sinterCardCommand", "get_keys_function": "sintercardGetKeys", "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "description": "Number of the elements in the resulting intersection.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "token": "LIMIT", "name": "limit", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/sinterstore.json000066400000000000000000000036441501533116600205030ustar00rootroot00000000000000{ "SINTERSTORE": { "summary": "Stores the intersect of multiple sets in a key.", "complexity": "O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.", "group": "set", "since": "1.0.0", "arity": -3, "function": "sinterstoreCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Number of the elements in the result set.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true } ] } } redis-8.0.2/src/commands/sismember.json000066400000000000000000000027341501533116600201070ustar00rootroot00000000000000{ "SISMEMBER": { "summary": "Determines whether a member belongs to a set.", "complexity": "O(1)", "group": "set", "since": "1.0.0", "arity": 3, "function": "sismemberCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": 0, "description": "The element is not a member of the set, or the key does not exist." }, { "const": 1, "description": "The element is a member of the set." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string" } ] } } redis-8.0.2/src/commands/slaveof.json000066400000000000000000000036671501533116600175660ustar00rootroot00000000000000{ "SLAVEOF": { "summary": "Sets a Redis server as a replica of another, or promotes it to being a master.", "complexity": "O(1)", "group": "server", "since": "1.0.0", "arity": 3, "function": "replicaofCommand", "deprecated_since": "5.0.0", "replaced_by": "`REPLICAOF`", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NOSCRIPT", "STALE" ], "arguments": [ { "name": "args", "type": "oneof", "arguments": [ { "name": "host-port", "type": "block", "arguments": [ { "name": "host", "type": "string" }, { "name": "port", "type": "integer" } ] }, { "name": "no-one", "type": "block", "arguments": [ { "name": "no", "type": "pure-token", "token": "NO" }, { "name": "one", "type": "pure-token", "token": "ONE" } ] } ] } ], "reply_schema": { "description": "slaveOf status", "type": "string", "pattern": "OK*" } } } redis-8.0.2/src/commands/slowlog-get.json000066400000000000000000000045251501533116600203640ustar00rootroot00000000000000{ "GET": { "summary": "Returns the slow log's entries.", "complexity": "O(N) where N is the number of entries returned", "group": "server", "since": "2.2.12", "arity": -2, "container": "SLOWLOG", "function": "slowlogCommand", "history": [ [ "4.0.0", "Added client IP address, port and name to the reply." ] ], "command_flags": [ "ADMIN", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "type": "array", "description": "Entries from the slow log in chronological order.", "uniqueItems": true, "items": { "type": "array", "minItems": 6, "maxItems": 6, "items": [ { "type": "integer", "description": "Slow log entry ID." }, { "type": "integer", "description": "The unix timestamp at which the logged command was processed.", "minimum": 0 }, { "type": "integer", "description": "The amount of time needed for its execution, in microseconds.", "minimum": 0 }, { "type": "array", "description": "The arguments of the command.", "items": { "type": "string" } }, { "type": "string", "description": "Client IP address and port." }, { "type": "string", "description": "Client name if set via the CLIENT SETNAME command." } ] } }, "arguments": [ { "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/slowlog-help.json000066400000000000000000000010431501533116600205250ustar00rootroot00000000000000{ "HELP": { "summary": "Show helpful text about the different subcommands", "complexity": "O(1)", "group": "server", "since": "6.2.0", "arity": 2, "container": "SLOWLOG", "function": "slowlogCommand", "command_flags": [ "LOADING", "STALE" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/slowlog-len.json000066400000000000000000000012471501533116600203610ustar00rootroot00000000000000{ "LEN": { "summary": "Returns the number of entries in the slow log.", "complexity": "O(1)", "group": "server", "since": "2.2.12", "arity": 2, "container": "SLOWLOG", "function": "slowlogCommand", "command_flags": [ "ADMIN", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:AGG_SUM", "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "type": "integer", "description": "Number of entries in the slow log.", "minimum": 0 } } } redis-8.0.2/src/commands/slowlog-reset.json000066400000000000000000000011201501533116600207130ustar00rootroot00000000000000{ "RESET": { "summary": "Clears all entries from the slow log.", "complexity": "O(N) where N is the number of entries in the slowlog", "group": "server", "since": "2.2.12", "arity": 2, "container": "SLOWLOG", "function": "slowlogCommand", "command_flags": [ "ADMIN", "LOADING", "STALE" ], "command_tips": [ "REQUEST_POLICY:ALL_NODES", "RESPONSE_POLICY:ALL_SUCCEEDED" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/slowlog.json000066400000000000000000000003161501533116600176010ustar00rootroot00000000000000{ "SLOWLOG": { "summary": "A container for slow log commands.", "complexity": "Depends on subcommand.", "group": "server", "since": "2.2.12", "arity": -2 } } redis-8.0.2/src/commands/smembers.json000066400000000000000000000025011501533116600177260ustar00rootroot00000000000000{ "SMEMBERS": { "summary": "Returns all members of a set.", "complexity": "O(N) where N is the set cardinality.", "group": "set", "since": "1.0.0", "arity": 2, "function": "smembersCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "All elements of the set.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/smismember.json000066400000000000000000000035121501533116600202570ustar00rootroot00000000000000{ "SMISMEMBER": { "summary": "Determines whether multiple members belong to a set.", "complexity": "O(N) where N is the number of elements being checked for membership", "group": "set", "since": "6.2.0", "arity": -3, "function": "smismemberCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List representing the membership of the given elements, in the same order as they are requested.", "minItems": 1, "items": { "oneOf": [ { "const": 0, "description": "Not a member of the set or the key does not exist." }, { "const": 1, "description": "A member of the set." } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/smove.json000066400000000000000000000041411501533116600172440ustar00rootroot00000000000000{ "SMOVE": { "summary": "Moves a member from one set to another.", "complexity": "O(1)", "group": "set", "since": "1.0.0", "arity": 4, "function": "smoveCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "const": 1, "description": "Element is moved." }, { "const": 0, "description": "The element is not a member of source and no operation was performed." } ] }, "arguments": [ { "name": "source", "type": "key", "key_spec_index": 0 }, { "name": "destination", "type": "key", "key_spec_index": 1 }, { "name": "member", "type": "string" } ] } } redis-8.0.2/src/commands/sort.json000066400000000000000000000120061501533116600171010ustar00rootroot00000000000000{ "SORT": { "summary": "Sorts the elements in a list, a set, or a sorted set, optionally storing the result.", "complexity": "O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is O(N).", "group": "generic", "since": "1.0.0", "arity": -2, "function": "sortCommand", "get_keys_function": "sortGetKeys", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SET", "SORTEDSET", "LIST", "DANGEROUS" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "notes": "For the optional BY/GET keyword. It is marked 'unknown' because the key names derive from the content of the key we sort", "flags": [ "RO", "ACCESS" ], "begin_search": { "unknown": null }, "find_keys": { "unknown": null } }, { "notes": "For the optional STORE keyword. It is marked 'unknown' because the keyword can appear anywhere in the argument array", "flags": [ "OW", "UPDATE" ], "begin_search": { "unknown": null }, "find_keys": { "unknown": null } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "token": "BY", "name": "by-pattern", "display": "pattern", "type": "pattern", "key_spec_index": 1, "optional": true }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] }, { "token": "GET", "name": "get-pattern", "display": "pattern", "key_spec_index": 1, "type": "pattern", "optional": true, "multiple": true, "multiple_token": true }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] }, { "name": "sorting", "token": "ALPHA", "type": "pure-token", "optional": true }, { "token": "STORE", "name": "destination", "type": "key", "key_spec_index": 2, "optional": true } ], "reply_schema": { "oneOf": [ { "description": "when the store option is specified the command returns the number of sorted elements in the destination list", "type": "integer", "minimum": 0 }, { "description": "when not passing the store option the command returns a list of sorted elements", "type": "array", "items": { "oneOf": [ { "type": "string" }, { "description": "GET option is specified, but no object was found", "type": "null" } ] } } ] } } } redis-8.0.2/src/commands/sort_ro.json000066400000000000000000000075601501533116600176120ustar00rootroot00000000000000{ "SORT_RO": { "summary": "Returns the sorted elements of a list, a set, or a sorted set.", "complexity": "O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is O(N).", "group": "generic", "since": "7.0.0", "arity": -2, "function": "sortroCommand", "get_keys_function": "sortROGetKeys", "command_flags": [ "READONLY" ], "acl_categories": [ "SET", "SORTEDSET", "LIST", "DANGEROUS" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "notes": "For the optional BY/GET keyword. It is marked 'unknown' because the key names derive from the content of the key we sort", "flags": [ "RO", "ACCESS" ], "begin_search": { "unknown": null }, "find_keys": { "unknown": null } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "token": "BY", "name": "by-pattern", "display": "pattern", "type": "pattern", "key_spec_index": 1, "optional": true }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] }, { "token": "GET", "name": "get-pattern", "display": "pattern", "key_spec_index": 1, "type": "pattern", "optional": true, "multiple": true, "multiple_token": true }, { "name": "order", "type": "oneof", "optional": true, "arguments": [ { "name": "asc", "type": "pure-token", "token": "ASC" }, { "name": "desc", "type": "pure-token", "token": "DESC" } ] }, { "name": "sorting", "token": "ALPHA", "type": "pure-token", "optional": true } ], "reply_schema": { "description": "a list of sorted elements", "type": "array", "items": { "oneOf": [ { "type": "string" }, { "description": "GET option is specified, but no object was found", "type": "null" } ] } } } } redis-8.0.2/src/commands/spop.json000066400000000000000000000043121501533116600170740ustar00rootroot00000000000000{ "SPOP": { "summary": "Returns one or more random members from a set after removing them. Deletes the set if the last member was popped.", "complexity": "Without the count argument O(1), otherwise O(N) where N is the value of the passed count.", "group": "set", "since": "1.0.0", "arity": -2, "function": "spopCommand", "history": [ [ "3.2.0", "Added the `count` argument." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "null", "description": "The key does not exist." }, { "type": "string", "description": "The removed member when 'COUNT' is not given." }, { "type": "array", "description": "List to the removed members when 'COUNT' is given.", "uniqueItems": true, "items": { "type": "string" } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer", "optional": true, "since": "3.2.0" } ] } } redis-8.0.2/src/commands/spublish.json000066400000000000000000000027021501533116600177450ustar00rootroot00000000000000{ "SPUBLISH": { "summary": "Post a message to a shard channel", "complexity": "O(N) where N is the number of clients subscribed to the receiving shard channel.", "group": "pubsub", "since": "7.0.0", "arity": 3, "function": "spublishCommand", "command_flags": [ "PUBSUB", "LOADING", "STALE", "FAST", "MAY_REPLICATE" ], "arguments": [ { "name": "shardchannel", "type": "string" }, { "name": "message", "type": "string" } ], "key_specs": [ { "flags": [ "NOT_KEY" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "the number of clients that received the message. Note that in a Redis Cluster, only clients that are connected to the same node as the publishing client are included in the count", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/srandmember.json000066400000000000000000000045231501533116600204160ustar00rootroot00000000000000{ "SRANDMEMBER": { "summary": "Get one or multiple random members from a set", "complexity": "Without the count argument O(1), otherwise O(N) where N is the absolute value of the passed count.", "group": "set", "since": "1.0.0", "arity": -2, "function": "srandmemberCommand", "history": [ [ "2.6.0", "Added the optional `count` argument." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer", "optional": true, "since": "2.6.0" } ], "reply_schema": { "oneOf": [ { "description": "In case `count` is not given and key doesn't exist", "type": "null" }, { "description": "In case `count` is not given, randomly selected element", "type": "string" }, { "description": "In case `count` is given, an array of elements", "type": "array", "items": { "type": "string" }, "minItems": 1 }, { "description": "In case `count` is given and key doesn't exist", "type": "array", "maxItems": 0 } ] } } } redis-8.0.2/src/commands/srem.json000066400000000000000000000031031501533116600170560ustar00rootroot00000000000000{ "SREM": { "summary": "Removes one or more members from a set. Deletes the set if the last member was removed.", "complexity": "O(N) where N is the number of members to be removed.", "group": "set", "since": "1.0.0", "arity": -3, "function": "sremCommand", "history": [ [ "2.4.0", "Accepts multiple `member` arguments." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Number of members that were removed from the set, not including non existing members.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/sscan.json000066400000000000000000000043021501533116600172210ustar00rootroot00000000000000{ "SSCAN": { "summary": "Iterates over members of a set.", "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", "group": "set", "since": "2.8.0", "arity": -3, "function": "sscanCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "cursor", "type": "integer" }, { "token": "MATCH", "name": "pattern", "type": "pattern", "optional": true }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ], "reply_schema": { "description": "cursor and scan response in array form", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "cursor", "type": "string" }, { "description": "list of set members", "type": "array", "items": { "type": "string" } } ] } } } redis-8.0.2/src/commands/ssubscribe.json000066400000000000000000000020771501533116600202650ustar00rootroot00000000000000{ "SSUBSCRIBE": { "summary": "Listens for messages published to shard channels.", "complexity": "O(N) where N is the number of shard channels to subscribe to.", "group": "pubsub", "since": "7.0.0", "arity": -2, "function": "ssubscribeCommand", "command_flags": [ "PUBSUB", "NOSCRIPT", "LOADING", "STALE" ], "arguments": [ { "name": "shardchannel", "type": "string", "multiple": true } ], "key_specs": [ { "flags": [ "NOT_KEY" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ] } } redis-8.0.2/src/commands/strlen.json000066400000000000000000000022671501533116600174310ustar00rootroot00000000000000{ "STRLEN": { "summary": "Returns the length of a string value.", "complexity": "O(1)", "group": "string", "since": "2.2.0", "arity": 2, "function": "strlenCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The length of the string value stored at key, or 0 when key does not exist.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/subscribe.json000066400000000000000000000011631501533116600200750ustar00rootroot00000000000000{ "SUBSCRIBE": { "summary": "Listens for messages published to channels.", "complexity": "O(N) where N is the number of channels to subscribe to.", "group": "pubsub", "since": "2.0.0", "arity": -2, "function": "subscribeCommand", "history": [], "command_flags": [ "PUBSUB", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "arguments": [ { "name": "channel", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/substr.json000066400000000000000000000033641501533116600174430ustar00rootroot00000000000000{ "SUBSTR": { "summary": "Returns a substring from a string value.", "complexity": "O(N) where N is the length of the returned string. The complexity is ultimately determined by the returned length, but because creating a substring from an existing string is very cheap, it can be considered O(1) for small strings.", "group": "string", "since": "1.0.0", "arity": 4, "function": "getrangeCommand", "deprecated_since": "2.0.0", "replaced_by": "`GETRANGE`", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "STRING" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "string", "description": "The substring of the string value stored at key, determined by the offsets start and end (both are inclusive)." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "integer" }, { "name": "end", "type": "integer" } ] } } redis-8.0.2/src/commands/sunion.json000066400000000000000000000026251501533116600174330ustar00rootroot00000000000000{ "SUNION": { "summary": "Returns the union of multiple sets.", "complexity": "O(N) where N is the total number of elements in all given sets.", "group": "set", "since": "1.0.0", "arity": -2, "function": "sunionCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT_ORDER" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List with the members of the resulting set.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/sunionstore.json000066400000000000000000000036051501533116600205070ustar00rootroot00000000000000{ "SUNIONSTORE": { "summary": "Stores the union of multiple sets in a key.", "complexity": "O(N) where N is the total number of elements in all given sets.", "group": "set", "since": "1.0.0", "arity": -3, "function": "sunionstoreCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Number of the elements in the resulting set.", "minimum": 0 }, "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true } ] } } redis-8.0.2/src/commands/sunsubscribe.json000066400000000000000000000021501501533116600206200ustar00rootroot00000000000000{ "SUNSUBSCRIBE": { "summary": "Stops listening to messages posted to shard channels.", "complexity": "O(N) where N is the number of shard channels to unsubscribe.", "group": "pubsub", "since": "7.0.0", "arity": -1, "function": "sunsubscribeCommand", "command_flags": [ "PUBSUB", "NOSCRIPT", "LOADING", "STALE" ], "arguments": [ { "name": "shardchannel", "type": "string", "optional": true, "multiple": true } ], "key_specs": [ { "flags": [ "NOT_KEY" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ] } } redis-8.0.2/src/commands/swapdb.json000066400000000000000000000013571501533116600174010ustar00rootroot00000000000000{ "SWAPDB": { "summary": "Swaps two Redis databases.", "complexity": "O(N) where N is the count of clients watching or blocking on keys from both databases.", "group": "server", "since": "4.0.0", "arity": 3, "function": "swapdbCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE", "DANGEROUS" ], "arguments": [ { "name": "index1", "type": "integer" }, { "name": "index2", "type": "integer" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/sync.json000066400000000000000000000005141501533116600170670ustar00rootroot00000000000000{ "SYNC": { "summary": "An internal command used in replication.", "group": "server", "since": "1.0.0", "arity": 1, "function": "syncCommand", "command_flags": [ "NO_ASYNC_LOADING", "ADMIN", "NO_MULTI", "NOSCRIPT" ] } } redis-8.0.2/src/commands/time.json000066400000000000000000000013071501533116600170520ustar00rootroot00000000000000{ "TIME": { "summary": "Returns the server time.", "complexity": "O(1)", "group": "server", "since": "2.6.0", "arity": 1, "function": "timeCommand", "command_flags": [ "LOADING", "STALE", "FAST" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "reply_schema": { "type": "array", "description": "Array containing two elements: Unix time in seconds and microseconds.", "minItems": 2, "maxItems": 2, "items": { "type": "string", "pattern": "[0-9]+" } } } } redis-8.0.2/src/commands/touch.json000066400000000000000000000026321501533116600172400ustar00rootroot00000000000000{ "TOUCH": { "summary": "Returns the number of existing keys out of those specified after updating the time they were last accessed.", "complexity": "O(N) where N is the number of keys that will be touched.", "group": "generic", "since": "3.2.1", "arity": -2, "function": "touchCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "REQUEST_POLICY:MULTI_SHARD", "RESPONSE_POLICY:AGG_SUM" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ], "reply_schema": { "description": "the number of touched keys", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/ttl.json000066400000000000000000000033431501533116600167210ustar00rootroot00000000000000{ "TTL": { "summary": "Returns the expiration time in seconds of a key.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": 2, "function": "ttlCommand", "history": [ [ "2.8.0", "Added the -2 reply." ] ], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "TTL in seconds.", "type": "integer", "minimum": 0 }, { "description": "The key exists but has no associated expire.", "const": -1 }, { "description": "The key does not exist.", "const": -2 } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/type.json000066400000000000000000000025001501533116600170710ustar00rootroot00000000000000{ "TYPE": { "summary": "Determines the type of value stored at a key.", "complexity": "O(1)", "group": "generic", "since": "1.0.0", "arity": 2, "function": "typeCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "KEYSPACE" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Key doesn't exist", "type": "null" }, { "description": "Type of the key", "type": "string" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/unlink.json000066400000000000000000000030261501533116600174140ustar00rootroot00000000000000{ "UNLINK": { "summary": "Asynchronously deletes one or more keys.", "complexity": "O(1) for each key removed regardless of its size. Then the command does O(N) work in a different thread in order to reclaim memory, where N is the number of allocations the deleted objects where composed of.", "group": "generic", "since": "4.0.0", "arity": -2, "function": "unlinkCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "KEYSPACE" ], "command_tips": [ "REQUEST_POLICY:MULTI_SHARD", "RESPONSE_POLICY:AGG_SUM" ], "key_specs": [ { "flags": [ "RM", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ], "reply_schema": { "description": "the number of keys that were unlinked", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/unsubscribe.json000066400000000000000000000012051501533116600204350ustar00rootroot00000000000000{ "UNSUBSCRIBE": { "summary": "Stops listening to messages posted to channels.", "complexity": "O(N) where N is the number of channels to unsubscribe.", "group": "pubsub", "since": "2.0.0", "arity": -1, "function": "unsubscribeCommand", "command_flags": [ "PUBSUB", "NOSCRIPT", "LOADING", "STALE", "SENTINEL" ], "arguments": [ { "name": "channel", "type": "string", "optional": true, "multiple": true } ] } } redis-8.0.2/src/commands/unwatch.json000066400000000000000000000010071501533116600175620ustar00rootroot00000000000000{ "UNWATCH": { "summary": "Forgets about watched keys of a transaction.", "complexity": "O(1)", "group": "transactions", "since": "2.2.0", "arity": 1, "function": "unwatchCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "ALLOW_BUSY" ], "acl_categories": [ "TRANSACTION" ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/wait.json000066400000000000000000000017411501533116600170620ustar00rootroot00000000000000{ "WAIT": { "summary": "Blocks until the asynchronous replication of all preceding write commands sent by the connection is completed.", "complexity": "O(1)", "group": "generic", "since": "3.0.0", "arity": 3, "function": "waitCommand", "command_flags": [ "BLOCKING" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:AGG_MIN" ], "reply_schema": { "type": "integer", "description": "The number of replicas reached by all the writes performed in the context of the current connection.", "minimum": 0 }, "arguments": [ { "name": "numreplicas", "type": "integer" }, { "name": "timeout", "type": "integer" } ] } } redis-8.0.2/src/commands/waitaof.json000066400000000000000000000027131501533116600175500ustar00rootroot00000000000000{ "WAITAOF": { "summary": "Blocks until all of the preceding write commands sent by the connection are written to the append-only file of the master and/or replicas.", "complexity": "O(1)", "group": "generic", "since": "7.2.0", "arity": 4, "function": "waitaofCommand", "command_flags": [ "BLOCKING" ], "acl_categories": [ "CONNECTION" ], "command_tips": [ "REQUEST_POLICY:ALL_SHARDS", "RESPONSE_POLICY:AGG_MIN" ], "reply_schema": { "type": "array", "description": "Number of local and remote AOF files in sync.", "minItems": 2, "maxItems": 2, "items": [ { "description": "Number of local AOF files.", "type": "integer", "minimum": 0 }, { "description": "Number of replica AOF files.", "type": "number", "minimum": 0 } ] }, "arguments": [ { "name": "numlocal", "type": "integer" }, { "name": "numreplicas", "type": "integer" }, { "name": "timeout", "type": "integer" } ] } } redis-8.0.2/src/commands/watch.json000066400000000000000000000023211501533116600172170ustar00rootroot00000000000000{ "WATCH": { "summary": "Monitors changes to keys to determine the execution of a transaction.", "complexity": "O(1) for every key.", "group": "transactions", "since": "2.2.0", "arity": -2, "function": "watchCommand", "command_flags": [ "NOSCRIPT", "LOADING", "STALE", "FAST", "ALLOW_BUSY" ], "acl_categories": [ "TRANSACTION" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 0 } } } ], "reply_schema": { "const": "OK" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true } ] } } redis-8.0.2/src/commands/xack.json000066400000000000000000000032671501533116600170510ustar00rootroot00000000000000{ "XACK": { "summary": "Returns the number of messages that were successfully acknowledged by the consumer group member of a stream.", "complexity": "O(1) for each message ID processed.", "group": "stream", "since": "5.0.0", "arity": -4, "function": "xackCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "ID", "type": "string", "multiple": true } ], "reply_schema": { "description": "The command returns the number of messages successfully acknowledged. Certain message IDs may no longer be part of the PEL (for example because they have already been acknowledged), and XACK will not count them as successfully acknowledged.", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/xadd.json000066400000000000000000000121321501533116600170320ustar00rootroot00000000000000{ "XADD": { "summary": "Appends a new message to a stream. Creates the key if it doesn't exist.", "complexity": "O(1) when adding a new entry, O(N) when trimming where N being the number of entries evicted.", "group": "stream", "since": "5.0.0", "arity": -5, "function": "xaddCommand", "history": [ [ "6.2.0", "Added the `NOMKSTREAM` option, `MINID` trimming strategy and the `LIMIT` option." ], [ "7.0.0", "Added support for the `-*` explicit ID form." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STREAM" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "notes": "UPDATE instead of INSERT because of the optional trimming feature", "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "token": "NOMKSTREAM", "name": "nomkstream", "type": "pure-token", "optional": true, "since": "6.2.0" }, { "name": "trim", "type": "block", "optional": true, "arguments": [ { "name": "strategy", "type": "oneof", "arguments": [ { "name": "maxlen", "type": "pure-token", "token": "MAXLEN" }, { "name": "minid", "type": "pure-token", "token": "MINID", "since": "6.2.0" } ] }, { "name": "operator", "type": "oneof", "optional": true, "arguments": [ { "name": "equal", "type": "pure-token", "token": "=" }, { "name": "approximately", "type": "pure-token", "token": "~" } ] }, { "name": "threshold", "type": "string" }, { "token": "LIMIT", "name": "count", "type": "integer", "optional": true, "since": "6.2.0" } ] }, { "name": "id-selector", "type": "oneof", "arguments": [ { "name": "auto-id", "type": "pure-token", "token": "*" }, { "name": "id", "type": "string" } ] }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "field", "type": "string" }, { "name": "value", "type": "string" } ] } ], "reply_schema": { "oneOf":[ { "description": "The ID of the added entry. The ID is the one auto-generated if * is passed as ID argument, otherwise the command just returns the same ID specified by the user during insertion.", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "The NOMKSTREAM option is given and the key doesn't exist.", "type": "null" } ] } } } redis-8.0.2/src/commands/xautoclaim.json000066400000000000000000000125641501533116600202710ustar00rootroot00000000000000{ "XAUTOCLAIM": { "summary": "Changes, or acquires, ownership of messages in a consumer group, as if the messages were delivered to as consumer group member.", "complexity": "O(1) if COUNT is small.", "group": "stream", "since": "6.2.0", "arity": -6, "function": "xautoclaimCommand", "history": [ [ "7.0.0", "Added an element to the reply array, containing deleted entries the command cleared from the PEL" ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "STREAM" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "description": "Claimed stream entries (with data, if `JUSTID` was not given).", "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "description": "Cursor for next call.", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Data", "type": "array", "items": { "type": "string" } } ] } }, { "description": "Entry IDs which no longer exist in the stream, and were deleted from the PEL in which they were found.", "type": "array", "items": { "type": "string", "pattern": "[0-9]+-[0-9]+" } } ] }, { "description": "Claimed stream entries (without data, if `JUSTID` was given).", "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "description": "Cursor for next call.", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "[0-9]+-[0-9]+" } }, { "description": "Entry IDs which no longer exist in the stream, and were deleted from the PEL in which they were found.", "type": "array", "items": { "type": "string", "pattern": "[0-9]+-[0-9]+" } } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "consumer", "type": "string" }, { "name": "min-idle-time", "type": "string" }, { "name": "start", "type": "string" }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true }, { "name": "justid", "token": "JUSTID", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/xclaim.json000066400000000000000000000102761501533116600173760ustar00rootroot00000000000000{ "XCLAIM": { "summary": "Changes, or acquires, ownership of a message in a consumer group, as if the message was delivered a consumer group member.", "complexity": "O(log N) with N being the number of messages in the PEL of the consumer group.", "group": "stream", "since": "5.0.0", "arity": -6, "function": "xclaimCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "STREAM" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "consumer", "type": "string" }, { "name": "min-idle-time", "type": "string" }, { "name": "ID", "type": "string", "multiple": true }, { "token": "IDLE", "name": "ms", "type": "integer", "optional": true }, { "token": "TIME", "name": "unix-time-milliseconds", "type": "unix-time", "optional": true }, { "token": "RETRYCOUNT", "name": "count", "type": "integer", "optional": true }, { "name": "force", "token": "FORCE", "type": "pure-token", "optional": true }, { "name": "justid", "token": "JUSTID", "type": "pure-token", "optional": true }, { "name": "lastid", "token": "LASTID", "type": "string", "optional": true } ], "reply_schema": { "description": "Stream entries with IDs matching the specified range.", "anyOf": [ { "description": "If JUSTID option is specified, return just an array of IDs of messages successfully claimed", "type": "array", "items": { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" } }, { "description": "array of stream entries that contains each entry as an array of 2 elements, the Entry ID and the entry data itself", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Data", "type": "array", "items": { "type": "string" } } ] } } ] } } } redis-8.0.2/src/commands/xdel.json000066400000000000000000000026211501533116600170500ustar00rootroot00000000000000{ "XDEL": { "summary": "Returns the number of messages after removing them from a stream.", "complexity": "O(1) for each single item to delete in the stream, regardless of the stream size.", "group": "stream", "since": "5.0.0", "arity": -3, "function": "xdelCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "ID", "type": "string", "multiple": true } ], "reply_schema": { "description": "The number of entries actually deleted", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/xgroup-create.json000066400000000000000000000042231501533116600207010ustar00rootroot00000000000000{ "CREATE": { "summary": "Creates a consumer group.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": -5, "container": "XGROUP", "function": "xgroupCommand", "history": [ [ "7.0.0", "Added the `entries_read` named argument." ] ], "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "id-selector", "type": "oneof", "arguments": [ { "name": "id", "type": "string" }, { "name": "new-id", "type": "pure-token", "token": "$" } ] }, { "token": "MKSTREAM", "name": "mkstream", "type": "pure-token", "optional": true }, { "name": "entriesread", "display": "entries-read", "token": "ENTRIESREAD", "type": "integer", "optional": true } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/xgroup-createconsumer.json000066400000000000000000000030201501533116600224470ustar00rootroot00000000000000{ "CREATECONSUMER": { "summary": "Creates a consumer in a consumer group.", "complexity": "O(1)", "group": "stream", "since": "6.2.0", "arity": 5, "container": "XGROUP", "function": "xgroupCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "INSERT" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "consumer", "type": "string" } ], "reply_schema": { "description": "The number of created consumers (0 or 1)", "oneOf": [ { "const": 1 }, { "const": 0 } ] } } } redis-8.0.2/src/commands/xgroup-delconsumer.json000066400000000000000000000026501501533116600217600ustar00rootroot00000000000000{ "DELCONSUMER": { "summary": "Deletes a consumer from a consumer group.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": 5, "container": "XGROUP", "function": "xgroupCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "consumer", "type": "string" } ], "reply_schema": { "description": "The number of pending messages that were yet associated with such a consumer", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/xgroup-destroy.json000066400000000000000000000027271501533116600211360ustar00rootroot00000000000000{ "DESTROY": { "summary": "Destroys a consumer group.", "complexity": "O(N) where N is the number of entries in the group's pending entries list (PEL).", "group": "stream", "since": "5.0.0", "arity": 4, "container": "XGROUP", "function": "xgroupCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" } ], "reply_schema": { "description": "The number of destroyed consumer groups (0 or 1)", "oneOf": [ { "const": 1 }, { "const": 0 } ] } } } redis-8.0.2/src/commands/xgroup-help.json000066400000000000000000000011411501533116600203620ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": 2, "container": "XGROUP", "function": "xgroupCommand", "command_flags": [ "LOADING", "STALE" ], "acl_categories": [ "STREAM" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/xgroup-setid.json000066400000000000000000000037471501533116600205600ustar00rootroot00000000000000{ "SETID": { "summary": "Sets the last-delivered ID of a consumer group.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": -5, "container": "XGROUP", "function": "xgroupCommand", "history": [ [ "7.0.0", "Added the optional `entries_read` argument." ] ], "command_flags": [ "WRITE" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "id-selector", "type": "oneof", "arguments": [ { "name": "id", "type": "string" }, { "name": "new-id", "type": "pure-token", "token": "$" } ] }, { "name": "entriesread", "display": "entries-read", "token": "ENTRIESREAD", "type": "integer", "optional": true } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/xgroup.json000066400000000000000000000003231501533116600174350ustar00rootroot00000000000000{ "XGROUP": { "summary": "A container for consumer groups commands.", "complexity": "Depends on subcommand.", "group": "stream", "since": "5.0.0", "arity": -2 } } redis-8.0.2/src/commands/xinfo-consumers.json000066400000000000000000000040731501533116600212560ustar00rootroot00000000000000{ "CONSUMERS": { "summary": "Returns a list of the consumers in a consumer group.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": 4, "container": "XINFO", "function": "xinfoCommand", "history": [ [ "7.2.0", "Added the `inactive` field, and changed the meaning of `idle`." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "STREAM" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" } ], "reply_schema": { "description": "Array list of consumers", "type": "array", "uniqueItems": true, "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string" }, "pending": { "type": "integer" }, "idle": { "type": "integer" }, "inactive": { "type": "integer" } } } } } } redis-8.0.2/src/commands/xinfo-groups.json000066400000000000000000000050001501533116600205460ustar00rootroot00000000000000{ "GROUPS": { "summary": "Returns a list of the consumer groups of a stream.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": 3, "container": "XINFO", "history": [ [ "7.0.0", "Added the `entries-read` and `lag` fields" ] ], "function": "xinfoCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string" }, "consumers": { "type": "integer" }, "pending": { "type": "integer" }, "last-delivered-id": { "type": "string", "pattern": "[0-9]+-[0-9]+" }, "entries-read": { "oneOf": [ { "type": "null" }, { "type": "integer" } ] }, "lag": { "oneOf": [ { "type": "null" }, { "type": "integer" } ] } } } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/xinfo-help.json000066400000000000000000000011371501533116600201660ustar00rootroot00000000000000{ "HELP": { "summary": "Returns helpful text about the different subcommands.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": 2, "container": "XINFO", "function": "xinfoCommand", "command_flags": [ "LOADING", "STALE" ], "acl_categories": [ "STREAM" ], "reply_schema": { "type": "array", "description": "Helpful text about subcommands.", "items": { "type": "string" } } } } redis-8.0.2/src/commands/xinfo-stream.json000066400000000000000000000426111501533116600205330ustar00rootroot00000000000000{ "STREAM": { "summary": "Returns information about a stream.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": -3, "container": "XINFO", "history": [ [ "6.0.0", "Added the `FULL` modifier." ], [ "7.0.0", "Added the `max-deleted-entry-id`, `entries-added`, `recorded-first-entry-id`, `entries-read` and `lag` fields" ], [ "7.2.0", "Added the `active-time` field, and changed the meaning of `seen-time`." ] ], "function": "xinfoCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Summary form, in case `FULL` was not given.", "type": "object", "additionalProperties": false, "properties": { "length": { "description": "the number of entries in the stream (see `XLEN`)", "type": "integer" }, "radix-tree-keys": { "description": "the number of keys in the underlying radix data structure", "type": "integer" }, "radix-tree-nodes": { "description": "the number of nodes in the underlying radix data structure", "type": "integer" }, "last-generated-id": { "description": "the ID of the least-recently entry that was added to the stream", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "max-deleted-entry-id": { "description": "the maximal entry ID that was deleted from the stream", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "recorded-first-entry-id": { "description": "cached copy of the first entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "entries-added": { "description": "the count of all entries added to the stream during its lifetime", "type": "integer" }, "groups": { "description": "the number of consumer groups defined for the stream", "type": "integer" }, "first-entry": { "description": "the first entry of the stream", "oneOf": [ { "type": "null" }, { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "data", "type": "array", "items": { "type": "string" } } ] } ] }, "last-entry": { "description": "the last entry of the stream", "oneOf": [ { "type": "null" }, { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "data", "type": "array", "items": { "type": "string" } } ] } ] } } }, { "description": "Extended form, in case `FULL` was given.", "type": "object", "additionalProperties": false, "properties": { "length": { "description": "the number of entries in the stream (see `XLEN`)", "type": "integer" }, "radix-tree-keys": { "description": "the number of keys in the underlying radix data structure", "type": "integer" }, "radix-tree-nodes": { "description": "the number of nodes in the underlying radix data structure", "type": "integer" }, "last-generated-id": { "description": "the ID of the least-recently entry that was added to the stream", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "max-deleted-entry-id": { "description": "the maximal entry ID that was deleted from the stream", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "recorded-first-entry-id": { "description": "cached copy of the first entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "entries-added": { "description": "the count of all entries added to the stream during its lifetime", "type": "integer" }, "entries": { "description": "all the entries of the stream", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "data", "type": "array", "items": { "type": "string" } } ] } }, "groups": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "name": { "description": "group name", "type": "string" }, "last-delivered-id": { "description": "last entry ID that was delivered to a consumer", "type": "string", "pattern": "[0-9]+-[0-9]+" }, "entries-read": { "description": "total number of entries ever read by consumers in the group", "oneOf": [ { "type": "null" }, { "type": "integer" } ] }, "lag": { "description": "number of entries left to be consumed from the stream", "oneOf": [ { "type": "null" }, { "type": "integer" } ] }, "pel-count": { "description": "total number of unacknowledged entries", "type": "integer" }, "pending": { "description": "data about all of the unacknowledged entries", "type": "array", "items": { "type": "array", "minItems": 4, "maxItems": 4, "items": [ { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Consumer name", "type": "string" }, { "description": "Delivery timestamp", "type": "integer" }, { "description": "Delivery count", "type": "integer" } ] } }, "consumers": { "description": "data about all of the consumers of the group", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "active-time": { "type": "integer", "description": "Last time this consumer was active (successful reading/claiming).", "minimum": 0 }, "name": { "description": "consumer name", "type": "string" }, "seen-time": { "description": "timestamp of the last interaction attempt of the consumer", "type": "integer", "minimum": 0 }, "pel-count": { "description": "number of unacknowledged entries that belong to the consumer", "type": "integer" }, "pending": { "description": "data about the unacknowledged entries", "type": "array", "items": { "type": "array", "minItems": 3, "maxItems": 3, "items": [ { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Delivery timestamp", "type": "integer" }, { "description": "Delivery count", "type": "integer" } ] } } } } } } } } } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "full-block", "type": "block", "optional": true, "arguments": [ { "name": "full", "token": "FULL", "type": "pure-token" }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ] } ] } } redis-8.0.2/src/commands/xinfo.json000066400000000000000000000003271501533116600172400ustar00rootroot00000000000000{ "XINFO": { "summary": "A container for stream introspection commands.", "complexity": "Depends on subcommand.", "group": "stream", "since": "5.0.0", "arity": -2 } } redis-8.0.2/src/commands/xlen.json000066400000000000000000000022271501533116600170640ustar00rootroot00000000000000{ "XLEN": { "summary": "Return the number of messages in a stream.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": 2, "function": "xlenCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ], "reply_schema": { "description": "The number of entries of the stream at key", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/xpending.json000066400000000000000000000131771501533116600177400ustar00rootroot00000000000000{ "XPENDING": { "summary": "Returns the information and entries from a stream consumer group's pending entries list.", "complexity": "O(N) with N being the number of elements returned, so asking for a small fixed number of entries per call is O(1). O(M), where M is the total number of entries scanned when used with the IDLE filter. When the command returns just the summary and the list of consumers is small, it runs in O(1) time; otherwise, an additional O(N) time for iterating every consumer.", "group": "stream", "since": "5.0.0", "arity": -3, "function": "xpendingCommand", "history": [ [ "6.2.0", "Added the `IDLE` option and exclusive range intervals." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "STREAM" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "description": "Extended form, in case `start` was given.", "type": "array", "items": { "type": "array", "minItems": 4, "maxItems": 4, "items": [ { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Consumer name", "type": "string" }, { "description": "Idle time", "type": "integer" }, { "description": "Delivery count", "type": "integer" } ] } }, { "description": "Summary form, in case `start` was not given.", "type": "array", "minItems": 4, "maxItems": 4, "items": [ { "description": "Total number of pending messages", "type": "integer" }, { "description": "Minimal pending entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Maximal pending entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Consumers with pending messages", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Consumer name", "type": "string" }, { "description": "Number of pending messages", "type": "string" } ] } } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "group", "type": "string" }, { "name": "filters", "type": "block", "optional": true, "arguments": [ { "token": "IDLE", "name": "min-idle-time", "type": "integer", "optional": true, "since": "6.2.0" }, { "name": "start", "type": "string" }, { "name": "end", "type": "string" }, { "name": "count", "type": "integer" }, { "name": "consumer", "type": "string", "optional": true } ] } ] } } redis-8.0.2/src/commands/xrange.json000066400000000000000000000046331501533116600174050ustar00rootroot00000000000000{ "XRANGE": { "summary": "Returns the messages from a stream within a range of IDs.", "complexity": "O(N) with N being the number of elements being returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).", "group": "stream", "since": "5.0.0", "arity": -4, "function": "xrangeCommand", "history": [ [ "6.2.0", "Added exclusive ranges." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Stream entries with IDs matching the specified range.", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Entry ID", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Data", "type": "array", "items": { "type": "string" } } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "string" }, { "name": "end", "type": "string" }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/xread.json000066400000000000000000000070551501533116600172250ustar00rootroot00000000000000{ "XREAD": { "summary": "Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.", "group": "stream", "since": "5.0.0", "arity": -4, "function": "xreadCommand", "get_keys_function": "xreadGetKeys", "command_flags": [ "BLOCKING", "READONLY" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "keyword": { "keyword": "STREAMS", "startfrom": 1 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 2 } } } ], "arguments": [ { "token": "COUNT", "name": "count", "type": "integer", "optional": true }, { "token": "BLOCK", "name": "milliseconds", "type": "integer", "optional": true }, { "name": "streams", "token": "STREAMS", "type": "block", "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "ID", "type": "string", "multiple": true } ] } ], "reply_schema": { "oneOf": [ { "description": "A map of key-value elements when each element composed of key name and the entries reported for that key", "type": "object", "patternProperties": { "^.*$": { "description": "The entries reported for that key", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "entry id", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "array of field-value pairs", "type": "array", "items": { "type": "string" } } ] } } } }, { "description": "If BLOCK option is given, and a timeout occurs, or there is no stream we can serve", "type": "null" } ] } } } redis-8.0.2/src/commands/xreadgroup.json000066400000000000000000000112331501533116600202730ustar00rootroot00000000000000{ "XREADGROUP": { "summary": "Returns new or historical messages from a stream for a consumer in a group. Blocks until a message is available otherwise.", "complexity": "For each stream mentioned: O(M) with M being the number of elements returned. If M is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1). On the other side when XREADGROUP blocks, XADD will pay the O(N) time in order to serve the N clients blocked on the stream getting new data.", "group": "stream", "since": "5.0.0", "arity": -7, "function": "xreadCommand", "get_keys_function": "xreadGetKeys", "command_flags": [ "BLOCKING", "WRITE" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "keyword": { "keyword": "STREAMS", "startfrom": 4 } }, "find_keys": { "range": { "lastkey": -1, "step": 1, "limit": 2 } } } ], "arguments": [ { "token": "GROUP", "name": "group-block", "type": "block", "arguments": [ { "name": "group", "type": "string" }, { "name": "consumer", "type": "string" } ] }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true }, { "token": "BLOCK", "name": "milliseconds", "type": "integer", "optional": true }, { "name": "noack", "token": "NOACK", "type": "pure-token", "optional": true }, { "name": "streams", "token": "STREAMS", "type": "block", "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "ID", "type": "string", "multiple": true } ] } ], "reply_schema": { "oneOf": [ { "description": "If BLOCK option is specified and the timeout expired", "type": "null" }, { "description": "A map of key-value elements when each element composed of key name and the entries reported for that key", "type": "object", "additionalProperties": { "description": "The entries reported for that key", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Stream id", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "oneOf": [ { "description": "Array of field-value pairs", "type": "array", "items": { "type": "string" } }, { "type": "null" } ] } ] } } } ] } } } redis-8.0.2/src/commands/xrevrange.json000066400000000000000000000046521501533116600201230ustar00rootroot00000000000000{ "XREVRANGE": { "summary": "Returns the messages from a stream within a range of IDs in reverse order.", "complexity": "O(N) with N being the number of elements returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).", "group": "stream", "since": "5.0.0", "arity": -4, "function": "xrevrangeCommand", "history": [ [ "6.2.0", "Added exclusive ranges." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "end", "type": "string" }, { "name": "start", "type": "string" }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ], "reply_schema": { "description": "An array of the entries with IDs matching the specified range", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Stream id", "type": "string", "pattern": "[0-9]+-[0-9]+" }, { "description": "Array of field-value pairs", "type": "array", "items": { "type": "string" } } ] } } } } redis-8.0.2/src/commands/xsetid.json000066400000000000000000000034411501533116600174150ustar00rootroot00000000000000{ "XSETID": { "summary": "An internal command for replicating stream values.", "complexity": "O(1)", "group": "stream", "since": "5.0.0", "arity": -3, "function": "xsetidCommand", "history": [ [ "7.0.0", "Added the `entries_added` and `max_deleted_entry_id` arguments." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "STREAM" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "last-id", "type": "string" }, { "name": "entries-added", "token": "ENTRIESADDED", "type": "integer", "optional": true, "since": "7.0.0" }, { "name": "max-deleted-id", "token": "MAXDELETEDID", "type": "string", "optional": true, "since": "7.0.0" } ], "reply_schema": { "const": "OK" } } } redis-8.0.2/src/commands/xtrim.json000066400000000000000000000066521501533116600172670ustar00rootroot00000000000000{ "XTRIM": { "summary": "Deletes messages from the beginning of a stream.", "complexity": "O(N), with N being the number of evicted entries. Constant times are very small however, since entries are organized in macro nodes containing multiple entries that can be released with a single deallocation.", "group": "stream", "since": "5.0.0", "arity": -4, "function": "xtrimCommand", "history": [ [ "6.2.0", "Added the `MINID` trimming strategy and the `LIMIT` option." ] ], "command_flags": [ "WRITE" ], "acl_categories": [ "STREAM" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "trim", "type": "block", "arguments": [ { "name": "strategy", "type": "oneof", "arguments": [ { "name": "maxlen", "type": "pure-token", "token": "MAXLEN" }, { "name": "minid", "type": "pure-token", "token": "MINID", "since": "6.2.0" } ] }, { "name": "operator", "type": "oneof", "optional": true, "arguments": [ { "name": "equal", "type": "pure-token", "token": "=" }, { "name": "approximately", "type": "pure-token", "token": "~" } ] }, { "name": "threshold", "type": "string" }, { "token": "LIMIT", "name": "count", "type": "integer", "optional": true, "since": "6.2.0" } ] } ], "reply_schema": { "description": "The number of entries deleted from the stream.", "type": "integer", "minimum": 0 } } } redis-8.0.2/src/commands/zadd.json000066400000000000000000000102761501533116600170430ustar00rootroot00000000000000{ "ZADD": { "summary": "Adds one or more members to a sorted set, or updates their scores. Creates the key if it doesn't exist.", "complexity": "O(log(N)) for each item added, where N is the number of elements in the sorted set.", "group": "sorted_set", "since": "1.2.0", "arity": -4, "function": "zaddCommand", "history": [ [ "2.4.0", "Accepts multiple elements." ], [ "3.0.2", "Added the `XX`, `NX`, `CH` and `INCR` options." ], [ "6.2.0", "Added the `GT` and `LT` options." ] ], "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf":[ { "description": "Operation was aborted (conflict with one of the `XX`/`NX`/`LT`/`GT` options).", "type": "null" }, { "description": "The number of new members (when the `CH` option is not used)", "type": "integer" }, { "description": "The number of new or updated members (when the `CH` option is used)", "type": "integer" }, { "description": "The updated score of the member (when the `INCR` option is used)", "type": "number" } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "condition", "type": "oneof", "optional": true, "since": "3.0.2", "arguments": [ { "name": "nx", "type": "pure-token", "token": "NX" }, { "name": "xx", "type": "pure-token", "token": "XX" } ] }, { "name": "comparison", "type": "oneof", "optional": true, "since": "6.2.0", "arguments": [ { "name": "gt", "type": "pure-token", "token": "GT" }, { "name": "lt", "type": "pure-token", "token": "LT" } ] }, { "name": "change", "token": "CH", "type": "pure-token", "optional": true, "since": "3.0.2" }, { "name": "increment", "token": "INCR", "type": "pure-token", "optional": true, "since": "3.0.2" }, { "name": "data", "type": "block", "multiple": true, "arguments": [ { "name": "score", "type": "double" }, { "name": "member", "type": "string" } ] } ] } } redis-8.0.2/src/commands/zcard.json000066400000000000000000000022621501533116600172200ustar00rootroot00000000000000{ "ZCARD": { "summary": "Returns the number of members in a sorted set.", "complexity": "O(1)", "group": "sorted_set", "since": "1.2.0", "arity": 2, "function": "zcardCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The cardinality (number of elements) of the sorted set, or 0 if key does not exist", "type": "integer" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 } ] } } redis-8.0.2/src/commands/zcount.json000066400000000000000000000027101501533116600174350ustar00rootroot00000000000000{ "ZCOUNT": { "summary": "Returns the count of members in a sorted set that have scores within a range.", "complexity": "O(log(N)) with N being the number of elements in the sorted set.", "group": "sorted_set", "since": "2.0.0", "arity": 4, "function": "zcountCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The number of elements in the specified score range", "type": "integer" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "min", "type": "double" }, { "name": "max", "type": "double" } ] } } redis-8.0.2/src/commands/zdiff.json000066400000000000000000000051771501533116600172270ustar00rootroot00000000000000{ "ZDIFF": { "summary": "Returns the difference between multiple sorted sets.", "complexity": "O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.", "group": "sorted_set", "since": "6.2.0", "arity": -3, "function": "zdiffCommand", "get_keys_function": "zunionInterDiffGetKeys", "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "anyOf": [ { "description": "A list of members. Returned in case `WITHSCORES` was not used.", "type": "array", "items": { "type": "string" } }, { "description": "Members and their scores. Returned in case `WITHSCORES` was used. In RESP2 this is returned as a flat array", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Member", "type": "string" }, { "description": "Score", "type": "number" } ] } } ] }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zdiffstore.json000066400000000000000000000042241501533116600202740ustar00rootroot00000000000000{ "ZDIFFSTORE": { "summary": "Stores the difference of multiple sorted sets in a key.", "complexity": "O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.", "group": "sorted_set", "since": "6.2.0", "arity": -4, "function": "zdiffstoreCommand", "get_keys_function": "zunionInterDiffStoreGetKeys", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "description": "Number of elements in the resulting sorted set at `destination`", "type": "integer" }, "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true } ] } } redis-8.0.2/src/commands/zincrby.json000066400000000000000000000027151501533116600176000ustar00rootroot00000000000000{ "ZINCRBY": { "summary": "Increments the score of a member in a sorted set.", "complexity": "O(log(N)) where N is the number of elements in the sorted set.", "group": "sorted_set", "since": "1.2.0", "arity": 4, "function": "zincrbyCommand", "command_flags": [ "WRITE", "DENYOOM", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The new score of `member`", "type": "number" }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "increment", "type": "integer" }, { "name": "member", "type": "string" } ] } } redis-8.0.2/src/commands/zinter.json000066400000000000000000000071721501533116600174350ustar00rootroot00000000000000{ "ZINTER": { "summary": "Returns the intersect of multiple sorted sets.", "complexity": "O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.", "group": "sorted_set", "since": "6.2.0", "arity": -3, "function": "zinterCommand", "get_keys_function": "zunionInterDiffGetKeys", "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "anyOf": [ { "description": "Result of intersection, containing only the member names. Returned in case `WITHSCORES` was not used.", "type": "array", "items": { "type": "string" } }, { "description": "Result of intersection, containing members and their scores. Returned in case `WITHSCORES` was used. In RESP2 this is returned as a flat array", "type": "array", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Member", "type": "string" }, { "description": "Score", "type": "number" } ] } } ] }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "token": "WEIGHTS", "name": "weight", "type": "integer", "optional": true, "multiple": true }, { "token": "AGGREGATE", "name": "aggregate", "type": "oneof", "optional": true, "arguments": [ { "name": "sum", "type": "pure-token", "token": "SUM" }, { "name": "min", "type": "pure-token", "token": "MIN" }, { "name": "max", "type": "pure-token", "token": "MAX" } ] }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zintercard.json000066400000000000000000000032401501533116600202570ustar00rootroot00000000000000{ "ZINTERCARD": { "summary": "Returns the number of members of the intersect of multiple sorted sets.", "complexity": "O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets.", "group": "sorted_set", "since": "7.0.0", "arity": -3, "function": "zinterCardCommand", "get_keys_function": "zunionInterDiffGetKeys", "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "description": "Number of elements in the resulting intersection.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "token": "LIMIT", "name": "limit", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/zinterstore.json000066400000000000000000000061241501533116600205060ustar00rootroot00000000000000{ "ZINTERSTORE": { "summary": "Stores the intersect of multiple sorted sets in a key.", "complexity": "O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.", "group": "sorted_set", "since": "2.0.0", "arity": -4, "function": "zinterstoreCommand", "get_keys_function": "zunionInterDiffStoreGetKeys", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "description": "Number of elements in the resulting sorted set.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true }, { "token": "WEIGHTS", "name": "weight", "type": "integer", "optional": true, "multiple": true }, { "token": "AGGREGATE", "name": "aggregate", "type": "oneof", "optional": true, "arguments": [ { "name": "sum", "type": "pure-token", "token": "SUM" }, { "name": "min", "type": "pure-token", "token": "MIN" }, { "name": "max", "type": "pure-token", "token": "MAX" } ] } ] } } redis-8.0.2/src/commands/zlexcount.json000066400000000000000000000027451501533116600201560ustar00rootroot00000000000000{ "ZLEXCOUNT": { "summary": "Returns the number of members in a sorted set within a lexicographical range.", "complexity": "O(log(N)) with N being the number of elements in the sorted set.", "group": "sorted_set", "since": "2.8.9", "arity": 4, "function": "zlexcountCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "Number of elements in the specified score range.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "min", "type": "string" }, { "name": "max", "type": "string" } ] } } redis-8.0.2/src/commands/zmpop.json000066400000000000000000000070761501533116600172720ustar00rootroot00000000000000{ "ZMPOP": { "summary": "Returns the highest- or lowest-scoring members from one or more sorted sets after removing them. Deletes the sorted set if the last member was popped.", "complexity": "O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.", "group": "sorted_set", "since": "7.0.0", "arity": -4, "function": "zmpopCommand", "get_keys_function": "zmpopGetKeys", "command_flags": [ "WRITE" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "oneOf": [ { "description": "No element could be popped.", "type": "null" }, { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Name of the key that elements were popped." }, { "type": "array", "description": "Popped elements.", "items": { "type": "array", "uniqueItems": true, "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Name of the member." }, { "type": "number", "description": "Score." } ] } } ] } ] }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "name": "where", "type": "oneof", "arguments": [ { "name": "min", "type": "pure-token", "token": "MIN" }, { "name": "max", "type": "pure-token", "token": "MAX" } ] }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/zmscore.json000066400000000000000000000034371501533116600176040ustar00rootroot00000000000000{ "ZMSCORE": { "summary": "Returns the score of one or more members in a sorted set.", "complexity": "O(N) where N is the number of members being requested.", "group": "sorted_set", "since": "6.2.0", "arity": -3, "function": "zmscoreCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "minItems": 1, "items": { "oneOf": [ { "type": "number", "description": "The score of the member (a double precision floating point number). In RESP2, this is returned as string." }, { "type": "null", "description": "Member does not exist in the sorted set." } ] } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/zpopmax.json000066400000000000000000000054211501533116600176130ustar00rootroot00000000000000{ "ZPOPMAX": { "summary": "Returns the highest-scoring members from a sorted set after removing them. Deletes the sorted set if the last member was popped.", "complexity": "O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.", "group": "sorted_set", "since": "5.0.0", "arity": -2, "function": "zpopmaxCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "type": "array", "description": "List of popped elements and scores when 'COUNT' isn't specified.", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Popped element." }, { "type": "number", "description": "Score." } ] }, { "type": "array", "description": "List of popped elements and scores when 'COUNT' is specified.", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Popped element." }, { "type": "number", "description": "Score." } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/zpopmin.json000066400000000000000000000054201501533116600176100ustar00rootroot00000000000000{ "ZPOPMIN": { "summary": "Returns the lowest-scoring members from a sorted set after removing them. Deletes the sorted set if the last member was popped.", "complexity": "O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.", "group": "sorted_set", "since": "5.0.0", "arity": -2, "function": "zpopminCommand", "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "ACCESS", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "type": "array", "description": "List of popped elements and scores when 'COUNT' isn't specified.", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Popped element." }, { "type": "number", "description": "Score." } ] }, { "type": "array", "description": "List of popped elements and scores when 'COUNT' is specified.", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Popped element." }, { "type": "number", "description": "Score." } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "count", "type": "integer", "optional": true } ] } } redis-8.0.2/src/commands/zrandmember.json000066400000000000000000000060101501533116600204160ustar00rootroot00000000000000{ "ZRANDMEMBER": { "summary": "Returns one or more random members from a sorted set.", "complexity": "O(N) where N is the number of members returned", "group": "sorted_set", "since": "6.2.0", "arity": -2, "function": "zrandmemberCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "type": "null", "description": "Key does not exist." }, { "type": "string", "description": "Randomly selected element when 'COUNT' is not used." }, { "type": "array", "description": "Randomly selected elements when 'COUNT' is used.", "items": { "type": "string" } }, { "type": "array", "description": "Randomly selected elements when 'COUNT' and 'WITHSCORES' modifiers are used.", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "Element." }, { "type": "number", "description": "Score." } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "options", "type": "block", "optional": true, "arguments": [ { "name": "count", "type": "integer" }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true } ] } ] } } redis-8.0.2/src/commands/zrange.json000066400000000000000000000100121501533116600173730ustar00rootroot00000000000000{ "ZRANGE": { "summary": "Returns members in a sorted set within a range of indexes.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.", "group": "sorted_set", "since": "1.2.0", "arity": -4, "function": "zrangeCommand", "history": [ [ "6.2.0", "Added the `REV`, `BYSCORE`, `BYLEX` and `LIMIT` options." ] ], "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "description": "A list of member elements", "type": "array", "uniqueItems": true, "items": { "type": "string" } }, { "description": "Members and their scores. Returned in case `WITHSCORES` was used. In RESP2 this is returned as a flat array", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "Member", "type": "string" }, { "description": "Score", "type": "number" } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "string" }, { "name": "stop", "type": "string" }, { "name": "sortby", "type": "oneof", "optional": true, "since": "6.2.0", "arguments": [ { "name": "byscore", "type": "pure-token", "token": "BYSCORE" }, { "name": "bylex", "type": "pure-token", "token": "BYLEX" } ] }, { "name": "rev", "token": "REV", "type": "pure-token", "optional": true, "since": "6.2.0" }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "since": "6.2.0", "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zrangebylex.json000066400000000000000000000044341501533116600204520ustar00rootroot00000000000000{ "ZRANGEBYLEX": { "summary": "Returns members in a sorted set within a lexicographical range.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", "group": "sorted_set", "since": "2.8.9", "arity": -4, "function": "zrangebylexCommand", "deprecated_since": "6.2.0", "replaced_by": "`ZRANGE` with the `BYLEX` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List of elements in the specified score range.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "min", "type": "string" }, { "name": "max", "type": "string" }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] } ] } } redis-8.0.2/src/commands/zrangebyscore.json000066400000000000000000000072471501533116600210020ustar00rootroot00000000000000{ "ZRANGEBYSCORE": { "summary": "Returns members in a sorted set within a range of scores.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", "group": "sorted_set", "since": "1.0.5", "arity": -4, "function": "zrangebyscoreCommand", "history": [ [ "2.0.0", "Added the `WITHSCORES` modifier." ] ], "deprecated_since": "6.2.0", "replaced_by": "`ZRANGE` with the `BYSCORE` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "type": "array", "description": "List of the elements in the specified score range, as not WITHSCORES", "uniqueItems": true, "items": { "type": "string", "description": "Element" } }, { "type": "array", "description": "List of the elements and their scores in the specified score range, as WITHSCORES used", "uniqueItems": true, "items": { "type": "array", "description": "Tuple of element and its score", "minItems": 2, "maxItems": 2, "items": [ { "description": "element", "type": "string" }, { "description": "score", "type": "number" } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "min", "type": "double" }, { "name": "max", "type": "double" }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true, "since": "2.0.0" }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] } ] } } redis-8.0.2/src/commands/zrangestore.json000066400000000000000000000063311501533116600204610ustar00rootroot00000000000000{ "ZRANGESTORE": { "summary": "Stores a range of members from sorted set in a key.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements stored into the destination key.", "group": "sorted_set", "since": "6.2.0", "arity": -5, "function": "zrangestoreCommand", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Number of elements in the resulting sorted set." }, "arguments": [ { "name": "dst", "type": "key", "key_spec_index": 0 }, { "name": "src", "type": "key", "key_spec_index": 1 }, { "name": "min", "type": "string" }, { "name": "max", "type": "string" }, { "name": "sortby", "type": "oneof", "optional": true, "arguments": [ { "name": "byscore", "type": "pure-token", "token": "BYSCORE" }, { "name": "bylex", "type": "pure-token", "token": "BYLEX" } ] }, { "name": "rev", "token": "REV", "type": "pure-token", "optional": true }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] } ] } } redis-8.0.2/src/commands/zrank.json000066400000000000000000000045471501533116600172520ustar00rootroot00000000000000{ "ZRANK": { "summary": "Returns the index of a member in a sorted set ordered by ascending scores.", "complexity": "O(log(N))", "group": "sorted_set", "since": "2.0.0", "arity": -3, "function": "zrankCommand", "history": [ [ "7.2.0", "Added the optional `WITHSCORE` argument." ] ], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "null", "description": "Key does not exist or the member does not exist in the sorted set." }, { "type": "integer", "description": "The rank of the member when 'WITHSCORE' is not used." }, { "type": "array", "description": "The rank and score of the member when 'WITHSCORE' is used.", "minItems": 2, "maxItems": 2, "items": [ { "type": "integer" }, { "type": "number" } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string" }, { "name": "withscore", "token": "WITHSCORE", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zrem.json000066400000000000000000000032131501533116600170670ustar00rootroot00000000000000{ "ZREM": { "summary": "Removes one or more members from a sorted set. Deletes the sorted set if all members were removed.", "complexity": "O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed.", "group": "sorted_set", "since": "1.2.0", "arity": -3, "function": "zremCommand", "history": [ [ "2.4.0", "Accepts multiple elements." ] ], "command_flags": [ "WRITE", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "description": "The number of members removed from the sorted set, not including non existing members.", "type": "integer", "minimum": 0 }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string", "multiple": true } ] } } redis-8.0.2/src/commands/zremrangebylex.json000066400000000000000000000030071501533116600211510ustar00rootroot00000000000000{ "ZREMRANGEBYLEX": { "summary": "Removes members in a sorted set within a lexicographical range. Deletes the sorted set if all members were removed.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", "group": "sorted_set", "since": "2.8.9", "arity": 4, "function": "zremrangebylexCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Number of elements removed." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "min", "type": "string" }, { "name": "max", "type": "string" } ] } } redis-8.0.2/src/commands/zremrangebyrank.json000066400000000000000000000030111501533116600213070ustar00rootroot00000000000000{ "ZREMRANGEBYRANK": { "summary": "Removes members in a sorted set within a range of indexes. Deletes the sorted set if all members were removed.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", "group": "sorted_set", "since": "2.0.0", "arity": 4, "function": "zremrangebyrankCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Number of elements removed." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "integer" }, { "name": "stop", "type": "integer" } ] } } redis-8.0.2/src/commands/zremrangebyscore.json000066400000000000000000000030051501533116600214720ustar00rootroot00000000000000{ "ZREMRANGEBYSCORE": { "summary": "Removes members in a sorted set within a range of scores. Deletes the sorted set if all members were removed.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.", "group": "sorted_set", "since": "1.2.0", "arity": 4, "function": "zremrangebyscoreCommand", "command_flags": [ "WRITE" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RW", "DELETE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "integer", "description": "Number of elements removed." }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "min", "type": "double" }, { "name": "max", "type": "double" } ] } } redis-8.0.2/src/commands/zrevrange.json000066400000000000000000000054221501533116600201210ustar00rootroot00000000000000{ "ZREVRANGE": { "summary": "Returns members in a sorted set within a range of indexes in reverse order.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.", "group": "sorted_set", "since": "1.2.0", "arity": -4, "function": "zrevrangeCommand", "deprecated_since": "6.2.0", "replaced_by": "`ZRANGE` with the `REV` argument", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "description": "List of member elements.", "type": "array", "uniqueItems": true, "items": { "type": "string" } }, { "description": "List of the members and their scores. Returned in case `WITHSCORES` was used.", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "member", "type": "string" }, { "description": "score", "type": "number" } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "start", "type": "integer" }, { "name": "stop", "type": "integer" }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zrevrangebylex.json000066400000000000000000000045021501533116600211630ustar00rootroot00000000000000{ "ZREVRANGEBYLEX": { "summary": "Returns members in a sorted set within a lexicographical range in reverse order.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", "group": "sorted_set", "since": "2.8.9", "arity": -4, "function": "zrevrangebylexCommand", "deprecated_since": "6.2.0", "replaced_by": "`ZRANGE` with the `REV` and `BYLEX` arguments", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "type": "array", "description": "List of the elements in the specified score range.", "uniqueItems": true, "items": { "type": "string" } }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "max", "type": "string" }, { "name": "min", "type": "string" }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] } ] } } redis-8.0.2/src/commands/zrevrangebyscore.json000066400000000000000000000072501501533116600215110ustar00rootroot00000000000000{ "ZREVRANGEBYSCORE": { "summary": "Returns members in a sorted set within a range of scores in reverse order.", "complexity": "O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).", "group": "sorted_set", "since": "2.2.0", "arity": -4, "function": "zrevrangebyscoreCommand", "history": [ [ "2.1.6", "`min` and `max` can be exclusive." ] ], "deprecated_since": "6.2.0", "replaced_by": "`ZRANGE` with the `REV` and `BYSCORE` arguments", "doc_flags": [ "DEPRECATED" ], "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "anyOf": [ { "type": "array", "description": "List of the elements in the specified score range, as not WITHSCORES", "uniqueItems": true, "items": { "type": "string", "description": "Element" } }, { "type": "array", "description": "List of the elements and their scores in the specified score range, as WITHSCORES used", "uniqueItems": true, "items": { "type": "array", "description": "Tuple of element and its score", "minItems": 2, "maxItems": 2, "items": [ { "type": "string", "description": "element" }, { "type": "number", "description": "score" } ] } } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "max", "type": "double" }, { "name": "min", "type": "double" }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true }, { "token": "LIMIT", "name": "limit", "type": "block", "optional": true, "arguments": [ { "name": "offset", "type": "integer" }, { "name": "count", "type": "integer" } ] } ] } } redis-8.0.2/src/commands/zrevrank.json000066400000000000000000000045561501533116600177670ustar00rootroot00000000000000{ "ZREVRANK": { "summary": "Returns the index of a member in a sorted set ordered by descending scores.", "complexity": "O(log(N))", "group": "sorted_set", "since": "2.0.0", "arity": -3, "function": "zrevrankCommand", "history": [ [ "7.2.0", "Added the optional `WITHSCORE` argument." ] ], "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "null", "description": "Key does not exist or the member does not exist in the sorted set." }, { "type": "integer", "description": "The rank of the member when 'WITHSCORE' is not used." }, { "type": "array", "description": "The rank and score of the member when 'WITHSCORE' is used.", "minItems": 2, "maxItems": 2, "items": [ { "type": "integer" }, { "type": "number" } ] } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string" }, { "name": "withscore", "token": "WITHSCORE", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zscan.json000066400000000000000000000045031501533116600172330ustar00rootroot00000000000000{ "ZSCAN": { "summary": "Iterates over members and scores of a sorted set.", "complexity": "O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.", "group": "sorted_set", "since": "2.8.0", "arity": -3, "function": "zscanCommand", "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "command_tips": [ "NONDETERMINISTIC_OUTPUT" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "cursor", "type": "integer" }, { "token": "MATCH", "name": "pattern", "type": "pattern", "optional": true }, { "token": "COUNT", "name": "count", "type": "integer", "optional": true } ], "reply_schema": { "description": "cursor and scan response in array form", "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "description": "cursor", "type": "string" }, { "description": "list of elements of the sorted set, where each even element is the member, and each odd value is its associated score", "type": "array", "items": { "type": "string" } } ] } } } redis-8.0.2/src/commands/zscore.json000066400000000000000000000031171501533116600174220ustar00rootroot00000000000000{ "ZSCORE": { "summary": "Returns the score of a member in a sorted set.", "complexity": "O(1)", "group": "sorted_set", "since": "1.2.0", "arity": 3, "function": "zscoreCommand", "command_flags": [ "READONLY", "FAST" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } } ], "reply_schema": { "oneOf": [ { "type": "number", "description": "The score of the member (a double precision floating point number). In RESP2, this is returned as string." }, { "type": "null", "description": "Member does not exist in the sorted set, or key does not exist." } ] }, "arguments": [ { "name": "key", "type": "key", "key_spec_index": 0 }, { "name": "member", "type": "string" } ] } } redis-8.0.2/src/commands/zunion.json000066400000000000000000000066371501533116600174510ustar00rootroot00000000000000{ "ZUNION": { "summary": "Returns the union of multiple sorted sets.", "complexity": "O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.", "group": "sorted_set", "since": "6.2.0", "arity": -3, "function": "zunionCommand", "get_keys_function": "zunionInterDiffGetKeys", "command_flags": [ "READONLY" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "anyOf": [ { "description": "The result of union when 'WITHSCORES' is not used.", "type": "array", "uniqueItems": true, "items": { "type": "string" } }, { "description": "The result of union when 'WITHSCORES' is used.", "type": "array", "uniqueItems": true, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": [ { "type": "string" }, { "type": "number" } ] } } ] }, "arguments": [ { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 0, "multiple": true }, { "token": "WEIGHTS", "name": "weight", "type": "integer", "optional": true, "multiple": true }, { "token": "AGGREGATE", "name": "aggregate", "type": "oneof", "optional": true, "arguments": [ { "name": "sum", "type": "pure-token", "token": "SUM" }, { "name": "min", "type": "pure-token", "token": "MIN" }, { "name": "max", "type": "pure-token", "token": "MAX" } ] }, { "name": "withscores", "token": "WITHSCORES", "type": "pure-token", "optional": true } ] } } redis-8.0.2/src/commands/zunionstore.json000066400000000000000000000060251501533116600205150ustar00rootroot00000000000000{ "ZUNIONSTORE": { "summary": "Stores the union of multiple sorted sets in a key.", "complexity": "O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.", "group": "sorted_set", "since": "2.0.0", "arity": -4, "function": "zunionstoreCommand", "get_keys_function": "zunionInterDiffStoreGetKeys", "command_flags": [ "WRITE", "DENYOOM" ], "acl_categories": [ "SORTEDSET" ], "key_specs": [ { "flags": [ "OW", "UPDATE" ], "begin_search": { "index": { "pos": 1 } }, "find_keys": { "range": { "lastkey": 0, "step": 1, "limit": 0 } } }, { "flags": [ "RO", "ACCESS" ], "begin_search": { "index": { "pos": 2 } }, "find_keys": { "keynum": { "keynumidx": 0, "firstkey": 1, "step": 1 } } } ], "reply_schema": { "description": "The number of elements in the resulting sorted set.", "type": "integer" }, "arguments": [ { "name": "destination", "type": "key", "key_spec_index": 0 }, { "name": "numkeys", "type": "integer" }, { "name": "key", "type": "key", "key_spec_index": 1, "multiple": true }, { "token": "WEIGHTS", "name": "weight", "type": "integer", "optional": true, "multiple": true }, { "token": "AGGREGATE", "name": "aggregate", "type": "oneof", "optional": true, "arguments": [ { "name": "sum", "type": "pure-token", "token": "SUM" }, { "name": "min", "type": "pure-token", "token": "MIN" }, { "name": "max", "type": "pure-token", "token": "MAX" } ] } ] } } redis-8.0.2/src/config.c000066400000000000000000004450601501533116600150410ustar00rootroot00000000000000/* Configuration file parsing and CONFIG GET/SET commands implementation. * * Copyright (c) 2009-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). * * Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. */ #include "server.h" #include "cluster.h" #include "connection.h" #include "bio.h" #include #include #include #include #include #include /*----------------------------------------------------------------------------- * Config file name-value maps. *----------------------------------------------------------------------------*/ typedef struct deprecatedConfig { const char *name; const int argc_min; const int argc_max; } deprecatedConfig; configEnum maxmemory_policy_enum[] = { {"volatile-lru", MAXMEMORY_VOLATILE_LRU}, {"volatile-lfu", MAXMEMORY_VOLATILE_LFU}, {"volatile-random",MAXMEMORY_VOLATILE_RANDOM}, {"volatile-ttl",MAXMEMORY_VOLATILE_TTL}, {"allkeys-lru",MAXMEMORY_ALLKEYS_LRU}, {"allkeys-lfu",MAXMEMORY_ALLKEYS_LFU}, {"allkeys-random",MAXMEMORY_ALLKEYS_RANDOM}, {"noeviction",MAXMEMORY_NO_EVICTION}, {NULL, 0} }; configEnum syslog_facility_enum[] = { {"user", LOG_USER}, {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {NULL, 0} }; configEnum loglevel_enum[] = { {"debug", LL_DEBUG}, {"verbose", LL_VERBOSE}, {"notice", LL_NOTICE}, {"warning", LL_WARNING}, {"nothing", LL_NOTHING}, {NULL,0} }; configEnum supervised_mode_enum[] = { {"upstart", SUPERVISED_UPSTART}, {"systemd", SUPERVISED_SYSTEMD}, {"auto", SUPERVISED_AUTODETECT}, {"no", SUPERVISED_NONE}, {NULL, 0} }; configEnum aof_fsync_enum[] = { {"everysec", AOF_FSYNC_EVERYSEC}, {"always", AOF_FSYNC_ALWAYS}, {"no", AOF_FSYNC_NO}, {NULL, 0} }; configEnum shutdown_on_sig_enum[] = { {"default", 0}, {"save", SHUTDOWN_SAVE}, {"nosave", SHUTDOWN_NOSAVE}, {"now", SHUTDOWN_NOW}, {"force", SHUTDOWN_FORCE}, {NULL, 0} }; configEnum repl_diskless_load_enum[] = { {"disabled", REPL_DISKLESS_LOAD_DISABLED}, {"on-empty-db", REPL_DISKLESS_LOAD_WHEN_DB_EMPTY}, {"swapdb", REPL_DISKLESS_LOAD_SWAPDB}, {NULL, 0} }; configEnum tls_auth_clients_enum[] = { {"no", TLS_CLIENT_AUTH_NO}, {"yes", TLS_CLIENT_AUTH_YES}, {"optional", TLS_CLIENT_AUTH_OPTIONAL}, {NULL, 0} }; configEnum oom_score_adj_enum[] = { {"no", OOM_SCORE_ADJ_NO}, {"yes", OOM_SCORE_RELATIVE}, {"relative", OOM_SCORE_RELATIVE}, {"absolute", OOM_SCORE_ADJ_ABSOLUTE}, {NULL, 0} }; configEnum acl_pubsub_default_enum[] = { {"allchannels", SELECTOR_FLAG_ALLCHANNELS}, {"resetchannels", 0}, {NULL, 0} }; configEnum sanitize_dump_payload_enum[] = { {"no", SANITIZE_DUMP_NO}, {"yes", SANITIZE_DUMP_YES}, {"clients", SANITIZE_DUMP_CLIENTS}, {NULL, 0} }; configEnum protected_action_enum[] = { {"no", PROTECTED_ACTION_ALLOWED_NO}, {"yes", PROTECTED_ACTION_ALLOWED_YES}, {"local", PROTECTED_ACTION_ALLOWED_LOCAL}, {NULL, 0} }; configEnum cluster_preferred_endpoint_type_enum[] = { {"ip", CLUSTER_ENDPOINT_TYPE_IP}, {"hostname", CLUSTER_ENDPOINT_TYPE_HOSTNAME}, {"unknown-endpoint", CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT}, {NULL, 0} }; configEnum propagation_error_behavior_enum[] = { {"ignore", PROPAGATION_ERR_BEHAVIOR_IGNORE}, {"panic", PROPAGATION_ERR_BEHAVIOR_PANIC}, {"panic-on-replicas", PROPAGATION_ERR_BEHAVIOR_PANIC_ON_REPLICAS}, {NULL, 0} }; /* Output buffer limits presets. */ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { {0, 0, 0}, /* normal */ {1024*1024*256, 1024*1024*64, 60}, /* slave */ {1024*1024*32, 1024*1024*8, 60} /* pubsub */ }; /* OOM Score defaults */ int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 }; /* Generic config infrastructure function pointers * int is_valid_fn(val, err) * Return 1 when val is valid, and 0 when invalid. * Optionally set err to a static error string. */ /* Configuration values that require no special handling to set, get, load or * rewrite. */ typedef struct boolConfigData { int *config; /* The pointer to the server config this value is stored in */ int default_value; /* The default value of the config on rewrite */ int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */ } boolConfigData; typedef struct stringConfigData { char **config; /* Pointer to the server config this value is stored in. */ const char *default_value; /* Default value of the config on rewrite. */ int (*is_valid_fn)(char* val, const char **err); /* Optional function to check validity of new value (generic doc above) */ int convert_empty_to_null; /* Boolean indicating if empty strings should be stored as a NULL value. */ } stringConfigData; typedef struct sdsConfigData { sds *config; /* Pointer to the server config this value is stored in. */ char *default_value; /* Default value of the config on rewrite. */ int (*is_valid_fn)(sds val, const char **err); /* Optional function to check validity of new value (generic doc above) */ int convert_empty_to_null; /* Boolean indicating if empty SDS strings should be stored as a NULL value. */ } sdsConfigData; typedef struct enumConfigData { int *config; /* The pointer to the server config this value is stored in */ configEnum *enum_value; /* The underlying enum type this data represents */ int default_value; /* The default value of the config on rewrite */ int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */ } enumConfigData; typedef enum numericType { NUMERIC_TYPE_INT, NUMERIC_TYPE_UINT, NUMERIC_TYPE_LONG, NUMERIC_TYPE_ULONG, NUMERIC_TYPE_LONG_LONG, NUMERIC_TYPE_ULONG_LONG, NUMERIC_TYPE_SIZE_T, NUMERIC_TYPE_SSIZE_T, NUMERIC_TYPE_OFF_T, NUMERIC_TYPE_TIME_T, } numericType; typedef struct numericConfigData { union { int *i; unsigned int *ui; long *l; unsigned long *ul; long long *ll; unsigned long long *ull; size_t *st; ssize_t *sst; off_t *ot; time_t *tt; } config; /* The pointer to the numeric config this value is stored in */ unsigned int flags; numericType numeric_type; /* An enum indicating the type of this value */ long long lower_bound; /* The lower bound of this numeric value */ long long upper_bound; /* The upper bound of this numeric value */ long long default_value; /* The default value of the config on rewrite */ int (*is_valid_fn)(long long val, const char **err); /* Optional function to check validity of new value (generic doc above) */ } numericConfigData; typedef union typeData { boolConfigData yesno; stringConfigData string; sdsConfigData sds; enumConfigData enumd; numericConfigData numeric; } typeData; typedef struct standardConfig standardConfig; typedef int (*apply_fn)(const char **err); typedef struct typeInterface { /* Called on server start, to init the server with default value */ void (*init)(standardConfig *config); /* Called on server startup and CONFIG SET, returns 1 on success, * 2 meaning no actual change done, 0 on error and can set a verbose err * string */ int (*set)(standardConfig *config, sds *argv, int argc, const char **err); /* Optional: called after `set()` to apply the config change. Used only in * the context of CONFIG SET. Returns 1 on success, 0 on failure. * Optionally set err to a static error string. */ apply_fn apply; /* Called on CONFIG GET, returns sds to be used in reply */ sds (*get)(standardConfig *config); /* Called on CONFIG REWRITE, required to rewrite the config state */ void (*rewrite)(standardConfig *config, const char *name, struct rewriteConfigState *state); } typeInterface; struct standardConfig { const char *name; /* The user visible name of this config */ const char *alias; /* An alias that can also be used for this config */ unsigned int flags; /* Flags for this specific config */ typeInterface interface; /* The function pointers that define the type interface */ typeData data; /* The type specific data exposed used by the interface */ configType type; /* The type of config this is. */ void *privdata; /* privdata for this config, for module configs this is a ModuleConfig struct */ }; dict *configs = NULL; /* Runtime config values */ /* Lookup a config by the provided sds string name, or return NULL * if the config does not exist */ static standardConfig *lookupConfig(const sds name) { dictEntry *de = dictFind(configs, name); return de ? dictGetVal(de) : NULL; } /*----------------------------------------------------------------------------- * Enum access functions *----------------------------------------------------------------------------*/ /* Get enum value from name. If there is no match INT_MIN is returned. */ int configEnumGetValue(configEnum *ce, sds *argv, int argc, int bitflags) { if (argc == 0 || (!bitflags && argc != 1)) return INT_MIN; int values = 0; for (int i = 0; i < argc; i++) { int matched = 0; for (configEnum *ceItem = ce; ceItem->name != NULL; ceItem++) { if (!strcasecmp(argv[i],ceItem->name)) { values |= ceItem->val; matched = 1; } } if (!matched) return INT_MIN; } return values; } /* Get enum name/s from value. If no matches are found "unknown" is returned. */ static sds configEnumGetName(configEnum *ce, int values, int bitflags) { sds names = NULL; int unmatched = values; for( ; ce->name != NULL; ce++) { if (values == ce->val) { /* Short path for perfect match */ sdsfree(names); return sdsnew(ce->name); } /* Note: for bitflags, we want them sorted from high to low, so that if there are several / partially * overlapping entries, we'll prefer the ones matching more bits. */ if (bitflags && ce->val && ce->val == (unmatched & ce->val)) { names = names ? sdscatfmt(names, " %s", ce->name) : sdsnew(ce->name); unmatched &= ~ce->val; } } if (!names || unmatched) { sdsfree(names); return sdsnew("unknown"); } return names; } /* Used for INFO generation. */ const char *evictPolicyToString(void) { for (configEnum *ce = maxmemory_policy_enum; ce->name != NULL; ce++) { if (server.maxmemory_policy == ce->val) return ce->name; } serverPanic("unknown eviction policy"); } /*----------------------------------------------------------------------------- * Config file parsing *----------------------------------------------------------------------------*/ int yesnotoi(char *s) { if (!strcasecmp(s,"yes")) return 1; else if (!strcasecmp(s,"no")) return 0; else return -1; } void appendServerSaveParams(time_t seconds, int changes) { server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1)); server.saveparams[server.saveparamslen].seconds = seconds; server.saveparams[server.saveparamslen].changes = changes; server.saveparamslen++; } void resetServerSaveParams(void) { zfree(server.saveparams); server.saveparams = NULL; server.saveparamslen = 0; } void queueLoadModule(sds path, sds *argv, int argc) { int i; struct moduleLoadQueueEntry *loadmod; loadmod = zmalloc(sizeof(struct moduleLoadQueueEntry)); loadmod->argv = argc ? zmalloc(sizeof(robj*)*argc) : NULL; loadmod->path = sdsnew(path); loadmod->argc = argc; for (i = 0; i < argc; i++) { loadmod->argv[i] = createRawStringObject(argv[i],sdslen(argv[i])); } listAddNodeTail(server.loadmodule_queue,loadmod); } /* Parse an array of `arg_len` sds strings, validate and populate * server.client_obuf_limits if valid. * Used in CONFIG SET and configuration file parsing. */ static int updateClientOutputBufferLimit(sds *args, int arg_len, const char **err) { int j; int class; unsigned long long hard, soft; int hard_err, soft_err; int soft_seconds; char *soft_seconds_eptr; clientBufferLimitsConfig values[CLIENT_TYPE_OBUF_COUNT]; int classes[CLIENT_TYPE_OBUF_COUNT] = {0}; /* We need a multiple of 4: */ if (arg_len % 4) { if (err) *err = "Wrong number of arguments in " "buffer limit configuration."; return 0; } /* Sanity check of single arguments, so that we either refuse the * whole configuration string or accept it all, even if a single * error in a single client class is present. */ for (j = 0; j < arg_len; j += 4) { class = getClientTypeByName(args[j]); if (class == -1 || class == CLIENT_TYPE_MASTER) { if (err) *err = "Invalid client class specified in " "buffer limit configuration."; return 0; } hard = memtoull(args[j+1], &hard_err); soft = memtoull(args[j+2], &soft_err); soft_seconds = strtoll(args[j+3], &soft_seconds_eptr, 10); if (hard_err || soft_err || soft_seconds < 0 || *soft_seconds_eptr != '\0') { if (err) *err = "Error in hard, soft or soft_seconds setting in " "buffer limit configuration."; return 0; } values[class].hard_limit_bytes = hard; values[class].soft_limit_bytes = soft; values[class].soft_limit_seconds = soft_seconds; classes[class] = 1; } /* Finally set the new config. */ for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) { if (classes[j]) server.client_obuf_limits[j] = values[j]; } return 1; } /* Note this is here to support detecting we're running a config set from * within conf file parsing. This is only needed to support the deprecated * abnormal aggregate `save T C` functionality. Remove in the future. */ static int reading_config_file; void loadServerConfigFromString(char *config) { deprecatedConfig deprecated_configs[] = { {"list-max-ziplist-entries", 2, 2}, {"list-max-ziplist-value", 2, 2}, {"lua-replicate-commands", 2, 2}, {"io-threads-do-reads", 2, 2}, {NULL, 0}, }; char buf[1024]; const char *err = NULL; int linenum = 0, totlines, i; sds *lines; sds *argv = NULL; int argc; reading_config_file = 1; lines = sdssplitlen(config,strlen(config),"\n",1,&totlines); for (i = 0; i < totlines; i++) { linenum = i+1; lines[i] = sdstrim(lines[i]," \t\r\n"); /* Skip comments and blank lines */ if (lines[i][0] == '#' || lines[i][0] == '\0') continue; /* Split into arguments */ argv = sdssplitargs(lines[i],&argc); if (argv == NULL) { err = "Unbalanced quotes in configuration line"; goto loaderr; } /* Skip this line if the resulting command vector is empty. */ if (argc == 0) { sdsfreesplitres(argv,argc); argv = NULL; continue; } sdstolower(argv[0]); /* Iterate the configs that are standard */ standardConfig *config = lookupConfig(argv[0]); if (config) { /* For normal single arg configs enforce we have a single argument. * Note that MULTI_ARG_CONFIGs need to validate arg count on their own */ if (!(config->flags & MULTI_ARG_CONFIG) && argc != 2) { err = "wrong number of arguments"; goto loaderr; } if ((config->flags & MULTI_ARG_CONFIG) && argc == 2 && sdslen(argv[1])) { /* For MULTI_ARG_CONFIGs, if we only have one argument, try to split it by spaces. * Only if the argument is not empty, otherwise something like --save "" will fail. * So that we can support something like --config "arg1 arg2 arg3". */ sds *new_argv; int new_argc; new_argv = sdssplitargs(argv[1], &new_argc); if (!config->interface.set(config, new_argv, new_argc, &err)) { if(new_argv) sdsfreesplitres(new_argv, new_argc); goto loaderr; } sdsfreesplitres(new_argv, new_argc); } else { /* Set config using all arguments that follows */ if (!config->interface.set(config, &argv[1], argc-1, &err)) { goto loaderr; } } sdsfreesplitres(argv,argc); argv = NULL; continue; } else { int match = 0; for (deprecatedConfig *config = deprecated_configs; config->name != NULL; config++) { if (!strcasecmp(argv[0], config->name) && config->argc_min <= argc && argc <= config->argc_max) { match = 1; break; } } if (match) { sdsfreesplitres(argv,argc); argv = NULL; continue; } } /* Execute config directives */ if (!strcasecmp(argv[0],"include") && argc == 2) { loadServerConfig(argv[1], 0, NULL); } else if (!strcasecmp(argv[0],"rename-command") && argc == 3) { struct redisCommand *cmd = lookupCommandBySds(argv[1]); int retval; if (!cmd) { err = "No such command in rename-command"; goto loaderr; } /* If the target command name is the empty string we just * remove it from the command table. */ retval = dictDelete(server.commands, argv[1]); serverAssert(retval == DICT_OK); /* Otherwise we re-add the command under a different name. */ if (sdslen(argv[2]) != 0) { sds copy = sdsdup(argv[2]); retval = dictAdd(server.commands, copy, cmd); if (retval != DICT_OK) { sdsfree(copy); err = "Target command name already exists"; goto loaderr; } } } else if (!strcasecmp(argv[0],"user") && argc >= 2) { int argc_err; if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) { const char *errmsg = ACLSetUserStringError(); snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s", argv[argc_err],errmsg); err = buf; goto loaderr; } } else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) { queueLoadModule(argv[1],&argv[2],argc-2); } else if (!strcasecmp(argv[0],"sentinel")) { /* argc == 1 is handled by main() as we need to enter the sentinel * mode ASAP. */ if (argc != 1) { if (!server.sentinel_mode) { err = "sentinel directive while not in sentinel mode"; goto loaderr; } queueSentinelConfig(argv+1,argc-1,linenum,lines[i]); } } else { /* Collect all unknown configurations into `module_configs_queue`. * These may include valid module configurations or invalid ones. * They will be validated later by loadModuleConfigs() against the * configurations declared by the loaded module(s). */ if (argc < 2) { err = "Bad directive or wrong number of arguments"; goto loaderr; } sds name = sdsdup(argv[0]); sds val = sdsdup(argv[1]); for (int i = 2; i < argc; i++) val = sdscatfmt(val, " %S", argv[i]); if (!dictReplace(server.module_configs_queue, name, val)) sdsfree(name); } sdsfreesplitres(argv,argc); argv = NULL; } if (server.logfile[0] != '\0') { FILE *logfp; /* Test if we are able to open the file. The server will not * be able to abort just for this problem later... */ logfp = fopen(server.logfile,"a"); if (logfp == NULL) { err = sdscatprintf(sdsempty(), "Can't open the log file: %s", strerror(errno)); goto loaderr; } fclose(logfp); } /* Sanity checks. */ if (server.cluster_enabled && server.masterhost) { err = "replicaof directive not allowed in cluster mode"; goto loaderr; } /* in case cluster mode is enabled dbnum must be 1 */ if (server.cluster_enabled && server.dbnum > 1) { serverLog(LL_WARNING, "WARNING: Changing databases number from %d to 1 since we are in cluster mode", server.dbnum); server.dbnum = 1; } /* To ensure backward compatibility and work while hz is out of range */ if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ; if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; sdsfreesplitres(lines,totlines); reading_config_file = 0; return; loaderr: if (argv) sdsfreesplitres(argv,argc); fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n", REDIS_VERSION); if (i < totlines) { fprintf(stderr, "Reading the configuration file, at line %d\n", linenum); fprintf(stderr, ">>> '%s'\n", lines[i]); } fprintf(stderr, "%s\n", err); exit(1); } /* Load the server configuration from the specified filename. * The function appends the additional configuration directives stored * in the 'options' string to the config file before loading. * * Both filename and options can be NULL, in such a case are considered * empty. This way loadServerConfig can be used to just load a file or * just load a string. */ #define CONFIG_READ_LEN 1024 void loadServerConfig(char *filename, char config_from_stdin, char *options) { sds config = sdsempty(); char buf[CONFIG_READ_LEN+1]; FILE *fp; glob_t globbuf; /* Load the file content */ if (filename) { /* The logic for handling wildcards has slightly different behavior in cases where * there is a failure to locate the included file. * Whether or not a wildcard is specified, we should ALWAYS log errors when attempting * to open included config files. * * However, we desire a behavioral difference between instances where a wildcard was * specified and those where it hasn't: * no wildcards : attempt to open the specified file and fail with a logged error * if the file cannot be found and opened. * with wildcards : attempt to glob the specified pattern; if no files match the * pattern, then gracefully continue on to the next entry in the * config file, as if the current entry was never encountered. * This will allow for empty conf.d directories to be included. */ if (strchr(filename, '*') || strchr(filename, '?') || strchr(filename, '[')) { /* A wildcard character detected in filename, so let us use glob */ if (glob(filename, 0, NULL, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; i++) { if ((fp = fopen(globbuf.gl_pathv[i], "r")) == NULL) { serverLog(LL_WARNING, "Fatal error, can't open config file '%s': %s", globbuf.gl_pathv[i], strerror(errno)); exit(1); } while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL) config = sdscat(config,buf); fclose(fp); } globfree(&globbuf); } } else { /* No wildcard in filename means we can use the original logic to read and * potentially fail traditionally */ if ((fp = fopen(filename, "r")) == NULL) { serverLog(LL_WARNING, "Fatal error, can't open config file '%s': %s", filename, strerror(errno)); exit(1); } while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL) config = sdscat(config,buf); fclose(fp); } } /* Append content from stdin */ if (config_from_stdin) { serverLog(LL_NOTICE,"Reading config from stdin"); fp = stdin; while(fgets(buf,CONFIG_READ_LEN+1,fp) != NULL) config = sdscat(config,buf); } /* Append the additional options */ if (options) { config = sdscat(config,"\n"); config = sdscat(config,options); } loadServerConfigFromString(config); sdsfree(config); } static int performInterfaceSet(standardConfig *config, sds value, const char **errstr) { sds *argv; int argc, res; if (config->flags & MULTI_ARG_CONFIG) { argv = sdssplitlen(value, sdslen(value), " ", 1, &argc); } else { argv = (char**)&value; argc = 1; } /* Set the config */ res = config->interface.set(config, argv, argc, errstr); if (config->flags & MULTI_ARG_CONFIG) sdsfreesplitres(argv, argc); return res; } /* Find the config by name and attempt to set it to value. */ int performModuleConfigSetFromName(sds name, sds value, const char **err) { standardConfig *config = lookupConfig(name); if (!config || !(config->flags & MODULE_CONFIG)) { *err = "Config name not found"; return 0; } return performInterfaceSet(config, value, err); } /* Find config by name and attempt to set it to its default value. */ int performModuleConfigSetDefaultFromName(sds name, const char **err) { standardConfig *config = lookupConfig(name); serverAssert(config); if (!(config->flags & MODULE_CONFIG)) { *err = "Config name not found"; return 0; } switch (config->type) { case BOOL_CONFIG: return setModuleBoolConfig(config->privdata, config->data.yesno.default_value, err); case SDS_CONFIG: return setModuleStringConfig(config->privdata, config->data.sds.default_value, err); case NUMERIC_CONFIG: return setModuleNumericConfig(config->privdata, config->data.numeric.default_value, err); case ENUM_CONFIG: return setModuleEnumConfig(config->privdata, config->data.enumd.default_value, err); default: serverPanic("Config type of module config is not allowed."); } return 0; } static void restoreBackupConfig(standardConfig **set_configs, sds *old_values, int count, apply_fn *apply_fns, list *module_configs) { int i; const char *errstr = "unknown error"; /* Set all backup values */ for (i = 0; i < count; i++) { if (!performInterfaceSet(set_configs[i], old_values[i], &errstr)) serverLog(LL_WARNING, "Failed restoring failed CONFIG SET command. Error setting %s to '%s': %s", set_configs[i]->name, old_values[i], errstr); } /* Apply backup */ if (apply_fns) { for (i = 0; i < count && apply_fns[i] != NULL; i++) { if (!apply_fns[i](&errstr)) serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr); } } if (module_configs) { if (!moduleConfigApplyConfig(module_configs, &errstr, NULL)) serverLog(LL_WARNING, "Failed applying restored failed CONFIG SET command: %s", errstr); } } /*----------------------------------------------------------------------------- * CONFIG SET implementation *----------------------------------------------------------------------------*/ void configSetCommand(client *c) { const char *errstr = NULL; const char *invalid_arg_name = NULL; const char *err_arg_name = NULL; standardConfig **set_configs; /* TODO: make this a dict for better performance */ list *module_configs_apply; const char **config_names; sds *new_values; sds *old_values = NULL; apply_fn *apply_fns; /* TODO: make this a set for better performance */ int config_count, i, j; int invalid_args = 0, deny_loading_error = 0; int *config_map_fns; /* Make sure we have an even number of arguments: conf-val pairs */ if (c->argc & 1) { addReplyErrorObject(c, shared.syntaxerr); return; } config_count = (c->argc - 2) / 2; module_configs_apply = listCreate(); set_configs = zcalloc(sizeof(standardConfig*)*config_count); config_names = zcalloc(sizeof(char*)*config_count); new_values = zmalloc(sizeof(sds*)*config_count); old_values = zcalloc(sizeof(sds*)*config_count); apply_fns = zcalloc(sizeof(apply_fn)*config_count); config_map_fns = zmalloc(sizeof(int)*config_count); /* Find all relevant configs */ for (i = 0; i < config_count; i++) { standardConfig *config = lookupConfig(c->argv[2+i*2]->ptr); /* Fail if we couldn't find this config */ if (!config) { if (!invalid_args) { invalid_arg_name = c->argv[2+i*2]->ptr; invalid_args = 1; } continue; } /* Note: it's important we run over ALL passed configs and check if we need to call `redactClientCommandArgument()`. * This is in order to avoid anyone using this command for a log/slowlog/monitor/etc. displaying sensitive info. * So even if we encounter an error we still continue running over the remaining arguments. */ if (config->flags & SENSITIVE_CONFIG) { redactClientCommandArgument(c,2+i*2+1); } /* We continue to make sure we redact all the configs */ if (invalid_args) continue; if (config->flags & IMMUTABLE_CONFIG || (config->flags & PROTECTED_CONFIG && !allowProtectedAction(server.enable_protected_configs, c))) { /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ errstr = (config->flags & IMMUTABLE_CONFIG) ? "can't set immutable config" : "can't set protected config"; err_arg_name = c->argv[2+i*2]->ptr; invalid_args = 1; continue; } if (server.loading && config->flags & DENY_LOADING_CONFIG) { /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ deny_loading_error = 1; invalid_args = 1; continue; } /* If this config appears twice then fail */ for (j = 0; j < i; j++) { if (set_configs[j] == config) { /* Note: we don't abort the loop since we still want to handle redacting sensitive configs (above) */ errstr = "duplicate parameter"; err_arg_name = c->argv[2+i*2]->ptr; invalid_args = 1; break; } } set_configs[i] = config; config_names[i] = config->name; new_values[i] = c->argv[2+i*2+1]->ptr; } if (invalid_args) goto err; /* Backup old values before setting new ones */ for (i = 0; i < config_count; i++) old_values[i] = set_configs[i]->interface.get(set_configs[i]); /* Set all new values (don't apply yet) */ for (i = 0; i < config_count; i++) { int res = performInterfaceSet(set_configs[i], new_values[i], &errstr); if (!res) { restoreBackupConfig(set_configs, old_values, i+1, NULL, NULL); err_arg_name = set_configs[i]->name; goto err; } else if (res == 1) { /* A new value was set, if this config has an apply function then store it for execution later */ if (set_configs[i]->flags & MODULE_CONFIG) { addModuleConfigApply(module_configs_apply, set_configs[i]->privdata); } else if (set_configs[i]->interface.apply) { /* Check if this apply function is already stored */ int exists = 0; for (j = 0; apply_fns[j] != NULL && j <= i; j++) { if (apply_fns[j] == set_configs[i]->interface.apply) { exists = 1; break; } } /* Apply function not stored, store it */ if (!exists) { apply_fns[j] = set_configs[i]->interface.apply; config_map_fns[j] = i; } } } } /* Apply all configs after being set */ for (i = 0; i < config_count && apply_fns[i] != NULL; i++) { if (!apply_fns[i](&errstr)) { serverLog(LL_WARNING, "Failed applying new configuration. Possibly related to new %s setting. Restoring previous settings.", set_configs[config_map_fns[i]]->name); restoreBackupConfig(set_configs, old_values, config_count, apply_fns, NULL); err_arg_name = set_configs[config_map_fns[i]]->name; goto err; } } /* Apply all module configs that were set. */ if (!moduleConfigApplyConfig(module_configs_apply, &errstr, &err_arg_name)) { serverLogRaw(LL_WARNING, "Failed applying new module configuration. Restoring previous settings."); restoreBackupConfig(set_configs, old_values, config_count, apply_fns, module_configs_apply); goto err; } RedisModuleConfigChangeV1 cc = {.num_changes = config_count, .config_names = config_names}; moduleFireServerEvent(REDISMODULE_EVENT_CONFIG, REDISMODULE_SUBEVENT_CONFIG_CHANGE, &cc); addReply(c,shared.ok); goto end; err: if (deny_loading_error) { /* We give the loading error precedence because it may be handled by clients differently, unlike a plain -ERR. */ addReplyErrorObject(c,shared.loadingerr); } else if (invalid_arg_name) { addReplyErrorFormat(c,"Unknown option or number of arguments for CONFIG SET - '%s'", invalid_arg_name); } else if (errstr) { addReplyErrorFormat(c,"CONFIG SET failed (possibly related to argument '%s') - %s", err_arg_name, errstr); } else { addReplyErrorFormat(c,"CONFIG SET failed (possibly related to argument '%s')", err_arg_name); } end: zfree(set_configs); zfree(config_names); zfree(new_values); for (i = 0; i < config_count; i++) sdsfree(old_values[i]); zfree(old_values); zfree(apply_fns); zfree(config_map_fns); listRelease(module_configs_apply); } /*----------------------------------------------------------------------------- * CONFIG GET implementation *----------------------------------------------------------------------------*/ void configGetCommand(client *c) { int i; dictEntry *de; dictIterator *di; /* Create a dictionary to store the matched configs */ dict *matches = dictCreate(&externalStringType); for (i = 0; i < c->argc - 2; i++) { robj *o = c->argv[2+i]; sds name = o->ptr; /* If the string doesn't contain glob patterns, just directly * look up the key in the dictionary. */ if (!strpbrk(name, "[*?")) { if (dictFind(matches, name)) continue; standardConfig *config = lookupConfig(name); if (config) { dictAdd(matches, name, config); } continue; } /* Otherwise, do a match against all items in the dictionary. */ di = dictGetIterator(configs); while ((de = dictNext(di)) != NULL) { standardConfig *config = dictGetVal(de); /* Note that hidden configs require an exact match (not a pattern) */ if (config->flags & HIDDEN_CONFIG) continue; if (dictFind(matches, config->name)) continue; if (stringmatch(name, dictGetKey(de), 1)) { dictAdd(matches, dictGetKey(de), config); } } dictReleaseIterator(di); } di = dictGetIterator(matches); addReplyMapLen(c, dictSize(matches)); while ((de = dictNext(di)) != NULL) { standardConfig *config = (standardConfig *) dictGetVal(de); addReplyBulkCString(c, dictGetKey(de)); addReplyBulkSds(c, config->interface.get(config)); } dictReleaseIterator(di); dictRelease(matches); } /*----------------------------------------------------------------------------- * CONFIG REWRITE implementation *----------------------------------------------------------------------------*/ #define REDIS_CONFIG_REWRITE_SIGNATURE "# Generated by CONFIG REWRITE" /* We use the following dictionary type to store where a configuration * option is mentioned in the old configuration file, so it's * like "maxmemory" -> list of line numbers (first line is zero). */ void dictListDestructor(dict *d, void *val); /* Sentinel config rewriting is implemented inside sentinel.c by * rewriteConfigSentinelOption(). */ void rewriteConfigSentinelOption(struct rewriteConfigState *state); dictType optionToLineDictType = { dictSdsCaseHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCaseCompare, /* key compare */ dictSdsDestructor, /* key destructor */ dictListDestructor, /* val destructor */ NULL /* allow to expand */ }; dictType optionSetDictType = { dictSdsCaseHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ dictSdsKeyCaseCompare, /* key compare */ dictSdsDestructor, /* key destructor */ NULL, /* val destructor */ NULL /* allow to expand */ }; /* The config rewrite state. */ struct rewriteConfigState { dict *option_to_line; /* Option -> list of config file lines map */ dict *rewritten; /* Dictionary of already processed options */ int numlines; /* Number of lines in current config */ sds *lines; /* Current lines as an array of sds strings */ int needs_signature; /* True if we need to append the rewrite signature. */ int force_write; /* True if we want all keywords to be force written. Currently only used for testing and debug information. */ }; /* Free the configuration rewrite state. */ void rewriteConfigReleaseState(struct rewriteConfigState *state) { sdsfreesplitres(state->lines,state->numlines); dictRelease(state->option_to_line); dictRelease(state->rewritten); zfree(state); } /* Create the configuration rewrite state */ struct rewriteConfigState *rewriteConfigCreateState(void) { struct rewriteConfigState *state = zmalloc(sizeof(*state)); state->option_to_line = dictCreate(&optionToLineDictType); state->rewritten = dictCreate(&optionSetDictType); state->numlines = 0; state->lines = NULL; state->needs_signature = 1; state->force_write = 0; return state; } /* Append the new line to the current configuration state. */ void rewriteConfigAppendLine(struct rewriteConfigState *state, sds line) { state->lines = zrealloc(state->lines, sizeof(char*) * (state->numlines+1)); state->lines[state->numlines++] = line; } /* Populate the option -> list of line numbers map. */ void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds option, int linenum) { list *l = dictFetchValue(state->option_to_line,option); if (l == NULL) { l = listCreate(); dictAdd(state->option_to_line,sdsdup(option),l); } listAddNodeTail(l,(void*)(long)linenum); } /* Add the specified option to the set of processed options. * This is useful as only unused lines of processed options will be blanked * in the config file, while options the rewrite process does not understand * remain untouched. */ void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option) { sds opt = sdsnew(option); if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt); } /* Read the old file, split it into lines to populate a newly created * config rewrite state, and return it to the caller. * * If it is impossible to read the old file, NULL is returned. * If the old file does not exist at all, an empty state is returned. */ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) { FILE *fp = fopen(path,"r"); if (fp == NULL && errno != ENOENT) return NULL; struct redis_stat sb; if (fp && redis_fstat(fileno(fp),&sb) == -1) { fclose(fp); return NULL; } int linenum = -1; struct rewriteConfigState *state = rewriteConfigCreateState(); if (fp == NULL) { return state; } if (sb.st_size == 0) { fclose(fp); return state; } /* Load the file content */ sds config = sdsnewlen(SDS_NOINIT,sb.st_size); if (fread(config,1,sb.st_size,fp) == 0) { sdsfree(config); rewriteConfigReleaseState(state); fclose(fp); return NULL; } int i, totlines; sds *lines = sdssplitlen(config,sdslen(config),"\n",1,&totlines); /* Read the old content line by line, populate the state. */ for (i = 0; i < totlines; i++) { int argc; sds *argv; sds line = sdstrim(lines[i],"\r\n\t "); lines[i] = NULL; linenum++; /* Zero based, so we init at -1 */ /* Handle comments and empty lines. */ if (line[0] == '#' || line[0] == '\0') { if (state->needs_signature && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE)) state->needs_signature = 0; rewriteConfigAppendLine(state,line); continue; } /* Not a comment, split into arguments. */ argv = sdssplitargs(line,&argc); if (argv == NULL || (!lookupConfig(argv[0]) && /* The following is a list of config features that are only supported in * config file parsing and are not recognized by lookupConfig */ strcasecmp(argv[0],"include") && strcasecmp(argv[0],"rename-command") && strcasecmp(argv[0],"user") && strcasecmp(argv[0],"loadmodule") && strcasecmp(argv[0],"sentinel"))) { /* The line is either unparsable for some reason, for * instance it may have unbalanced quotes, may contain a * config that doesn't exist anymore, for instance a module that got * unloaded. Load it as a comment. */ sds aux = sdsnew("# ??? "); aux = sdscatsds(aux,line); if (argv) sdsfreesplitres(argv, argc); sdsfree(line); rewriteConfigAppendLine(state,aux); continue; } sdstolower(argv[0]); /* We only want lowercase config directives. */ /* Now we populate the state according to the content of this line. * Append the line and populate the option -> line numbers map. */ rewriteConfigAppendLine(state,line); /* If this is a alias config, replace it with the original name. */ standardConfig *s_conf = lookupConfig(argv[0]); if (s_conf && s_conf->flags & ALIAS_CONFIG) { sdsfree(argv[0]); argv[0] = sdsnew(s_conf->alias); } /* If this is sentinel config, we use sentinel "sentinel " as option to avoid messing up the sequence. */ if (server.sentinel_mode && argc > 1 && !strcasecmp(argv[0],"sentinel")) { sds sentinelOption = sdsempty(); sentinelOption = sdscatfmt(sentinelOption,"%S %S",argv[0],argv[1]); rewriteConfigAddLineNumberToOption(state,sentinelOption,linenum); sdsfree(sentinelOption); } else { rewriteConfigAddLineNumberToOption(state,argv[0],linenum); } sdsfreesplitres(argv,argc); } fclose(fp); sdsfreesplitres(lines,totlines); sdsfree(config); return state; } /* Rewrite the specified configuration option with the new "line". * It progressively uses lines of the file that were already used for the same * configuration option in the old version of the file, removing that line from * the map of options -> line numbers. * * If there are lines associated with a given configuration option and * "force" is non-zero, the line is appended to the configuration file. * Usually "force" is true when an option has not its default value, so it * must be rewritten even if not present previously. * * The first time a line is appended into a configuration file, a comment * is added to show that starting from that point the config file was generated * by CONFIG REWRITE. * * "line" is either used, or freed, so the caller does not need to free it * in any way. */ int rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force) { sds o = sdsnew(option); list *l = dictFetchValue(state->option_to_line,o); rewriteConfigMarkAsProcessed(state,option); if (!l && !force && !state->force_write) { /* Option not used previously, and we are not forced to use it. */ sdsfree(line); sdsfree(o); return 0; } if (l) { listNode *ln = listFirst(l); int linenum = (long) ln->value; /* There are still lines in the old configuration file we can reuse * for this option. Replace the line with the new one. */ listDelNode(l,ln); if (listLength(l) == 0) dictDelete(state->option_to_line,o); sdsfree(state->lines[linenum]); state->lines[linenum] = line; } else { /* Append a new line. */ if (state->needs_signature) { rewriteConfigAppendLine(state, sdsnew(REDIS_CONFIG_REWRITE_SIGNATURE)); state->needs_signature = 0; } rewriteConfigAppendLine(state,line); } sdsfree(o); return 1; } /* Write the long long 'bytes' value as a string in a way that is parsable * inside redis.conf. If possible uses the GB, MB, KB notation. */ int rewriteConfigFormatMemory(char *buf, size_t len, long long bytes) { int gb = 1024*1024*1024; int mb = 1024*1024; int kb = 1024; if (bytes && (bytes % gb) == 0) { return snprintf(buf,len,"%lldgb",bytes/gb); } else if (bytes && (bytes % mb) == 0) { return snprintf(buf,len,"%lldmb",bytes/mb); } else if (bytes && (bytes % kb) == 0) { return snprintf(buf,len,"%lldkb",bytes/kb); } else { return snprintf(buf,len,"%lld",bytes); } } /* Rewrite a simple "option-name " configuration option. */ void rewriteConfigBytesOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { char buf[64]; int force = value != defvalue; sds line; rewriteConfigFormatMemory(buf,sizeof(buf),value); line = sdscatprintf(sdsempty(),"%s %s",option,buf); rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite a simple "option-name n%" configuration option. */ void rewriteConfigPercentOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { int force = value != defvalue; sds line = sdscatprintf(sdsempty(),"%s %lld%%",option,value); rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite a yes/no option. */ void rewriteConfigYesNoOption(struct rewriteConfigState *state, const char *option, int value, int defvalue) { int force = value != defvalue; sds line = sdscatprintf(sdsempty(),"%s %s",option, value ? "yes" : "no"); rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite a string option. */ void rewriteConfigStringOption(struct rewriteConfigState *state, const char *option, char *value, const char *defvalue) { int force = 1; sds line; /* String options set to NULL need to be not present at all in the * configuration file to be set to NULL again at the next reboot. */ if (value == NULL) { rewriteConfigMarkAsProcessed(state,option); return; } /* Set force to zero if the value is set to its default. */ if (defvalue && strcmp(value,defvalue) == 0) force = 0; line = sdsnew(option); line = sdscatlen(line, " ", 1); line = sdscatrepr(line, value, strlen(value)); rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite a SDS string option. */ void rewriteConfigSdsOption(struct rewriteConfigState *state, const char *option, sds value, const char *defvalue) { int force = 1; sds line; /* If there is no value set, we don't want the SDS option * to be present in the configuration at all. */ if (value == NULL) { rewriteConfigMarkAsProcessed(state, option); return; } /* Set force to zero if the value is set to its default. */ if (defvalue && strcmp(value, defvalue) == 0) force = 0; line = sdsnew(option); line = sdscatlen(line, " ", 1); line = sdscatrepr(line, value, sdslen(value)); rewriteConfigRewriteLine(state, option, line, force); } /* Rewrite a numerical (long long range) option. */ void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { int force = value != defvalue; sds line = sdscatprintf(sdsempty(),"%s %lld",option,value); rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite an octal option. */ void rewriteConfigOctalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { int force = value != defvalue; sds line = sdscatprintf(sdsempty(),"%s %llo",option,value); rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite an enumeration option. It takes as usually state and option name, * and in addition the enumeration array and the default value for the * option. */ void rewriteConfigEnumOption(struct rewriteConfigState *state, const char *option, int value, standardConfig *config) { int multiarg = config->flags & MULTI_ARG_CONFIG; sds names = configEnumGetName(config->data.enumd.enum_value,value,multiarg); sds line = sdscatfmt(sdsempty(),"%s %s",option,names); sdsfree(names); int force = value != config->data.enumd.default_value; rewriteConfigRewriteLine(state,option,line,force); } /* Rewrite the save option. */ void rewriteConfigSaveOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); int j; sds line; /* In Sentinel mode we don't need to rewrite the save parameters */ if (server.sentinel_mode) { rewriteConfigMarkAsProcessed(state,name); return; } /* Rewrite save parameters, or an empty 'save ""' line to avoid the * defaults from being used. */ if (!server.saveparamslen) { rewriteConfigRewriteLine(state,name,sdsnew("save \"\""),1); } else { for (j = 0; j < server.saveparamslen; j++) { line = sdscatprintf(sdsempty(),"save %ld %d", (long) server.saveparams[j].seconds, server.saveparams[j].changes); rewriteConfigRewriteLine(state,name,line,1); } } /* Mark "save" as processed in case server.saveparamslen is zero. */ rewriteConfigMarkAsProcessed(state,name); } /* Rewrite the user option. */ void rewriteConfigUserOption(struct rewriteConfigState *state) { /* If there is a user file defined we just mark this configuration * directive as processed, so that all the lines containing users * inside the config file gets discarded. */ if (server.acl_filename[0] != '\0') { rewriteConfigMarkAsProcessed(state,"user"); return; } /* Otherwise scan the list of users and rewrite every line. Note that * in case the list here is empty, the effect will just be to comment * all the users directive inside the config file. */ raxIterator ri; raxStart(&ri,Users); raxSeek(&ri,"^",NULL,0); while(raxNext(&ri)) { user *u = ri.data; sds line = sdsnew("user "); line = sdscatsds(line,u->name); line = sdscatlen(line," ",1); robj *descr = ACLDescribeUser(u); line = sdscatsds(line,descr->ptr); decrRefCount(descr); rewriteConfigRewriteLine(state,"user",line,1); } raxStop(&ri); /* Mark "user" as processed in case there are no defined users. */ rewriteConfigMarkAsProcessed(state,"user"); } /* Rewrite the dir option, always using absolute paths.*/ void rewriteConfigDirOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); char cwd[1024]; if (getcwd(cwd,sizeof(cwd)) == NULL) { rewriteConfigMarkAsProcessed(state,name); return; /* no rewrite on error. */ } rewriteConfigStringOption(state,name,cwd,NULL); } /* Rewrite the slaveof option. */ void rewriteConfigReplicaOfOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); sds line; /* If this is a master, we want all the slaveof config options * in the file to be removed. Note that if this is a cluster instance * we don't want a slaveof directive inside redis.conf. */ if (server.cluster_enabled || server.masterhost == NULL) { rewriteConfigMarkAsProcessed(state, name); return; } line = sdscatprintf(sdsempty(),"%s %s %d", name, server.masterhost, server.masterport); rewriteConfigRewriteLine(state,name,line,1); } /* Rewrite the notify-keyspace-events option. */ void rewriteConfigNotifyKeyspaceEventsOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); int force = server.notify_keyspace_events != 0; sds line, flags; flags = keyspaceEventsFlagsToString(server.notify_keyspace_events); line = sdsnew(name); line = sdscatlen(line, " ", 1); line = sdscatrepr(line, flags, sdslen(flags)); sdsfree(flags); rewriteConfigRewriteLine(state,name,line,force); } /* Rewrite the client-output-buffer-limit option. */ void rewriteConfigClientOutputBufferLimitOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); int j; for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) { int force = (server.client_obuf_limits[j].hard_limit_bytes != clientBufferLimitsDefaults[j].hard_limit_bytes) || (server.client_obuf_limits[j].soft_limit_bytes != clientBufferLimitsDefaults[j].soft_limit_bytes) || (server.client_obuf_limits[j].soft_limit_seconds != clientBufferLimitsDefaults[j].soft_limit_seconds); sds line; char hard[64], soft[64]; rewriteConfigFormatMemory(hard,sizeof(hard), server.client_obuf_limits[j].hard_limit_bytes); rewriteConfigFormatMemory(soft,sizeof(soft), server.client_obuf_limits[j].soft_limit_bytes); char *typename = getClientTypeName(j); if (!strcmp(typename,"slave")) typename = "replica"; line = sdscatprintf(sdsempty(),"%s %s %s %s %ld", name, typename, hard, soft, (long) server.client_obuf_limits[j].soft_limit_seconds); rewriteConfigRewriteLine(state,name,line,force); } } /* Rewrite the oom-score-adj-values option. */ void rewriteConfigOOMScoreAdjValuesOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); int force = 0; int j; sds line; line = sdsnew(name); line = sdscatlen(line, " ", 1); for (j = 0; j < CONFIG_OOM_COUNT; j++) { if (server.oom_score_adj_values[j] != configOOMScoreAdjValuesDefaults[j]) force = 1; line = sdscatprintf(line, "%d", server.oom_score_adj_values[j]); if (j+1 != CONFIG_OOM_COUNT) line = sdscatlen(line, " ", 1); } rewriteConfigRewriteLine(state,name,line,force); } /* Rewrite the bind option. */ void rewriteConfigBindOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); int force = 1; sds line, addresses; int is_default = 0; /* Compare server.bindaddr with CONFIG_DEFAULT_BINDADDR */ if (server.bindaddr_count == CONFIG_DEFAULT_BINDADDR_COUNT) { is_default = 1; char *default_bindaddr[CONFIG_DEFAULT_BINDADDR_COUNT] = CONFIG_DEFAULT_BINDADDR; for (int j = 0; j < CONFIG_DEFAULT_BINDADDR_COUNT; j++) { if (strcmp(server.bindaddr[j], default_bindaddr[j]) != 0) { is_default = 0; break; } } } if (is_default) { rewriteConfigMarkAsProcessed(state,name); return; } /* Rewrite as bind ... */ if (server.bindaddr_count > 0) addresses = sdsjoin(server.bindaddr,server.bindaddr_count," "); else addresses = sdsnew("\"\""); line = sdsnew(name); line = sdscatlen(line, " ", 1); line = sdscatsds(line, addresses); sdsfree(addresses); rewriteConfigRewriteLine(state,name,line,force); } /* Rewrite the loadmodule option. */ void rewriteConfigLoadmoduleOption(struct rewriteConfigState *state) { sds line; dictIterator *di = dictGetIterator(modules); dictEntry *de; while ((de = dictNext(di)) != NULL) { struct RedisModule *module = dictGetVal(de); /* Internal modules doesn't have path and are not part of the configuration file */ if (sdslen(module->loadmod->path) == 0) continue; line = sdsnew("loadmodule "); line = sdscatsds(line, module->loadmod->path); for (int i = 0; i < module->loadmod->argc; i++) { line = sdscatlen(line, " ", 1); line = sdscatsds(line, module->loadmod->argv[i]->ptr); } rewriteConfigRewriteLine(state,"loadmodule",line,1); } dictReleaseIterator(di); /* Mark "loadmodule" as processed in case modules is empty. */ rewriteConfigMarkAsProcessed(state,"loadmodule"); } /* Glue together the configuration lines in the current configuration * rewrite state into a single string, stripping multiple empty lines. */ sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) { sds content = sdsempty(); int j, was_empty = 0; for (j = 0; j < state->numlines; j++) { /* Every cluster of empty lines is turned into a single empty line. */ if (sdslen(state->lines[j]) == 0) { if (was_empty) continue; was_empty = 1; } else { was_empty = 0; } content = sdscatsds(content,state->lines[j]); content = sdscatlen(content,"\n",1); } return content; } /* At the end of the rewrite process the state contains the remaining * map between "option name" => "lines in the original config file". * Lines used by the rewrite process were removed by the function * rewriteConfigRewriteLine(), all the other lines are "orphaned" and * should be replaced by empty lines. * * This function does just this, iterating all the option names and * blanking all the lines still associated. */ void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) { dictIterator *di = dictGetIterator(state->option_to_line); dictEntry *de; while((de = dictNext(di)) != NULL) { list *l = dictGetVal(de); sds option = dictGetKey(de); /* Don't blank lines about options the rewrite process * don't understand. */ if (dictFind(state->rewritten,option) == NULL) { serverLog(LL_DEBUG,"Not rewritten option: %s", option); continue; } while(listLength(l)) { listNode *ln = listFirst(l); int linenum = (long) ln->value; sdsfree(state->lines[linenum]); state->lines[linenum] = sdsempty(); listDelNode(l,ln); } } dictReleaseIterator(di); } /* This function returns a string representation of all the config options * marked with DEBUG_CONFIG, which can be used to help with debugging. */ sds getConfigDebugInfo(void) { struct rewriteConfigState *state = rewriteConfigCreateState(); state->force_write = 1; /* Force the output */ state->needs_signature = 0; /* Omit the rewrite signature */ /* Iterate the configs and "rewrite" the ones that have * the debug flag. */ dictIterator *di = dictGetIterator(configs); dictEntry *de; while ((de = dictNext(di)) != NULL) { standardConfig *config = dictGetVal(de); if (!(config->flags & DEBUG_CONFIG)) continue; config->interface.rewrite(config, config->name, state); } dictReleaseIterator(di); sds info = rewriteConfigGetContentFromState(state); rewriteConfigReleaseState(state); return info; } /* This function replaces the old configuration file with the new content * in an atomic manner. * * The function returns 0 on success, otherwise -1 is returned and errno * is set accordingly. */ int rewriteConfigOverwriteFile(char *configfile, sds content) { int fd = -1; int retval = -1; char tmp_conffile[PATH_MAX]; const char *tmp_suffix = ".XXXXXX"; size_t offset = 0; ssize_t written_bytes = 0; int old_errno; int tmp_path_len = snprintf(tmp_conffile, sizeof(tmp_conffile), "%s%s", configfile, tmp_suffix); if (tmp_path_len <= 0 || (unsigned int)tmp_path_len >= sizeof(tmp_conffile)) { serverLog(LL_WARNING, "Config file full path is too long"); errno = ENAMETOOLONG; return retval; } #if defined(_GNU_SOURCE) && !defined(__HAIKU__) fd = mkostemp(tmp_conffile, O_CLOEXEC); #else /* There's a theoretical chance here to leak the FD if a module thread forks & execv in the middle */ fd = mkstemp(tmp_conffile); #endif if (fd == -1) { serverLog(LL_WARNING, "Could not create tmp config file (%s)", strerror(errno)); return retval; } while (offset < sdslen(content)) { written_bytes = write(fd, content + offset, sdslen(content) - offset); if (written_bytes <= 0) { if (errno == EINTR) continue; /* FD is blocking, no other retryable errors */ serverLog(LL_WARNING, "Failed after writing (%zd) bytes to tmp config file (%s)", offset, strerror(errno)); goto cleanup; } offset+=written_bytes; } if (fsync(fd)) serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno)); else if (fchmod(fd, 0644 & ~server.umask) == -1) serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno)); else if (rename(tmp_conffile, configfile) == -1) serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno)); else if (fsyncFileDir(configfile) == -1) serverLog(LL_WARNING, "Could not sync config file dir (%s)", strerror(errno)); else { retval = 0; serverLog(LL_DEBUG, "Rewritten config file (%s) successfully", configfile); } cleanup: old_errno = errno; close(fd); if (retval) unlink(tmp_conffile); errno = old_errno; return retval; } /* Rewrite the configuration file at "path". * If the configuration file already exists, we try at best to retain comments * and overall structure. * * Configuration parameters that are at their default value, unless already * explicitly included in the old configuration file, are not rewritten. * The force_write flag overrides this behavior and forces everything to be * written. This is currently only used for testing purposes. * * On error -1 is returned and errno is set accordingly, otherwise 0. */ int rewriteConfig(char *path, int force_write) { struct rewriteConfigState *state; sds newcontent; int retval; /* Step 1: read the old config into our rewrite state. */ if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1; if (force_write) state->force_write = 1; /* Step 2: rewrite every single option, replacing or appending it inside * the rewrite state. */ /* Iterate the configs that are standard */ dictIterator *di = dictGetIterator(configs); dictEntry *de; while ((de = dictNext(di)) != NULL) { standardConfig *config = dictGetVal(de); /* Only rewrite the primary names */ if (config->flags & ALIAS_CONFIG) continue; if (config->interface.rewrite) config->interface.rewrite(config, dictGetKey(de), state); } dictReleaseIterator(di); rewriteConfigUserOption(state); rewriteConfigLoadmoduleOption(state); /* Rewrite Sentinel config if in Sentinel mode. */ if (server.sentinel_mode) rewriteConfigSentinelOption(state); /* Step 3: remove all the orphaned lines in the old file, that is, lines * that were used by a config option and are no longer used, like in case * of multiple "save" options or duplicated options. */ rewriteConfigRemoveOrphaned(state); /* Step 4: generate a new configuration file from the modified state * and write it into the original file. */ newcontent = rewriteConfigGetContentFromState(state); retval = rewriteConfigOverwriteFile(server.configfile,newcontent); sdsfree(newcontent); rewriteConfigReleaseState(state); return retval; } /*----------------------------------------------------------------------------- * Configs that fit one of the major types and require no special handling *----------------------------------------------------------------------------*/ #define LOADBUF_SIZE 256 static char loadbuf[LOADBUF_SIZE]; #define embedCommonConfig(config_name, config_alias, config_flags) \ .name = (config_name), \ .alias = (config_alias), \ .flags = (config_flags), #define embedConfigInterface(initfn, setfn, getfn, rewritefn, applyfn) .interface = { \ .init = (initfn), \ .set = (setfn), \ .get = (getfn), \ .rewrite = (rewritefn), \ .apply = (applyfn) \ }, /* What follows is the generic config types that are supported. To add a new * config with one of these types, add it to the standardConfig table with * the creation macro for each type. * * Each type contains the following: * * A function defining how to load this type on startup. * * A function defining how to update this type on CONFIG SET. * * A function defining how to serialize this type on CONFIG SET. * * A function defining how to rewrite this type on CONFIG REWRITE. * * A Macro defining how to create this type. */ /* Bool Configs */ static void boolConfigInit(standardConfig *config) { *config->data.yesno.config = config->data.yesno.default_value; } static int boolConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(argc); int yn = yesnotoi(argv[0]); if (yn == -1) { *err = "argument must be 'yes' or 'no'"; return 0; } if (config->data.yesno.is_valid_fn && !config->data.yesno.is_valid_fn(yn, err)) return 0; int prev = config->flags & MODULE_CONFIG ? getModuleBoolConfig(config->privdata) : *(config->data.yesno.config); if (prev != yn) { if (config->flags & MODULE_CONFIG) { return setModuleBoolConfig(config->privdata, yn, err); } *(config->data.yesno.config) = yn; return 1; } return (config->flags & VOLATILE_CONFIG) ? 1 : 2; } static sds boolConfigGet(standardConfig *config) { if (config->flags & MODULE_CONFIG) { return sdsnew(getModuleBoolConfig(config->privdata) ? "yes" : "no"); } return sdsnew(*config->data.yesno.config ? "yes" : "no"); } static void boolConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { int val = config->flags & MODULE_CONFIG ? getModuleBoolConfig(config->privdata) : *(config->data.yesno.config); rewriteConfigYesNoOption(state, name, val, config->data.yesno.default_value); } #define createBoolConfig(name, alias, flags, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite, apply) \ .type = BOOL_CONFIG, \ .data.yesno = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ } \ } /* String Configs */ static void stringConfigInit(standardConfig *config) { *config->data.string.config = (config->data.string.convert_empty_to_null && !config->data.string.default_value) ? NULL : zstrdup(config->data.string.default_value); } static int stringConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(argc); if (config->data.string.is_valid_fn && !config->data.string.is_valid_fn(argv[0], err)) return 0; char *prev = *config->data.string.config; char *new = (config->data.string.convert_empty_to_null && !argv[0][0]) ? NULL : argv[0]; if (new != prev && (new == NULL || prev == NULL || strcmp(prev, new))) { *config->data.string.config = new != NULL ? zstrdup(new) : NULL; zfree(prev); return 1; } return (config->flags & VOLATILE_CONFIG) ? 1 : 2; } static sds stringConfigGet(standardConfig *config) { return sdsnew(*config->data.string.config ? *config->data.string.config : ""); } static void stringConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { rewriteConfigStringOption(state, name,*(config->data.string.config), config->data.string.default_value); } /* SDS Configs */ static void sdsConfigInit(standardConfig *config) { *config->data.sds.config = (config->data.sds.convert_empty_to_null && !config->data.sds.default_value) ? NULL : sdsnew(config->data.sds.default_value); } static int sdsConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(argc); if (config->data.sds.is_valid_fn && !config->data.sds.is_valid_fn(argv[0], err)) return 0; sds prev = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config; sds new = (config->data.string.convert_empty_to_null && (sdslen(argv[0]) == 0)) ? NULL : argv[0]; /* if prev and new configuration are not equal, set the new one */ if (new != prev && (new == NULL || prev == NULL || sdscmp(prev, new))) { /* If MODULE_CONFIG flag is set, then free temporary prev getModuleStringConfig returned. * Otherwise, free the actual previous config value Redis held (Same action, different reasons) */ sdsfree(prev); if (config->flags & MODULE_CONFIG) { return setModuleStringConfig(config->privdata, new, err); } *config->data.sds.config = new != NULL ? sdsdup(new) : NULL; return 1; } if (config->flags & MODULE_CONFIG && prev) sdsfree(prev); return (config->flags & VOLATILE_CONFIG) ? 1 : 2; } static sds sdsConfigGet(standardConfig *config) { sds val = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config; if (val) { if (config->flags & MODULE_CONFIG) return val; return sdsdup(val); } else { return sdsnew(""); } } static void sdsConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { sds val = config->flags & MODULE_CONFIG ? getModuleStringConfig(config->privdata) : *config->data.sds.config; rewriteConfigSdsOption(state, name, val, config->data.sds.default_value); if ((val) && (config->flags & MODULE_CONFIG)) sdsfree(val); } #define ALLOW_EMPTY_STRING 0 #define EMPTY_STRING_IS_NULL 1 #define createStringConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite, apply) \ .type = STRING_CONFIG, \ .data.string = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ .convert_empty_to_null = (empty_to_null), \ } \ } #define createSDSConfig(name, alias, flags, empty_to_null, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite, apply) \ .type = SDS_CONFIG, \ .data.sds = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ .convert_empty_to_null = (empty_to_null), \ } \ } /* Enum configs */ static void enumConfigInit(standardConfig *config) { *config->data.enumd.config = config->data.enumd.default_value; } static int enumConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { int enumval; int bitflags = !!(config->flags & MULTI_ARG_CONFIG); enumval = configEnumGetValue(config->data.enumd.enum_value, argv, argc, bitflags); if (enumval == INT_MIN) { sds enumerr = sdsnew("argument(s) must be one of the following: "); configEnum *enumNode = config->data.enumd.enum_value; while(enumNode->name != NULL) { enumerr = sdscatlen(enumerr, enumNode->name, strlen(enumNode->name)); enumerr = sdscatlen(enumerr, ", ", 2); enumNode++; } sdsrange(enumerr,0,-3); /* Remove final ", ". */ redis_strlcpy(loadbuf, enumerr, LOADBUF_SIZE); sdsfree(enumerr); *err = loadbuf; return 0; } if (config->data.enumd.is_valid_fn && !config->data.enumd.is_valid_fn(enumval, err)) return 0; int prev = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config); if (prev != enumval) { if (config->flags & MODULE_CONFIG) return setModuleEnumConfig(config->privdata, enumval, err); *(config->data.enumd.config) = enumval; return 1; } return (config->flags & VOLATILE_CONFIG) ? 1 : 2; } static sds enumConfigGet(standardConfig *config) { int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config); int bitflags = !!(config->flags & MULTI_ARG_CONFIG); return configEnumGetName(config->data.enumd.enum_value,val,bitflags); } static void enumConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config); rewriteConfigEnumOption(state, name, val, config); } #define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, apply) { \ embedCommonConfig(name, alias, flags) \ embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite, apply) \ .type = ENUM_CONFIG, \ .data.enumd = { \ .config = &(config_addr), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ .enum_value = (enum), \ } \ } /* Gets a 'long long val' and sets it into the union, using a macro to get * compile time type check. */ int setNumericType(standardConfig *config, long long val, const char **err) { if (config->data.numeric.numeric_type == NUMERIC_TYPE_INT) { *(config->data.numeric.config.i) = (int) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_UINT) { *(config->data.numeric.config.ui) = (unsigned int) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG) { *(config->data.numeric.config.l) = (long) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { *(config->data.numeric.config.ul) = (unsigned long) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { if (config->flags & MODULE_CONFIG) return setModuleNumericConfig(config->privdata, val, err); else *(config->data.numeric.config.ll) = (long long) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { *(config->data.numeric.config.ull) = (unsigned long long) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { *(config->data.numeric.config.st) = (size_t) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { *(config->data.numeric.config.sst) = (ssize_t) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { *(config->data.numeric.config.ot) = (off_t) val; } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { *(config->data.numeric.config.tt) = (time_t) val; } return 1; } /* Gets a 'long long val' and sets it with the value from the union, using a * macro to get compile time type check. */ #define GET_NUMERIC_TYPE(val) \ if (config->data.numeric.numeric_type == NUMERIC_TYPE_INT) { \ val = *(config->data.numeric.config.i); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_UINT) { \ val = *(config->data.numeric.config.ui); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG) { \ val = *(config->data.numeric.config.l); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG) { \ val = *(config->data.numeric.config.ul); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_LONG_LONG) { \ if (config->flags & MODULE_CONFIG) val = getModuleNumericConfig(config->privdata); \ else val = *(config->data.numeric.config.ll); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG) { \ val = *(config->data.numeric.config.ull); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { \ val = *(config->data.numeric.config.st); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_SSIZE_T) { \ val = *(config->data.numeric.config.sst); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_OFF_T) { \ val = *(config->data.numeric.config.ot); \ } else if (config->data.numeric.numeric_type == NUMERIC_TYPE_TIME_T) { \ val = *(config->data.numeric.config.tt); \ } /* Numeric configs */ static void numericConfigInit(standardConfig *config) { setNumericType(config, config->data.numeric.default_value, NULL); } static int numericBoundaryCheck(standardConfig *config, long long ll, const char **err) { if (config->data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG || config->data.numeric.numeric_type == NUMERIC_TYPE_UINT || config->data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { /* Boundary check for unsigned types */ unsigned long long ull = ll; unsigned long long upper_bound = config->data.numeric.upper_bound; unsigned long long lower_bound = config->data.numeric.lower_bound; if (ull > upper_bound || ull < lower_bound) { if (config->data.numeric.flags & OCTAL_CONFIG) { snprintf(loadbuf, LOADBUF_SIZE, "argument must be between %llo and %llo inclusive", lower_bound, upper_bound); } else { snprintf(loadbuf, LOADBUF_SIZE, "argument must be between %llu and %llu inclusive", lower_bound, upper_bound); } *err = loadbuf; return 0; } } else { /* Boundary check for percentages */ if (config->data.numeric.flags & PERCENT_CONFIG && ll < 0) { if (ll < config->data.numeric.lower_bound) { snprintf(loadbuf, LOADBUF_SIZE, "percentage argument must be less or equal to %lld", -config->data.numeric.lower_bound); *err = loadbuf; return 0; } } /* Boundary check for signed types */ else if (ll > config->data.numeric.upper_bound || ll < config->data.numeric.lower_bound) { snprintf(loadbuf, LOADBUF_SIZE, "argument must be between %lld and %lld inclusive", config->data.numeric.lower_bound, config->data.numeric.upper_bound); *err = loadbuf; return 0; } } return 1; } static int numericParseString(standardConfig *config, sds value, const char **err, long long *res) { /* First try to parse as memory */ if (config->data.numeric.flags & MEMORY_CONFIG) { int memerr; *res = memtoull(value, &memerr); if (!memerr) return 1; } /* Attempt to parse as percent */ if (config->data.numeric.flags & PERCENT_CONFIG && sdslen(value) > 1 && value[sdslen(value)-1] == '%' && string2ll(value, sdslen(value)-1, res) && *res >= 0) { /* We store percentage as negative value */ *res = -*res; return 1; } /* Attempt to parse as an octal number */ if (config->data.numeric.flags & OCTAL_CONFIG) { char *endptr; errno = 0; *res = strtoll(value, &endptr, 8); if (errno == 0 && *endptr == '\0') return 1; /* No overflow or invalid characters */ } /* Attempt a simple number (no special flags set) */ if (!config->data.numeric.flags && string2ll(value, sdslen(value), res)) return 1; /* Select appropriate error string */ if (config->data.numeric.flags & MEMORY_CONFIG && config->data.numeric.flags & PERCENT_CONFIG) *err = "argument must be a memory or percent value" ; else if (config->data.numeric.flags & MEMORY_CONFIG) *err = "argument must be a memory value"; else if (config->data.numeric.flags & OCTAL_CONFIG) *err = "argument couldn't be parsed as an octal number"; else *err = "argument couldn't be parsed into an integer"; return 0; } static int numericConfigSet(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(argc); long long ll, prev = 0; if (!numericParseString(config, argv[0], err, &ll)) return 0; if (!numericBoundaryCheck(config, ll, err)) return 0; if (config->data.numeric.is_valid_fn && !config->data.numeric.is_valid_fn(ll, err)) return 0; GET_NUMERIC_TYPE(prev) if (prev != ll) { return setNumericType(config, ll, err); } return (config->flags & VOLATILE_CONFIG) ? 1 : 2; } static sds numericConfigGet(standardConfig *config) { char buf[128]; long long value = 0; GET_NUMERIC_TYPE(value) if (config->data.numeric.flags & PERCENT_CONFIG && value < 0) { int len = ll2string(buf, sizeof(buf), -value); buf[len] = '%'; buf[len+1] = '\0'; } else if (config->data.numeric.flags & MEMORY_CONFIG) { ull2string(buf, sizeof(buf), value); } else if (config->data.numeric.flags & OCTAL_CONFIG) { snprintf(buf, sizeof(buf), "%llo", value); } else { ll2string(buf, sizeof(buf), value); } return sdsnew(buf); } static void numericConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) { long long value = 0; GET_NUMERIC_TYPE(value) if (config->data.numeric.flags & PERCENT_CONFIG && value < 0) { rewriteConfigPercentOption(state, name, -value, config->data.numeric.default_value); } else if (config->data.numeric.flags & MEMORY_CONFIG) { rewriteConfigBytesOption(state, name, value, config->data.numeric.default_value); } else if (config->data.numeric.flags & OCTAL_CONFIG) { rewriteConfigOctalOption(state, name, value, config->data.numeric.default_value); } else { rewriteConfigNumericalOption(state, name, value, config->data.numeric.default_value); } } #define embedCommonNumericalConfig(name, alias, _flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) { \ embedCommonConfig(name, alias, _flags) \ embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite, apply) \ .type = NUMERIC_CONFIG, \ .data.numeric = { \ .lower_bound = (lower), \ .upper_bound = (upper), \ .default_value = (default), \ .is_valid_fn = (is_valid), \ .flags = (num_conf_flags), #define createIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_INT, \ .config.i = &(config_addr) \ } \ } #define createUIntConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_UINT, \ .config.ui = &(config_addr) \ } \ } #define createLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_LONG, \ .config.l = &(config_addr) \ } \ } #define createULongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_ULONG, \ .config.ul = &(config_addr) \ } \ } #define createLongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_LONG_LONG, \ .config.ll = &(config_addr) \ } \ } #define createULongLongConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_ULONG_LONG, \ .config.ull = &(config_addr) \ } \ } #define createSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_SIZE_T, \ .config.st = &(config_addr) \ } \ } #define createSSizeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_SSIZE_T, \ .config.sst = &(config_addr) \ } \ } #define createTimeTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_TIME_T, \ .config.tt = &(config_addr) \ } \ } #define createOffTConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ embedCommonNumericalConfig(name, alias, flags, lower, upper, config_addr, default, num_conf_flags, is_valid, apply) \ .numeric_type = NUMERIC_TYPE_OFF_T, \ .config.ot = &(config_addr) \ } \ } #define createSpecialConfig(name, alias, modifiable, setfn, getfn, rewritefn, applyfn) { \ .type = SPECIAL_CONFIG, \ embedCommonConfig(name, alias, modifiable) \ embedConfigInterface(NULL, setfn, getfn, rewritefn, applyfn) \ } static int isValidActiveDefrag(int val, const char **err) { #ifndef HAVE_DEFRAG if (val) { *err = "Active defragmentation cannot be enabled: it " "requires a Redis server compiled with a modified Jemalloc " "like the one shipped by default with the Redis source " "distribution"; return 0; } #else UNUSED(val); UNUSED(err); #endif return 1; } static int isValidDBfilename(char *val, const char **err) { if (!pathIsBaseName(val)) { *err = "dbfilename can't be a path, just a filename"; return 0; } return 1; } static int isValidAOFfilename(char *val, const char **err) { if (!strcmp(val, "")) { *err = "appendfilename can't be empty"; return 0; } if (!pathIsBaseName(val)) { *err = "appendfilename can't be a path, just a filename"; return 0; } return 1; } static int isValidAOFdirname(char *val, const char **err) { if (!strcmp(val, "")) { *err = "appenddirname can't be empty"; return 0; } if (!pathIsBaseName(val)) { *err = "appenddirname can't be a path, just a dirname"; return 0; } return 1; } static int isValidShutdownOnSigFlags(int val, const char **err) { /* Individual arguments are validated by createEnumConfig logic. * We just need to ensure valid combinations here. */ if (val & SHUTDOWN_NOSAVE && val & SHUTDOWN_SAVE) { *err = "shutdown options SAVE and NOSAVE can't be used simultaneously"; return 0; } return 1; } static int isValidAnnouncedNodename(char *val,const char **err) { if (!(isValidAuxString(val,sdslen(val)))) { *err = "Announced human node name contained invalid character"; return 0; } return 1; } static int isValidAnnouncedHostname(char *val, const char **err) { if (strlen(val) >= NET_HOST_STR_LEN) { *err = "Hostnames must be less than " STRINGIFY(NET_HOST_STR_LEN) " characters"; return 0; } int i = 0; char c; while ((c = val[i])) { /* We just validate the character set to make sure that everything * is parsed and handled correctly. */ if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-') || (c == '.'))) { *err = "Hostnames may only contain alphanumeric characters, " "hyphens or dots"; return 0; } c = val[i++]; } return 1; } /* Validate specified string is a valid proc-title-template */ static int isValidProcTitleTemplate(char *val, const char **err) { if (!validateProcTitleTemplate(val)) { *err = "template format is invalid or contains unknown variables"; return 0; } return 1; } static int updateLocaleCollate(const char **err) { const char *s = setlocale(LC_COLLATE, server.locale_collate); if (s == NULL) { *err = "Invalid locale name"; return 0; } return 1; } static int updateProcTitleTemplate(const char **err) { if (redisSetProcTitle(NULL) == C_ERR) { *err = "failed to set process title"; return 0; } return 1; } static int updateHZ(const char **err) { UNUSED(err); /* Hz is more a hint from the user, so we accept values out of range * but cap them to reasonable values. */ if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ; if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; server.hz = server.config_hz; return 1; } static int updatePort(const char **err) { connListener *listener = listenerByType(CONN_TYPE_SOCKET); serverAssert(listener != NULL); listener->bindaddr = server.bindaddr; listener->bindaddr_count = server.bindaddr_count; listener->port = server.port; clusterUpdateMyselfAnnouncedPorts(); listener->ct = connectionByType(CONN_TYPE_SOCKET); if (changeListener(listener) == C_ERR) { *err = "Unable to listen on this port. Check server logs."; return 0; } return 1; } static int updateDefragConfiguration(const char **err) { UNUSED(err); server.active_defrag_configuration_changed = 1; return 1; } static int updateJemallocBgThread(const char **err) { UNUSED(err); set_jemalloc_bg_thread(server.jemalloc_bg_thread); return 1; } static int updateReplBacklogSize(const char **err) { UNUSED(err); resizeReplicationBacklog(); return 1; } static int updateMaxmemory(const char **err) { UNUSED(err); if (server.maxmemory) { size_t used = zmalloc_used_memory()-freeMemoryGetNotCountedMemory(); if (server.maxmemory < used) { serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", server.maxmemory, used); } startEvictionTimeProc(); } return 1; } static int updateGoodSlaves(const char **err) { UNUSED(err); refreshGoodSlavesCount(); return 1; } static int updateWatchdogPeriod(const char **err) { UNUSED(err); applyWatchdogPeriod(); return 1; } static int updateAppendonly(const char **err) { /* If loading flag is set, AOF might have been stopped temporarily, and it * will be restarted depending on server.aof_enabled flag after loading is * completed. So, we just need to update 'server.aof_enabled' which has been * updated already before calling this function. */ if (server.loading) return 1; if (!server.aof_enabled && server.aof_state != AOF_OFF) { stopAppendOnly(); } else if (server.aof_enabled && server.aof_state == AOF_OFF) { if (startAppendOnly() == C_ERR) { *err = "Unable to turn on AOF. Check server logs."; return 0; } } return 1; } static int updateAofAutoGCEnabled(const char **err) { UNUSED(err); if (!server.aof_disable_auto_gc) { aofDelHistoryFiles(); } return 1; } static int updateSighandlerEnabled(const char **err) { UNUSED(err); if (server.crashlog_enabled) setupSigSegvHandler(); else removeSigSegvHandlers(); return 1; } static int updateMaxclients(const char **err) { unsigned int new_maxclients = server.maxclients; adjustOpenFilesLimit(); if (server.maxclients != new_maxclients) { static char msg[128]; snprintf(msg, sizeof(msg), "The operating system is not able to handle the specified number of clients, try with %d", server.maxclients); *err = msg; return 0; } size_t newsize = server.maxclients + CONFIG_FDSET_INCR; if ((unsigned int) aeGetSetSize(server.el) < newsize) { if (aeResizeSetSize(server.el, newsize) == AE_ERR || resizeAllIOThreadsEventLoops(newsize) == AE_ERR) { *err = "The event loop API used by Redis is not able to handle the specified number of clients"; return 0; } } return 1; } static int updateOOMScoreAdj(const char **err) { if (setOOMScoreAdj(-1) == C_ERR) { *err = "Failed to set current oom_score_adj. Check server logs."; return 0; } return 1; } int updateRequirePass(const char **err) { UNUSED(err); /* The old "requirepass" directive just translates to setting * a password to the default user. The only thing we do * additionally is to remember the cleartext password in this * case, for backward compatibility with Redis <= 5. */ ACLUpdateDefaultUserPassword(server.requirepass); return 1; } int updateAppendFsync(const char **err) { UNUSED(err); if (server.aof_fsync == AOF_FSYNC_ALWAYS) { /* Wait for all bio jobs related to AOF to drain before proceeding. This prevents a race * between updates to `fsynced_reploff_pending` done in the main thread and those done on the * worker thread. */ bioDrainWorker(BIO_AOF_FSYNC); } return 1; } /* applyBind affects both TCP and TLS (if enabled) together */ static int applyBind(const char **err) { connListener *tcp_listener = listenerByType(CONN_TYPE_SOCKET); connListener *tls_listener = listenerByType(CONN_TYPE_TLS); serverAssert(tcp_listener != NULL); tcp_listener->bindaddr = server.bindaddr; tcp_listener->bindaddr_count = server.bindaddr_count; tcp_listener->port = server.port; tcp_listener->ct = connectionByType(CONN_TYPE_SOCKET); if (changeListener(tcp_listener) == C_ERR) { *err = "Failed to bind to specified addresses."; if (tls_listener) closeListener(tls_listener); /* failed with TLS together */ return 0; } if (server.tls_port != 0) { serverAssert(tls_listener != NULL); tls_listener->bindaddr = server.bindaddr; tls_listener->bindaddr_count = server.bindaddr_count; tls_listener->port = server.tls_port; tls_listener->ct = connectionByType(CONN_TYPE_TLS); if (changeListener(tls_listener) == C_ERR) { *err = "Failed to bind to specified addresses."; closeListener(tcp_listener); /* failed with TCP together */ return 0; } } return 1; } int updateClusterFlags(const char **err) { UNUSED(err); clusterUpdateMyselfFlags(); return 1; } static int updateClusterAnnouncedPort(const char **err) { UNUSED(err); clusterUpdateMyselfAnnouncedPorts(); return 1; } static int updateClusterIp(const char **err) { UNUSED(err); clusterUpdateMyselfIp(); return 1; } int updateClusterHostname(const char **err) { UNUSED(err); clusterUpdateMyselfHostname(); return 1; } int updateClusterHumanNodename(const char **err) { UNUSED(err); clusterUpdateMyselfHumanNodename(); return 1; } static int applyTlsCfg(const char **err) { UNUSED(err); /* If TLS is enabled, try to configure OpenSSL. */ if ((server.tls_port || server.tls_replication || server.tls_cluster) && connTypeConfigure(connectionTypeTls(), &server.tls_ctx_config, 1) == C_ERR) { *err = "Unable to update TLS configuration. Check server logs."; return 0; } return 1; } static int applyTLSPort(const char **err) { /* Configure TLS in case it wasn't enabled */ if (connTypeConfigure(connectionTypeTls(), &server.tls_ctx_config, 0) == C_ERR) { *err = "Unable to update TLS configuration. Check server logs."; return 0; } connListener *listener = listenerByType(CONN_TYPE_TLS); serverAssert(listener != NULL); listener->bindaddr = server.bindaddr; listener->bindaddr_count = server.bindaddr_count; listener->port = server.tls_port; listener->ct = connectionByType(CONN_TYPE_TLS); clusterUpdateMyselfAnnouncedPorts(); if (changeListener(listener) == C_ERR) { *err = "Unable to listen on this port. Check server logs."; return 0; } return 1; } static int setConfigDirOption(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(config); if (argc != 1) { *err = "wrong number of arguments"; return 0; } if (chdir(argv[0]) == -1) { *err = strerror(errno); return 0; } return 1; } static sds getConfigDirOption(standardConfig *config) { UNUSED(config); char buf[1024]; if (getcwd(buf,sizeof(buf)) == NULL) buf[0] = '\0'; return sdsnew(buf); } static int setConfigSaveOption(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(config); int j; /* Special case: treat single arg "" as zero args indicating empty save configuration */ if (argc == 1 && !strcasecmp(argv[0],"")) { resetServerSaveParams(); argc = 0; } /* Perform sanity check before setting the new config: * - Even number of args * - Seconds >= 1, changes >= 0 */ if (argc & 1) { *err = "Invalid save parameters"; return 0; } for (j = 0; j < argc; j++) { char *eptr; long val; val = strtoll(argv[j], &eptr, 10); if (eptr[0] != '\0' || ((j & 1) == 0 && val < 1) || ((j & 1) == 1 && val < 0)) { *err = "Invalid save parameters"; return 0; } } /* Finally set the new config */ if (!reading_config_file) { resetServerSaveParams(); } else { /* We don't reset save params before loading, because if they're not part * of the file the defaults should be used. */ static int save_loaded = 0; if (!save_loaded) { save_loaded = 1; resetServerSaveParams(); } } for (j = 0; j < argc; j += 2) { time_t seconds; int changes; seconds = strtoll(argv[j],NULL,10); changes = strtoll(argv[j+1],NULL,10); appendServerSaveParams(seconds, changes); } return 1; } static sds getConfigSaveOption(standardConfig *config) { UNUSED(config); sds buf = sdsempty(); int j; for (j = 0; j < server.saveparamslen; j++) { buf = sdscatprintf(buf,"%jd %d", (intmax_t)server.saveparams[j].seconds, server.saveparams[j].changes); if (j != server.saveparamslen-1) buf = sdscatlen(buf," ",1); } return buf; } static int setConfigClientOutputBufferLimitOption(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(config); return updateClientOutputBufferLimit(argv, argc, err); } static sds getConfigClientOutputBufferLimitOption(standardConfig *config) { UNUSED(config); sds buf = sdsempty(); int j; for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) { buf = sdscatprintf(buf,"%s %llu %llu %ld", getClientTypeName(j), server.client_obuf_limits[j].hard_limit_bytes, server.client_obuf_limits[j].soft_limit_bytes, (long) server.client_obuf_limits[j].soft_limit_seconds); if (j != CLIENT_TYPE_OBUF_COUNT-1) buf = sdscatlen(buf," ",1); } return buf; } /* Parse an array of CONFIG_OOM_COUNT sds strings, validate and populate * server.oom_score_adj_values if valid. */ static int setConfigOOMScoreAdjValuesOption(standardConfig *config, sds *argv, int argc, const char **err) { int i; int values[CONFIG_OOM_COUNT]; int change = 0; UNUSED(config); if (argc != CONFIG_OOM_COUNT) { *err = "wrong number of arguments"; return 0; } for (i = 0; i < CONFIG_OOM_COUNT; i++) { char *eptr; long long val = strtoll(argv[i], &eptr, 10); if (*eptr != '\0' || val < -2000 || val > 2000) { if (err) *err = "Invalid oom-score-adj-values, elements must be between -2000 and 2000."; return 0; } values[i] = val; } /* Verify that the values make sense. If they don't omit a warning but * keep the configuration, which may still be valid for privileged processes. */ if (values[CONFIG_OOM_REPLICA] < values[CONFIG_OOM_MASTER] || values[CONFIG_OOM_BGCHILD] < values[CONFIG_OOM_REPLICA]) { serverLog(LL_WARNING, "The oom-score-adj-values configuration may not work for non-privileged processes! " "Please consult the documentation."); } for (i = 0; i < CONFIG_OOM_COUNT; i++) { if (server.oom_score_adj_values[i] != values[i]) { server.oom_score_adj_values[i] = values[i]; change = 1; } } return change ? 1 : 2; } static sds getConfigOOMScoreAdjValuesOption(standardConfig *config) { UNUSED(config); sds buf = sdsempty(); int j; for (j = 0; j < CONFIG_OOM_COUNT; j++) { buf = sdscatprintf(buf,"%d", server.oom_score_adj_values[j]); if (j != CONFIG_OOM_COUNT-1) buf = sdscatlen(buf," ",1); } return buf; } static int setConfigNotifyKeyspaceEventsOption(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(config); if (argc != 1) { *err = "wrong number of arguments"; return 0; } int flags = keyspaceEventsStringToFlags(argv[0]); if (flags == -1) { *err = "Invalid event class character. Use 'Ag$lshzxeKEtmdn'."; return 0; } server.notify_keyspace_events = flags; return 1; } static sds getConfigNotifyKeyspaceEventsOption(standardConfig *config) { UNUSED(config); return keyspaceEventsFlagsToString(server.notify_keyspace_events); } static int setConfigBindOption(standardConfig *config, sds* argv, int argc, const char **err) { UNUSED(config); int j; if (argc > CONFIG_BINDADDR_MAX) { *err = "Too many bind addresses specified."; return 0; } /* A single empty argument is treated as a zero bindaddr count */ if (argc == 1 && sdslen(argv[0]) == 0) argc = 0; /* Free old bind addresses */ for (j = 0; j < server.bindaddr_count; j++) { zfree(server.bindaddr[j]); } for (j = 0; j < argc; j++) server.bindaddr[j] = zstrdup(argv[j]); server.bindaddr_count = argc; return 1; } static int setConfigReplicaOfOption(standardConfig *config, sds* argv, int argc, const char **err) { UNUSED(config); if (argc != 2) { *err = "wrong number of arguments"; return 0; } sdsfree(server.masterhost); server.masterhost = NULL; if (!strcasecmp(argv[0], "no") && !strcasecmp(argv[1], "one")) { return 1; } char *ptr; server.masterport = strtol(argv[1], &ptr, 10); if (server.masterport < 0 || server.masterport > 65535 || *ptr != '\0') { *err = "Invalid master port"; return 0; } server.masterhost = sdsnew(argv[0]); server.repl_state = REPL_STATE_CONNECT; return 1; } static sds getConfigBindOption(standardConfig *config) { UNUSED(config); return sdsjoin(server.bindaddr,server.bindaddr_count," "); } static sds getConfigReplicaOfOption(standardConfig *config) { UNUSED(config); char buf[256]; if (server.masterhost) snprintf(buf,sizeof(buf),"%s %d", server.masterhost, server.masterport); else buf[0] = '\0'; return sdsnew(buf); } int allowProtectedAction(int config, client *c) { return (config == PROTECTED_ACTION_ALLOWED_YES) || (config == PROTECTED_ACTION_ALLOWED_LOCAL && (connIsLocal(c->conn) == 1)); } static int setConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config, sds *argv, int argc, const char **err) { UNUSED(config); zfree(server.latency_tracking_info_percentiles); server.latency_tracking_info_percentiles = NULL; server.latency_tracking_info_percentiles_len = argc; /* Special case: treat single arg "" as zero args indicating empty percentile configuration */ if (argc == 1 && sdslen(argv[0]) == 0) server.latency_tracking_info_percentiles_len = 0; else server.latency_tracking_info_percentiles = zmalloc(sizeof(double)*argc); for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) { double percentile; if (!string2d(argv[j], sdslen(argv[j]), &percentile)) { *err = "Invalid latency-tracking-info-percentiles parameters"; goto configerr; } if (percentile > 100.0 || percentile < 0.0) { *err = "latency-tracking-info-percentiles parameters should sit between [0.0,100.0]"; goto configerr; } server.latency_tracking_info_percentiles[j] = percentile; } return 1; configerr: zfree(server.latency_tracking_info_percentiles); server.latency_tracking_info_percentiles = NULL; server.latency_tracking_info_percentiles_len = 0; return 0; } static sds getConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config) { UNUSED(config); sds buf = sdsempty(); for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) { char fbuf[128]; size_t len = snprintf(fbuf, sizeof(fbuf), "%f", server.latency_tracking_info_percentiles[j]); len = trimDoubleString(fbuf, len); buf = sdscatlen(buf, fbuf, len); if (j != server.latency_tracking_info_percentiles_len-1) buf = sdscatlen(buf," ",1); } return buf; } /* Rewrite the latency-tracking-info-percentiles option. */ void rewriteConfigLatencyTrackingInfoPercentilesOutputOption(standardConfig *config, const char *name, struct rewriteConfigState *state) { UNUSED(config); sds line = sdsnew(name); /* Rewrite latency-tracking-info-percentiles parameters, * or an empty 'latency-tracking-info-percentiles ""' line to avoid the * defaults from being used. */ if (!server.latency_tracking_info_percentiles_len) { line = sdscat(line," \"\""); } else { for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) { char fbuf[128]; size_t len = snprintf(fbuf, sizeof(fbuf), " %f", server.latency_tracking_info_percentiles[j]); len = trimDoubleString(fbuf, len); line = sdscatlen(line, fbuf, len); } } rewriteConfigRewriteLine(state,name,line,1); } static int applyClientMaxMemoryUsage(const char **err) { UNUSED(err); listIter li; listNode *ln; /* server.client_mem_usage_buckets is an indication that the previous config * was non-zero, in which case we can exit and no apply is needed. */ if(server.maxmemory_clients !=0 && server.client_mem_usage_buckets) return 1; if (server.maxmemory_clients != 0) initServerClientMemUsageBuckets(); pauseAllIOThreads(); /* When client eviction is enabled update memory buckets for all clients. * When disabled, clear that data structure. */ listRewind(server.clients, &li); while ((ln = listNext(&li)) != NULL) { client *c = listNodeValue(ln); if (server.maxmemory_clients == 0) { /* Remove client from memory usage bucket. */ removeClientFromMemUsageBucket(c, 0); } else { /* Update each client(s) memory usage and add to appropriate bucket. */ updateClientMemUsageAndBucket(c); } } resumeAllIOThreads(); if (server.maxmemory_clients == 0) freeServerClientMemUsageBuckets(); return 1; } standardConfig static_configs[] = { /* Bool configs */ createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL), createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL), createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL), createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL), createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL), createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, server.rdb_del_sync_files, 0, NULL, NULL), createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, server.activerehashing, 1, NULL, NULL), createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL), createBoolConfig("set-proc-title", NULL, IMMUTABLE_CONFIG, server.set_proc_title, 1, NULL, NULL), /* Should setproctitle be used? */ createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/ createBoolConfig("lazyfree-lazy-eviction", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-expire", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-server-del", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-user-del", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL), createBoolConfig("lazyfree-lazy-user-flush", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lazyfree_lazy_user_flush , 0, NULL, NULL), createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL), createBoolConfig("repl-diskless-sync", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.repl_diskless_sync, 1, NULL, NULL), createBoolConfig("repl-rdb-channel", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.repl_rdb_channel, 1, NULL, NULL), createBoolConfig("aof-rewrite-incremental-fsync", NULL, MODIFIABLE_CONFIG, server.aof_rewrite_incremental_fsync, 1, NULL, NULL), createBoolConfig("no-appendfsync-on-rewrite", NULL, MODIFIABLE_CONFIG, server.aof_no_fsync_on_rewrite, 0, NULL, NULL), createBoolConfig("cluster-require-full-coverage", NULL, MODIFIABLE_CONFIG, server.cluster_require_full_coverage, 1, NULL, NULL), createBoolConfig("rdb-save-incremental-fsync", NULL, MODIFIABLE_CONFIG, server.rdb_save_incremental_fsync, 1, NULL, NULL), createBoolConfig("aof-load-truncated", NULL, MODIFIABLE_CONFIG, server.aof_load_truncated, 1, NULL, NULL), createBoolConfig("aof-use-rdb-preamble", NULL, MODIFIABLE_CONFIG, server.aof_use_rdb_preamble, 1, NULL, NULL), createBoolConfig("aof-timestamp-enabled", NULL, MODIFIABLE_CONFIG, server.aof_timestamp_enabled, 0, NULL, NULL), createBoolConfig("cluster-replica-no-failover", "cluster-slave-no-failover", MODIFIABLE_CONFIG, server.cluster_slave_no_failover, 0, NULL, updateClusterFlags), /* Failover by default. */ createBoolConfig("replica-lazy-flush", "slave-lazy-flush", MODIFIABLE_CONFIG, server.repl_slave_lazy_flush, 0, NULL, NULL), createBoolConfig("replica-serve-stale-data", "slave-serve-stale-data", MODIFIABLE_CONFIG, server.repl_serve_stale_data, 1, NULL, NULL), createBoolConfig("replica-read-only", "slave-read-only", DEBUG_CONFIG | MODIFIABLE_CONFIG, server.repl_slave_ro, 1, NULL, NULL), createBoolConfig("replica-ignore-maxmemory", "slave-ignore-maxmemory", MODIFIABLE_CONFIG, server.repl_slave_ignore_maxmemory, 1, NULL, NULL), createBoolConfig("jemalloc-bg-thread", NULL, MODIFIABLE_CONFIG, server.jemalloc_bg_thread, 1, NULL, updateJemallocBgThread), createBoolConfig("activedefrag", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.active_defrag_enabled, 0, isValidActiveDefrag, NULL), createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, server.syslog_enabled, 0, NULL, NULL), createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, server.cluster_enabled, 0, NULL, NULL), createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly), createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL), createBoolConfig("cluster-allow-pubsubshard-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_pubsubshard_when_down, 1, NULL, NULL), createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, server.crashlog_enabled, 1, NULL, updateSighandlerEnabled), createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, server.memcheck_enabled, 1, NULL, NULL), createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.use_exit_on_panic, 0, NULL, NULL), createBoolConfig("disable-thp", NULL, IMMUTABLE_CONFIG, server.disable_thp, 1, NULL, NULL), createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL), createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, NULL, NULL), createBoolConfig("latency-tracking", NULL, MODIFIABLE_CONFIG, server.latency_tracking_enabled, 1, NULL, NULL), createBoolConfig("aof-disable-auto-gc", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.aof_disable_auto_gc, 0, NULL, updateAofAutoGCEnabled), createBoolConfig("replica-ignore-disk-write-errors", NULL, MODIFIABLE_CONFIG, server.repl_ignore_disk_write_error, 0, NULL, NULL), createBoolConfig("hide-user-data-from-log", NULL, MODIFIABLE_CONFIG, server.hide_user_data_from_log, 0, NULL, NULL), /* String Configs */ createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL), createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.unixsocket, NULL, NULL, NULL), createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL), createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL), createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL), createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp), createStringConfig("cluster-config-file", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf", NULL, NULL), createStringConfig("cluster-announce-hostname", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname), createStringConfig("cluster-announce-human-nodename", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_human_nodename, NULL, isValidAnnouncedNodename, updateClusterHumanNodename), createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL), createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL), createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL), createStringConfig("appenddirname", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_dirname, "appendonlydir", isValidAOFdirname, NULL), createStringConfig("server-cpulist", "server_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL), createStringConfig("bio-cpulist", "bio_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL), createStringConfig("aof-rewrite-cpulist", "aof_rewrite_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL), createStringConfig("bgsave-cpulist", "bgsave_cpulist", IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL), createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "", NULL, NULL), createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate), createStringConfig("bind-source-addr", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bind_source_addr, NULL, NULL, NULL), createStringConfig("logfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.logfile, "", NULL, NULL), #ifdef LOG_REQ_RES createStringConfig("req-res-logfile", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, EMPTY_STRING_IS_NULL, server.req_res_logfile, NULL, NULL, NULL), #endif createStringConfig("locale-collate", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.locale_collate, "", NULL, updateLocaleCollate), /* SDS Configs */ createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL), createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.requirepass, NULL, NULL, updateRequirePass), /* Enum Configs */ createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL), createEnumConfig("syslog-facility", NULL, IMMUTABLE_CONFIG, syslog_facility_enum, server.syslog_facility, LOG_LOCAL0, NULL, NULL), createEnumConfig("repl-diskless-load", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG | DENY_LOADING_CONFIG, repl_diskless_load_enum, server.repl_diskless_load, REPL_DISKLESS_LOAD_DISABLED, NULL, NULL), createEnumConfig("loglevel", NULL, MODIFIABLE_CONFIG, loglevel_enum, server.verbosity, LL_NOTICE, NULL, NULL), createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL), createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, server.aof_fsync, AOF_FSYNC_EVERYSEC, NULL, updateAppendFsync), createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, server.oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj), createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, server.acl_pubsub_default, 0, NULL, NULL), createEnumConfig("sanitize-dump-payload", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, sanitize_dump_payload_enum, server.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL), createEnumConfig("enable-protected-configs", NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_protected_configs, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL), createEnumConfig("enable-debug-command", NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_debug_cmd, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL), createEnumConfig("enable-module-command", NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_module_cmd, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL), createEnumConfig("cluster-preferred-endpoint-type", NULL, MODIFIABLE_CONFIG, cluster_preferred_endpoint_type_enum, server.cluster_preferred_endpoint_type, CLUSTER_ENDPOINT_TYPE_IP, NULL, NULL), createEnumConfig("propagation-error-behavior", NULL, MODIFIABLE_CONFIG, propagation_error_behavior_enum, server.propagation_error_behavior, PROPAGATION_ERR_BEHAVIOR_IGNORE, NULL, NULL), createEnumConfig("shutdown-on-sigint", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigint, 0, isValidShutdownOnSigFlags, NULL), createEnumConfig("shutdown-on-sigterm", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigterm, 0, isValidShutdownOnSigFlags, NULL), /* Integer configs */ createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL), createIntConfig("port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, updatePort), /* TCP port. */ createIntConfig("io-threads", NULL, DEBUG_CONFIG | IMMUTABLE_CONFIG, 1, 128, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */ createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */ createIntConfig("list-max-listpack-size", "list-max-ziplist-size", MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_listpack_size, -2, INTEGER_CONFIG, NULL, NULL), createIntConfig("tcp-keepalive", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcpkeepalive, 300, INTEGER_CONFIG, NULL, NULL), createIntConfig("cluster-migration-barrier", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_migration_barrier, 1, INTEGER_CONFIG, NULL, NULL), createIntConfig("active-defrag-cycle-min", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_min, 1, INTEGER_CONFIG, NULL, updateDefragConfiguration), /* Default: 1% CPU min (at lower threshold) */ createIntConfig("active-defrag-cycle-max", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_max, 25, INTEGER_CONFIG, NULL, updateDefragConfiguration), /* Default: 25% CPU max (at upper threshold) */ createIntConfig("active-defrag-threshold-lower", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_lower, 10, INTEGER_CONFIG, NULL, NULL), /* Default: don't defrag when fragmentation is below 10% */ createIntConfig("active-defrag-threshold-upper", NULL, MODIFIABLE_CONFIG, 0, 1000, server.active_defrag_threshold_upper, 100, INTEGER_CONFIG, NULL, updateDefragConfiguration), /* Default: maximum defrag force at 100% fragmentation */ createIntConfig("lfu-log-factor", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_log_factor, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("lfu-decay-time", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.lfu_decay_time, 1, INTEGER_CONFIG, NULL, NULL), createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, server.slave_priority, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL), createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, 64, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL), createIntConfig("maxmemory-eviction-tenacity", NULL, MODIFIABLE_CONFIG, 0, 100, server.maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */ createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, server.slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */ createIntConfig("cluster-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.cluster_port, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, updateClusterAnnouncedPort), /* Default: Use +10000 offset. */ createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_port, 0, INTEGER_CONFIG, NULL, updateClusterAnnouncedPort), /* Use server.port */ createIntConfig("cluster-announce-tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_tls_port, 0, INTEGER_CONFIG, NULL, updateClusterAnnouncedPort), /* Use server.tls_port */ createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_timeout, 60, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("list-compress-depth", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, INT_MIN, INT_MAX, server.rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, INT_MIN, INT_MAX, server.key_load_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, server.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */ createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ), createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves), createIntConfig("min-replicas-max-lag", "min-slaves-max-lag", MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_max_lag, 10, INTEGER_CONFIG, NULL, updateGoodSlaves), createIntConfig("watchdog-period", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, 0, INT_MAX, server.watchdog_period, 0, INTEGER_CONFIG, NULL, updateWatchdogPeriod), createIntConfig("shutdown-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.shutdown_timeout, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-diskless-sync-max-replicas", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_diskless_sync_max_replicas, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("cluster-compatibility-sample-ratio", NULL, MODIFIABLE_CONFIG, 0, 100, server.cluster_compatibility_sample_ratio, 0, INTEGER_CONFIG, NULL, NULL), /* Unsigned int configs */ createUIntConfig("maxclients", NULL, MODIFIABLE_CONFIG, 1, UINT_MAX, server.maxclients, 10000, INTEGER_CONFIG, NULL, updateMaxclients), createUIntConfig("unixsocketperm", NULL, IMMUTABLE_CONFIG, 0, 0777, server.unixsocketperm, 0, OCTAL_CONFIG, NULL, NULL), createUIntConfig("socket-mark-id", NULL, IMMUTABLE_CONFIG, 0, UINT_MAX, server.socket_mark_id, 0, INTEGER_CONFIG, NULL, NULL), createUIntConfig("max-new-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_conns_per_cycle, 10, INTEGER_CONFIG, NULL, NULL), createUIntConfig("max-new-tls-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_tls_conns_per_cycle, 1, INTEGER_CONFIG, NULL, NULL), #ifdef LOG_REQ_RES createUIntConfig("client-default-resp", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, 2, 3, server.client_default_resp, 2, INTEGER_CONFIG, NULL, NULL), #endif /* Unsigned Long configs */ createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */ createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL), createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL), /* Long Long configs */ createLongLongConfig("busy-reply-threshold", "lua-time-limit", MODIFIABLE_CONFIG, 0, LONG_MAX, server.busy_reply_threshold, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */ createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("cluster-ping-interval", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, 0, LLONG_MAX, server.cluster_ping_interval, 0, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("proto-max-bulk-len", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */ createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */ createLongLongConfig("replica-full-sync-buffer-limit", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.repl_full_sync_buffer_limit, 0, MEMORY_CONFIG, NULL, NULL), /* Default: Inherits 'client-output-buffer-limit ' */ /* Unsigned Long Long configs */ createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory), createULongLongConfig("cluster-link-sendbuf-limit", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.cluster_link_msg_queue_limit_bytes, 0, MEMORY_CONFIG, NULL, NULL), /* Size_t configs */ createSizeTConfig("hash-max-listpack-entries", "hash-max-ziplist-entries", MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_listpack_entries, 512, INTEGER_CONFIG, NULL, NULL), createSizeTConfig("set-max-intset-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL), createSizeTConfig("set-max-listpack-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_listpack_entries, 128, INTEGER_CONFIG, NULL, NULL), createSizeTConfig("set-max-listpack-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_listpack_value, 64, INTEGER_CONFIG, NULL, NULL), createSizeTConfig("zset-max-listpack-entries", "zset-max-ziplist-entries", MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_listpack_entries, 128, INTEGER_CONFIG, NULL, NULL), createSizeTConfig("active-defrag-ignore-bytes", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */ createSizeTConfig("hash-max-listpack-value", "hash-max-ziplist-value", MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_listpack_value, 64, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("zset-max-listpack-value", "zset-max-ziplist-value", MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_listpack_value, 64, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */ createSizeTConfig("client-query-buffer-limit", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.client_max_querybuf_len, 1024*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Default: 1GB max query buffer. */ createSSizeTConfig("maxmemory-clients", NULL, MODIFIABLE_CONFIG, -100, SSIZE_MAX, server.maxmemory_clients, 0, MEMORY_CONFIG | PERCENT_CONFIG, NULL, applyClientMaxMemoryUsage), /* Other configs */ createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */ createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL), createOffTConfig("loading-process-events-interval-bytes", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, 1024, INT_MAX, server.loading_process_events_interval_bytes, 1024*512, INTEGER_CONFIG, NULL, NULL), createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, applyTLSPort), /* TCP port. */ createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, applyTlsCfg), createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, applyTlsCfg), createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, applyTlsCfg), createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, applyTlsCfg), createEnumConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, server.tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL), createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, applyTlsCfg), createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, applyTlsCfg), createStringConfig("tls-cert-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, applyTlsCfg), createStringConfig("tls-key-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, applyTlsCfg), createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, applyTlsCfg), createStringConfig("tls-client-cert-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, applyTlsCfg), createStringConfig("tls-client-key-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, applyTlsCfg), createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, applyTlsCfg), createStringConfig("tls-dh-params-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, applyTlsCfg), createStringConfig("tls-ca-cert-file", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, applyTlsCfg), createStringConfig("tls-ca-cert-dir", NULL, VOLATILE_CONFIG | MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, applyTlsCfg), createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, applyTlsCfg), createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, applyTlsCfg), createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, applyTlsCfg), /* Special configs */ createSpecialConfig("dir", NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG | DENY_LOADING_CONFIG, setConfigDirOption, getConfigDirOption, rewriteConfigDirOption, NULL), createSpecialConfig("save", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigSaveOption, getConfigSaveOption, rewriteConfigSaveOption, NULL), createSpecialConfig("client-output-buffer-limit", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigClientOutputBufferLimitOption, getConfigClientOutputBufferLimitOption, rewriteConfigClientOutputBufferLimitOption, NULL), createSpecialConfig("oom-score-adj-values", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigOOMScoreAdjValuesOption, getConfigOOMScoreAdjValuesOption, rewriteConfigOOMScoreAdjValuesOption, updateOOMScoreAdj), createSpecialConfig("notify-keyspace-events", NULL, MODIFIABLE_CONFIG, setConfigNotifyKeyspaceEventsOption, getConfigNotifyKeyspaceEventsOption, rewriteConfigNotifyKeyspaceEventsOption, NULL), createSpecialConfig("bind", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigBindOption, getConfigBindOption, rewriteConfigBindOption, applyBind), createSpecialConfig("replicaof", "slaveof", IMMUTABLE_CONFIG | MULTI_ARG_CONFIG, setConfigReplicaOfOption, getConfigReplicaOfOption, rewriteConfigReplicaOfOption, NULL), createSpecialConfig("latency-tracking-info-percentiles", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, setConfigLatencyTrackingInfoPercentilesOutputOption, getConfigLatencyTrackingInfoPercentilesOutputOption, rewriteConfigLatencyTrackingInfoPercentilesOutputOption, NULL), /* NULL Terminator, this is dropped when we convert to the runtime array. */ {NULL} }; /* Create a new config by copying the passed in config. Returns 1 on success * or 0 when their was already a config with the same name.. */ int registerConfigValue(const char *name, const standardConfig *config, int alias) { standardConfig *new = zmalloc(sizeof(standardConfig)); memcpy(new, config, sizeof(standardConfig)); if (alias) { new->flags |= ALIAS_CONFIG; new->name = config->alias; new->alias = config->name; } return dictAdd(configs, sdsnew(name), new) == DICT_OK; } /* Initialize configs to their default values and create and populate the * runtime configuration dictionary. */ void initConfigValues(void) { configs = dictCreate(&sdsHashDictType); dictExpand(configs, sizeof(static_configs) / sizeof(standardConfig)); for (standardConfig *config = static_configs; config->name != NULL; config++) { if (config->interface.init) config->interface.init(config); /* Add the primary config to the dictionary. */ int ret = registerConfigValue(config->name, config, 0); serverAssert(ret); /* Aliases are the same as their primary counter parts, but they * also have a flag indicating they are the alias. */ if (config->alias) { int ret = registerConfigValue(config->alias, config, ALIAS_CONFIG); serverAssert(ret); } } } /* Remove a config by name from the configs dict. */ void removeConfig(sds name) { standardConfig *config = lookupConfig(name); if (!config) return; if (config->flags & MODULE_CONFIG) { sdsfree((sds) config->name); sdsfree((sds) config->alias); switch (config->type) { case BOOL_CONFIG: break; case NUMERIC_CONFIG: break; case SDS_CONFIG: if (config->data.sds.default_value) sdsfree((sds)config->data.sds.default_value); break; case ENUM_CONFIG: { configEnum *enumNode = config->data.enumd.enum_value; while(enumNode->name != NULL) { zfree(enumNode->name); enumNode++; } zfree(config->data.enumd.enum_value); } break; case SPECIAL_CONFIG: /* Not used by modules */ case STRING_CONFIG: /* Not used by modules */ default: serverAssert(0); break; } } dictDelete(configs, name); } /*----------------------------------------------------------------------------- * Module Config *----------------------------------------------------------------------------*/ /* Create a bool/string/enum/numeric standardConfig for a module config in the configs dictionary */ /* On removeConfig(), name and alias will be sdsfree() */ void addModuleBoolConfig(sds name, sds alias, int flags, void *privdata, int default_val) { int config_dummy_address; standardConfig sc = createBoolConfig(name, alias, flags | MODULE_CONFIG, config_dummy_address, default_val, NULL, NULL); sc.data.yesno.config = NULL; sc.privdata = privdata; registerConfigValue(name, &sc, 0); /* If alias available, deep copy standardConfig and register again */ if (alias) { sc.name = sdsdup(name); sc.alias = sdsdup(alias); registerConfigValue(sc.alias, &sc, 1); } } /* On removeConfig(), name, default_val, and alias will be sdsfree() */ void addModuleStringConfig(sds name, sds alias, int flags, void *privdata, sds default_val) { sds config_dummy_address; standardConfig sc = createSDSConfig(name, alias, flags | MODULE_CONFIG, 0, config_dummy_address, default_val, NULL, NULL); sc.data.sds.config = NULL; sc.privdata = privdata; registerConfigValue(name, &sc, 0); /* memcpy sc */ /* If alias available, deep copy standardConfig and register again */ if (alias) { sc.name = sdsdup(name); sc.alias = sdsdup(alias); if (default_val) sc.data.sds.default_value = sdsdup(default_val); registerConfigValue(sc.alias, &sc, 1); } } /* On removeConfig(), name, default_val, alias and enum_vals will be freed */ void addModuleEnumConfig(sds name, sds alias, int flags, void *privdata, int default_val, configEnum *enum_vals, int num_enum_vals) { int config_dummy_address; standardConfig sc = createEnumConfig(name, alias, flags | MODULE_CONFIG, enum_vals, config_dummy_address, default_val, NULL, NULL); sc.data.enumd.config = NULL; sc.privdata = privdata; registerConfigValue(name, &sc, 0); /* If alias available, deep copy standardConfig and register again */ if (alias) { sc.name = sdsdup(name); sc.alias = sdsdup(alias); sc.data.enumd.enum_value = zmalloc((num_enum_vals + 1) * sizeof(configEnum)); for (int i = 0; i < num_enum_vals; i++) { sc.data.enumd.enum_value[i].name = zstrdup(enum_vals[i].name); sc.data.enumd.enum_value[i].val = enum_vals[i].val; } sc.data.enumd.enum_value[num_enum_vals].name = NULL; sc.data.enumd.enum_value[num_enum_vals].val = 0; registerConfigValue(sc.alias, &sc, 1); } } /* On removeConfig(), it will free name, and alias if it is not NULL */ void addModuleNumericConfig(sds name, sds alias, int flags, void *privdata, long long default_val, int conf_flags, long long lower, long long upper) { long long config_dummy_address; standardConfig sc = createLongLongConfig(name, alias, flags | MODULE_CONFIG, lower, upper, config_dummy_address, default_val, conf_flags, NULL, NULL); sc.data.numeric.config.ll = NULL; sc.privdata = privdata; registerConfigValue(name, &sc, 0); /* If alias available, deep copy standardConfig and register again */ if (alias) { sc.name = sdsdup(name); sc.alias = sdsdup(alias); registerConfigValue(sc.alias, &sc, 1); } } /*----------------------------------------------------------------------------- * CONFIG HELP *----------------------------------------------------------------------------*/ void configHelpCommand(client *c) { const char *help[] = { "GET ", " Return parameters matching the glob-like and their values.", "SET ", " Set the configuration to .", "RESETSTAT", " Reset statistics reported by the INFO command.", "REWRITE", " Rewrite the configuration file.", NULL }; addReplyHelp(c, help); } /*----------------------------------------------------------------------------- * CONFIG RESETSTAT *----------------------------------------------------------------------------*/ void configResetStatCommand(client *c) { resetServerStats(); resetCommandTableStats(server.commands); resetErrorTableStats(); addReply(c,shared.ok); } /*----------------------------------------------------------------------------- * CONFIG REWRITE *----------------------------------------------------------------------------*/ void configRewriteCommand(client *c) { if (server.configfile == NULL) { addReplyError(c,"The server is running without a config file"); return; } if (rewriteConfig(server.configfile, 0) == -1) { /* save errno in case of being tainted. */ int err = errno; serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(err)); addReplyErrorFormat(c,"Rewriting config file: %s", strerror(err)); } else { serverLog(LL_NOTICE,"CONFIG REWRITE executed with success."); addReply(c,shared.ok); } } int configExists(const sds name) { return lookupConfig(name) != NULL; } redis-8.0.2/src/config.h000066400000000000000000000243321501533116600150410ustar00rootroot00000000000000/* * Copyright (c) 2009-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). */ #ifndef __CONFIG_H #define __CONFIG_H #ifdef __APPLE__ #include // for fcntl(fd, F_FULLFSYNC) #include #endif #ifdef __linux__ #include #include #endif #if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 #define MAC_OS_10_6_DETECTED #endif /* Define redis_fstat to fstat or fstat64() */ #if defined(__APPLE__) && !defined(MAC_OS_10_6_DETECTED) #define redis_fstat fstat64 #define redis_stat stat64 #else #define redis_fstat fstat #define redis_stat stat #endif #ifndef CACHE_LINE_SIZE #if defined(__aarch64__) && defined(__APPLE__) #define CACHE_LINE_SIZE 128 #else #define CACHE_LINE_SIZE 64 #endif #endif /* Test for proc filesystem */ #ifdef __linux__ #define HAVE_PROC_STAT 1 #define HAVE_PROC_MAPS 1 #define HAVE_PROC_SMAPS 1 #define HAVE_PROC_SOMAXCONN 1 #define HAVE_PROC_OOM_SCORE_ADJ 1 #define HAVE_EVENT_FD 1 #endif /* Test for task_info() */ #if defined(__APPLE__) #define HAVE_TASKINFO 1 #endif /* Test for somaxconn check */ #if defined(__APPLE__) || defined(__FreeBSD__) #define HAVE_SYSCTL_KIPC_SOMAXCONN 1 #elif defined(__OpenBSD__) #define HAVE_SYSCTL_KERN_SOMAXCONN 1 #endif /* Test for backtrace() */ #if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \ defined(__FreeBSD__) || ((defined(__OpenBSD__) || defined(__NetBSD__) || defined(__sun)) && defined(USE_BACKTRACE))\ || defined(__DragonFly__) || (defined(__UCLIBC__) && defined(__UCLIBC_HAS_BACKTRACE__)) #define HAVE_BACKTRACE 1 #endif /* MSG_NOSIGNAL. */ #ifdef __linux__ #define HAVE_MSG_NOSIGNAL 1 #if defined(SO_MARK) #define HAVE_SOCKOPTMARKID 1 #define SOCKOPTMARKID SO_MARK #endif #endif /* Test for polling API */ #ifdef __linux__ #define HAVE_EPOLL 1 #endif /* Test for accept4() */ #if defined(__linux__) || defined(OpenBSD5_7) || \ (__FreeBSD__ >= 10 || __FreeBSD_version >= 1000000) || \ (defined(NetBSD8_0) || __NetBSD_Version__ >= 800000000) #define HAVE_ACCEPT4 1 #endif #if (defined(__APPLE__) && defined(MAC_OS_10_6_DETECTED)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__) #define HAVE_KQUEUE 1 #endif #ifdef __sun #include #ifdef _DTRACE_VERSION #define HAVE_EVPORT 1 #define HAVE_PSINFO 1 #endif #endif /* Test for __builtin_prefetch() * Supported in LLVM since 2.9: https://releases.llvm.org/2.9/docs/ReleaseNotes.html * Supported in GCC since 3.1 but we use 4.9 given it's too old: https://gcc.gnu.org/gcc-3.1/changes.html. */ #if defined(__clang__) && (__clang_major__ > 2 || (__clang_major__ == 2 && __clang_minor__ >= 9)) #define HAS_BUILTIN_PREFETCH 1 #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) #define HAS_BUILTIN_PREFETCH 1 #else #define HAS_BUILTIN_PREFETCH 0 #endif #if HAS_BUILTIN_PREFETCH #define redis_prefetch_read(addr) __builtin_prefetch(addr, 0, 3) /* Read with high locality */ #define redis_prefetch_write(addr) __builtin_prefetch(addr, 1, 3) /* Write with high locality */ #else #define redis_prefetch_read(addr) ((void)(addr)) /* No-op if unsupported */ #define redis_prefetch_write(addr) ((void)(addr)) /* No-op if unsupported */ #endif /* Define redis_fsync to fdatasync() in Linux and fsync() for all the rest */ #if defined(__linux__) #define redis_fsync(fd) fdatasync(fd) #elif defined(__APPLE__) #define redis_fsync(fd) fcntl(fd, F_FULLFSYNC) #else #define redis_fsync(fd) fsync(fd) #endif #if defined(__FreeBSD__) #if defined(SO_USER_COOKIE) #define HAVE_SOCKOPTMARKID 1 #define SOCKOPTMARKID SO_USER_COOKIE #endif #endif #if defined(__OpenBSD__) #if defined(SO_RTABLE) #define HAVE_SOCKOPTMARKID 1 #define SOCKOPTMARKID SO_RTABLE #endif #endif #if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) #define redis_unreachable __builtin_unreachable #else #define redis_unreachable abort #endif #if __GNUC__ >= 3 #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #else #define likely(x) (x) #define unlikely(x) (x) #endif #if defined(__has_attribute) #if __has_attribute(no_sanitize) #define REDIS_NO_SANITIZE(sanitizer) __attribute__((no_sanitize(sanitizer))) #endif #endif #if !defined(REDIS_NO_SANITIZE) #define REDIS_NO_SANITIZE(sanitizer) #endif /* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use * the plain fsync() call. */ #if (defined(__linux__) && defined(SYNC_FILE_RANGE_WAIT_BEFORE)) #define HAVE_SYNC_FILE_RANGE 1 #define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE) #elif defined(__APPLE__) #define rdb_fsync_range(fd,off,size) fcntl(fd, F_FULLFSYNC) #else #define rdb_fsync_range(fd,off,size) fsync(fd) #endif /* Check if we can use setproctitle(). * BSD systems have support for it, we provide an implementation for * Linux and osx. */ #if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__) #define USE_SETPROCTITLE #endif #if defined(__HAIKU__) #define ESOCKTNOSUPPORT 0 #endif #if (defined __linux || defined __APPLE__) #define USE_SETPROCTITLE #define INIT_SETPROCTITLE_REPLACEMENT void spt_init(int argc, char *argv[]); void setproctitle(const char *fmt, ...); #endif /* Byte ordering detection */ #include /* This will likely define BYTE_ORDER */ #ifndef BYTE_ORDER #if (BSD >= 199103) # include #else #if defined(linux) || defined(__linux__) # include #else #define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */ #define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */ #define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/ #if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || \ defined(vax) || defined(ns32000) || defined(sun386) || \ defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ defined(__alpha__) || defined(__alpha) #define BYTE_ORDER LITTLE_ENDIAN #endif #if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \ defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\ defined(apollo) || defined(__convex__) || defined(_CRAY) || \ defined(__hppa) || defined(__hp9000) || \ defined(__hp9000s300) || defined(__hp9000s700) || \ defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc) #define BYTE_ORDER BIG_ENDIAN #endif #endif /* linux */ #endif /* BSD */ #endif /* BYTE_ORDER */ /* Sometimes after including an OS-specific header that defines the * endianness we end with __BYTE_ORDER but not with BYTE_ORDER that is what * the Redis code uses. In this case let's define everything without the * underscores. */ #ifndef BYTE_ORDER #ifdef __BYTE_ORDER #if defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) #ifndef LITTLE_ENDIAN #define LITTLE_ENDIAN __LITTLE_ENDIAN #endif #ifndef BIG_ENDIAN #define BIG_ENDIAN __BIG_ENDIAN #endif #if (__BYTE_ORDER == __LITTLE_ENDIAN) #define BYTE_ORDER LITTLE_ENDIAN #else #define BYTE_ORDER BIG_ENDIAN #endif #endif #endif #endif #if !defined(BYTE_ORDER) || \ (BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN) /* you must determine what the correct bit order is for * your compiler - the next line is an intentional error * which will force your compiles to bomb until you fix * the above macros. */ #error "Undefined or invalid BYTE_ORDER" #endif #if (__i386 || __amd64 || __powerpc__) && __GNUC__ #define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #if defined(__clang__) #define HAVE_ATOMIC #endif #if (defined(__GLIBC__) && defined(__GLIBC_PREREQ)) #if (GNUC_VERSION >= 40100 && __GLIBC_PREREQ(2, 6)) #define HAVE_ATOMIC #endif #endif #endif /* Make sure we can test for ARM just checking for __arm__, since sometimes * __arm is defined but __arm__ is not. */ #if defined(__arm) && !defined(__arm__) #define __arm__ #endif #if defined (__aarch64__) && !defined(__arm64__) #define __arm64__ #endif /* Make sure we can test for SPARC just checking for __sparc__. */ #if defined(__sparc) && !defined(__sparc__) #define __sparc__ #endif #if defined(__sparc__) || defined(__arm__) #define USE_ALIGNED_ACCESS #endif /* Define for redis_set_thread_title */ #ifdef __linux__ #define redis_set_thread_title(name) pthread_setname_np(pthread_self(), name) #else #if (defined __FreeBSD__ || defined __OpenBSD__) #include #define redis_set_thread_title(name) pthread_set_name_np(pthread_self(), name) #elif defined __NetBSD__ #include #define redis_set_thread_title(name) pthread_setname_np(pthread_self(), "%s", name) #elif defined __HAIKU__ #include #define redis_set_thread_title(name) rename_thread(find_thread(0), name) #else #if (defined __APPLE__ && defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070) int pthread_setname_np(const char *name); #include #define redis_set_thread_title(name) pthread_setname_np(name) #else #define redis_set_thread_title(name) #endif #endif #endif /* Check if we can use setcpuaffinity(). */ #if (defined __linux || defined __NetBSD__ || defined __FreeBSD__ || defined __DragonFly__) #define USE_SETCPUAFFINITY void setcpuaffinity(const char *cpulist); #endif /* Test for posix_fadvise() */ #if defined(__linux__) || __FreeBSD__ >= 10 #define HAVE_FADVISE #endif #if defined(__x86_64__) && ((defined(__GNUC__) && __GNUC__ > 5) || (defined(__clang__))) #if defined(__has_attribute) && __has_attribute(target) #define HAVE_POPCNT #define ATTRIBUTE_TARGET_POPCNT __attribute__((target("popcnt"))) #else #define ATTRIBUTE_TARGET_POPCNT #endif #else #define ATTRIBUTE_TARGET_POPCNT #endif /* Check if we can compile AVX2 code */ #if defined (__x86_64__) && ((defined(__GNUC__) && __GNUC__ >= 5) || (defined(__clang__) && __clang_major__ >= 4)) #if defined(__has_attribute) && __has_attribute(target) #define HAVE_AVX2 #endif #endif #if defined (HAVE_AVX2) #define ATTRIBUTE_TARGET_AVX2 __attribute__((target("avx2"))) #else #define ATTRIBUTE_TARGET_AVX2 #endif #endif redis-8.0.2/src/connection.c000066400000000000000000000134231501533116600157250ustar00rootroot00000000000000/* ========================================================================== * connection.c - connection layer framework * -------------------------------------------------------------------------- * Copyright (C) 2022 zhenwei pi * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit * persons to whom the Software is furnished to do so, subject to the * following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. * ========================================================================== */ #include "server.h" #include "connection.h" static ConnectionType *connTypes[CONN_TYPE_MAX]; int connTypeRegister(ConnectionType *ct) { const char *typename = ct->get_type(NULL); ConnectionType *tmpct; int type; /* find an empty slot to store the new connection type */ for (type = 0; type < CONN_TYPE_MAX; type++) { tmpct = connTypes[type]; if (!tmpct) break; /* ignore case, we really don't care "tls"/"TLS" */ if (!strcasecmp(typename, tmpct->get_type(NULL))) { serverLog(LL_WARNING, "Connection types %s already registered", typename); return C_ERR; } } serverLog(LL_VERBOSE, "Connection type %s registered", typename); connTypes[type] = ct; if (ct->init) { ct->init(); } return C_OK; } int connTypeInitialize(void) { /* currently socket connection type is necessary */ serverAssert(RedisRegisterConnectionTypeSocket() == C_OK); /* currently unix socket connection type is necessary */ serverAssert(RedisRegisterConnectionTypeUnix() == C_OK); /* may fail if without BUILD_TLS=yes */ RedisRegisterConnectionTypeTLS(); return C_OK; } ConnectionType *connectionByType(const char *typename) { ConnectionType *ct; for (int type = 0; type < CONN_TYPE_MAX; type++) { ct = connTypes[type]; if (!ct) break; if (!strcasecmp(typename, ct->get_type(NULL))) return ct; } serverLog(LL_WARNING, "Missing implement of connection type %s", typename); return NULL; } /* Cache TCP connection type, query it by string once */ ConnectionType *connectionTypeTcp(void) { static ConnectionType *ct_tcp = NULL; if (ct_tcp != NULL) return ct_tcp; ct_tcp = connectionByType(CONN_TYPE_SOCKET); serverAssert(ct_tcp != NULL); return ct_tcp; } /* Cache TLS connection type, query it by string once */ ConnectionType *connectionTypeTls(void) { static ConnectionType *ct_tls = NULL; static int cached = 0; /* Unlike the TCP and Unix connections, the TLS one can be missing * So we need the cached pointer to handle NULL correctly too. */ if (!cached) { cached = 1; ct_tls = connectionByType(CONN_TYPE_TLS); } return ct_tls; } /* Cache Unix connection type, query it by string once */ ConnectionType *connectionTypeUnix(void) { static ConnectionType *ct_unix = NULL; if (ct_unix != NULL) return ct_unix; ct_unix = connectionByType(CONN_TYPE_UNIX); return ct_unix; } int connectionIndexByType(const char *typename) { ConnectionType *ct; for (int type = 0; type < CONN_TYPE_MAX; type++) { ct = connTypes[type]; if (!ct) break; if (!strcasecmp(typename, ct->get_type(NULL))) return type; } return -1; } void connTypeCleanupAll(void) { ConnectionType *ct; int type; for (type = 0; type < CONN_TYPE_MAX; type++) { ct = connTypes[type]; if (!ct) break; if (ct->cleanup) ct->cleanup(); } } /* walk all the connection types until has pending data */ int connTypeHasPendingData(struct aeEventLoop *el) { ConnectionType *ct; int type; int ret = 0; for (type = 0; type < CONN_TYPE_MAX; type++) { ct = connTypes[type]; if (ct && ct->has_pending_data && (ret = ct->has_pending_data(el))) { return ret; } } return ret; } /* walk all the connection types and process pending data for each connection type */ int connTypeProcessPendingData(struct aeEventLoop *el) { ConnectionType *ct; int type; int ret = 0; for (type = 0; type < CONN_TYPE_MAX; type++) { ct = connTypes[type]; if (ct && ct->process_pending_data) { ret += ct->process_pending_data(el); } } return ret; } sds getListensInfoString(sds info) { for (int j = 0; j < CONN_TYPE_MAX; j++) { connListener *listener = &server.listeners[j]; if (listener->ct == NULL) continue; info = sdscatfmt(info, "listener%i:name=%s", j, listener->ct->get_type(NULL)); for (int i = 0; i < listener->count; i++) { info = sdscatfmt(info, ",bind=%s", listener->bindaddr[i]); } if (listener->port) info = sdscatfmt(info, ",port=%i", listener->port); info = sdscatfmt(info, "\r\n"); } return info; } redis-8.0.2/src/connection.h000066400000000000000000000403241501533116600157320ustar00rootroot00000000000000 /* * Copyright (c) 2019-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). */ #ifndef __REDIS_CONNECTION_H #define __REDIS_CONNECTION_H #include #include #include #include #include "ae.h" #define CONN_INFO_LEN 32 #define CONN_ADDR_STR_LEN 128 /* Similar to INET6_ADDRSTRLEN, hoping to handle other protocols. */ struct aeEventLoop; typedef struct connection connection; typedef struct connListener connListener; typedef enum { CONN_STATE_NONE = 0, CONN_STATE_CONNECTING, CONN_STATE_ACCEPTING, CONN_STATE_CONNECTED, CONN_STATE_CLOSED, CONN_STATE_ERROR } ConnectionState; #define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */ #define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */ #define CONN_TYPE_SOCKET "tcp" #define CONN_TYPE_UNIX "unix" #define CONN_TYPE_TLS "tls" #define CONN_TYPE_MAX 8 /* 8 is enough to be extendable */ typedef void (*ConnectionCallbackFunc)(struct connection *conn); typedef struct ConnectionType { /* connection type */ const char *(*get_type)(struct connection *conn); /* connection type initialize & finalize & configure */ void (*init)(void); /* auto-call during register */ void (*cleanup)(void); int (*configure)(void *priv, int reconfigure); /* ae & accept & listen & error & address handler */ void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask); aeFileProc *accept_handler; int (*addr)(connection *conn, char *ip, size_t ip_len, int *port, int remote); int (*is_local)(connection *conn); int (*listen)(connListener *listener); /* create/shutdown/close connection */ connection* (*conn_create)(struct aeEventLoop *el); connection* (*conn_create_accepted)(struct aeEventLoop *el, int fd, void *priv); void (*shutdown)(struct connection *conn); void (*close)(struct connection *conn); /* connect & accept */ int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler); int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout); int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler); /* IO */ int (*write)(struct connection *conn, const void *data, size_t data_len); int (*writev)(struct connection *conn, const struct iovec *iov, int iovcnt); int (*read)(struct connection *conn, void *buf, size_t buf_len); int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier); int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler); const char *(*get_last_error)(struct connection *conn); ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout); ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout); ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout); /* event loop */ void (*unbind_event_loop)(struct connection *conn); int (*rebind_event_loop)(struct connection *conn, aeEventLoop *el); /* pending data */ int (*has_pending_data)(struct aeEventLoop *el); int (*process_pending_data)(struct aeEventLoop *el); /* TLS specified methods */ sds (*get_peer_cert)(struct connection *conn); } ConnectionType; struct connection { ConnectionType *type; ConnectionState state; int last_errno; int fd; short int flags; short int refs; unsigned short int iovcnt; void *private_data; struct aeEventLoop *el; ConnectionCallbackFunc conn_handler; ConnectionCallbackFunc write_handler; ConnectionCallbackFunc read_handler; }; #define CONFIG_BINDADDR_MAX 16 /* Setup a listener by a connection type */ struct connListener { int fd[CONFIG_BINDADDR_MAX]; int count; char **bindaddr; int bindaddr_count; int port; ConnectionType *ct; void *priv; /* used by connection type specified data */ }; /* The connection module does not deal with listening and accepting sockets, * so we assume we have a socket when an incoming connection is created. * * The fd supplied should therefore be associated with an already accept()ed * socket. * * connAccept() may directly call accept_handler(), or return and call it * at a later time. This behavior is a bit awkward but aims to reduce the need * to wait for the next event loop, if no additional handshake is required. * * IMPORTANT: accept_handler may decide to close the connection, calling connClose(). * To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED * in this case, and connAccept() returns with an error. * * connAccept() callers must always check the return value and on error (C_ERR) * a connClose() must be called. */ static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) { return conn->type->accept(conn, accept_handler); } /* Establish a connection. The connect_handler will be called when the connection * is established, or if an error has occurred. * * The connection handler will be responsible to set up any read/write handlers * as needed. * * If C_ERR is returned, the operation failed and the connection handler shall * not be expected. */ static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr, ConnectionCallbackFunc connect_handler) { return conn->type->connect(conn, addr, port, src_addr, connect_handler); } /* Blocking connect. * * NOTE: This is implemented in order to simplify the transition to the abstract * connections, but should probably be refactored out of cluster.c and replication.c, * in favor of a pure async implementation. */ static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) { return conn->type->blocking_connect(conn, addr, port, timeout); } /* Write to connection, behaves the same as write(2). * * Like write(2), a short write is possible. A -1 return indicates an error. * * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use * connGetState() to see if the connection state is still CONN_STATE_CONNECTED. */ static inline int connWrite(connection *conn, const void *data, size_t data_len) { return conn->type->write(conn, data, data_len); } /* Gather output data from the iovcnt buffers specified by the members of the iov * array: iov[0], iov[1], ..., iov[iovcnt-1] and write to connection, behaves the same as writev(3). * * Like writev(3), a short write is possible. A -1 return indicates an error. * * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use * connGetState() to see if the connection state is still CONN_STATE_CONNECTED. */ static inline int connWritev(connection *conn, const struct iovec *iov, int iovcnt) { return conn->type->writev(conn, iov, iovcnt); } /* Read from the connection, behaves the same as read(2). * * Like read(2), a short read is possible. A return value of 0 will indicate the * connection was closed, and -1 will indicate an error. * * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use * connGetState() to see if the connection state is still CONN_STATE_CONNECTED. */ static inline int connRead(connection *conn, void *buf, size_t buf_len) { int ret = conn->type->read(conn, buf, buf_len); return ret; } /* Register a write handler, to be called when the connection is writable. * If NULL, the existing handler is removed. */ static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) { return conn->type->set_write_handler(conn, func, 0); } /* Register a read handler, to be called when the connection is readable. * If NULL, the existing handler is removed. */ static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) { return conn->type->set_read_handler(conn, func); } /* Set a write handler, and possibly enable a write barrier, this flag is * cleared when write handler is changed or removed. * With barrier enabled, we never fire the event if the read handler already * fired in the same event loop iteration. Useful when you want to persist * things to disk before sending replies, and want to do that in a group fashion. */ static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCallbackFunc func, int barrier) { return conn->type->set_write_handler(conn, func, barrier); } static inline void connShutdown(connection *conn) { conn->type->shutdown(conn); } static inline void connClose(connection *conn) { conn->type->close(conn); } /* Returns the last error encountered by the connection, as a string. If no error, * a NULL is returned. */ static inline const char *connGetLastError(connection *conn) { return conn->type->get_last_error(conn); } static inline ssize_t connSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) { return conn->type->sync_write(conn, ptr, size, timeout); } static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) { return conn->type->sync_read(conn, ptr, size, timeout); } static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) { return conn->type->sync_readline(conn, ptr, size, timeout); } /* Return CONN_TYPE_* for the specified connection */ static inline const char *connGetType(connection *conn) { return conn->type->get_type(conn); } static inline int connLastErrorRetryable(connection *conn) { return conn->last_errno == EINTR; } /* Get address information of a connection. * remote works as boolean type to get local/remote address */ static inline int connAddr(connection *conn, char *ip, size_t ip_len, int *port, int remote) { if (conn && conn->type->addr) { return conn->type->addr(conn, ip, ip_len, port, remote); } return -1; } /* Format an IP,port pair into something easy to parse. If IP is IPv6 * (matches for ":"), the ip is surrounded by []. IP and port are just * separated by colons. This the standard to display addresses within Redis. */ static inline int formatAddr(char *buf, size_t buf_len, char *ip, int port) { return snprintf(buf, buf_len, strchr(ip,':') ? "[%s]:%d" : "%s:%d", ip, port); } static inline int connFormatAddr(connection *conn, char *buf, size_t buf_len, int remote) { char ip[CONN_ADDR_STR_LEN]; int port; if (connAddr(conn, ip, sizeof(ip), &port, remote) < 0) { return -1; } return formatAddr(buf, buf_len, ip, port); } static inline int connAddrPeerName(connection *conn, char *ip, size_t ip_len, int *port) { return connAddr(conn, ip, ip_len, port, 1); } static inline int connAddrSockName(connection *conn, char *ip, size_t ip_len, int *port) { return connAddr(conn, ip, ip_len, port, 0); } /* Test a connection is local or loopback. * Return -1 on failure, 0 is not a local connection, 1 is a local connection */ static inline int connIsLocal(connection *conn) { if (conn && conn->type->is_local) { return conn->type->is_local(conn); } return -1; } static inline int connGetState(connection *conn) { return conn->state; } /* Returns true if a write handler is registered */ static inline int connHasWriteHandler(connection *conn) { return conn->write_handler != NULL; } /* Returns true if a read handler is registered */ static inline int connHasReadHandler(connection *conn) { return conn->read_handler != NULL; } /* Returns true if the connection is bound to an event loop */ static inline int connHasEventLoop(connection *conn) { return conn->el != NULL; } /* Unbind the current event loop from the connection, so that it can be * rebind to a different event loop in the future. */ static inline void connUnbindEventLoop(connection *conn) { if (conn->el == NULL) return; connSetReadHandler(conn, NULL); connSetWriteHandler(conn, NULL); if (conn->type->unbind_event_loop) conn->type->unbind_event_loop(conn); conn->el = NULL; } /* Rebind the connection to another event loop, read/write handlers must not * be installed in the current event loop */ static inline int connRebindEventLoop(connection *conn, aeEventLoop *el) { return conn->type->rebind_event_loop(conn, el); } /* Associate a private data pointer with the connection */ static inline void connSetPrivateData(connection *conn, void *data) { conn->private_data = data; } /* Get the associated private data pointer */ static inline void *connGetPrivateData(connection *conn) { return conn->private_data; } /* Return a text that describes the connection, suitable for inclusion * in CLIENT LIST and similar outputs. * * For sockets, we always return "fd=" to maintain compatibility. */ static inline const char *connGetInfo(connection *conn, char *buf, size_t buf_len) { snprintf(buf, buf_len-1, "fd=%i", conn == NULL ? -1 : conn->fd); return buf; } /* anet-style wrappers to conns */ int connBlock(connection *conn); int connNonBlock(connection *conn); int connEnableTcpNoDelay(connection *conn); int connDisableTcpNoDelay(connection *conn); int connKeepAlive(connection *conn, int interval); int connSendTimeout(connection *conn, long long ms); int connRecvTimeout(connection *conn, long long ms); /* Get cert for the secure connection */ static inline sds connGetPeerCert(connection *conn) { if (conn->type->get_peer_cert) { return conn->type->get_peer_cert(conn); } return NULL; } /* Initialize the redis connection framework */ int connTypeInitialize(void); /* Register a connection type into redis connection framework */ int connTypeRegister(ConnectionType *ct); /* Lookup a connection type by type name */ ConnectionType *connectionByType(const char *typename); /* Fast path to get TCP connection type */ ConnectionType *connectionTypeTcp(void); /* Fast path to get TLS connection type */ ConnectionType *connectionTypeTls(void); /* Fast path to get Unix connection type */ ConnectionType *connectionTypeUnix(void); /* Lookup the index of a connection type by type name, return -1 if not found */ int connectionIndexByType(const char *typename); /* Create a connection of specified type */ static inline connection *connCreate(struct aeEventLoop *el, ConnectionType *ct) { return ct->conn_create(el); } /* Create an accepted connection of specified type. * priv is connection type specified argument */ static inline connection *connCreateAccepted(struct aeEventLoop *el, ConnectionType *ct, int fd, void *priv) { return ct->conn_create_accepted(el, fd, priv); } /* Configure a connection type. A typical case is to configure TLS. * priv is connection type specified, * reconfigure is boolean type to specify if overwrite the original config */ static inline int connTypeConfigure(ConnectionType *ct, void *priv, int reconfigure) { return ct->configure(priv, reconfigure); } /* Walk all the connection types and cleanup them all if possible */ void connTypeCleanupAll(void); /* Test all the connection type has pending data or not. */ int connTypeHasPendingData(struct aeEventLoop *el); /* walk all the connection types and process pending data for each connection type */ int connTypeProcessPendingData(struct aeEventLoop *el); /* Listen on an initialized listener */ static inline int connListen(connListener *listener) { return listener->ct->listen(listener); } /* Get accept_handler of a connection type */ static inline aeFileProc *connAcceptHandler(ConnectionType *ct) { if (ct) return ct->accept_handler; return NULL; } /* Get Listeners information, note that caller should free the non-empty string */ sds getListensInfoString(sds info); int RedisRegisterConnectionTypeSocket(void); int RedisRegisterConnectionTypeUnix(void); int RedisRegisterConnectionTypeTLS(void); /* Return 1 if connection is using TLS protocol, 0 if otherwise. */ static inline int connIsTLS(connection *conn) { return conn && conn->type == connectionTypeTls(); } #endif /* __REDIS_CONNECTION_H */ redis-8.0.2/src/connhelpers.h000066400000000000000000000037601501533116600161160ustar00rootroot00000000000000 /* * Copyright (c) 2019-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). */ #ifndef __REDIS_CONNHELPERS_H #define __REDIS_CONNHELPERS_H #include "connection.h" /* These are helper functions that are common to different connection * implementations (currently sockets in connection.c and TLS in tls.c). * * Currently helpers implement the mechanisms for invoking connection * handlers and tracking connection references, to allow safe destruction * of connections from within a handler. */ /* Increment connection references. * * Inside a connection handler, we guarantee refs >= 1 so it is always * safe to connClose(). * * In other cases where we don't want to prematurely lose the connection, * it can go beyond 1 as well; currently it is only done by connAccept(). */ static inline void connIncrRefs(connection *conn) { conn->refs++; } /* Decrement connection references. * * Note that this is not intended to provide any automatic free logic! * callHandler() takes care of that for the common flows, and anywhere an * explicit connIncrRefs() is used, the caller is expected to take care of * that. */ static inline void connDecrRefs(connection *conn) { conn->refs--; } static inline int connHasRefs(connection *conn) { return conn->refs; } /* Helper for connection implementations to call handlers: * 1. Increment refs to protect the connection. * 2. Execute the handler (if set). * 3. Decrement refs and perform deferred close, if refs==0. */ static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) { connIncrRefs(conn); if (handler) handler(conn); connDecrRefs(conn); if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) { if (!connHasRefs(conn)) connClose(conn); return 0; } return 1; } #endif /* __REDIS_CONNHELPERS_H */ redis-8.0.2/src/crc16.c000066400000000000000000000105261501533116600145050ustar00rootroot00000000000000#include "server.h" /* * Copyright 2001-2010 Georges Menie (www.menie.org) * Copyright 2010-current Redis Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of the University of California, Berkeley nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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. */ /* CRC16 implementation according to CCITT standards. * * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the * following parameters: * * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" * Width : 16 bit * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) * Initialization : 0000 * Reflect Input byte : False * Reflect Output CRC : False * Xor constant to output CRC : 0000 * Output for "123456789" : 31C3 */ static const uint16_t crc16tab[256]= { 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 }; uint16_t crc16(const char *buf, int len) { int counter; uint16_t crc = 0; for (counter = 0; counter < len; counter++) crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; return crc; } redis-8.0.2/src/crc16_slottable.h000066400000000000000000003333751501533116600165750ustar00rootroot00000000000000#ifndef _CRC16_TABLE_H__ #define _CRC16_TABLE_H__ /* A table of the shortest possible alphanumeric string that is mapped by redis' crc16 * to any given redis cluster slot. * * The array indexes are slot numbers, so that given a desired slot, this string is guaranteed * to make redis cluster route a request to the shard holding this slot */ typedef char crc16_alphastring[4]; const crc16_alphastring crc16_slot_table[] = { "06S", "Qi", "5L5", "4Iu", "4gY", "460", "1Y7", "1LV", "0QG", "ru", "7Ok", "4ji", "4DE", "65n", "2JH", "I8", "F9", "SX", "7nF", "4KD", "4eh", "6PK", "2ke", "1Ng", "0Sv", "4L", "491", "4hX", "4Ft", "5C4", "2Hy", "09R", "021", "0cX", "4Xv", "6mU", "6Cy", "42R", "0Mt", "nF", "cv", "1Pe", "5kK", "6NI", "74L", "4UF", "0nh", "MZ", "2TJ", "0ai", "4ZG", "6od", "6AH", "40c", "0OE", "lw", "aG", "0Bu", "5iz", "6Lx", "5R7", "4Ww", "0lY", "Ok", "5n3", "4ks", "8YE", "7g", "2KR", "1nP", "714", "64t", "69D", "4Ho", "07I", "Ps", "2hN", "1ML", "4fC", "7CA", "avs", "4iB", "0Rl", "5V", "2Ic", "08H", "4Gn", "66E", "aUo", "b4e", "05x", "RB", "8f", "8VD", "4dr", "5a2", "4zp", "6OS", "bl", "355", "0or", "1j2", "75V", "bno", "4Yl", "6lO", "Ap", "0bB", "0Ln", "2yM", "6Bc", "43H", "4xA", "6Mb", "22D", "14", "0mC", "Nq", "6cN", "4Vm", "ban", "aDl", "CA", "14Z", "8GG", "mm", "549", "41y", "53t", "464", "1Y3", "1LR", "06W", "Qm", "5L1", "4Iq", "4DA", "65j", "2JL", "1oN", "0QC", "6y", "7Oo", "4jm", "4el", "6PO", "9x", "1Nc", "04f", "2EM", "7nB", "bqs", "4Fp", "5C0", "d6F", "09V", "0Sr", "4H", "495", "bRo", "aio", "42V", "0Mp", "nB", "025", "17u", "4Xr", "6mQ", "74H", "4UB", "0nl", "3Kn", "cr", "1Pa", "5kO", "6NM", "6AL", "40g", "0OA", "ls", "2TN", "0am", "4ZC", "aEr", "5R3", "4Ws", "18t", "Oo", "aC", "0Bq", "bCl", "afn", "2KV", "1nT", "5Uz", "64p", "5n7", "4kw", "0PY", "7c", "2hJ", "1MH", "4fG", "6Sd", "7mi", "4Hk", "07M", "Pw", "2Ig", "08L", "4Gj", "66A", "7LD", "4iF", "0Rh", "5R", "8b", "1Oy", "4dv", "5a6", "7oX", "4JZ", "0qt", "RF", "0ov", "LD", "4A9", "4TX", "4zt", "6OW", "bh", "0AZ", "z9", "oX", "6Bg", "43L", "4Yh", "6lK", "At", "0bF", "0mG", "Nu", "6cJ", "4Vi", "4xE", "6Mf", "2vH", "10", "8GC", "mi", "5p5", "4uu", "5Kx", "4N8", "CE", "1pV", "0QO", "6u", "7Oc", "4ja", "4DM", "65f", "3Za", "I0", "0rS", "Qa", "68V", "b7F", "4gQ", "468", "dSo", "285", "274", "4D", "499", "4hP", "b8G", "67W", "0h3", "09Z", "F1", "SP", "7nN", "4KL", "51I", "6PC", "9t", "1No", "21g", "1Pm", "5kC", "6NA", "74D", "4UN", "X3", "MR", "029", "0cP", "bbM", "79t", "4c3", "42Z", "8Dd", "nN", "aO", "8Ke", "4yS", "4l2", "76u", "635", "0lQ", "Oc", "BS", "W2", "4ZO", "6ol", "7Qa", "40k", "0OM", "2zn", "69L", "4Hg", "07A", "2Fj", "2hF", "k6", "4fK", "6Sh", "7Ny", "6K9", "0PU", "7o", "2KZ", "1nX", "4EW", "4P6", "7oT", "4JV", "05p", "RJ", "8n", "1Ou", "4dz", "6QY", "7LH", "4iJ", "d7", "qV", "2Ik", "1li", "4Gf", "66M", "4Yd", "6lG", "Ax", "0bJ", "z5", "oT", "6Bk", "4wH", "4zx", "aeI", "bd", "0AV", "0oz", "LH", "4A5", "4TT", "5Kt", "4N4", "CI", "14R", "0NW", "me", "541", "41q", "4xI", "6Mj", "22L", "u4", "0mK", "Ny", "6cF", "4Ve", "4DI", "65b", "2JD", "I4", "0QK", "6q", "7Og", "4je", "4gU", "4r4", "2iX", "1LZ", "0rW", "Qe", "5L9", "4Iy", "4Fx", "5C8", "0h7", "1mw", "0Sz", "pH", "7MV", "4hT", "4ed", "6PG", "9p", "1Nk", "F5", "ST", "7nJ", "4KH", "7pH", "4UJ", "X7", "MV", "cz", "1Pi", "5kG", "6NE", "4c7", "4vV", "0Mx", "nJ", "0v5", "0cT", "4Xz", "6mY", "6bX", "5GZ", "0lU", "Og", "aK", "0By", "4yW", "4l6", "6AD", "40o", "0OI", "2zj", "BW", "W6", "4ZK", "6oh", "2hB", "k2", "4fO", "6Sl", "69H", "4Hc", "07E", "2Fn", "d5e", "83m", "4ES", "4P2", "a0F", "bQL", "0PQ", "7k", "8j", "1Oq", "50W", "hbv", "7oP", "4JR", "05t", "RN", "2Io", "08D", "4Gb", "66I", "7LL", "4iN", "d3", "5Z", "z1", "oP", "6Bo", "43D", "5IA", "6lC", "2Wm", "0bN", "8ff", "LL", "4A1", "4TP", "cPn", "aeM", "0T3", "0AR", "0NS", "ma", "545", "41u", "5Kp", "4N0", "CM", "14V", "0mO", "2Xl", "6cB", "4Va", "4xM", "6Mn", "22H", "18", "04s", "SI", "7nW", "4KU", "4ey", "6PZ", "9m", "1Nv", "e4", "pU", "7MK", "4hI", "4Fe", "67N", "2Hh", "09C", "06B", "Qx", "68O", "4Id", "4gH", "6Rk", "2iE", "j5", "0QV", "6l", "5o8", "4jx", "4DT", "4Q5", "2JY", "82j", "BJ", "0ax", "4ZV", "4O7", "552", "40r", "0OT", "lf", "aV", "t7", "4yJ", "6Li", "6bE", "4Wf", "0lH", "Oz", "2Vj", "0cI", "4Xg", "6mD", "6Ch", "42C", "0Me", "nW", "cg", "1Pt", "5kZ", "6NX", "7pU", "4UW", "0ny", "MK", "7LQ", "4iS", "267", "5G", "0i0", "08Y", "b9D", "66T", "7oM", "4JO", "G2", "RS", "8w", "1Ol", "4dc", "7Aa", "atS", "4kb", "0PL", "7v", "2KC", "H3", "4EN", "64e", "69U", "b6E", "07X", "Pb", "dRl", "296", "4fR", "4s3", "4xP", "4m1", "22U", "8Jf", "0mR", "0x3", "77v", "626", "5Km", "6no", "CP", "V1", "0NN", "3kL", "7Pb", "41h", "4za", "6OB", "20d", "0AO", "Y0", "LQ", "6an", "4TM", "bcN", "78w", "Aa", "0bS", "8Eg", "oM", "4b0", "43Y", "51T", "azL", "9i", "1Nr", "04w", "SM", "7nS", "4KQ", "4Fa", "67J", "2Hl", "09G", "e0", "4Y", "7MO", "4hM", "4gL", "6Ro", "2iA", "j1", "06F", "2Gm", "68K", "5YA", "4DP", "4Q1", "d4f", "82n", "0QR", "6h", "a1E", "bPO", "556", "40v", "0OP", "lb", "BN", "15U", "4ZR", "4O3", "6bA", "4Wb", "0lL", "2Yo", "aR", "t3", "4yN", "6Lm", "6Cl", "42G", "0Ma", "nS", "2Vn", "0cM", "4Xc", "79i", "74Y", "4US", "8ge", "MO", "cc", "1Pp", "bAL", "adN", "0i4", "1lt", "5WZ", "66P", "7LU", "4iW", "0Ry", "5C", "8s", "1Oh", "4dg", "6QD", "7oI", "4JK", "G6", "RW", "2KG", "H7", "4EJ", "64a", "7Nd", "4kf", "0PH", "7r", "1X8", "1MY", "4fV", "4s7", "69Q", "4Hz", "0sT", "Pf", "0mV", "Nd", "5S8", "4Vx", "4xT", "4m5", "22Q", "0Cz", "0NJ", "mx", "7Pf", "41l", "5Ki", "6nk", "CT", "V5", "Y4", "LU", "6aj", "4TI", "4ze", "6OF", "by", "0AK", "2l9", "oI", "4b4", "4wU", "4Yy", "6lZ", "Ae", "0bW", "0So", "4U", "7MC", "4hA", "4Fm", "67F", "3XA", "09K", "0ps", "SA", "aTl", "b5f", "4eq", "6PR", "9e", "8WG", "8XF", "6d", "5o0", "4jp", "707", "65w", "1z2", "1oS", "06J", "Qp", "68G", "4Il", "53i", "6Rc", "2iM", "1LO", "23G", "07", "4yB", "6La", "6bM", "4Wn", "18i", "Or", "BB", "0ap", "c4D", "aEo", "5q2", "40z", "8FD", "ln", "co", "346", "5kR", "6NP", "74U", "bol", "0nq", "MC", "2Vb", "0cA", "4Xo", "6mL", "7SA", "42K", "0Mm", "2xN", "7oE", "4JG", "05a", "2DJ", "2jf", "1Od", "4dk", "6QH", "482", "5yz", "0Ru", "5O", "0i8", "08Q", "4Gw", "5B7", "5M6", "4Hv", "07P", "Pj", "1X4", "1MU", "4fZ", "473", "7Nh", "4kj", "0PD", "sv", "2KK", "1nI", "4EF", "64m", "5Ke", "6ng", "CX", "V9", "0NF", "mt", "7Pj", "4uh", "4xX", "4m9", "1F6", "0Cv", "0mZ", "Nh", "5S4", "4Vt", "4Yu", "6lV", "Ai", "16r", "0Lw", "oE", "4b8", "43Q", "4zi", "6OJ", "bu", "0AG", "Y8", "LY", "6af", "4TE", "4Fi", "67B", "2Hd", "09O", "e8", "4Q", "7MG", "4hE", "4eu", "6PV", "9a", "1Nz", "0pw", "SE", "aTh", "4KY", "4DX", "4Q9", "1z6", "1oW", "0QZ", "rh", "5o4", "4jt", "4gD", "6Rg", "2iI", "j9", "06N", "Qt", "68C", "4Ih", "6bI", "4Wj", "0lD", "Ov", "aZ", "03", "4yF", "6Le", "5q6", "4tv", "0OX", "lj", "BF", "0at", "4ZZ", "6oy", "74Q", "5Ez", "0nu", "MG", "ck", "1Px", "5kV", "6NT", "6Cd", "42O", "0Mi", "2xJ", "2Vf", "0cE", "4Xk", "6mH", "2jb", "8VY", "4do", "6QL", "7oA", "4JC", "05e", "2DN", "d7E", "08U", "4Gs", "5B3", "486", "bSl", "0Rq", "5K", "1X0", "1MQ", "52w", "477", "5M2", "4Hr", "07T", "Pn", "2KO", "1nM", "4EB", "64i", "7Nl", "4kn", "8YX", "7z", "0NB", "mp", "7Pn", "41d", "5Ka", "6nc", "2UM", "14G", "19w", "Nl", "5S0", "4Vp", "bBo", "agm", "1F2", "0Cr", "0Ls", "oA", "ahl", "43U", "4Yq", "6lR", "Am", "16v", "0oo", "2ZL", "6ab", "4TA", "4zm", "6ON", "bq", "0AC", "2VY", "0cz", "4XT", "4M5", "570", "42p", "0MV", "nd", "cT", "v5", "5ki", "6Nk", "74n", "4Ud", "0nJ", "Mx", "By", "0aK", "4Ze", "6oF", "6Aj", "40A", "y4", "lU", "ae", "0BW", "4yy", "581", "4B4", "4WU", "18R", "OI", "06q", "QK", "7lU", "4IW", "53R", "6RX", "0I4", "1Lt", "g6", "rW", "7OI", "4jK", "4Dg", "65L", "2Jj", "1oh", "0pH", "Sz", "7nd", "4Kf", "4eJ", "6Pi", "2kG", "h7", "0ST", "4n", "7Mx", "4hz", "4FV", "4S7", "1x8", "09p", "4zR", "4o3", "bN", "8Hd", "0oP", "Lb", "75t", "604", "4YN", "6lm", "AR", "T3", "0LL", "2yo", "6BA", "43j", "4xc", "agR", "22f", "0CM", "0ma", "NS", "6cl", "4VO", "baL", "aDN", "Cc", "14x", "8Ge", "mO", "7PQ", "4uS", "7NS", "4kQ", "245", "7E", "0k2", "1nr", "coo", "64V", "69f", "4HM", "E0", "PQ", "2hl", "1Mn", "4fa", "6SB", "7Lb", "5yA", "0RN", "5t", "2IA", "J1", "4GL", "66g", "aUM", "b4G", "05Z", "0d3", "8D", "8Vf", "4dP", "459", "574", "42t", "0MR", "0X3", "dln", "17W", "4XP", "4M1", "74j", "5EA", "0nN", "3KL", "cP", "29", "5km", "6No", "6An", "40E", "y0", "lQ", "2Tl", "0aO", "4Za", "6oB", "4B0", "4WQ", "18V", "OM", "aa", "0BS", "bCN", "585", "53V", "axN", "0I0", "1Lp", "06u", "QO", "68x", "4IS", "4Dc", "65H", "2Jn", "1ol", "g2", "rS", "7OM", "4jO", "4eN", "6Pm", "9Z", "h3", "04D", "2Eo", "aTS", "4Kb", "4FR", "4S3", "d6d", "09t", "0SP", "4j", "a3G", "bRM", "0oT", "Lf", "6aY", "4Tz", "4zV", "4o7", "bJ", "0Ax", "0LH", "oz", "6BE", "43n", "4YJ", "6li", "AV", "T7", "0me", "NW", "6ch", "4VK", "4xg", "6MD", "22b", "0CI", "0Ny", "mK", "7PU", "4uW", "5KZ", "6nX", "Cg", "1pt", "0k6", "1nv", "4Ey", "64R", "7NW", "4kU", "241", "7A", "2hh", "1Mj", "4fe", "6SF", "69b", "4HI", "E4", "PU", "2IE", "J5", "4GH", "66c", "7Lf", "4id", "0RJ", "5p", "2jY", "8Vb", "4dT", "4q5", "5O8", "4Jx", "0qV", "Rd", "21E", "25", "5ka", "6Nc", "74f", "4Ul", "0nB", "Mp", "1f2", "0cr", "bbo", "79V", "578", "42x", "395", "nl", "am", "364", "4yq", "589", "76W", "bmn", "0ls", "OA", "Bq", "0aC", "4Zm", "6oN", "6Ab", "40I", "0Oo", "2zL", "0Qm", "6W", "7OA", "4jC", "4Do", "65D", "2Jb", "82Q", "06y", "QC", "68t", "b7d", "4gs", "5b3", "dSM", "8UE", "8ZD", "4f", "5m2", "4hr", "725", "67u", "1x0", "09x", "04H", "Sr", "7nl", "4Kn", "4eB", "6Pa", "9V", "1NM", "4YF", "6le", "AZ", "0bh", "0LD", "ov", "6BI", "43b", "4zZ", "6Oy", "bF", "0At", "0oX", "Lj", "5Q6", "4Tv", "5KV", "6nT", "Ck", "14p", "0Nu", "mG", "7PY", "41S", "4xk", "6MH", "22n", "0CE", "0mi", "2XJ", "6cd", "4VG", "69n", "4HE", "E8", "PY", "2hd", "1Mf", "4fi", "6SJ", "ath", "4kY", "0Pw", "7M", "2Kx", "1nz", "4Eu", "6pV", "5O4", "4Jt", "05R", "Rh", "8L", "1OW", "4dX", "451", "7Lj", "4ih", "0RF", "qt", "2II", "J9", "4GD", "66o", "74b", "4Uh", "0nF", "Mt", "cX", "21", "5ke", "6Ng", "5s4", "4vt", "0MZ", "nh", "1f6", "0cv", "4XX", "4M9", "4B8", "4WY", "0lw", "OE", "ai", "1Rz", "4yu", "6LV", "6Af", "40M", "y8", "lY", "Bu", "0aG", "4Zi", "6oJ", "4Dk", "6qH", "2Jf", "1od", "0Qi", "6S", "7OE", "4jG", "4gw", "5b7", "0I8", "1Lx", "0ru", "QG", "68p", "5Yz", "4FZ", "67q", "1x4", "1mU", "0SX", "4b", "5m6", "4hv", "4eF", "6Pe", "9R", "1NI", "04L", "Sv", "7nh", "4Kj", "8EX", "or", "6BM", "43f", "4YB", "6la", "2WO", "0bl", "8fD", "Ln", "5Q2", "4Tr", "cPL", "aeo", "bB", "0Ap", "0Nq", "mC", "ajn", "41W", "5KR", "6nP", "Co", "14t", "0mm", "2XN", "77I", "4VC", "4xo", "6ML", "22j", "0CA", "3xA", "1Mb", "4fm", "6SN", "69j", "4HA", "07g", "2FL", "d5G", "83O", "4Eq", "64Z", "a0d", "bQn", "0Ps", "7I", "8H", "1OS", "50u", "455", "5O0", "4Jp", "05V", "Rl", "2IM", "08f", "5Wa", "66k", "7Ln", "4il", "0RB", "5x", "Bh", "0aZ", "4Zt", "6oW", "4a9", "40P", "0Ov", "lD", "at", "0BF", "4yh", "6LK", "6bg", "4WD", "Z9", "OX", "2VH", "U8", "4XE", "6mf", "6CJ", "42a", "0MG", "nu", "cE", "1PV", "5kx", "4n8", "5P5", "4Uu", "8gC", "Mi", "04Q", "Sk", "5N7", "4Kw", "51r", "442", "9O", "1NT", "0SE", "pw", "7Mi", "4hk", "4FG", "67l", "2HJ", "09a", "3", "QZ", "68m", "4IF", "4gj", "6RI", "2ig", "1Le", "0Qt", "6N", "7OX", "4jZ", "4Dv", "5A6", "0j9", "1oy", "4xr", "6MQ", "22w", "377", "0mp", "NB", "77T", "blm", "5KO", "6nM", "Cr", "14i", "0Nl", "3kn", "ajs", "41J", "4zC", "aer", "20F", "36", "0oA", "Ls", "6aL", "4To", "bcl", "78U", "AC", "0bq", "386", "oo", "5r3", "4ws", "5l1", "4iq", "9Kf", "5e", "1y3", "1lR", "736", "66v", "7oo", "4Jm", "05K", "Rq", "8U", "1ON", "4dA", "6Qb", "7NB", "bQs", "0Pn", "7T", "2Ka", "1nc", "4El", "64G", "69w", "b6g", "07z", "1v2", "dRN", "8TF", "4fp", "5c0", "akm", "40T", "0Or", "1J2", "Bl", "15w", "4Zp", "6oS", "6bc", "5Ga", "0ln", "2YM", "ap", "0BB", "4yl", "6LO", "6CN", "42e", "0MC", "nq", "2VL", "0co", "4XA", "6mb", "5P1", "4Uq", "8gG", "Mm", "cA", "1PR", "bAn", "adl", "51v", "446", "9K", "1NP", "04U", "So", "5N3", "4Ks", "4FC", "67h", "2HN", "09e", "0SA", "ps", "7Mm", "4ho", "4gn", "6RM", "2ic", "1La", "7", "2GO", "68i", "4IB", "4Dr", "5A2", "d4D", "82L", "0Qp", "6J", "a1g", "bPm", "0mt", "NF", "6cy", "4VZ", "4xv", "6MU", "0V9", "0CX", "0Nh", "mZ", "7PD", "41N", "5KK", "6nI", "Cv", "14m", "0oE", "Lw", "6aH", "4Tk", "4zG", "6Od", "20B", "32", "0LY", "ok", "5r7", "4ww", "5Iz", "6lx", "AG", "0bu", "1y7", "1lV", "4GY", "4R8", "5l5", "4iu", "1Bz", "5a", "8Q", "i8", "4dE", "6Qf", "7ok", "4Ji", "05O", "Ru", "2Ke", "1ng", "4Eh", "64C", "7NF", "4kD", "f9", "7P", "2hy", "3m9", "4ft", "5c4", "69s", "4HX", "0sv", "PD", "23e", "0BN", "5iA", "6LC", "6bo", "4WL", "Z1", "OP", "0t3", "0aR", "c4f", "aEM", "4a1", "40X", "8Ff", "lL", "cM", "8Ig", "5kp", "4n0", "74w", "617", "0nS", "Ma", "3Fa", "U0", "4XM", "6mn", "6CB", "42i", "0MO", "2xl", "0SM", "4w", "7Ma", "4hc", "4FO", "67d", "2HB", "K2", "04Y", "Sc", "aTN", "b5D", "4eS", "4p2", "9G", "8We", "256", "6F", "7OP", "4jR", "cnl", "65U", "0j1", "1oq", "D3", "QR", "68e", "4IN", "4gb", "6RA", "2io", "1Lm", "5KG", "6nE", "Cz", "14a", "x7", "mV", "7PH", "41B", "4xz", "592", "0V5", "0CT", "0mx", "NJ", "4C7", "4VV", "4YW", "4L6", "AK", "0by", "0LU", "og", "563", "43s", "4zK", "6Oh", "bW", "w6", "0oI", "2Zj", "6aD", "4Tg", "7og", "4Je", "05C", "Ry", "2jD", "i4", "4dI", "6Qj", "5l9", "4iy", "0RW", "5m", "2IX", "08s", "4GU", "4R4", "7mV", "4HT", "07r", "PH", "0H7", "1Mw", "4fx", "5c8", "7NJ", "4kH", "f5", "sT", "2Ki", "1nk", "4Ed", "64O", "6bk", "4WH", "Z5", "OT", "ax", "0BJ", "4yd", "6LG", "4a5", "4tT", "0Oz", "lH", "Bd", "0aV", "4Zx", "aEI", "5P9", "4Uy", "0nW", "Me", "cI", "1PZ", "5kt", "4n4", "6CF", "42m", "0MK", "ny", "2VD", "U4", "4XI", "6mj", "4FK", "6sh", "2HF", "K6", "0SI", "4s", "7Me", "4hg", "4eW", "4p6", "9C", "1NX", "0pU", "Sg", "7ny", "6k9", "4Dz", "65Q", "0j5", "1ou", "0Qx", "6B", "7OT", "4jV", "4gf", "6RE", "2ik", "1Li", "D7", "QV", "68a", "4IJ", "x3", "mR", "7PL", "41F", "5KC", "6nA", "2Uo", "14e", "19U", "NN", "4C3", "4VR", "bBM", "596", "0V1", "0CP", "0LQ", "oc", "567", "43w", "4YS", "4L2", "AO", "16T", "0oM", "2Zn", "75i", "4Tc", "4zO", "6Ol", "bS", "w2", "8Y", "i0", "4dM", "6Qn", "7oc", "4Ja", "05G", "2Dl", "d7g", "08w", "4GQ", "4R0", "a2D", "bSN", "0RS", "5i", "0H3", "1Ms", "52U", "ayM", "7mR", "4HP", "07v", "PL", "2Km", "1no", "5UA", "64K", "7NN", "4kL", "f1", "7X", "5nw", "4k7", "fJ", "0Ex", "0kT", "Hf", "6eY", "4Pz", "5Mk", "6hi", "EV", "P7", "0HH", "kz", "6FE", "47n", "48o", "6ID", "26b", "0GI", "0ie", "JW", "6gh", "4RK", "5OZ", "6jX", "Gg", "0dU", "0Jy", "iK", "4d6", "4qW", "4z4", "4oU", "1DZ", "3A", "Ye", "0zW", "4Ay", "5D9", "6yj", "4LI", "A4", "TU", "zy", "0YK", "4be", "6WF", "6XG", "4md", "0VJ", "1p", "2ME", "N5", "4CH", "62c", "5K8", "4Nx", "0uV", "Vd", "xH", "8Rb", "5pu", "4u5", "D", "13W", "5Lq", "4I1", "534", "46t", "0IR", "28y", "gP", "69", "5om", "6Jo", "6dC", "5AA", "0jN", "3OL", "2Pl", "0eO", "aT1", "6kB", "6En", "44E", "98", "hQ", "ea", "0FS", "49u", "abL", "4F0", "4SQ", "8ag", "KM", "02u", "UO", "4X2", "4MS", "57V", "a8F", "0M0", "0XQ", "c2", "vS", "7KM", "4nO", "5PB", "61H", "2Nn", "1kl", "00D", "2Ao", "6zA", "4Ob", "4aN", "6Tm", "yR", "l3", "0WP", "0j", "a7G", "58W", "4BR", "4W3", "ZN", "84l", "0kP", "Hb", "71t", "644", "5ns", "4k3", "fN", "8Ld", "0HL", "29g", "6FA", "47j", "5Mo", "6hm", "ER", "P3", "0ia", "JS", "6gl", "4RO", "48k", "7Ya", "26f", "0GM", "8Ce", "iO", "4d2", "4qS", "beL", "hYw", "Gc", "0dQ", "Ya", "0zS", "cko", "60V", "4z0", "4oQ", "205", "3E", "2ll", "0YO", "4ba", "6WB", "6yn", "4LM", "A0", "TQ", "2MA", "N1", "4CL", "62g", "6XC", "59I", "0VN", "1t", "xL", "8Rf", "54y", "419", "aQM", "b0G", "01Z", "3PP", "530", "46p", "0IV", "jd", "DH", "0gz", "5Lu", "4I5", "6dG", "4Qd", "0jJ", "Ix", "gT", "r5", "5oi", "6Jk", "6Ej", "44A", "0Kg", "hU", "Fy", "0eK", "5ND", "6kF", "4F4", "4SU", "1xZ", "KI", "ee", "0FW", "49q", "5x9", "57R", "6VX", "0M4", "0XU", "02q", "UK", "4X6", "4MW", "5PF", "61L", "2Nj", "1kh", "c6", "vW", "7KI", "4nK", "4aJ", "6Ti", "yV", "l7", "0tH", "Wz", "6zE", "4Of", "4BV", "4W7", "ZJ", "0yx", "0WT", "0n", "6YY", "4lz", "5Mc", "6ha", "2SO", "0fl", "1Xa", "kr", "6FM", "47f", "bDm", "aao", "fB", "0Ep", "8bD", "Hn", "5U2", "4Pr", "5OR", "5Z3", "Go", "10t", "0Jq", "iC", "ann", "45W", "48g", "6IL", "ds", "0GA", "0im", "3Lo", "73I", "4RC", "6yb", "4LA", "03g", "2BL", "zq", "0YC", "4bm", "6WN", "a4d", "bUn", "0Ts", "3I", "Ym", "87O", "4Aq", "5D1", "5K0", "4Np", "01V", "Vl", "2nQ", "1KS", "54u", "415", "6XO", "4ml", "0VB", "1x", "2MM", "0xn", "5Sa", "62k", "gX", "61", "5oe", "6Jg", "6dK", "4Qh", "0jF", "It", "L", "0gv", "5Ly", "4I9", "5w4", "4rt", "0IZ", "jh", "ei", "1Vz", "5mT", "5x5", "4F8", "4SY", "0hw", "KE", "Fu", "0eG", "5NH", "6kJ", "6Ef", "44M", "90", "hY", "0Ui", "2S", "7KE", "4nG", "5PJ", "6uH", "Xw", "1kd", "0vu", "UG", "6xx", "790", "4cw", "5f7", "0M8", "0XY", "0WX", "0b", "5i6", "4lv", "4BZ", "63q", "ZF", "0yt", "00L", "Wv", "6zI", "4Oj", "4aF", "6Te", "yZ", "0Zh", "0HD", "kv", "6FI", "47b", "5Mg", "6he", "EZ", "0fh", "0kX", "Hj", "5U6", "4Pv", "7N9", "6Ky", "fF", "0Et", "0Ju", "iG", "6Dx", "45S", "5OV", "5Z7", "Gk", "0dY", "0ii", "3Lk", "6gd", "4RG", "48c", "6IH", "dw", "0GE", "zu", "0YG", "4bi", "6WJ", "6yf", "4LE", "A8", "TY", "Yi", "1jz", "4Au", "5D5", "4z8", "4oY", "0Tw", "3M", "xD", "1KW", "54q", "411", "5K4", "4Nt", "01R", "Vh", "2MI", "N9", "4CD", "62o", "6XK", "4mh", "0VF", "ut", "6dO", "4Ql", "0jB", "Ip", "25E", "65", "5oa", "6Jc", "538", "46x", "9Pg", "jl", "H", "0gr", "bfo", "aCm", "72W", "bin", "0hs", "KA", "em", "324", "49y", "5x1", "6Eb", "44I", "94", "3nm", "Fq", "0eC", "5NL", "6kN", "5PN", "61D", "Xs", "86Q", "0Um", "2W", "7KA", "4nC", "4cs", "5f3", "39W", "8QE", "02y", "UC", "aRn", "794", "765", "63u", "ZB", "0yp", "9Ne", "0f", "5i2", "4lr", "4aB", "6Ta", "2oO", "0Zl", "00H", "Wr", "6zM", "4On", "5lW", "5y6", "dj", "0GX", "0it", "JF", "6gy", "4RZ", "5OK", "6jI", "Gv", "0dD", "83", "iZ", "6De", "45N", "5nf", "6Kd", "24B", "72", "0kE", "Hw", "6eH", "4Pk", "5Mz", "6hx", "EG", "0fu", "0HY", "kk", "5v7", "4sw", "5h5", "4mu", "1Fz", "1a", "2MT", "0xw", "4CY", "4V8", "7kk", "4Ni", "01O", "Vu", "xY", "m8", "54l", "6Uf", "6Zg", "4oD", "b9", "3P", "Yt", "0zF", "4Ah", "60C", "4Y9", "4LX", "0wv", "TD", "zh", "0YZ", "4bt", "5g4", "Fl", "11w", "5NQ", "6kS", "aom", "44T", "0Kr", "1N2", "ep", "0FB", "49d", "6HO", "6fc", "5Ca", "0hn", "3Ml", "U", "0go", "bfr", "6ib", "6GN", "46e", "0IC", "jq", "gA", "0Ds", "bEn", "hyU", "5T1", "4Qq", "8cG", "Im", "00U", "Wo", "5J3", "4Os", "55v", "406", "yC", "0Zq", "0WA", "ts", "6YL", "4lo", "4BC", "63h", "2LN", "0ym", "02d", "2CO", "6xa", "4MB", "4cn", "6VM", "2mc", "1Ha", "0Up", "2J", "a5g", "bTm", "5PS", "5E2", "Xn", "86L", "0ip", "JB", "73T", "bhm", "48z", "5y2", "dn", "337", "87", "3on", "6Da", "45J", "5OO", "6jM", "Gr", "10i", "0kA", "Hs", "6eL", "4Po", "5nb", "aar", "24F", "76", "8AE", "ko", "5v3", "4ss", "bgl", "aBn", "EC", "0fq", "2MP", "0xs", "776", "62v", "5h1", "4mq", "9Of", "1e", "2nL", "1KN", "54h", "6Ub", "7ko", "4Nm", "01K", "Vq", "Yp", "0zB", "4Al", "60G", "6Zc", "bUs", "0Tn", "3T", "zl", "8PF", "4bp", "5g0", "aSm", "787", "03z", "1r2", "4e9", "44P", "0Kv", "hD", "Fh", "0eZ", "5NU", "6kW", "6fg", "4SD", "0hj", "KX", "et", "0FF", "5mI", "6HK", "6GJ", "46a", "0IG", "ju", "Q", "Q8", "5Ld", "6if", "5T5", "4Qu", "1zz", "Ii", "gE", "0Dw", "5ox", "4j8", "55r", "402", "yG", "0Zu", "00Q", "Wk", "5J7", "4Ow", "4BG", "63l", "2LJ", "0yi", "0WE", "tw", "6YH", "4lk", "4cj", "6VI", "2mg", "0XD", "0vh", "UZ", "6xe", "4MF", "5PW", "5E6", "Xj", "1ky", "0Ut", "2N", "7KX", "4nZ", "5OC", "6jA", "2Qo", "0dL", "1ZA", "iR", "6Dm", "45F", "48v", "acO", "db", "0GP", "94M", "JN", "4G3", "4RR", "5Mr", "4H2", "EO", "12T", "0HQ", "kc", "527", "47w", "5nn", "6Kl", "fS", "s2", "0kM", "3NO", "71i", "4Pc", "7kc", "4Na", "01G", "3PM", "xQ", "m0", "54d", "6Un", "a6D", "59T", "0VS", "1i", "197", "85o", "4CQ", "4V0", "4Y1", "4LP", "03v", "TL", "0L3", "0YR", "56U", "a9E", "6Zo", "4oL", "b1", "3X", "2Om", "0zN", "5QA", "60K", "ex", "0FJ", "49l", "6HG", "6fk", "4SH", "0hf", "KT", "Fd", "0eV", "5NY", "aAI", "4e5", "4pT", "0Kz", "hH", "gI", "1TZ", "5ot", "4j4", "5T9", "4Qy", "0jW", "Ie", "DU", "Q4", "5Lh", "6ij", "6GF", "46m", "0IK", "jy", "0WI", "0s", "6YD", "4lg", "4BK", "6wh", "ZW", "O6", "0tU", "Wg", "6zX", "6o9", "4aW", "4t6", "yK", "0Zy", "0Ux", "2B", "7KT", "4nV", "bzI", "61Q", "Xf", "1ku", "02l", "UV", "6xi", "4MJ", "4cf", "6VE", "2mk", "0XH", "0Jd", "iV", "6Di", "45B", "5OG", "6jE", "Gz", "0dH", "0ix", "JJ", "4G7", "4RV", "48r", "6IY", "df", "0GT", "0HU", "kg", "523", "47s", "5Mv", "4H6", "EK", "0fy", "0kI", "3NK", "6eD", "4Pg", "5nj", "6Kh", "fW", "s6", "xU", "m4", "5ph", "6Uj", "7kg", "4Ne", "01C", "Vy", "193", "1hZ", "4CU", "4V4", "5h9", "4my", "0VW", "1m", "zd", "0YV", "4bx", "5g8", "4Y5", "4LT", "03r", "TH", "Yx", "0zJ", "4Ad", "60O", "6Zk", "4oH", "b5", "wT", "6fo", "4SL", "0hb", "KP", "27e", "0FN", "49h", "6HC", "4e1", "44X", "8Bf", "hL", "0p3", "0eR", "bdO", "aAM", "70w", "657", "0jS", "Ia", "gM", "8Mg", "5op", "4j0", "6GB", "46i", "0IO", "28d", "Y", "Q0", "5Ll", "6in", "4BO", "63d", "ZS", "O2", "0WM", "0w", "7Ia", "4lc", "4aS", "4t2", "yO", "8Se", "00Y", "Wc", "aPN", "b1D", "bzM", "61U", "Xb", "1kq", "216", "2F", "7KP", "4nR", "4cb", "6VA", "2mo", "0XL", "02h", "UR", "6xm", "4MN", "5j7", "4ow", "0TY", "3c", "YG", "0zu", "5Qz", "60p", "6yH", "4Lk", "03M", "Tw", "2lJ", "0Yi", "4bG", "6Wd", "6Xe", "4mF", "0Vh", "1R", "2Mg", "0xD", "4Cj", "62A", "7kX", "4NZ", "0ut", "VF", "xj", "1Ky", "5pW", "5e6", "5nU", "6KW", "fh", "0EZ", "0kv", "HD", "4E9", "4PX", "5MI", "6hK", "Et", "0fF", "0Hj", "kX", "6Fg", "47L", "48M", "6If", "dY", "50", "0iG", "Ju", "6gJ", "4Ri", "5Ox", "4J8", "GE", "0dw", "1Zz", "ii", "5t5", "4qu", "02W", "Um", "5H1", "4Mq", "57t", "424", "2mP", "0Xs", "0UC", "2y", "7Ko", "4nm", "bzr", "61j", "2NL", "1kN", "00f", "2AM", "6zc", "bus", "4al", "6TO", "yp", "0ZB", "0Wr", "0H", "a7e", "58u", "4Bp", "5G0", "Zl", "84N", "f", "13u", "5LS", "5Y2", "amo", "46V", "0Ip", "jB", "gr", "1Ta", "5oO", "6JM", "6da", "4QB", "0jl", "3On", "2PN", "0em", "5Nb", "aAr", "6EL", "44g", "0KA", "hs", "eC", "0Fq", "49W", "abn", "5V3", "4Ss", "8aE", "Ko", "YC", "0zq", "754", "60t", "5j3", "4os", "9Md", "3g", "2lN", "0Ym", "4bC", "7GA", "6yL", "4Lo", "03I", "Ts", "2Mc", "1ha", "4Cn", "62E", "6Xa", "4mB", "0Vl", "1V", "xn", "8RD", "5pS", "5e2", "aQo", "b0e", "01x", "VB", "0kr", "1n2", "71V", "bjo", "5nQ", "6KS", "fl", "315", "0Hn", "29E", "6Fc", "47H", "5MM", "6hO", "Ep", "0fB", "0iC", "Jq", "6gN", "4Rm", "48I", "6Ib", "26D", "54", "8CG", "im", "509", "45y", "ben", "hYU", "GA", "0ds", "4cY", "420", "2mT", "0Xw", "02S", "Ui", "5H5", "4Mu", "5Pd", "61n", "XY", "M8", "0UG", "vu", "7Kk", "4ni", "4ah", "6TK", "yt", "0ZF", "B9", "WX", "6zg", "4OD", "4Bt", "5G4", "Zh", "0yZ", "0Wv", "0L", "4y9", "4lX", "6Gy", "46R", "0It", "jF", "b", "0gX", "5LW", "5Y6", "6de", "4QF", "0jh", "IZ", "gv", "0DD", "5oK", "6JI", "6EH", "44c", "0KE", "hw", "2PJ", "0ei", "5Nf", "6kd", "5V7", "4Sw", "0hY", "Kk", "eG", "0Fu", "49S", "6Hx", "7ia", "4Lc", "03E", "2Bn", "zS", "o2", "4bO", "6Wl", "a4F", "bUL", "0TQ", "3k", "YO", "87m", "4AS", "4T2", "7kP", "4NR", "01t", "VN", "xb", "1Kq", "54W", "hfv", "6Xm", "4mN", "1FA", "1Z", "2Mo", "0xL", "4Cb", "62I", "5MA", "6hC", "2Sm", "0fN", "0Hb", "kP", "6Fo", "47D", "bDO", "aaM", "0P3", "0ER", "8bf", "HL", "4E1", "4PP", "5Op", "4J0", "GM", "10V", "0JS", "ia", "505", "45u", "48E", "6In", "dQ", "58", "0iO", "3LM", "6gB", "4Ra", "0UK", "2q", "7Kg", "4ne", "5Ph", "61b", "XU", "M4", "0vW", "Ue", "5H9", "4My", "4cU", "4v4", "2mX", "1HZ", "0Wz", "tH", "4y5", "4lT", "4Bx", "5G8", "Zd", "0yV", "B5", "WT", "6zk", "4OH", "4ad", "6TG", "yx", "0ZJ", "gz", "0DH", "5oG", "6JE", "6di", "4QJ", "0jd", "IV", "n", "0gT", "680", "6iY", "4g7", "4rV", "0Ix", "jJ", "eK", "0Fy", "5mv", "4h6", "6fX", "5CZ", "0hU", "Kg", "FW", "S6", "5Nj", "6kh", "6ED", "44o", "0KI", "3nK", "zW", "o6", "4bK", "6Wh", "6yD", "4Lg", "03A", "2Bj", "YK", "0zy", "4AW", "4T6", "6ZX", "6O9", "0TU", "3o", "xf", "1Ku", "54S", "6UY", "7kT", "4NV", "01p", "VJ", "2Mk", "0xH", "4Cf", "62M", "6Xi", "4mJ", "0Vd", "uV", "0Hf", "kT", "6Fk", "4sH", "5ME", "6hG", "Ex", "0fJ", "0kz", "HH", "4E5", "4PT", "5nY", "aaI", "fd", "0EV", "0JW", "ie", "501", "45q", "5Ot", "4J4", "GI", "10R", "0iK", "Jy", "6gF", "4Re", "48A", "6Ij", "dU", "q4", "5Pl", "61f", "XQ", "M0", "0UO", "2u", "7Kc", "4na", "4cQ", "428", "39u", "8Qg", "0vS", "Ua", "aRL", "b3F", "bxO", "63W", "0l3", "0yR", "234", "0D", "4y1", "4lP", "55I", "6TC", "2om", "0ZN", "B1", "WP", "6zo", "4OL", "6dm", "4QN", "1zA", "IR", "25g", "0DL", "5oC", "6JA", "4g3", "46Z", "9PE", "jN", "j", "0gP", "684", "aCO", "72u", "675", "0hQ", "Kc", "eO", "8Oe", "5mr", "4h2", "7Ua", "44k", "0KM", "3nO", "FS", "S2", "5Nn", "6kl", "4x6", "4mW", "0Vy", "1C", "0m4", "0xU", "5SZ", "62P", "7kI", "4NK", "C6", "VW", "2nj", "1Kh", "54N", "6UD", "6ZE", "4of", "0TH", "3r", "YV", "L7", "4AJ", "60a", "6yY", "4Lz", "0wT", "Tf", "zJ", "0Yx", "4bV", "4w7", "5lu", "4i5", "dH", "0Gz", "0iV", "Jd", "5W8", "4Rx", "5Oi", "6jk", "GT", "R5", "0JJ", "ix", "6DG", "45l", "5nD", "6KF", "fy", "0EK", "0kg", "HU", "6ej", "4PI", "5MX", "5X9", "Ee", "0fW", "1XZ", "kI", "4f4", "4sU", "00w", "WM", "4Z0", "4OQ", "55T", "hgu", "ya", "0ZS", "a0", "0Y", "6Yn", "4lM", "4Ba", "63J", "2Ll", "0yO", "02F", "2Cm", "6xC", "aG0", "4cL", "6Vo", "2mA", "n1", "0UR", "2h", "a5E", "bTO", "5Pq", "4U1", "XL", "86n", "FN", "11U", "5Ns", "4K3", "516", "44v", "0KP", "hb", "eR", "p3", "49F", "6Hm", "6fA", "4Sb", "0hL", "3MN", "w", "0gM", "5LB", "7ya", "6Gl", "46G", "0Ia", "jS", "gc", "0DQ", "bEL", "hyw", "4D2", "4QS", "8ce", "IO", "0m0", "0xQ", "byL", "62T", "4x2", "4mS", "227", "1G", "2nn", "1Kl", "54J", "7Ea", "7kM", "4NO", "C2", "VS", "YR", "L3", "4AN", "60e", "6ZA", "4ob", "0TL", "3v", "zN", "8Pd", "4bR", "4w3", "aSO", "b2E", "03X", "Tb", "0iR", "3LP", "73v", "666", "48X", "4i1", "dL", "8Nf", "0JN", "3oL", "6DC", "45h", "5Om", "6jo", "GP", "R1", "0kc", "HQ", "6en", "4PM", "a09", "6KB", "24d", "0EO", "8Ag", "kM", "4f0", "47Y", "697", "aBL", "Ea", "0fS", "4ay", "5d9", "ye", "0ZW", "00s", "WI", "4Z4", "4OU", "4Be", "63N", "Zy", "0yK", "a4", "tU", "6Yj", "4lI", "4cH", "6Vk", "2mE", "n5", "02B", "Ux", "6xG", "4Md", "5Pu", "4U5", "XH", "86j", "0UV", "2l", "5k8", "4nx", "512", "44r", "0KT", "hf", "FJ", "0ex", "5Nw", "4K7", "6fE", "4Sf", "0hH", "Kz", "eV", "p7", "49B", "6Hi", "6Gh", "46C", "0Ie", "jW", "s", "0gI", "5LF", "6iD", "4D6", "4QW", "0jy", "IK", "gg", "0DU", "5oZ", "6JX", "7kA", "4NC", "01e", "3Po", "xs", "8RY", "54F", "6UL", "a6f", "59v", "0Vq", "1K", "d3E", "85M", "4Cs", "5F3", "5I2", "4Lr", "03T", "Tn", "zB", "0Yp", "56w", "437", "6ZM", "4on", "1Da", "3z", "2OO", "0zl", "4AB", "60i", "5Oa", "6jc", "2QM", "0dn", "0JB", "ip", "6DO", "45d", "48T", "acm", "1B2", "0Gr", "94o", "Jl", "5W0", "4Rp", "5MP", "5X1", "Em", "12v", "0Hs", "kA", "all", "47U", "5nL", "6KN", "fq", "0EC", "0ko", "3Nm", "6eb", "4PA", "a8", "0Q", "6Yf", "4lE", "4Bi", "63B", "Zu", "0yG", "0tw", "WE", "4Z8", "4OY", "4au", "5d5", "yi", "1Jz", "0UZ", "vh", "5k4", "4nt", "5Py", "4U9", "XD", "1kW", "02N", "Ut", "6xK", "4Mh", "4cD", "6Vg", "2mI", "n9", "eZ", "43", "49N", "6He", "6fI", "4Sj", "0hD", "Kv", "FF", "0et", "7n9", "6ky", "5u6", "4pv", "0KX", "hj", "gk", "0DY", "5oV", "5z7", "6dx", "5Az", "0ju", "IG", "Dw", "0gE", "5LJ", "6iH", "6Gd", "46O", "0Ii", "28B", "xw", "1Kd", "54B", "6UH", "7kE", "4NG", "01a", "3Pk", "0m8", "0xY", "4Cw", "5F7", "6Xx", "59r", "0Vu", "1O", "zF", "0Yt", "4bZ", "433", "5I6", "4Lv", "03P", "Tj", "YZ", "0zh", "4AF", "60m", "6ZI", "4oj", "0TD", "wv", "0JF", "it", "6DK", "4qh", "5Oe", "6jg", "GX", "R9", "0iZ", "Jh", "5W4", "4Rt", "48P", "4i9", "dD", "0Gv", "0Hw", "kE", "4f8", "47Q", "5MT", "5X5", "Ei", "12r", "0kk", "HY", "6ef", "4PE", "5nH", "6KJ", "fu", "0EG", "4Bm", "63F", "Zq", "0yC", "0Wo", "0U", "6Yb", "4lA", "4aq", "5d1", "ym", "8SG", "0ts", "WA", "aPl", "b1f", "747", "61w", "2NQ", "1kS", "9Lg", "2d", "5k0", "4np", "57i", "6Vc", "2mM", "0Xn", "02J", "Up", "6xO", "4Ml", "6fM", "4Sn", "1xa", "Kr", "27G", "47", "49J", "6Ha", "5u2", "44z", "8BD", "hn", "FB", "0ep", "bdm", "aAo", "70U", "bkl", "0jq", "IC", "go", "306", "5oR", "5z3", "7WA", "46K", "0Im", "28F", "Ds", "0gA", "5LN", "6iL", "0cY", "020", "6mT", "4Xw", "42S", "6Cx", "nG", "0Mu", "1Pd", "cw", "6NH", "5kJ", "4UG", "74M", "3Kk", "0ni", "0ah", "BZ", "6oe", "4ZF", "40b", "6AI", "lv", "0OD", "0Bt", "aF", "6Ly", "4yZ", "4Wv", "5R6", "Oj", "0lX", "Qh", "06R", "4It", "5L4", "461", "4gX", "1LW", "1Y6", "rt", "0QF", "4jh", "7Oj", "65o", "4DD", "I9", "2JI", "SY", "F8", "4KE", "7nG", "6PJ", "4ei", "1Nf", "2kd", "4M", "0Sw", "4hY", "490", "5C5", "4Fu", "09S", "2Hx", "6OR", "4zq", "354", "bm", "LA", "0os", "bnn", "75W", "6lN", "4Ym", "0bC", "Aq", "2yL", "0Lo", "43I", "6Bb", "6Mc", "5ha", "15", "22E", "Np", "0mB", "4Vl", "6cO", "aDm", "bao", "1pS", "1e2", "ml", "8GF", "41x", "548", "4kr", "5n2", "7f", "8YD", "1nQ", "2KS", "64u", "715", "4Hn", "69E", "Pr", "07H", "1MM", "2hO", "6Sa", "4fB", "4iC", "7LA", "5W", "0Rm", "08I", "2Ib", "66D", "4Go", "b4d", "aUn", "RC", "05y", "8VE", "8g", "5a3", "4ds", "42W", "ain", "nC", "0Mq", "17t", "024", "6mP", "4Xs", "4UC", "74I", "3Ko", "0nm", "8IY", "cs", "6NL", "5kN", "40f", "6AM", "lr", "8FX", "0al", "2TO", "6oa", "4ZB", "4Wr", "5R2", "On", "18u", "0Bp", "aB", "afo", "bCm", "465", "53u", "1LS", "1Y2", "Ql", "06V", "4Ip", "5L0", "65k", "5Ta", "1oO", "2JM", "6x", "0QB", "4jl", "7On", "6PN", "4em", "1Nb", "9y", "2EL", "04g", "4KA", "7nC", "5C1", "4Fq", "09W", "d6G", "4I", "0Ss", "bRn", "494", "LE", "0ow", "4TY", "4A8", "6OV", "4zu", "1Qz", "bi", "oY", "z8", "43M", "6Bf", "6lJ", "4Yi", "0bG", "Au", "Nt", "0mF", "4Vh", "6cK", "6Mg", "4xD", "11", "22A", "mh", "0NZ", "4ut", "5p4", "4N9", "5Ky", "1pW", "CD", "1nU", "2KW", "64q", "4EZ", "4kv", "5n6", "7b", "0PX", "1MI", "2hK", "6Se", "4fF", "4Hj", "69A", "Pv", "07L", "08M", "2If", "6rH", "4Gk", "4iG", "7LE", "5S", "0Ri", "1Ox", "8c", "5a7", "4dw", "5Zz", "7oY", "RG", "0qu", "1Pl", "21f", "adR", "5kB", "4UO", "74E", "MS", "X2", "0cQ", "028", "79u", "bbL", "4vS", "4c2", "nO", "8De", "8Kd", "aN", "4l3", "4yR", "634", "76t", "Ob", "0lP", "W3", "BR", "6om", "4ZN", "40j", "6AA", "2zo", "0OL", "6t", "0QN", "5zA", "7Ob", "65g", "4DL", "I1", "2JA", "0g3", "06Z", "b7G", "68W", "469", "4gP", "284", "dSn", "4E", "275", "4hQ", "498", "67V", "b8F", "1mr", "0h2", "SQ", "F0", "4KM", "7nO", "6PB", "4ea", "1Nn", "9u", "6lF", "4Ye", "0bK", "Ay", "oU", "z4", "43A", "6Bj", "6OZ", "4zy", "0AW", "be", "LI", "2O9", "4TU", "4A4", "4N5", "5Ku", "14S", "CH", "md", "0NV", "41p", "540", "6Mk", "4xH", "u5", "22M", "Nx", "0mJ", "4Vd", "6cG", "4Hf", "69M", "Pz", "0sH", "k7", "2hG", "6Si", "4fJ", "4kz", "7Nx", "7n", "0PT", "1nY", "dqh", "4P7", "4EV", "4JW", "7oU", "RK", "05q", "1Ot", "8o", "6QX", "50R", "4iK", "7LI", "qW", "d6", "08A", "2Ij", "66L", "4Gg", "4UK", "74A", "MW", "X6", "1Ph", "21b", "6ND", "5kF", "4vW", "4c6", "nK", "0My", "0cU", "0v4", "6mX", "5HZ", "4Wz", "6bY", "Of", "0lT", "0Bx", "aJ", "4l7", "4yV", "40n", "6AE", "lz", "0OH", "W7", "BV", "6oi", "4ZJ", "65c", "4DH", "I5", "2JE", "6p", "0QJ", "4jd", "7Of", "4r5", "4gT", "280", "2iY", "Qd", "0rV", "4Ix", "5L8", "5C9", "4Fy", "1mv", "0h6", "4A", "1CZ", "4hU", "7MW", "6PF", "4ee", "1Nj", "9q", "SU", "F4", "4KI", "7nK", "oQ", "z0", "43E", "6Bn", "6lB", "4Ya", "0bO", "2Wl", "LM", "8fg", "4TQ", "4A0", "aeL", "cPo", "0AS", "ba", "3kP", "0NR", "41t", "544", "4N1", "5Kq", "14W", "CL", "2Xm", "0mN", "5FA", "6cC", "6Mo", "4xL", "19", "22I", "k3", "2hC", "6Sm", "4fN", "4Hb", "69I", "2Fo", "07D", "83l", "d5d", "4P3", "4ER", "bQM", "a0G", "7j", "0PP", "1Op", "8k", "hbw", "50V", "4JS", "7oQ", "RO", "05u", "08E", "2In", "66H", "4Gc", "4iO", "7LM", "qS", "d2", "0ay", "BK", "4O6", "4ZW", "40s", "553", "lg", "0OU", "t6", "aW", "6Lh", "4yK", "4Wg", "6bD", "2Yj", "0lI", "0cH", "2Vk", "6mE", "4Xf", "42B", "6Ci", "nV", "0Md", "1Pu", "cf", "6NY", "bAI", "4UV", "7pT", "MJ", "0nx", "SH", "04r", "4KT", "7nV", "azI", "4ex", "1Nw", "9l", "pT", "e5", "4hH", "7MJ", "67O", "4Fd", "09B", "2Hi", "Qy", "06C", "4Ie", "68N", "6Rj", "4gI", "j4", "2iD", "6m", "0QW", "4jy", "5o9", "4Q4", "4DU", "1oZ", "2JX", "4m0", "4xQ", "8Jg", "22T", "Na", "0mS", "627", "77w", "6nn", "5Kl", "V0", "CQ", "3kM", "0NO", "41i", "7Pc", "6OC", "5jA", "0AN", "20e", "LP", "Y1", "4TL", "6ao", "78v", "bcO", "0bR", "0w3", "oL", "8Ef", "43X", "4b1", "4iR", "7LP", "5F", "266", "08X", "0i1", "66U", "b9E", "4JN", "7oL", "RR", "G3", "1Om", "8v", "6QA", "4db", "4kc", "7Na", "7w", "0PM", "H2", "2KB", "64d", "4EO", "b6D", "69T", "Pc", "07Y", "297", "dRm", "4s2", "4fS", "40w", "557", "lc", "0OQ", "15T", "BO", "4O2", "4ZS", "4Wc", "76i", "2Yn", "0lM", "t2", "aS", "6Ll", "4yO", "42F", "6Cm", "nR", "8Dx", "0cL", "2Vo", "6mA", "4Xb", "4UR", "74X", "MN", "8gd", "1Pq", "cb", "adO", "bAM", "azM", "51U", "1Ns", "9h", "SL", "04v", "4KP", "7nR", "67K", "5VA", "09F", "2Hm", "4X", "e1", "4hL", "7MN", "6Rn", "4gM", "j0", "3ya", "2Gl", "06G", "4Ia", "68J", "4Q0", "4DQ", "82o", "d4g", "6i", "0QS", "bPN", "a1D", "Ne", "0mW", "4Vy", "5S9", "4m4", "4xU", "1SZ", "22P", "my", "0NK", "41m", "7Pg", "6nj", "5Kh", "V4", "CU", "LT", "Y5", "4TH", "6ak", "6OG", "4zd", "0AJ", "bx", "oH", "0Lz", "4wT", "4b5", "78r", "4Yx", "0bV", "Ad", "1lu", "0i5", "66Q", "4Gz", "4iV", "7LT", "5B", "0Rx", "1Oi", "8r", "6QE", "4df", "4JJ", "7oH", "RV", "G7", "H6", "2KF", "6ph", "4EK", "4kg", "7Ne", "7s", "0PI", "1MX", "1X9", "4s6", "4fW", "5XZ", "69P", "Pg", "0sU", "06", "23F", "afr", "4yC", "4Wo", "6bL", "Os", "0lA", "0aq", "BC", "aEn", "c4E", "4ts", "5q3", "lo", "8FE", "347", "cn", "6NQ", "5kS", "bom", "74T", "MB", "0np", "17i", "2Vc", "6mM", "4Xn", "42J", "6Ca", "2xO", "0Ml", "4T", "0Sn", "5xa", "7MB", "67G", "4Fl", "09J", "2Ha", "1u2", "04z", "b5g", "aTm", "6PS", "4ep", "8WF", "9d", "6e", "8XG", "4jq", "5o1", "65v", "706", "1oR", "1z3", "Qq", "06K", "4Im", "68F", "6Rb", "4gA", "1LN", "2iL", "6nf", "5Kd", "V8", "CY", "mu", "0NG", "41a", "7Pk", "4m8", "4xY", "0Cw", "1F7", "Ni", "19r", "4Vu", "5S5", "6lW", "4Yt", "0bZ", "Ah", "oD", "0Lv", "43P", "4b9", "6OK", "4zh", "0AF", "bt", "LX", "Y9", "4TD", "6ag", "4JF", "7oD", "RZ", "0qh", "1Oe", "2jg", "6QI", "4dj", "4iZ", "483", "5N", "0Rt", "08P", "0i9", "5B6", "4Gv", "4Hw", "5M7", "Pk", "07Q", "1MT", "1X5", "472", "52r", "4kk", "7Ni", "sw", "0PE", "1nH", "2KJ", "64l", "4EG", "4Wk", "6bH", "Ow", "0lE", "02", "23B", "6Ld", "4yG", "4tw", "5q7", "lk", "0OY", "0au", "BG", "6ox", "5Jz", "4UZ", "74P", "MF", "0nt", "1Py", "cj", "6NU", "5kW", "42N", "6Ce", "nZ", "0Mh", "0cD", "2Vg", "6mI", "4Xj", "67C", "4Fh", "09N", "2He", "4P", "e9", "4hD", "7MF", "6PW", "4et", "3n9", "2ky", "SD", "0pv", "4KX", "7nZ", "4Q8", "4DY", "1oV", "1z7", "6a", "1Az", "4ju", "5o5", "6Rf", "4gE", "j8", "2iH", "Qu", "06O", "4Ii", "68B", "mq", "0NC", "41e", "7Po", "6nb", "bar", "14F", "2UL", "Nm", "19v", "4Vq", "5S1", "agl", "bBn", "0Cs", "1F3", "1I2", "0Lr", "43T", "ahm", "6lS", "4Yp", "16w", "Al", "2ZM", "0on", "5Da", "6ac", "6OO", "4zl", "0AB", "bp", "1Oa", "8z", "6QM", "4dn", "4JB", "aUs", "2DO", "05d", "08T", "d7D", "5B2", "4Gr", "bSm", "487", "5J", "0Rp", "1MP", "1X1", "476", "52v", "4Hs", "5M3", "Po", "07U", "1nL", "2KN", "64h", "4EC", "4ko", "7Nm", "ss", "0PA", "QJ", "06p", "4IV", "7lT", "6RY", "4gz", "1Lu", "0I5", "rV", "g7", "4jJ", "7OH", "65M", "4Df", "1oi", "2Jk", "2Ej", "04A", "4Kg", "7ne", "6Ph", "4eK", "h6", "2kF", "4o", "0SU", "5xZ", "7My", "4S6", "4FW", "09q", "1x9", "17R", "2VX", "4M4", "4XU", "42q", "571", "ne", "0MW", "v4", "cU", "6Nj", "5kh", "4Ue", "74o", "My", "0nK", "0aJ", "Bx", "6oG", "4Zd", "4tH", "6Ak", "lT", "y5", "0BV", "ad", "580", "4yx", "4WT", "4B5", "OH", "0lz", "4kP", "7NR", "7D", "244", "1ns", "0k3", "64W", "con", "4HL", "69g", "PP", "E1", "1Mo", "2hm", "6SC", "52I", "4ia", "7Lc", "5u", "0RO", "J0", "3Ya", "66f", "4GM", "b4F", "aUL", "Ra", "0qS", "8Vg", "8E", "458", "4dQ", "4o2", "4zS", "8He", "bO", "Lc", "0oQ", "605", "75u", "6ll", "4YO", "T2", "AS", "2yn", "0LM", "43k", "7Ra", "6MA", "4xb", "0CL", "22g", "NR", "19I", "4VN", "6cm", "aDO", "baM", "14y", "Cb", "mN", "8Gd", "41Z", "7PP", "axO", "53W", "1Lq", "0I1", "QN", "06t", "4IR", "68y", "65I", "4Db", "1om", "2Jo", "6Z", "g3", "4jN", "7OL", "6Pl", "4eO", "h2", "2kB", "2En", "04E", "4Kc", "7na", "4S2", "4FS", "09u", "d6e", "4k", "0SQ", "bRL", "a3F", "42u", "575", "na", "0MS", "17V", "dlo", "4M0", "4XQ", "4Ua", "74k", "3KM", "0nO", "28", "cQ", "6Nn", "5kl", "40D", "6Ao", "lP", "y1", "0aN", "2Tm", "6oC", "5JA", "4WP", "4B1", "OL", "18W", "0BR", "0W3", "584", "bCO", "1nw", "0k7", "64S", "4Ex", "4kT", "7NV", "sH", "0Pz", "1Mk", "2hi", "6SG", "4fd", "4HH", "69c", "PT", "E5", "J4", "2ID", "66b", "4GI", "4ie", "7Lg", "5q", "0RK", "1OZ", "8A", "4q4", "4dU", "4Jy", "5O9", "Re", "0qW", "Lg", "0oU", "5DZ", "6aX", "4o6", "4zW", "0Ay", "bK", "2yj", "0LI", "43o", "6BD", "6lh", "4YK", "T6", "AW", "NV", "0md", "4VJ", "6ci", "6ME", "4xf", "0CH", "22c", "mJ", "0Nx", "4uV", "7PT", "6nY", "baI", "1pu", "Cf", "6V", "0Ql", "4jB", "aus", "65E", "4Dn", "1oa", "2Jc", "QB", "06x", "b7e", "68u", "5b2", "4gr", "8UD", "dSL", "4g", "8ZE", "4hs", "5m3", "67t", "724", "09y", "1x1", "Ss", "04I", "4Ko", "7nm", "azr", "4eC", "1NL", "9W", "24", "21D", "6Nb", "bAr", "4Um", "74g", "Mq", "0nC", "0cs", "1f3", "79W", "bbn", "42y", "579", "nm", "394", "365", "al", "588", "4yp", "bmo", "76V", "1i2", "0lr", "0aB", "Bp", "6oO", "4Zl", "40H", "6Ac", "2zM", "0On", "4HD", "69o", "PX", "E9", "1Mg", "2he", "6SK", "4fh", "4kX", "7NZ", "7L", "0Pv", "3N9", "2Ky", "6pW", "4Et", "4Ju", "5O5", "Ri", "05S", "1OV", "8M", "450", "4dY", "4ii", "7Lk", "qu", "0RG", "J8", "2IH", "66n", "4GE", "6ld", "4YG", "0bi", "2WJ", "ow", "0LE", "43c", "6BH", "6Ox", "5jz", "0Au", "bG", "Lk", "0oY", "4Tw", "5Q7", "6nU", "5KW", "14q", "Cj", "mF", "0Nt", "41R", "7PX", "6MI", "4xj", "0CD", "22o", "NZ", "0mh", "4VF", "6ce", "65A", "4Dj", "1oe", "2Jg", "6R", "0Qh", "4jF", "7OD", "5b6", "4gv", "1Ly", "0I9", "QF", "0rt", "4IZ", "68q", "67p", "5Vz", "1mT", "1x5", "4c", "0SY", "4hw", "5m7", "6Pd", "4eG", "1NH", "9S", "Sw", "04M", "4Kk", "7ni", "4Ui", "74c", "Mu", "0nG", "20", "cY", "6Nf", "5kd", "4vu", "5s5", "ni", "390", "0cw", "1f7", "4M8", "4XY", "4WX", "4B9", "OD", "0lv", "0BZ", "ah", "6LW", "4yt", "40L", "6Ag", "lX", "y9", "0aF", "Bt", "6oK", "4Zh", "1Mc", "2ha", "6SO", "4fl", "5Xa", "69k", "2FM", "07f", "83N", "d5F", "6pS", "4Ep", "bQo", "a0e", "7H", "0Pr", "1OR", "8I", "454", "50t", "4Jq", "5O1", "Rm", "05W", "08g", "2IL", "66j", "4GA", "4im", "7Lo", "5y", "0RC", "os", "0LA", "43g", "6BL", "78I", "4YC", "0bm", "2WN", "Lo", "8fE", "4Ts", "5Q3", "aen", "cPM", "0Aq", "bC", "mB", "0Np", "41V", "ajo", "6nQ", "5KS", "14u", "Cn", "2XO", "0ml", "4VB", "6ca", "6MM", "4xn", "1Sa", "22k", "Sj", "04P", "4Kv", "5N6", "443", "4eZ", "1NU", "9N", "pv", "0SD", "4hj", "7Mh", "67m", "4FF", "1mI", "2HK", "2GJ", "2", "4IG", "68l", "6RH", "4gk", "1Ld", "2if", "6O", "0Qu", "5zz", "7OY", "5A7", "4Dw", "1ox", "0j8", "15r", "Bi", "6oV", "4Zu", "40Q", "4a8", "lE", "0Ow", "0BG", "au", "6LJ", "4yi", "4WE", "6bf", "OY", "Z8", "U9", "2VI", "6mg", "4XD", "4vh", "6CK", "nt", "0MF", "1PW", "cD", "4n9", "5ky", "4Ut", "5P4", "Mh", "0nZ", "4ip", "5l0", "5d", "9Kg", "08z", "1y2", "66w", "737", "4Jl", "7on", "Rp", "05J", "1OO", "8T", "6Qc", "50i", "4kA", "7NC", "7U", "0Po", "1nb", "dqS", "64F", "4Em", "b6f", "69v", "PA", "0ss", "8TG", "dRO", "5c1", "4fq", "6MP", "4xs", "376", "22v", "NC", "0mq", "bll", "77U", "6nL", "5KN", "14h", "Cs", "3ko", "0Nm", "41K", "7PA", "6Oa", "4zB", "37", "20G", "Lr", "8fX", "4Tn", "6aM", "78T", "bcm", "0bp", "AB", "on", "387", "43z", "5r2", "447", "51w", "1NQ", "9J", "Sn", "04T", "4Kr", "5N2", "67i", "4FB", "09d", "2HO", "4z", "1Ca", "4hn", "7Ml", "6RL", "4go", "8UY", "2ib", "2GN", "6", "4IC", "68h", "5A3", "4Ds", "82M", "d4E", "6K", "0Qq", "bPl", "a1f", "40U", "akl", "lA", "0Os", "15v", "Bm", "6oR", "4Zq", "4WA", "6bb", "2YL", "0lo", "0BC", "aq", "6LN", "4ym", "42d", "6CO", "np", "0MB", "0cn", "2VM", "6mc", "5Ha", "4Up", "5P0", "Ml", "8gF", "1PS", "1E2", "adm", "bAo", "1lW", "1y6", "4R9", "4GX", "4it", "5l4", "qh", "0RZ", "i9", "8P", "6Qg", "4dD", "4Jh", "7oj", "Rt", "05N", "1nf", "2Kd", "64B", "4Ei", "4kE", "7NG", "7Q", "f8", "1Mz", "2hx", "5c5", "4fu", "4HY", "69r", "PE", "0sw", "NG", "0mu", "5Fz", "6cx", "6MT", "4xw", "0CY", "0V8", "3kk", "0Ni", "41O", "7PE", "6nH", "5KJ", "14l", "Cw", "Lv", "0oD", "4Tj", "6aI", "6Oe", "4zF", "33", "bZ", "oj", "0LX", "4wv", "5r6", "6ly", "4YZ", "0bt", "AF", "4v", "0SL", "4hb", "awS", "67e", "4FN", "K3", "2HC", "Sb", "04X", "b5E", "aTO", "4p3", "4eR", "8Wd", "9F", "6G", "257", "4jS", "7OQ", "65T", "cnm", "1op", "0j0", "QS", "D2", "4IO", "68d", "7Ba", "4gc", "1Ll", "2in", "0BO", "23d", "6LB", "4ya", "4WM", "6bn", "OQ", "Z0", "0aS", "Ba", "aEL", "c4g", "40Y", "4a0", "lM", "8Fg", "8If", "cL", "4n1", "5kq", "616", "74v", "3KP", "0nR", "U1", "2VA", "6mo", "4XL", "42h", "6CC", "2xm", "0MN", "4Jd", "7of", "Rx", "05B", "i5", "2jE", "6Qk", "4dH", "4ix", "5l8", "5l", "0RV", "08r", "2IY", "4R5", "4GT", "4HU", "7mW", "PI", "07s", "1Mv", "0H6", "5c9", "4fy", "4kI", "7NK", "sU", "f4", "1nj", "2Kh", "64N", "4Ee", "6nD", "5KF", "1ph", "2Uj", "mW", "x6", "41C", "7PI", "593", "5hZ", "0CU", "0V4", "NK", "0my", "4VW", "4C6", "4L7", "4YV", "0bx", "AJ", "of", "0LT", "43r", "562", "6Oi", "4zJ", "w7", "bV", "Lz", "0oH", "4Tf", "6aE", "67a", "4FJ", "K7", "2HG", "4r", "0SH", "4hf", "7Md", "4p7", "4eV", "1NY", "9B", "Sf", "0pT", "4Kz", "7nx", "65P", "5TZ", "1ot", "0j4", "6C", "0Qy", "4jW", "7OU", "6RD", "4gg", "1Lh", "2ij", "QW", "D6", "4IK", "7lI", "4WI", "6bj", "OU", "Z4", "0BK", "ay", "6LF", "4ye", "4tU", "4a4", "lI", "2o9", "0aW", "Be", "6oZ", "4Zy", "4Ux", "5P8", "Md", "0nV", "8Ib", "cH", "4n5", "5ku", "42l", "6CG", "nx", "0MJ", "U5", "2VE", "6mk", "4XH", "i1", "8X", "6Qo", "4dL", "5ZA", "7ob", "2Dm", "05F", "08v", "d7f", "4R1", "4GP", "bSO", "a2E", "5h", "0RR", "1Mr", "0H2", "ayL", "52T", "4HQ", "69z", "PM", "07w", "1nn", "2Kl", "64J", "4Ea", "4kM", "7NO", "7Y", "f0", "mS", "x2", "41G", "7PM", "aDR", "5KB", "14d", "2Un", "NO", "19T", "4VS", "4C2", "597", "bBL", "0CQ", "0V0", "ob", "0LP", "43v", "566", "4L3", "4YR", "16U", "AN", "2Zo", "0oL", "4Tb", "6aA", "6Om", "4zN", "w3", "bR", "4oT", "4z5", "wH", "0Tz", "0zV", "Yd", "5D8", "4Ax", "4LH", "6yk", "TT", "A5", "0YJ", "zx", "6WG", "4bd", "4me", "6XF", "1q", "0VK", "N4", "2MD", "62b", "4CI", "4Ny", "5K9", "Ve", "0uW", "1KZ", "xI", "4u4", "5pt", "4k6", "5nv", "0Ey", "fK", "Hg", "0kU", "641", "6eX", "6hh", "5Mj", "P6", "EW", "29b", "0HI", "47o", "6FD", "6IE", "48n", "0GH", "dz", "JV", "0id", "4RJ", "6gi", "6jY", "beI", "0dT", "Gf", "iJ", "0Jx", "4qV", "4d7", "UN", "02t", "4MR", "4X3", "a8G", "57W", "0XP", "0M1", "2Z", "c3", "4nN", "7KL", "61I", "5PC", "1km", "2No", "2An", "00E", "4Oc", "7ja", "6Tl", "4aO", "l2", "yS", "0k", "0WQ", "58V", "a7F", "4W2", "4BS", "84m", "ZO", "13V", "E", "4I0", "5Lp", "46u", "535", "ja", "0IS", "68", "gQ", "6Jn", "5ol", "4Qa", "6dB", "3OM", "0jO", "0eN", "2Pm", "6kC", "5NA", "44D", "6Eo", "hP", "99", "0FR", "0S3", "abM", "49t", "4SP", "4F1", "KL", "8af", "0zR", "0o3", "60W", "ckn", "4oP", "4z1", "3D", "204", "0YN", "2lm", "6WC", "56I", "4LL", "6yo", "TP", "A1", "N0", "903", "62f", "4CM", "4ma", "6XB", "1u", "0VO", "8Rg", "xM", "418", "54x", "b0F", "aQL", "Va", "0uS", "Hc", "0kQ", "645", "71u", "4k2", "5nr", "8Le", "fO", "29f", "0HM", "47k", "7Va", "6hl", "5Mn", "P2", "ES", "JR", "1yA", "4RN", "6gm", "6IA", "48j", "0GL", "26g", "iN", "8Cd", "45Z", "4d3", "hYv", "beM", "0dP", "Gb", "6VY", "4cz", "0XT", "0M5", "UJ", "02p", "4MV", "4X7", "61M", "5PG", "1ki", "Xz", "vV", "c7", "4nJ", "7KH", "6Th", "4aK", "l6", "yW", "2Aj", "00A", "4Og", "6zD", "4W6", "4BW", "0yy", "ZK", "0o", "0WU", "58R", "6YX", "46q", "531", "je", "0IW", "13R", "A", "4I4", "5Lt", "4Qe", "6dF", "Iy", "0jK", "r4", "gU", "6Jj", "5oh", "4pH", "6Ek", "hT", "0Kf", "0eJ", "Fx", "6kG", "5NE", "4ST", "4F5", "KH", "0hz", "0FV", "ed", "5x8", "49p", "bvs", "6yc", "2BM", "03f", "0YB", "zp", "6WO", "4bl", "bUo", "a4e", "3H", "0Tr", "87N", "Yl", "5D0", "4Ap", "4Nq", "5K1", "Vm", "01W", "1KR", "xA", "414", "54t", "4mm", "6XN", "1y", "0VC", "0xo", "2ML", "62j", "4CA", "7xA", "5Mb", "0fm", "2SN", "ks", "0HA", "47g", "6FL", "aan", "bDl", "0Eq", "fC", "Ho", "8bE", "4Ps", "5U3", "5Z2", "5OS", "10u", "Gn", "iB", "0Jp", "45V", "ano", "6IM", "48f", "1Wa", "dr", "3Ln", "0il", "4RB", "6ga", "2R", "0Uh", "4nF", "7KD", "61A", "5PK", "1ke", "Xv", "UF", "0vt", "4MZ", "6xy", "5f6", "4cv", "0XX", "0M9", "0c", "0WY", "4lw", "5i7", "63p", "5Rz", "0yu", "ZG", "Ww", "00M", "4Ok", "6zH", "6Td", "4aG", "0Zi", "2oJ", "60", "gY", "6Jf", "5od", "4Qi", "6dJ", "Iu", "0jG", "0gw", "M", "4I8", "5Lx", "4ru", "5w5", "ji", "1Yz", "0FZ", "eh", "5x4", "5mU", "4SX", "4F9", "KD", "0hv", "0eF", "Ft", "6kK", "5NI", "44L", "6Eg", "hX", "91", "0YF", "zt", "6WK", "4bh", "4LD", "6yg", "TX", "A9", "0zZ", "Yh", "5D4", "4At", "4oX", "4z9", "3L", "0Tv", "1KV", "xE", "410", "54p", "4Nu", "5K5", "Vi", "01S", "N8", "2MH", "62n", "4CE", "4mi", "6XJ", "uu", "0VG", "kw", "0HE", "47c", "6FH", "6hd", "5Mf", "0fi", "2SJ", "Hk", "0kY", "4Pw", "5U7", "6Kx", "5nz", "0Eu", "fG", "iF", "0Jt", "45R", "6Dy", "5Z6", "5OW", "0dX", "Gj", "JZ", "0ih", "4RF", "6ge", "6II", "48b", "0GD", "dv", "61E", "5PO", "1ka", "Xr", "2V", "0Ul", "4nB", "aqs", "5f2", "4cr", "8QD", "39V", "UB", "02x", "795", "aRo", "63t", "764", "0yq", "ZC", "0g", "9Nd", "4ls", "5i3", "7DA", "4aC", "0Zm", "2oN", "Ws", "00I", "4Oo", "6zL", "4Qm", "6dN", "Iq", "0jC", "64", "25D", "6Jb", "bEr", "46y", "539", "jm", "9Pf", "0gs", "I", "aCl", "bfn", "bio", "72V", "1m2", "0hr", "325", "el", "5x0", "49x", "44H", "6Ec", "3nl", "95", "0eB", "Fp", "6kO", "5NM", "4mt", "5h4", "uh", "0VZ", "0xv", "2MU", "4V9", "4CX", "4Nh", "7kj", "Vt", "01N", "m9", "xX", "6Ug", "54m", "4oE", "6Zf", "3Q", "b8", "0zG", "Yu", "60B", "4Ai", "4LY", "4Y8", "TE", "0ww", "1Iz", "zi", "5g5", "4bu", "5y7", "5lV", "0GY", "dk", "JG", "0iu", "5Bz", "6gx", "6jH", "5OJ", "0dE", "Gw", "3ok", "82", "45O", "6Dd", "6Ke", "5ng", "73", "fZ", "Hv", "0kD", "4Pj", "6eI", "6hy", "7m9", "0ft", "EF", "kj", "0HX", "4sv", "5v6", "Wn", "00T", "4Or", "5J2", "407", "55w", "0Zp", "yB", "0z", "1Ga", "4ln", "6YM", "63i", "4BB", "0yl", "2LO", "2CN", "02e", "4MC", "7hA", "6VL", "4co", "0XA", "2mb", "2K", "0Uq", "bTl", "a5f", "5E3", "5PR", "86M", "Xo", "11v", "Fm", "6kR", "5NP", "44U", "aol", "hA", "0Ks", "0FC", "eq", "6HN", "49e", "4SA", "6fb", "3Mm", "0ho", "0gn", "T", "6ic", "5La", "46d", "6GO", "jp", "0IB", "0Dr", "1A2", "hyT", "bEo", "4Qp", "5T0", "Il", "8cF", "0xr", "2MQ", "62w", "777", "4mp", "5h0", "1d", "9Og", "1KO", "2nM", "6Uc", "54i", "4Nl", "7kn", "Vp", "01J", "0zC", "Yq", "60F", "4Am", "4oA", "6Zb", "3U", "0To", "8PG", "zm", "5g1", "4bq", "786", "aSl", "TA", "0ws", "JC", "0iq", "bhl", "73U", "5y3", "5lR", "336", "do", "3oo", "86", "45K", "7TA", "6jL", "5ON", "0dA", "Gs", "Hr", "8bX", "4Pn", "6eM", "6Ka", "5nc", "77", "24G", "kn", "8AD", "47z", "5v2", "aBo", "bgm", "0fp", "EB", "403", "4aZ", "0Zt", "yF", "Wj", "00P", "4Ov", "5J6", "63m", "4BF", "0yh", "ZZ", "tv", "0WD", "4lj", "6YI", "6VH", "4ck", "0XE", "2mf", "2CJ", "02a", "4MG", "6xd", "5E7", "5PV", "1kx", "Xk", "2O", "0Uu", "bTh", "7KY", "44Q", "4e8", "hE", "0Kw", "11r", "Fi", "6kV", "5NT", "4SE", "6ff", "KY", "0hk", "0FG", "eu", "6HJ", "49a", "4rh", "6GK", "jt", "0IF", "Q9", "P", "6ig", "5Le", "4Qt", "5T4", "Ih", "0jZ", "0Dv", "gD", "4j9", "5oy", "aD0", "7kb", "3PL", "01F", "m1", "xP", "6Uo", "54e", "59U", "a6E", "1h", "0VR", "85n", "196", "4V1", "4CP", "4LQ", "4Y0", "TM", "03w", "0YS", "za", "a9D", "56T", "4oM", "6Zn", "3Y", "b0", "0zO", "2Ol", "60J", "4Aa", "7za", "5OB", "0dM", "2Qn", "iS", "0Ja", "45G", "6Dl", "acN", "48w", "0GQ", "dc", "JO", "94L", "4RS", "4G2", "4H3", "5Ms", "12U", "EN", "kb", "0HP", "47v", "526", "6Km", "5no", "s3", "fR", "3NN", "0kL", "4Pb", "6eA", "0r", "0WH", "4lf", "6YE", "63a", "4BJ", "O7", "ZV", "Wf", "0tT", "4Oz", "6zY", "4t7", "4aV", "0Zx", "yJ", "2C", "0Uy", "4nW", "7KU", "61P", "5PZ", "1kt", "Xg", "UW", "02m", "4MK", "6xh", "6VD", "4cg", "0XI", "2mj", "0FK", "ey", "6HF", "49m", "4SI", "6fj", "KU", "0hg", "0eW", "Fe", "6kZ", "5NX", "4pU", "4e4", "hI", "2k9", "0Dz", "gH", "4j5", "5ou", "4Qx", "5T8", "Id", "0jV", "Q5", "DT", "6ik", "5Li", "46l", "6GG", "jx", "0IJ", "m5", "xT", "6Uk", "54a", "4Nd", "7kf", "Vx", "01B", "0xz", "192", "4V5", "4CT", "4mx", "5h8", "1l", "0VV", "0YW", "ze", "5g9", "4by", "4LU", "4Y4", "TI", "03s", "0zK", "Yy", "60N", "4Ae", "4oI", "6Zj", "wU", "b4", "iW", "0Je", "45C", "6Dh", "6jD", "5OF", "0dI", "2Qj", "JK", "0iy", "4RW", "4G6", "6IX", "48s", "0GU", "dg", "kf", "0HT", "47r", "522", "4H7", "5Mw", "0fx", "EJ", "Hz", "0kH", "4Pf", "6eE", "6Ki", "5nk", "s7", "fV", "63e", "4BN", "O3", "ZR", "0v", "0WL", "4lb", "6YA", "4t3", "4aR", "8Sd", "yN", "Wb", "00X", "b1E", "aPO", "61T", "bzL", "1kp", "Xc", "2G", "217", "4nS", "7KQ", "7Fa", "4cc", "0XM", "2mn", "US", "02i", "4MO", "6xl", "4SM", "6fn", "KQ", "0hc", "0FO", "27d", "6HB", "49i", "44Y", "4e0", "hM", "8Bg", "0eS", "Fa", "aAL", "bdN", "656", "70v", "3OP", "0jR", "8Mf", "gL", "4j1", "5oq", "46h", "6GC", "28e", "0IN", "Q1", "X", "6io", "5Lm", "6KV", "5nT", "1Uz", "fi", "HE", "0kw", "4PY", "4E8", "6hJ", "5MH", "0fG", "Eu", "kY", "0Hk", "47M", "6Ff", "6Ig", "48L", "51", "dX", "Jt", "0iF", "4Rh", "6gK", "4J9", "5Oy", "0dv", "GD", "ih", "0JZ", "4qt", "5t4", "4ov", "5j6", "3b", "0TX", "0zt", "YF", "60q", "4AZ", "4Lj", "6yI", "Tv", "03L", "0Yh", "zZ", "6We", "4bF", "4mG", "6Xd", "1S", "0Vi", "0xE", "2Mf", "6vH", "4Ck", "bth", "7kY", "VG", "0uu", "1Kx", "xk", "5e7", "5pV", "13t", "g", "5Y3", "5LR", "46W", "amn", "jC", "0Iq", "0DA", "gs", "6JL", "5oN", "4QC", "70I", "3Oo", "0jm", "0el", "2PO", "6ka", "5Nc", "44f", "6EM", "hr", "8BX", "0Fp", "eB", "abo", "49V", "4Sr", "5V2", "Kn", "8aD", "Ul", "02V", "4Mp", "5H0", "425", "57u", "0Xr", "2mQ", "2x", "0UB", "4nl", "7Kn", "61k", "5Pa", "1kO", "2NM", "2AL", "00g", "4OA", "6zb", "6TN", "4am", "0ZC", "yq", "0I", "0Ws", "58t", "a7d", "5G1", "4Bq", "84O", "Zm", "HA", "0ks", "bjn", "71W", "6KR", "5nP", "314", "fm", "29D", "0Ho", "47I", "6Fb", "6hN", "5ML", "0fC", "Eq", "Jp", "0iB", "4Rl", "6gO", "6Ic", "48H", "55", "26E", "il", "8CF", "45x", "508", "hYT", "beo", "0dr", "1a2", "0zp", "YB", "60u", "755", "4or", "5j2", "3f", "9Me", "0Yl", "2lO", "6Wa", "4bB", "4Ln", "6yM", "Tr", "03H", "0xA", "2Mb", "62D", "4Co", "4mC", "7HA", "1W", "0Vm", "8RE", "xo", "5e3", "54Z", "b0d", "aQn", "VC", "01y", "46S", "6Gx", "jG", "0Iu", "0gY", "c", "5Y7", "5LV", "4QG", "6dd", "3Ok", "0ji", "0DE", "gw", "6JH", "5oJ", "44b", "6EI", "hv", "0KD", "0eh", "FZ", "6ke", "5Ng", "4Sv", "5V6", "Kj", "0hX", "0Ft", "eF", "6Hy", "49R", "421", "4cX", "0Xv", "2mU", "Uh", "02R", "4Mt", "5H4", "61o", "5Pe", "M9", "XX", "vt", "0UF", "4nh", "7Kj", "6TJ", "4ai", "0ZG", "yu", "WY", "B8", "4OE", "6zf", "5G5", "4Bu", "1iz", "Zi", "0M", "0Ww", "4lY", "4y8", "6hB", "aW1", "0fO", "2Sl", "kQ", "0Hc", "47E", "6Fn", "aaL", "bDN", "0ES", "fa", "HM", "8bg", "4PQ", "4E0", "4J1", "5Oq", "10W", "GL", "3oP", "0JR", "45t", "504", "6Io", "48D", "59", "dP", "3LL", "0iN", "5BA", "6gC", "4Lb", "6yA", "2Bo", "03D", "o3", "zR", "6Wm", "4bN", "bUM", "a4G", "3j", "0TP", "87l", "YN", "4T3", "4AR", "4NS", "7kQ", "VO", "01u", "1Kp", "xc", "hfw", "54V", "4mO", "6Xl", "uS", "0Va", "0xM", "2Mn", "62H", "4Cc", "0DI", "25b", "6JD", "5oF", "4QK", "6dh", "IW", "0je", "0gU", "o", "6iX", "5LZ", "4rW", "4g6", "jK", "0Iy", "0Fx", "eJ", "4h7", "5mw", "4Sz", "6fY", "Kf", "0hT", "S7", "FV", "6ki", "5Nk", "44n", "6EE", "hz", "0KH", "2p", "0UJ", "4nd", "7Kf", "61c", "5Pi", "M5", "XT", "Ud", "0vV", "4Mx", "5H8", "4v5", "4cT", "0Xz", "2mY", "0A", "1GZ", "4lU", "4y4", "5G9", "4By", "0yW", "Ze", "WU", "B4", "4OI", "6zj", "6TF", "4ae", "0ZK", "yy", "kU", "0Hg", "47A", "6Fj", "6hF", "5MD", "0fK", "Ey", "HI", "2K9", "4PU", "4E4", "6KZ", "5nX", "0EW", "fe", "id", "0JV", "45p", "500", "4J5", "5Ou", "0dz", "GH", "Jx", "0iJ", "4Rd", "6gG", "6Ik", "5li", "q5", "dT", "o7", "zV", "6Wi", "4bJ", "4Lf", "6yE", "Tz", "0wH", "0zx", "YJ", "4T7", "4AV", "4oz", "6ZY", "3n", "0TT", "1Kt", "xg", "6UX", "54R", "4NW", "7kU", "VK", "01q", "0xI", "2Mj", "62L", "4Cg", "4mK", "6Xh", "uW", "0Ve", "4QO", "6dl", "IS", "0ja", "0DM", "25f", "7Za", "5oB", "4rS", "4g2", "jO", "9PD", "0gQ", "k", "aCN", "685", "674", "72t", "Kb", "0hP", "8Od", "eN", "4h3", "49Z", "44j", "6EA", "3nN", "0KL", "S3", "FR", "6km", "5No", "61g", "5Pm", "M1", "XP", "2t", "0UN", "ad0", "7Kb", "429", "4cP", "8Qf", "39t", "0c3", "02Z", "b3G", "aRM", "63V", "bxN", "0yS", "Za", "0E", "235", "4lQ", "4y0", "6TB", "4aa", "0ZO", "2ol", "WQ", "B0", "4OM", "6zn", "4i4", "5lt", "1WZ", "dI", "Je", "0iW", "4Ry", "5W9", "6jj", "5Oh", "R4", "GU", "iy", "0JK", "45m", "6DF", "6KG", "5nE", "0EJ", "fx", "HT", "0kf", "4PH", "6ek", "5X8", "5MY", "0fV", "Ed", "kH", "0Hz", "4sT", "4f5", "4mV", "4x7", "1B", "0Vx", "0xT", "0m5", "62Q", "4Cz", "4NJ", "7kH", "VV", "C7", "1Ki", "xz", "6UE", "54O", "4og", "6ZD", "3s", "0TI", "L6", "YW", "6th", "4AK", "6l9", "6yX", "Tg", "0wU", "0Yy", "zK", "4w6", "4bW", "11T", "FO", "4K2", "5Nr", "44w", "517", "hc", "0KQ", "p2", "eS", "6Hl", "49G", "4Sc", "72i", "3MO", "0hM", "0gL", "v", "6iA", "5LC", "46F", "6Gm", "jR", "1YA", "0DP", "gb", "hyv", "bEM", "4QR", "4D3", "IN", "8cd", "WL", "00v", "4OP", "4Z1", "hgt", "55U", "0ZR", "0O3", "0X", "a1", "4lL", "6Yo", "63K", "5RA", "0yN", "2Lm", "2Cl", "02G", "4Ma", "6xB", "6Vn", "4cM", "n0", "39i", "2i", "0US", "bTN", "a5D", "4U0", "5Pp", "86o", "XM", "Ja", "0iS", "667", "73w", "4i0", "48Y", "8Ng", "dM", "3oM", "0JO", "45i", "6DB", "6jn", "5Ol", "R0", "GQ", "HP", "0kb", "4PL", "6eo", "6KC", "5nA", "0EN", "24e", "kL", "8Af", "47X", "4f1", "aBM", "696", "0fR", "0s3", "0xP", "0m1", "62U", "byM", "4mR", "4x3", "1F", "226", "1Km", "2no", "6UA", "54K", "4NN", "7kL", "VR", "C3", "L2", "YS", "60d", "4AO", "4oc", "7Ja", "3w", "0TM", "8Pe", "zO", "4w2", "4bS", "b2D", "aSN", "Tc", "03Y", "44s", "513", "hg", "0KU", "0ey", "FK", "4K6", "5Nv", "4Sg", "6fD", "3MK", "0hI", "p6", "eW", "6Hh", "49C", "46B", "6Gi", "jV", "0Id", "0gH", "r", "6iE", "5LG", "4QV", "4D7", "IJ", "0jx", "0DT", "gf", "6JY", "bEI", "5d8", "4ax", "0ZV", "yd", "WH", "00r", "4OT", "4Z5", "63O", "4Bd", "0yJ", "Zx", "tT", "a5", "4lH", "6Yk", "6Vj", "4cI", "n4", "2mD", "Uy", "02C", "4Me", "6xF", "4U4", "5Pt", "1kZ", "XI", "2m", "0UW", "4ny", "5k9", "6jb", "ber", "0do", "2QL", "iq", "0JC", "45e", "6DN", "acl", "48U", "0Gs", "dA", "Jm", "94n", "4Rq", "5W1", "5X0", "5MQ", "12w", "El", "1M2", "0Hr", "47T", "alm", "6KO", "5nM", "0EB", "fp", "3Nl", "0kn", "bjs", "6ec", "4NB", "aQs", "3Pn", "01d", "1Ka", "xr", "6UM", "54G", "59w", "a6g", "1J", "0Vp", "85L", "d3D", "5F2", "4Cr", "4Ls", "5I3", "To", "03U", "0Yq", "zC", "436", "56v", "4oo", "6ZL", "ws", "0TA", "0zm", "2ON", "60h", "4AC", "42", "27B", "6Hd", "49O", "4Sk", "6fH", "Kw", "0hE", "0eu", "FG", "6kx", "5Nz", "4pw", "5u7", "hk", "0KY", "0DX", "gj", "5z6", "5oW", "4QZ", "6dy", "IF", "0jt", "0gD", "Dv", "6iI", "5LK", "46N", "6Ge", "jZ", "0Ih", "0P", "a9", "4lD", "6Yg", "63C", "4Bh", "0yF", "Zt", "WD", "0tv", "4OX", "4Z9", "5d4", "4at", "0ZZ", "yh", "2a", "1Ez", "4nu", "5k5", "4U8", "5Px", "1kV", "XE", "Uu", "02O", "4Mi", "6xJ", "6Vf", "4cE", "n8", "2mH", "iu", "0JG", "45a", "6DJ", "6jf", "5Od", "R8", "GY", "Ji", "1yz", "4Ru", "5W5", "4i8", "48Q", "0Gw", "dE", "kD", "0Hv", "47P", "4f9", "5X4", "5MU", "0fZ", "Eh", "HX", "0kj", "4PD", "6eg", "6KK", "5nI", "0EF", "ft", "1Ke", "xv", "6UI", "54C", "4NF", "7kD", "VZ", "0uh", "0xX", "0m9", "5F6", "4Cv", "4mZ", "6Xy", "1N", "0Vt", "0Yu", "zG", "432", "56r", "4Lw", "5I7", "Tk", "03Q", "0zi", "2OJ", "60l", "4AG", "4ok", "6ZH", "ww", "0TE", "4So", "6fL", "Ks", "0hA", "46", "27F", "7XA", "49K", "4ps", "5u3", "ho", "8BE", "0eq", "FC", "aAn", "bdl", "bkm", "70T", "IB", "0jp", "307", "gn", "5z2", "5oS", "46J", "6Ga", "28G", "0Il", "13i", "z", "6iM", "5LO", "63G", "4Bl", "0yB", "Zp", "0T", "0Wn", "58i", "6Yc", "5d0", "4ap", "8SF", "yl", "1q2", "00z", "b1g", "aPm", "61v", "746", "1kR", "XA", "2e", "9Lf", "4nq", "5k1", "6Vb", "4cA", "0Xo", "2mL", "Uq", "02K", "4Mm", "6xN", "8YG", "7e", "5n1", "4kq", "716", "64v", "2KP", "1nR", "07K", "Pq", "69F", "4Hm", "4fA", "6Sb", "2hL", "1MN", "0Rn", "5T", "7LB", "5ya", "4Gl", "66G", "2Ia", "08J", "05z", "1t2", "aUm", "b4g", "4dp", "5a0", "8d", "8VF", "bn", "357", "4zr", "6OQ", "75T", "bnm", "0op", "LB", "Ar", "16i", "4Yn", "6lM", "6Ba", "43J", "0Ll", "2yO", "22F", "16", "4xC", "agr", "6cL", "4Vo", "0mA", "Ns", "CC", "14X", "bal", "aDn", "5p3", "4us", "8GE", "mo", "5L7", "4Iw", "06Q", "Qk", "1Y5", "1LT", "53r", "462", "7Oi", "4jk", "0QE", "rw", "2JJ", "1oH", "4DG", "65l", "7nD", "4KF", "0ph", "SZ", "2kg", "1Ne", "4ej", "6PI", "493", "4hZ", "0St", "4N", "0h9", "09P", "4Fv", "5C6", "4Xt", "6mW", "023", "0cZ", "0Mv", "nD", "4c9", "42P", "5kI", "6NK", "ct", "1Pg", "X9", "MX", "74N", "4UD", "4ZE", "6of", "BY", "W8", "0OG", "lu", "6AJ", "40a", "4yY", "4l8", "aE", "0Bw", "18r", "Oi", "5R5", "4Wu", "4EY", "4P8", "2KT", "1nV", "8YC", "7a", "5n5", "4ku", "4fE", "6Sf", "2hH", "k8", "07O", "Pu", "69B", "4Hi", "4Gh", "66C", "2Ie", "08N", "d9", "5P", "7LF", "4iD", "4dt", "5a4", "2jy", "3o9", "0qv", "RD", "7oZ", "4JX", "6ay", "4TZ", "0ot", "LF", "bj", "0AX", "4zv", "6OU", "6Be", "43N", "0Lh", "oZ", "Av", "0bD", "4Yj", "6lI", "6cH", "4Vk", "0mE", "Nw", "22B", "12", "4xG", "6Md", "5p7", "4uw", "0NY", "mk", "CG", "1pT", "5Kz", "6nx", "1Y1", "1LP", "53v", "466", "5L3", "4Is", "06U", "Qo", "2JN", "1oL", "4DC", "65h", "7Om", "4jo", "0QA", "rs", "9z", "1Na", "4en", "6PM", "aTs", "4KB", "04d", "2EO", "d6D", "09T", "4Fr", "5C2", "497", "bRm", "0Sp", "4J", "0Mr", "1H2", "aim", "42T", "4Xp", "6mS", "027", "17w", "0nn", "3Kl", "74J", "5Ea", "5kM", "6NO", "cp", "1Pc", "0OC", "lq", "6AN", "40e", "4ZA", "6ob", "2TL", "0ao", "18v", "Om", "5R1", "4Wq", "bCn", "afl", "aA", "0Bs", "07C", "Py", "69N", "4He", "4fI", "6Sj", "2hD", "k4", "0PW", "7m", "5n9", "4ky", "4EU", "4P4", "2KX", "1nZ", "05r", "RH", "7oV", "4JT", "4dx", "5a8", "8l", "1Ow", "d5", "qT", "7LJ", "4iH", "4Gd", "66O", "2Ii", "08B", "Az", "0bH", "4Yf", "6lE", "6Bi", "43B", "z7", "oV", "bf", "0AT", "4zz", "6OY", "4A7", "4TV", "0ox", "LJ", "CK", "14P", "5Kv", "4N6", "543", "41s", "0NU", "mg", "22N", "u6", "4xK", "6Mh", "6cD", "4Vg", "0mI", "2Xj", "7Oa", "4jc", "0QM", "6w", "2JB", "I2", "4DO", "65d", "68T", "b7D", "06Y", "Qc", "dSm", "287", "4gS", "4r2", "7MP", "4hR", "276", "4F", "0h1", "09X", "b8E", "67U", "7nL", "4KN", "F3", "SR", "9v", "1Nm", "4eb", "6PA", "5kA", "6NC", "21e", "1Po", "X1", "MP", "74F", "4UL", "bbO", "79v", "0v3", "0cR", "8Df", "nL", "4c1", "42X", "4yQ", "4l0", "aM", "8Kg", "0lS", "Oa", "76w", "637", "4ZM", "6on", "BQ", "W0", "0OO", "2zl", "6AB", "40i", "4fM", "6Sn", "3xa", "k0", "07G", "2Fl", "69J", "4Ha", "4EQ", "4P0", "d5g", "83o", "0PS", "7i", "a0D", "bQN", "50U", "hbt", "8h", "1Os", "05v", "RL", "7oR", "4JP", "5WA", "66K", "2Im", "08F", "d1", "5X", "7LN", "4iL", "6Bm", "43F", "z3", "oR", "2Wo", "0bL", "4Yb", "6lA", "4A3", "4TR", "8fd", "LN", "bb", "0AP", "cPl", "aeO", "547", "41w", "0NQ", "mc", "CO", "14T", "5Kr", "4N2", "77i", "4Vc", "0mM", "2Xn", "22J", "u2", "4xO", "6Ml", "2JF", "I6", "4DK", "6qh", "7Oe", "4jg", "0QI", "6s", "1Y9", "1LX", "4gW", "4r6", "68P", "5YZ", "0rU", "Qg", "0h5", "1mu", "4Fz", "67Q", "7MT", "4hV", "0Sx", "4B", "9r", "1Ni", "4ef", "6PE", "7nH", "4KJ", "F7", "SV", "X5", "MT", "74B", "4UH", "5kE", "6NG", "cx", "1Pk", "0Mz", "nH", "4c5", "4vT", "4Xx", "79r", "0v7", "0cV", "0lW", "Oe", "5R9", "4Wy", "4yU", "4l4", "aI", "1RZ", "0OK", "ly", "6AF", "40m", "4ZI", "6oj", "BU", "W4", "265", "5E", "488", "4iQ", "b9F", "66V", "0i2", "1lr", "G0", "RQ", "7oO", "4JM", "4da", "6QB", "8u", "1On", "0PN", "7t", "7Nb", "aa0", "4EL", "64g", "2KA", "H1", "07Z", "0f3", "69W", "b6G", "4fP", "479", "dRn", "294", "22W", "8Jd", "4xR", "4m3", "77t", "624", "0mP", "Nb", "CR", "V3", "5Ko", "6nm", "ajS", "41j", "0NL", "3kN", "20f", "0AM", "4zc", "aeR", "6al", "4TO", "Y2", "LS", "Ac", "0bQ", "bcL", "78u", "4b2", "4wS", "8Ee", "oO", "7nU", "4KW", "04q", "SK", "9o", "1Nt", "51R", "6PX", "7MI", "4hK", "e6", "pW", "2Hj", "09A", "4Fg", "67L", "68M", "4If", "0rH", "Qz", "2iG", "j7", "4gJ", "6Ri", "7Ox", "4jz", "0QT", "6n", "1z8", "1oY", "4DV", "4Q7", "4ZT", "4O5", "BH", "0az", "0OV", "ld", "550", "40p", "4yH", "6Lk", "aT", "t5", "0lJ", "Ox", "6bG", "4Wd", "4Xe", "6mF", "2Vh", "0cK", "0Mg", "nU", "6Cj", "42A", "5kX", "6NZ", "ce", "1Pv", "2N9", "MI", "7pW", "4UU", "4Gy", "5B9", "0i6", "1lv", "1BZ", "5A", "7LW", "4iU", "4de", "6QF", "8q", "1Oj", "G4", "RU", "7oK", "4JI", "4EH", "64c", "2KE", "H5", "0PJ", "7p", "7Nf", "4kd", "4fT", "4s5", "2hY", "290", "0sV", "Pd", "5M8", "4Hx", "6cY", "4Vz", "0mT", "Nf", "1F8", "0Cx", "4xV", "4m7", "7Pd", "41n", "0NH", "mz", "CV", "V7", "5Kk", "6ni", "6ah", "4TK", "Y6", "LW", "20b", "0AI", "4zg", "6OD", "4b6", "4wW", "0Ly", "oK", "Ag", "0bU", "5IZ", "6lX", "9k", "1Np", "51V", "azN", "7nQ", "4KS", "04u", "SO", "2Hn", "09E", "4Fc", "67H", "7MM", "4hO", "e2", "pS", "2iC", "j3", "4gN", "6Rm", "68I", "4Ib", "06D", "2Go", "d4d", "82l", "4DR", "4Q3", "a1G", "bPM", "0QP", "6j", "0OR", "0Z3", "554", "40t", "4ZP", "4O1", "BL", "15W", "0lN", "2Ym", "6bC", "5GA", "4yL", "6Lo", "aP", "09", "0Mc", "nQ", "6Cn", "42E", "4Xa", "6mB", "2Vl", "0cO", "8gg", "MM", "7pS", "4UQ", "bAN", "adL", "ca", "1Pr", "G8", "RY", "7oG", "4JE", "4di", "6QJ", "2jd", "1Of", "0Rw", "5M", "480", "4iY", "4Gu", "5B5", "2Ix", "08S", "07R", "Ph", "5M4", "4Ht", "4fX", "471", "1X6", "1MW", "0PF", "st", "7Nj", "4kh", "4ED", "64o", "2KI", "H9", "CZ", "14A", "5Kg", "6ne", "7Ph", "41b", "0ND", "mv", "1F4", "0Ct", "4xZ", "6My", "5S6", "4Vv", "0mX", "Nj", "Ak", "0bY", "4Yw", "6lT", "6Bx", "43S", "0Lu", "oG", "bw", "0AE", "4zk", "6OH", "6ad", "4TG", "0oi", "2ZJ", "7MA", "4hC", "0Sm", "4W", "2Hb", "09I", "4Fo", "67D", "aTn", "b5d", "04y", "SC", "9g", "8WE", "4es", "6PP", "5o2", "4jr", "8XD", "6f", "1z0", "1oQ", "705", "65u", "68E", "4In", "06H", "Qr", "2iO", "1LM", "4gB", "6Ra", "5ia", "6Lc", "23E", "05", "0lB", "Op", "6bO", "4Wl", "c4F", "aEm", "1d2", "0ar", "8FF", "ll", "558", "40x", "5kP", "6NR", "cm", "344", "0ns", "MA", "74W", "bon", "4Xm", "6mN", "3FA", "0cC", "0Mo", "2xL", "6Cb", "42I", "4dm", "6QN", "8y", "1Ob", "05g", "2DL", "7oC", "4JA", "4Gq", "5B1", "d7G", "08W", "0Rs", "5I", "484", "bSn", "52u", "475", "1X2", "1MS", "07V", "Pl", "5M0", "4Hp", "5Ua", "64k", "2KM", "1nO", "0PB", "7x", "7Nn", "4kl", "7Pl", "41f", "8GX", "mr", "2UO", "14E", "5Kc", "6na", "5S2", "4Vr", "19u", "Nn", "1F0", "0Cp", "bBm", "ago", "ahn", "43W", "0Lq", "oC", "Ao", "16t", "4Ys", "6lP", "75I", "4TC", "0om", "2ZN", "bs", "0AA", "4zo", "6OL", "2Hf", "09M", "4Fk", "6sH", "7ME", "4hG", "0Si", "4S", "9c", "1Nx", "4ew", "6PT", "7nY", "bqh", "0pu", "SG", "1z4", "1oU", "4DZ", "65q", "5o6", "4jv", "0QX", "6b", "2iK", "1LI", "4gF", "6Re", "68A", "4Ij", "06L", "Qv", "0lF", "Ot", "6bK", "4Wh", "4yD", "6Lg", "aX", "01", "0OZ", "lh", "5q4", "4tt", "4ZX", "4O9", "BD", "0av", "0nw", "ME", "74S", "4UY", "5kT", "6NV", "ci", "1Pz", "0Mk", "nY", "6Cf", "42M", "4Xi", "6mJ", "2Vd", "0cG", "bL", "8Hf", "4zP", "4o1", "75v", "606", "0oR", "0z3", "AP", "T1", "4YL", "6lo", "6BC", "43h", "0LN", "2ym", "22d", "0CO", "4xa", "6MB", "6cn", "4VM", "0mc", "NQ", "Ca", "14z", "baN", "aDL", "7PS", "41Y", "8Gg", "mM", "247", "7G", "7NQ", "4kS", "com", "64T", "0k0", "1np", "E2", "PS", "69d", "4HO", "4fc", "7Ca", "2hn", "1Ml", "0RL", "5v", "avS", "4ib", "4GN", "66e", "2IC", "J3", "05X", "Rb", "aUO", "b4E", "4dR", "4q3", "8F", "8Vd", "4XV", "4M7", "1f8", "0cx", "0MT", "nf", "572", "42r", "5kk", "6Ni", "cV", "v7", "0nH", "Mz", "74l", "4Uf", "4Zg", "6oD", "2Tj", "0aI", "y6", "lW", "6Ah", "40C", "5iZ", "583", "ag", "0BU", "0ly", "OK", "4B6", "4WW", "7lW", "4IU", "06s", "QI", "0I6", "1Lv", "4gy", "5b9", "7OK", "4jI", "g4", "rU", "2Jh", "1oj", "4De", "65N", "7nf", "4Kd", "04B", "Sx", "2kE", "h5", "4eH", "6Pk", "5m8", "4hx", "0SV", "4l", "2HY", "09r", "4FT", "4S5", "5Q8", "4Tx", "0oV", "Ld", "bH", "0Az", "4zT", "4o5", "6BG", "43l", "0LJ", "ox", "AT", "T5", "4YH", "6lk", "6cj", "4VI", "0mg", "NU", "2vh", "0CK", "4xe", "6MF", "7PW", "4uU", "2n9", "mI", "Ce", "1pv", "5KX", "6nZ", "5UZ", "64P", "0k4", "1nt", "0Py", "7C", "7NU", "4kW", "4fg", "6SD", "2hj", "1Mh", "E6", "PW", "7mI", "4HK", "4GJ", "66a", "2IG", "J7", "0RH", "5r", "7Ld", "4if", "4dV", "4q7", "8B", "1OY", "0qT", "Rf", "7ox", "4Jz", "0MP", "nb", "576", "42v", "4XR", "4M3", "dll", "17U", "0nL", "3KN", "74h", "4Ub", "5ko", "6Nm", "cR", "v3", "y2", "lS", "6Al", "40G", "4Zc", "aER", "2Tn", "0aM", "18T", "OO", "4B2", "4WS", "bCL", "587", "ac", "0BQ", "0I2", "1Lr", "53T", "axL", "68z", "4IQ", "06w", "QM", "2Jl", "1on", "4Da", "65J", "7OO", "4jM", "g0", "6Y", "9X", "h1", "4eL", "6Po", "7nb", "aA0", "04F", "2Em", "d6f", "09v", "4FP", "4S1", "a3E", "bRO", "0SR", "4h", "AX", "T9", "4YD", "6lg", "6BK", "4wh", "0LF", "ot", "bD", "0Av", "4zX", "4o9", "5Q4", "4Tt", "0oZ", "Lh", "Ci", "14r", "5KT", "6nV", "ajh", "41Q", "0Nw", "mE", "22l", "0CG", "4xi", "6MJ", "6cf", "4VE", "0mk", "NY", "07a", "2FJ", "69l", "4HG", "4fk", "6SH", "2hf", "1Md", "0Pu", "7O", "7NY", "bQh", "4Ew", "6pT", "0k8", "1nx", "05P", "Rj", "5O6", "4Jv", "4dZ", "453", "8N", "1OU", "0RD", "qv", "7Lh", "4ij", "4GF", "66m", "2IK", "1lI", "5kc", "6Na", "21G", "27", "8gX", "Mr", "74d", "4Un", "bbm", "79T", "1f0", "0cp", "397", "nn", "5s2", "42z", "4ys", "6LP", "ao", "366", "0lq", "OC", "76U", "bml", "4Zo", "6oL", "Bs", "0aA", "0Om", "2zN", "7QA", "40K", "7OC", "4jA", "0Qo", "6U", "3ZA", "1ob", "4Dm", "65F", "68v", "b7f", "0rs", "QA", "dSO", "8UG", "4gq", "5b1", "5m0", "4hp", "8ZF", "4d", "1x2", "09z", "727", "67w", "7nn", "4Kl", "04J", "Sp", "9T", "1NO", "51i", "6Pc", "6BO", "43d", "0LB", "op", "2WM", "0bn", "5Ia", "6lc", "5Q0", "4Tp", "8fF", "Ll", "1D2", "0Ar", "cPN", "aem", "ajl", "41U", "0Ns", "mA", "Cm", "14v", "5KP", "6nR", "6cb", "4VA", "0mo", "2XL", "22h", "0CC", "4xm", "6MN", "4fo", "6SL", "2hb", "8TY", "07e", "2FN", "69h", "4HC", "4Es", "64X", "d5E", "83M", "0Pq", "7K", "a0f", "bQl", "50w", "457", "8J", "1OQ", "05T", "Rn", "5O2", "4Jr", "4GB", "66i", "2IO", "08d", "1Ba", "5z", "7Ll", "4in", "0nD", "Mv", "7ph", "4Uj", "5kg", "6Ne", "cZ", "23", "0MX", "nj", "5s6", "4vv", "4XZ", "6my", "1f4", "0ct", "0lu", "OG", "6bx", "5Gz", "4yw", "6LT", "ak", "0BY", "0Oi", "2zJ", "6Ad", "40O", "4Zk", "6oH", "Bw", "0aE", "2Jd", "1of", "4Di", "65B", "7OG", "4jE", "g8", "6Q", "2ix", "1Lz", "4gu", "5b5", "68r", "4IY", "0rw", "QE", "1x6", "1mW", "4FX", "4S9", "5m4", "4ht", "0SZ", "ph", "9P", "h9", "4eD", "6Pg", "7nj", "4Kh", "04N", "St", "22u", "375", "4xp", "598", "77V", "blo", "0mr", "1h2", "Cp", "14k", "5KM", "6nO", "7PB", "41H", "0Nn", "3kl", "20D", "34", "4zA", "6Ob", "6aN", "4Tm", "0oC", "Lq", "AA", "0bs", "bcn", "78W", "569", "43y", "384", "om", "9Kd", "5g", "5l3", "4is", "734", "66t", "1y1", "08y", "05I", "Rs", "7om", "4Jo", "4dC", "7AA", "8W", "1OL", "0Pl", "7V", "ats", "4kB", "4En", "64E", "2Kc", "1na", "07x", "PB", "69u", "b6e", "4fr", "5c2", "dRL", "8TD", "4Zv", "6oU", "Bj", "0aX", "0Ot", "lF", "6Ay", "40R", "4yj", "6LI", "av", "0BD", "0lh", "OZ", "6be", "4WF", "4XG", "6md", "2VJ", "0ci", "0ME", "nw", "6CH", "42c", "5kz", "6Nx", "cG", "1PT", "0nY", "Mk", "5P7", "4Uw", "5N5", "4Ku", "04S", "Si", "9M", "1NV", "4eY", "440", "7Mk", "4hi", "0SG", "pu", "2HH", "K8", "4FE", "67n", "68o", "4ID", "1", "QX", "2ie", "1Lg", "4gh", "6RK", "7OZ", "4jX", "0Qv", "6L", "2Jy", "3O9", "4Dt", "5A4", "4C9", "4VX", "0mv", "ND", "22q", "0CZ", "4xt", "6MW", "7PF", "41L", "x9", "mX", "Ct", "14o", "5KI", "6nK", "6aJ", "4Ti", "0oG", "Lu", "bY", "30", "4zE", "6Of", "5r5", "4wu", "380", "oi", "AE", "0bw", "4YY", "4L8", "5Wz", "66p", "1y5", "1lT", "0RY", "5c", "5l7", "4iw", "4dG", "6Qd", "8S", "1OH", "05M", "Rw", "7oi", "4Jk", "4Ej", "64A", "2Kg", "1ne", "0Ph", "7R", "7ND", "4kF", "4fv", "5c6", "0H9", "1My", "0st", "PF", "69q", "4HZ", "0Op", "lB", "ako", "40V", "4Zr", "6oQ", "Bn", "15u", "0ll", "2YO", "6ba", "4WB", "4yn", "6LM", "ar", "1Ra", "0MA", "ns", "6CL", "42g", "4XC", "79I", "2VN", "0cm", "8gE", "Mo", "5P3", "4Us", "bAl", "adn", "cC", "1PP", "9I", "1NR", "51t", "444", "5N1", "4Kq", "04W", "Sm", "2HL", "09g", "4FA", "67j", "7Mo", "4hm", "0SC", "4y", "2ia", "1Lc", "4gl", "6RO", "68k", "5Ya", "5", "2GM", "d4F", "82N", "4Dp", "5A0", "a1e", "bPo", "0Qr", "6H", "Cx", "14c", "5KE", "6nG", "7PJ", "4uH", "x5", "mT", "0V7", "0CV", "4xx", "590", "4C5", "4VT", "0mz", "NH", "AI", "16R", "4YU", "4L4", "561", "43q", "0LW", "oe", "bU", "w4", "4zI", "6Oj", "6aF", "4Te", "0oK", "Ly", "05A", "2Dj", "7oe", "4Jg", "4dK", "6Qh", "2jF", "i6", "0RU", "5o", "7Ly", "5yZ", "4GW", "4R6", "1y9", "08q", "07p", "PJ", "7mT", "4HV", "4fz", "6SY", "0H5", "1Mu", "f7", "sV", "7NH", "4kJ", "4Ef", "64M", "2Kk", "1ni", "4yb", "6LA", "23g", "0BL", "Z3", "OR", "6bm", "4WN", "c4d", "aEO", "Bb", "0aP", "8Fd", "lN", "4a3", "40Z", "5kr", "4n2", "cO", "8Ie", "0nQ", "Mc", "74u", "615", "4XO", "6ml", "2VB", "U2", "0MM", "2xn", "7Sa", "42k", "7Mc", "4ha", "0SO", "4u", "3Xa", "K0", "4FM", "67f", "aTL", "b5F", "0pS", "Sa", "9E", "8Wg", "4eQ", "448", "7OR", "4jP", "254", "6D", "0j3", "1os", "cnn", "65W", "68g", "4IL", "9", "QP", "2im", "1Lo", "53I", "6RC", "7PN", "41D", "x1", "mP", "2Um", "14g", "5KA", "6nC", "4C1", "4VP", "19W", "NL", "0V3", "0CR", "bBO", "594", "565", "43u", "0LS", "oa", "AM", "16V", "4YQ", "4L0", "6aB", "4Ta", "0oO", "2Zl", "bQ", "38", "4zM", "6On", "4dO", "6Ql", "2jB", "i2", "05E", "2Dn", "7oa", "4Jc", "4GS", "4R2", "d7e", "08u", "0RQ", "5k", "a2F", "bSL", "52W", "ayO", "0H1", "1Mq", "07t", "PN", "69y", "4HR", "4Eb", "64I", "2Ko", "1nm", "f3", "7Z", "7NL", "4kN", "Z7", "OV", "6bi", "4WJ", "4yf", "6LE", "az", "0BH", "0Ox", "lJ", "4a7", "4tV", "4Zz", "6oY", "Bf", "0aT", "0nU", "Mg", "74q", "5EZ", "5kv", "4n6", "cK", "1PX", "0MI", "2xj", "6CD", "42o", "4XK", "6mh", "2VF", "U6", "2HD", "K4", "4FI", "67b", "7Mg", "4he", "0SK", "4q", "9A", "1NZ", "4eU", "4p4", "5N9", "4Ky", "0pW", "Se", "0j7", "1ow", "4Dx", "5A8", "7OV", "4jT", "0Qz", "rH", "2ii", "1Lk", "4gd", "6RG", "68c", "4IH", "D5", "QT", "5Ls", "4I3", "F", "13U", "0IP", "jb", "536", "46v", "5oo", "6Jm", "gR", "r3", "0jL", "3ON", "6dA", "4Qb", "5NB", "aAR", "2Pn", "0eM", "0Ka", "hS", "6El", "44G", "49w", "abN", "ec", "0FQ", "8ae", "KO", "4F2", "4SS", "4X0", "4MQ", "02w", "UM", "0M2", "0XS", "57T", "a8D", "7KO", "4nM", "c0", "2Y", "2Nl", "1kn", "aJ1", "61J", "6zC", "aE0", "00F", "2Am", "yP", "l1", "4aL", "6To", "a7E", "58U", "0WR", "0h", "ZL", "84n", "4BP", "4W1", "fH", "0Ez", "5nu", "4k5", "5U8", "4Px", "0kV", "Hd", "ET", "P5", "5Mi", "6hk", "6FG", "47l", "0HJ", "kx", "dy", "0GK", "48m", "6IF", "6gj", "4RI", "0ig", "JU", "Ge", "0dW", "5OX", "5Z9", "4d4", "4qU", "1ZZ", "iI", "0Ty", "3C", "4z6", "4oW", "5QZ", "60P", "Yg", "0zU", "A6", "TW", "6yh", "4LK", "4bg", "6WD", "2lj", "0YI", "0VH", "1r", "6XE", "4mf", "4CJ", "62a", "2MG", "N7", "0uT", "Vf", "7kx", "4Nz", "5pw", "4u7", "xJ", "1KY", "0IT", "jf", "532", "46r", "5Lw", "4I7", "B", "0gx", "0jH", "Iz", "6dE", "4Qf", "5ok", "6Ji", "gV", "r7", "0Ke", "hW", "6Eh", "44C", "5NF", "6kD", "2Pj", "0eI", "0hy", "KK", "4F6", "4SW", "49s", "6HX", "eg", "0FU", "0M6", "0XW", "4cy", "5f9", "4X4", "4MU", "02s", "UI", "Xy", "1kj", "5PD", "61N", "7KK", "4nI", "c4", "vU", "yT", "l5", "4aH", "6Tk", "6zG", "4Od", "00B", "Wx", "ZH", "0yz", "4BT", "4W5", "5i8", "4lx", "0WV", "0l", "71v", "646", "0kR", "3NP", "fL", "8Lf", "5nq", "4k1", "6FC", "47h", "0HN", "29e", "EP", "P1", "5Mm", "6ho", "6gn", "4RM", "0ic", "JQ", "26d", "0GO", "48i", "6IB", "4d0", "45Y", "8Cg", "iM", "Ga", "0dS", "beN", "hYu", "ckm", "60T", "Yc", "0zQ", "207", "3G", "4z2", "4oS", "4bc", "7Ga", "2ln", "0YM", "A2", "TS", "6yl", "4LO", "4CN", "62e", "2MC", "N3", "0VL", "1v", "6XA", "4mb", "5ps", "4u3", "xN", "8Rd", "01X", "Vb", "aQO", "b0E", "5og", "6Je", "gZ", "63", "0jD", "Iv", "6dI", "4Qj", "7l9", "6iy", "N", "0gt", "0IX", "jj", "5w6", "4rv", "5mV", "5x7", "ek", "0FY", "0hu", "KG", "6fx", "5Cz", "5NJ", "6kH", "Fw", "0eE", "92", "3nk", "6Ed", "44O", "7KG", "4nE", "c8", "2Q", "Xu", "1kf", "5PH", "61B", "4X8", "4MY", "0vw", "UE", "2mx", "1Hz", "4cu", "5f5", "5i4", "4lt", "0WZ", "th", "ZD", "0yv", "4BX", "4W9", "6zK", "4Oh", "00N", "Wt", "yX", "l9", "4aD", "6Tg", "2SM", "0fn", "5Ma", "6hc", "6FO", "47d", "0HB", "kp", "24Y", "0Er", "bDo", "aam", "5U0", "4Pp", "8bF", "Hl", "Gm", "10v", "5OP", "5Z1", "anl", "45U", "0Js", "iA", "dq", "0GC", "48e", "6IN", "6gb", "4RA", "0io", "3Lm", "03e", "2BN", "7iA", "4LC", "4bo", "6WL", "zs", "0YA", "0Tq", "3K", "a4f", "bUl", "4As", "5D3", "Yo", "87M", "01T", "Vn", "5K2", "4Nr", "54w", "417", "xB", "1KQ", "1Fa", "1z", "6XM", "4mn", "4CB", "62i", "2MO", "0xl", "1za", "Ir", "6dM", "4Qn", "5oc", "6Ja", "25G", "67", "9Pe", "jn", "5w2", "46z", "bfm", "aCo", "J", "0gp", "0hq", "KC", "72U", "bil", "5mR", "5x3", "eo", "326", "96", "3no", "7UA", "44K", "5NN", "6kL", "Fs", "0eA", "Xq", "1kb", "5PL", "61F", "7KC", "4nA", "0Uo", "2U", "39U", "8QG", "4cq", "5f1", "aRl", "796", "0vs", "UA", "2LQ", "0yr", "767", "63w", "5i0", "4lp", "9Ng", "0d", "2oM", "0Zn", "55i", "6Tc", "6zO", "4Ol", "00J", "Wp", "6FK", "4sh", "0HF", "kt", "EX", "P9", "5Me", "6hg", "5U4", "4Pt", "0kZ", "Hh", "fD", "0Ev", "5ny", "4k9", "4d8", "45Q", "0Jw", "iE", "Gi", "10r", "5OT", "5Z5", "6gf", "4RE", "0ik", "JY", "du", "0GG", "48a", "6IJ", "4bk", "6WH", "zw", "0YE", "03a", "2BJ", "6yd", "4LG", "4Aw", "5D7", "Yk", "0zY", "0Tu", "3O", "6Zx", "bUh", "54s", "413", "xF", "1KU", "01P", "Vj", "5K6", "4Nv", "4CF", "62m", "2MK", "0xh", "0VD", "uv", "6XI", "4mj", "5NS", "6kQ", "Fn", "11u", "0Kp", "hB", "aoo", "44V", "49f", "6HM", "er", "1Va", "0hl", "3Mn", "6fa", "4SB", "5Lb", "7yA", "W", "0gm", "0IA", "js", "6GL", "46g", "bEl", "hyW", "gC", "0Dq", "8cE", "Io", "5T3", "4Qs", "5J1", "4Oq", "00W", "Wm", "yA", "0Zs", "55t", "404", "6YN", "4lm", "0WC", "0y", "2LL", "0yo", "4BA", "63j", "6xc", "bws", "02f", "2CM", "2ma", "0XB", "4cl", "6VO", "a5e", "bTo", "0Ur", "2H", "Xl", "86N", "5PQ", "5E0", "dh", "0GZ", "5lU", "5y4", "4G9", "4RX", "0iv", "JD", "Gt", "0dF", "5OI", "6jK", "6Dg", "45L", "81", "iX", "fY", "70", "5nd", "6Kf", "6eJ", "4Pi", "0kG", "Hu", "EE", "0fw", "5Mx", "4H8", "5v5", "4su", "1Xz", "ki", "0VY", "1c", "5h7", "4mw", "5Sz", "62p", "2MV", "0xu", "01M", "Vw", "7ki", "4Nk", "54n", "6Ud", "2nJ", "1KH", "0Th", "3R", "6Ze", "4oF", "4Aj", "60A", "Yv", "0zD", "0wt", "TF", "6yy", "4LZ", "4bv", "5g6", "zj", "0YX", "0Kt", "hF", "6Ey", "44R", "5NW", "6kU", "Fj", "0eX", "0hh", "KZ", "6fe", "4SF", "49b", "6HI", "ev", "0FD", "0IE", "jw", "6GH", "46c", "5Lf", "6id", "S", "0gi", "0jY", "Ik", "5T7", "4Qw", "5oz", "6Jx", "gG", "0Du", "yE", "0Zw", "4aY", "400", "5J5", "4Ou", "00S", "Wi", "ZY", "O8", "4BE", "63n", "6YJ", "4li", "0WG", "tu", "2me", "0XF", "4ch", "6VK", "6xg", "4MD", "02b", "UX", "Xh", "3K9", "5PU", "5E4", "7KZ", "4nX", "0Uv", "2L", "73V", "bho", "0ir", "1l2", "dl", "335", "48x", "5y0", "6Dc", "45H", "85", "3ol", "Gp", "0dB", "5OM", "6jO", "6eN", "4Pm", "0kC", "Hq", "24D", "74", "bDr", "6Kb", "529", "47y", "8AG", "km", "EA", "0fs", "bgn", "aBl", "774", "62t", "199", "0xq", "9Od", "1g", "5h3", "4ms", "54j", "7EA", "2nN", "1KL", "01I", "Vs", "7km", "4No", "4An", "60E", "Yr", "1ja", "0Tl", "3V", "6Za", "4oB", "4br", "5g2", "zn", "8PD", "03x", "TB", "aSo", "785", "49n", "6HE", "ez", "0FH", "0hd", "KV", "6fi", "4SJ", "bdI", "6kY", "Ff", "0eT", "0Kx", "hJ", "4e7", "4pV", "5ov", "4j6", "gK", "0Dy", "0jU", "Ig", "6dX", "5AZ", "5Lj", "6ih", "DW", "Q6", "0II", "28b", "6GD", "46o", "6YF", "4le", "0WK", "0q", "ZU", "O4", "4BI", "63b", "5J9", "4Oy", "0tW", "We", "yI", "1JZ", "4aU", "4t4", "7KV", "4nT", "0Uz", "vH", "Xd", "1kw", "5PY", "5E8", "6xk", "4MH", "02n", "UT", "2mi", "0XJ", "4cd", "6VG", "2Qm", "0dN", "5OA", "6jC", "6Do", "45D", "89", "iP", "0R3", "0GR", "48t", "acM", "4G1", "4RP", "94O", "JL", "EM", "12V", "5Mp", "4H0", "525", "47u", "0HS", "ka", "fQ", "78", "5nl", "6Kn", "6eB", "4Pa", "0kO", "3NM", "01E", "3PO", "7ka", "4Nc", "54f", "6Ul", "xS", "m2", "0VQ", "1k", "a6F", "59V", "4CS", "4V2", "195", "85m", "03t", "TN", "4Y3", "4LR", "56W", "a9G", "zb", "0YP", "b3", "3Z", "6Zm", "4oN", "4Ab", "60I", "2Oo", "0zL", "1xA", "KR", "6fm", "4SN", "49j", "6HA", "27g", "0FL", "8Bd", "hN", "4e3", "44Z", "bdM", "aAO", "Fb", "0eP", "0jQ", "Ic", "70u", "655", "5or", "4j2", "gO", "8Me", "0IM", "28f", "7Wa", "46k", "5Ln", "6il", "DS", "Q2", "ZQ", "O0", "4BM", "63f", "6YB", "4la", "0WO", "0u", "yM", "8Sg", "4aQ", "408", "aPL", "b1F", "0tS", "Wa", "0n3", "1ks", "bzO", "61W", "7KR", "4nP", "214", "2D", "2mm", "0XN", "57I", "6VC", "6xo", "4ML", "02j", "UP", "6Dk", "4qH", "0Jf", "iT", "Gx", "0dJ", "5OE", "6jG", "4G5", "4RT", "0iz", "JH", "dd", "0GV", "48p", "5y8", "521", "47q", "0HW", "ke", "EI", "12R", "5Mt", "4H4", "6eF", "4Pe", "0kK", "Hy", "fU", "s4", "5nh", "6Kj", "54b", "6Uh", "xW", "m6", "01A", "3PK", "7ke", "4Ng", "4CW", "4V6", "191", "0xy", "0VU", "1o", "6XX", "59R", "4bz", "6WY", "zf", "0YT", "03p", "TJ", "4Y7", "4LV", "4Af", "60M", "Yz", "0zH", "b7", "wV", "6Zi", "4oJ", "5H3", "4Ms", "02U", "Uo", "2mR", "0Xq", "57v", "426", "7Km", "4no", "0UA", "vs", "2NN", "1kL", "5Pb", "61h", "6za", "4OB", "00d", "2AO", "yr", "1Ja", "4an", "6TM", "a7g", "58w", "0Wp", "0J", "Zn", "84L", "4Br", "5G2", "5LQ", "5Y0", "d", "13w", "0Ir", "1L2", "amm", "46T", "5oM", "6JO", "gp", "0DB", "0jn", "3Ol", "6dc", "5Aa", "bdr", "6kb", "2PL", "0eo", "0KC", "hq", "6EN", "44e", "49U", "abl", "eA", "0Fs", "8aG", "Km", "5V1", "4Sq", "1Dz", "3a", "5j5", "4ou", "4AY", "4T8", "YE", "0zw", "03O", "Tu", "6yJ", "4Li", "4bE", "6Wf", "zY", "o8", "0Vj", "1P", "6Xg", "4mD", "4Ch", "62C", "2Me", "0xF", "0uv", "VD", "7kZ", "4NX", "5pU", "5e4", "xh", "3k9", "fj", "0EX", "5nW", "6KU", "6ey", "4PZ", "0kt", "HF", "Ev", "0fD", "5MK", "6hI", "6Fe", "47N", "0Hh", "kZ", "26B", "52", "48O", "6Id", "6gH", "4Rk", "0iE", "Jw", "GG", "0du", "5Oz", "6jx", "5t7", "4qw", "0JY", "ik", "2mV", "0Xu", "57r", "422", "5H7", "4Mw", "02Q", "Uk", "2NJ", "1kH", "5Pf", "61l", "7Ki", "4nk", "0UE", "vw", "yv", "0ZD", "4aj", "6TI", "6ze", "4OF", "0th", "WZ", "Zj", "0yX", "4Bv", "5G6", "6Yy", "4lZ", "0Wt", "0N", "0Iv", "jD", "4g9", "46P", "5LU", "5Y4", "Dh", "0gZ", "0jj", "IX", "6dg", "4QD", "5oI", "6JK", "gt", "0DF", "0KG", "hu", "6EJ", "44a", "5Nd", "6kf", "FY", "S8", "1xz", "Ki", "5V5", "4Su", "49Q", "4h8", "eE", "0Fw", "756", "60v", "YA", "0zs", "9Mf", "3e", "5j1", "4oq", "4bA", "6Wb", "2lL", "0Yo", "03K", "Tq", "6yN", "4Lm", "4Cl", "62G", "2Ma", "0xB", "0Vn", "1T", "6Xc", "59i", "54Y", "5e0", "xl", "8RF", "01z", "1p2", "aQm", "b0g", "71T", "bjm", "0kp", "HB", "fn", "317", "5nS", "6KQ", "6Fa", "47J", "0Hl", "29G", "Er", "12i", "5MO", "6hM", "6gL", "4Ro", "0iA", "Js", "26F", "56", "48K", "7YA", "5t3", "4qs", "8CE", "io", "GC", "0dq", "bel", "hYW", "7Ke", "4ng", "0UI", "2s", "XW", "M6", "5Pj", "6uh", "6xX", "6m9", "0vU", "Ug", "2mZ", "0Xy", "4cW", "4v6", "4y7", "4lV", "0Wx", "0B", "Zf", "0yT", "4Bz", "63Q", "6zi", "4OJ", "B7", "WV", "yz", "0ZH", "4af", "6TE", "5oE", "6JG", "gx", "0DJ", "0jf", "IT", "6dk", "4QH", "5LY", "5Y8", "l", "0gV", "0Iz", "jH", "4g5", "4rT", "5mt", "4h4", "eI", "1VZ", "0hW", "Ke", "5V9", "4Sy", "5Nh", "6kj", "FU", "S4", "0KK", "hy", "6EF", "44m", "03G", "2Bl", "6yB", "4La", "4bM", "6Wn", "zQ", "o0", "0TS", "3i", "a4D", "bUN", "4AQ", "4T0", "YM", "87o", "01v", "VL", "7kR", "4NP", "54U", "hft", "0N3", "1Ks", "0Vb", "1X", "6Xo", "4mL", "5SA", "62K", "2Mm", "0xN", "2So", "0fL", "5MC", "6hA", "6Fm", "47F", "1XA", "kR", "fb", "0EP", "bDM", "aaO", "4E3", "4PR", "8bd", "HN", "GO", "10T", "5Or", "4J2", "507", "45w", "0JQ", "ic", "dS", "q2", "48G", "6Il", "73i", "4Rc", "0iM", "3LO", "XS", "M2", "5Pn", "61d", "7Ka", "4nc", "0UM", "2w", "39w", "8Qe", "4cS", "4v2", "aRN", "b3D", "02Y", "Uc", "Zb", "0yP", "bxM", "63U", "4y3", "4lR", "236", "0F", "2oo", "0ZL", "4ab", "6TA", "6zm", "4ON", "B3", "WR", "0jb", "IP", "6do", "4QL", "5oA", "6JC", "25e", "0DN", "9PG", "jL", "4g1", "46X", "686", "aCM", "h", "0gR", "0hS", "Ka", "72w", "677", "49Y", "4h0", "eM", "8Og", "0KO", "3nM", "6EB", "44i", "5Nl", "6kn", "FQ", "S0", "4bI", "6Wj", "zU", "o4", "03C", "Ty", "6yF", "4Le", "4AU", "4T4", "YI", "1jZ", "0TW", "3m", "5j9", "4oy", "54Q", "5e8", "xd", "1Kw", "01r", "VH", "7kV", "4NT", "4Cd", "62O", "2Mi", "0xJ", "0Vf", "uT", "6Xk", "4mH", "6Fi", "47B", "0Hd", "kV", "Ez", "0fH", "5MG", "6hE", "4E7", "4PV", "0kx", "HJ", "ff", "0ET", "bDI", "6KY", "503", "45s", "0JU", "ig", "GK", "0dy", "5Ov", "4J6", "6gD", "4Rg", "0iI", "3LK", "dW", "q6", "48C", "6Ih", "4Z2", "4OS", "00u", "WO", "yc", "0ZQ", "55V", "hgw", "6Yl", "4lO", "a2", "tS", "2Ln", "0yM", "4Bc", "63H", "6xA", "4Mb", "02D", "2Co", "2mC", "n3", "4cN", "6Vm", "a5G", "bTM", "0UP", "2j", "XN", "86l", "5Ps", "4U3", "5Nq", "4K1", "FL", "11W", "0KR", "3nP", "514", "44t", "49D", "6Ho", "eP", "49", "0hN", "3ML", "6fC", "5CA", "aV1", "6iB", "u", "0gO", "0Ic", "jQ", "6Gn", "46E", "bEN", "hyu", "ga", "0DS", "8cg", "IM", "4D0", "4QQ", "1FZ", "1A", "4x4", "4mU", "4Cy", "5F9", "0m6", "0xW", "C4", "VU", "7kK", "4NI", "54L", "6UF", "xy", "1Kj", "0TJ", "3p", "6ZG", "4od", "4AH", "60c", "YT", "L5", "0wV", "Td", "5I8", "4Lx", "4bT", "4w5", "zH", "0Yz", "dJ", "0Gx", "5lw", "4i7", "6gY", "4Rz", "0iT", "Jf", "GV", "R7", "5Ok", "6ji", "6DE", "45n", "0JH", "iz", "24b", "0EI", "5nF", "6KD", "6eh", "4PK", "0ke", "HW", "Eg", "0fU", "5MZ", "6hX", "4f6", "4sW", "0Hy", "kK", "yg", "0ZU", "55R", "6TX", "4Z6", "4OW", "00q", "WK", "2Lj", "0yI", "4Bg", "63L", "6Yh", "4lK", "a6", "tW", "2mG", "n7", "4cJ", "6Vi", "6xE", "4Mf", "0vH", "Uz", "XJ", "1kY", "5Pw", "4U7", "7Kx", "4nz", "0UT", "2n", "0KV", "hd", "510", "44p", "5Nu", "4K5", "FH", "0ez", "0hJ", "Kx", "6fG", "4Sd", "5mi", "6Hk", "eT", "p5", "0Ig", "jU", "6Gj", "46A", "5LD", "6iF", "q", "0gK", "1zZ", "II", "4D4", "4QU", "5oX", "5z9", "ge", "0DW", "byN", "62V", "0m2", "0xS", "225", "1E", "4x0", "4mQ", "54H", "6UB", "2nl", "1Kn", "C0", "VQ", "7kO", "4NM", "4AL", "60g", "YP", "L1", "0TN", "3t", "6ZC", "ae0", "4bP", "439", "zL", "8Pf", "03Z", "0b3", "aSM", "b2G", "73t", "664", "0iP", "Jb", "dN", "8Nd", "48Z", "4i3", "6DA", "45j", "0JL", "3oN", "GR", "R3", "5Oo", "6jm", "6el", "4PO", "0ka", "HS", "24f", "0EM", "5nB", "aaR", "4f2", "4sS", "8Ae", "kO", "Ec", "0fQ", "695", "aBN", "6Yd", "4lG", "0Wi", "0S", "Zw", "0yE", "4Bk", "6wH", "6zx", "buh", "0tu", "WG", "yk", "0ZY", "4aw", "5d7", "5k6", "4nv", "0UX", "2b", "XF", "1kU", "741", "61q", "6xI", "4Mj", "02L", "Uv", "2mK", "0Xh", "4cF", "6Ve", "49L", "6Hg", "eX", "41", "0hF", "Kt", "6fK", "4Sh", "5Ny", "4K9", "FD", "0ev", "0KZ", "hh", "5u4", "4pt", "5oT", "5z5", "gi", "1Tz", "0jw", "IE", "4D8", "4QY", "5LH", "6iJ", "Du", "0gG", "0Ik", "jY", "6Gf", "46M", "01g", "3Pm", "7kC", "4NA", "54D", "6UN", "xq", "1Kb", "0Vs", "1I", "a6d", "59t", "4Cq", "5F1", "d3G", "85O", "03V", "Tl", "5I0", "4Lp", "56u", "435", "2lQ", "0Yr", "0TB", "3x", "6ZO", "4ol", "5Qa", "60k", "2OM", "0zn", "2QO", "0dl", "5Oc", "6ja", "6DM", "45f", "1Za", "ir", "dB", "0Gp", "48V", "aco", "5W2", "4Rr", "94m", "Jn", "Eo", "12t", "5MR", "5X3", "aln", "47W", "0Hq", "kC", "fs", "0EA", "5nN", "6KL", "71I", "4PC", "0km", "3No", "Zs", "0yA", "4Bo", "63D", "7IA", "4lC", "0Wm", "0W", "yo", "8SE", "4as", "5d3", "aPn", "b1d", "00y", "WC", "XB", "1kQ", "745", "61u", "5k2", "4nr", "9Le", "2f", "2mO", "0Xl", "4cB", "6Va", "6xM", "4Mn", "02H", "Ur", "0hB", "Kp", "6fO", "4Sl", "49H", "6Hc", "27E", "45", "8BF", "hl", "518", "44x", "bdo", "aAm", "2PQ", "0er", "0js", "IA", "70W", "bkn", "5oP", "5z1", "gm", "304", "0Io", "28D", "6Gb", "46I", "5LL", "6iN", "y", "0gC", "5pH", "6UJ", "xu", "1Kf", "C8", "VY", "7kG", "4NE", "4Cu", "5F5", "2Mx", "1hz", "0Vw", "1M", "4x8", "4mY", "4bX", "431", "zD", "0Yv", "03R", "Th", "5I4", "4Lt", "4AD", "60o", "YX", "L9", "0TF", "wt", "6ZK", "4oh", "6DI", "45b", "0JD", "iv", "GZ", "0dh", "5Og", "6je", "5W6", "4Rv", "0iX", "Jj", "dF", "0Gt", "48R", "6Iy", "6Fx", "47S", "0Hu", "kG", "Ek", "0fY", "5MV", "5X7", "6ed", "4PG", "0ki", "3Nk", "fw", "0EE", "5nJ", "6KH", "356", "bo", "6OP", "4zs", "bnl", "75U", "LC", "0oq", "0bA", "As", "6lL", "4Yo", "43K", "7RA", "2yN", "0Lm", "17", "22G", "6Ma", "4xB", "4Vn", "6cM", "Nr", "19i", "14Y", "CB", "aDo", "bam", "41z", "5p2", "mn", "8GD", "7d", "8YF", "4kp", "5n0", "64w", "717", "1nS", "2KQ", "Pp", "07J", "4Hl", "69G", "6Sc", "52i", "1MO", "2hM", "5U", "0Ro", "4iA", "7LC", "66F", "4Gm", "08K", "3YA", "RA", "0qs", "b4f", "aUl", "5a1", "4dq", "8VG", "8e", "6mV", "4Xu", "17r", "022", "nE", "0Mw", "42Q", "4c8", "6NJ", "5kH", "1Pf", "cu", "MY", "X8", "4UE", "74O", "6og", "4ZD", "W9", "BX", "lt", "0OF", "4th", "6AK", "4l9", "4yX", "0Bv", "aD", "Oh", "0lZ", "4Wt", "5R4", "4Iv", "5L6", "Qj", "06P", "1LU", "1Y4", "463", "4gZ", "4jj", "7Oh", "rv", "0QD", "1oI", "2JK", "65m", "4DF", "4KG", "7nE", "2EJ", "04a", "1Nd", "2kf", "6PH", "4ek", "5xz", "492", "4O", "0Su", "09Q", "0h8", "5C7", "4Fw", "5Dz", "6ax", "LG", "0ou", "0AY", "bk", "6OT", "4zw", "43O", "6Bd", "2yJ", "0Li", "0bE", "Aw", "6lH", "4Yk", "4Vj", "6cI", "Nv", "0mD", "13", "22C", "6Me", "4xF", "4uv", "5p6", "mj", "0NX", "1pU", "CF", "6ny", "7k9", "4P9", "4EX", "1nW", "2KU", "sh", "0PZ", "4kt", "5n4", "6Sg", "4fD", "k9", "2hI", "Pt", "07N", "4Hh", "69C", "66B", "4Gi", "08O", "2Id", "5Q", "d8", "4iE", "7LG", "5a5", "4du", "1Oz", "8a", "RE", "0qw", "4JY", "aUh", "nA", "0Ms", "42U", "ail", "6mR", "4Xq", "17v", "026", "3Km", "0no", "4UA", "74K", "6NN", "5kL", "1Pb", "cq", "lp", "0OB", "40d", "6AO", "6oc", "5Ja", "0an", "2TM", "Ol", "18w", "4Wp", "5R0", "afm", "bCo", "0Br", "1G2", "1LQ", "1Y0", "467", "53w", "4Ir", "5L2", "Qn", "06T", "1oM", "2JO", "65i", "4DB", "4jn", "7Ol", "6z", "1Aa", "8WY", "2kb", "6PL", "4eo", "4KC", "7nA", "2EN", "04e", "09U", "d6E", "5C3", "4Fs", "bRl", "496", "4K", "0Sq", "0bI", "2Wj", "6lD", "4Yg", "43C", "6Bh", "oW", "z6", "0AU", "bg", "6OX", "5jZ", "4TW", "4A6", "LK", "0oy", "14Q", "CJ", "4N7", "5Kw", "41r", "542", "mf", "0NT", "u7", "22O", "6Mi", "4xJ", "4Vf", "6cE", "Nz", "0mH", "Px", "07B", "4Hd", "69O", "6Sk", "4fH", "k5", "2hE", "7l", "0PV", "4kx", "5n8", "4P5", "4ET", "83j", "2KY", "RI", "05s", "4JU", "7oW", "5a9", "4dy", "1Ov", "8m", "qU", "d4", "4iI", "7LK", "66N", "4Ge", "08C", "2Ih", "6NB", "a59", "1Pn", "21d", "MQ", "X0", "4UM", "74G", "79w", "bbN", "0cS", "0v2", "nM", "8Dg", "42Y", "4c0", "4l1", "4yP", "8Kf", "aL", "0y3", "0lR", "636", "76v", "6oo", "4ZL", "W1", "BP", "2zm", "0ON", "40h", "6AC", "4jb", "auS", "6v", "0QL", "I3", "2JC", "65e", "4DN", "b7E", "68U", "Qb", "06X", "286", "dSl", "4r3", "4gR", "4hS", "7MQ", "4G", "277", "09Y", "0h0", "67T", "b8D", "4KO", "7nM", "SS", "F2", "1Nl", "9w", "azR", "4ec", "43G", "6Bl", "oS", "z2", "0bM", "2Wn", "78i", "4Yc", "4TS", "4A2", "LO", "8fe", "0AQ", "bc", "aeN", "cPm", "41v", "546", "mb", "0NP", "14U", "CN", "4N3", "5Ks", "4Vb", "6cA", "2Xo", "0mL", "u3", "22K", "6Mm", "4xN", "6So", "4fL", "k1", "2hA", "2Fm", "07F", "5XA", "69K", "4P1", "4EP", "83n", "d5f", "7h", "0PR", "bQO", "a0E", "hbu", "50T", "1Or", "8i", "RM", "05w", "4JQ", "7oS", "66J", "4Ga", "08G", "2Il", "5Y", "d0", "4iM", "7LO", "MU", "X4", "4UI", "74C", "6NF", "5kD", "1Pj", "cy", "nI", "2m9", "4vU", "4c4", "6mZ", "4Xy", "0cW", "0v6", "Od", "0lV", "4Wx", "5R8", "4l5", "4yT", "0Bz", "aH", "lx", "0OJ", "40l", "6AG", "6ok", "4ZH", "W5", "BT", "I7", "2JG", "65a", "4DJ", "4jf", "7Od", "6r", "0QH", "1LY", "1Y8", "4r7", "4gV", "4Iz", "68Q", "Qf", "0rT", "1mt", "0h4", "67P", "5VZ", "4hW", "7MU", "4C", "0Sy", "1Nh", "9s", "6PD", "4eg", "4KK", "7nI", "SW", "F6", "8Je", "22V", "4m2", "4xS", "625", "77u", "Nc", "0mQ", "V2", "CS", "6nl", "5Kn", "41k", "7Pa", "3kO", "0NM", "0AL", "20g", "6OA", "4zb", "4TN", "6am", "LR", "Y3", "0bP", "Ab", "78t", "bcM", "43Z", "4b3", "oN", "8Ed", "5D", "264", "4iP", "489", "66W", "b9G", "08Z", "0i3", "RP", "G1", "4JL", "7oN", "6QC", "50I", "1Oo", "8t", "7u", "0PO", "4ka", "7Nc", "64f", "4EM", "H0", "963", "Pa", "0sS", "b6F", "69V", "478", "4fQ", "295", "dRo", "4O4", "4ZU", "15R", "BI", "le", "0OW", "40q", "551", "6Lj", "4yI", "t4", "aU", "Oy", "0lK", "4We", "6bF", "6mG", "4Xd", "0cJ", "2Vi", "nT", "0Mf", "4vH", "6Ck", "adI", "5kY", "1Pw", "cd", "MH", "0nz", "4UT", "7pV", "4KV", "7nT", "SJ", "04p", "1Nu", "9n", "6PY", "4ez", "4hJ", "7MH", "pV", "e7", "1mi", "2Hk", "67M", "4Ff", "4Ig", "68L", "2Gj", "06A", "j6", "2iF", "6Rh", "4gK", "5zZ", "7Oy", "6o", "0QU", "1oX", "1z9", "4Q6", "4DW", "5FZ", "6cX", "Ng", "0mU", "0Cy", "1F9", "4m6", "4xW", "41o", "7Pe", "3kK", "0NI", "V6", "CW", "6nh", "5Kj", "4TJ", "6ai", "LV", "Y7", "0AH", "bz", "6OE", "4zf", "4wV", "4b7", "oJ", "0Lx", "0bT", "Af", "6lY", "4Yz", "5B8", "4Gx", "1lw", "0i7", "qH", "0Rz", "4iT", "7LV", "6QG", "4dd", "1Ok", "8p", "RT", "G5", "4JH", "7oJ", "64b", "4EI", "H4", "2KD", "7q", "0PK", "4ke", "7Ng", "4s4", "4fU", "1MZ", "2hX", "Pe", "0sW", "4Hy", "5M9", "la", "0OS", "40u", "555", "4O0", "4ZQ", "15V", "BM", "2Yl", "0lO", "4Wa", "6bB", "6Ln", "4yM", "08", "aQ", "nP", "0Mb", "42D", "6Co", "6mC", "5HA", "0cN", "2Vm", "ML", "8gf", "4UP", "74Z", "adM", "bAO", "1Ps", "0U3", "1Nq", "9j", "azO", "51W", "4KR", "7nP", "SN", "04t", "09D", "2Ho", "67I", "4Fb", "4hN", "7ML", "4Z", "e3", "j2", "2iB", "6Rl", "4gO", "4Ic", "68H", "2Gn", "06E", "82m", "d4e", "4Q2", "4DS", "bPL", "a1F", "6k", "0QQ", "1pH", "2UJ", "6nd", "5Kf", "41c", "7Pi", "mw", "0NE", "0Cu", "1F5", "6Mx", "5hz", "4Vw", "5S7", "Nk", "0mY", "0bX", "Aj", "6lU", "4Yv", "43R", "6By", "oF", "0Lt", "0AD", "bv", "6OI", "4zj", "4TF", "6ae", "LZ", "0oh", "RX", "G9", "4JD", "7oF", "6QK", "4dh", "1Og", "2je", "5L", "0Rv", "4iX", "481", "5B4", "4Gt", "08R", "2Iy", "Pi", "07S", "4Hu", "5M5", "470", "4fY", "1MV", "1X7", "su", "0PG", "4ki", "7Nk", "64n", "4EE", "H8", "2KH", "6Lb", "4yA", "04", "23D", "Oq", "0lC", "4Wm", "6bN", "aEl", "c4G", "0as", "BA", "lm", "8FG", "40y", "559", "6NS", "5kQ", "345", "cl", "1k2", "0nr", "boo", "74V", "6mO", "4Xl", "0cB", "2Va", "2xM", "0Mn", "42H", "6Cc", "4hB", "aws", "4V", "0Sl", "09H", "2Hc", "67E", "4Fn", "b5e", "aTo", "SB", "04x", "8WD", "9f", "6PQ", "4er", "4js", "5o3", "6g", "8XE", "1oP", "1z1", "65t", "704", "4Io", "68D", "Qs", "06I", "1LL", "2iN", "7BA", "4gC", "41g", "7Pm", "ms", "0NA", "14D", "2UN", "aDr", "5Kb", "4Vs", "5S3", "No", "19t", "0Cq", "1F1", "agn", "bBl", "43V", "aho", "oB", "0Lp", "16u", "An", "6lQ", "4Yr", "4TB", "6aa", "2ZO", "0ol", "1Qa", "br", "6OM", "4zn", "6QO", "4dl", "1Oc", "8x", "2DM", "05f", "5Za", "7oB", "5B0", "4Gp", "08V", "d7F", "5H", "0Rr", "bSo", "485", "474", "52t", "1MR", "1X3", "Pm", "07W", "4Hq", "5M1", "64j", "4EA", "1nN", "2KL", "7y", "0PC", "4km", "7No", "Ou", "0lG", "4Wi", "6bJ", "6Lf", "4yE", "00", "aY", "li", "8FC", "4tu", "5q5", "4O8", "4ZY", "0aw", "BE", "MD", "0nv", "4UX", "74R", "6NW", "5kU", "341", "ch", "nX", "0Mj", "42L", "6Cg", "6mK", "4Xh", "0cF", "2Ve", "09L", "2Hg", "67A", "4Fj", "4hF", "7MD", "4R", "0Sh", "1Ny", "9b", "6PU", "4ev", "4KZ", "7nX", "SF", "0pt", "1oT", "1z5", "65p", "5Tz", "4jw", "5o7", "6c", "0QY", "1LH", "2iJ", "6Rd", "4gG", "4Ik", "7li", "Qw", "06M", "7F", "246", "4kR", "7NP", "64U", "col", "1nq", "0k1", "PR", "E3", "4HN", "69e", "6SA", "4fb", "1Mm", "2ho", "5w", "0RM", "4ic", "7La", "66d", "4GO", "J2", "2IB", "Rc", "05Y", "b4D", "aUN", "4q2", "4dS", "8Ve", "8G", "8Hg", "bM", "4o0", "4zQ", "607", "75w", "La", "0oS", "T0", "AQ", "6ln", "4YM", "43i", "6BB", "2yl", "0LO", "0CN", "22e", "6MC", "5hA", "4VL", "6co", "NP", "0mb", "1ps", "0u3", "aDM", "baO", "41X", "7PR", "mL", "8Gf", "4IT", "7lV", "QH", "06r", "1Lw", "0I7", "5b8", "4gx", "4jH", "7OJ", "rT", "g5", "1ok", "2Ji", "65O", "4Dd", "4Ke", "7ng", "Sy", "04C", "h4", "2kD", "6Pj", "4eI", "4hy", "5m9", "4m", "0SW", "09s", "2HX", "4S4", "4FU", "4M6", "4XW", "0cy", "1f9", "ng", "0MU", "42s", "573", "6Nh", "5kj", "v6", "cW", "3KK", "0nI", "4Ug", "74m", "6oE", "4Zf", "0aH", "Bz", "lV", "y7", "40B", "6Ai", "582", "4yz", "0BT", "af", "OJ", "0lx", "4WV", "4B7", "64Q", "4Ez", "1nu", "0k5", "7B", "0Px", "4kV", "7NT", "6SE", "4ff", "1Mi", "2hk", "PV", "E7", "4HJ", "69a", "6rh", "4GK", "J6", "2IF", "5s", "0RI", "4ig", "7Le", "4q6", "4dW", "1OX", "8C", "Rg", "0qU", "5ZZ", "7oy", "4Ty", "5Q9", "Le", "0oW", "1QZ", "bI", "4o4", "4zU", "43m", "6BF", "oy", "0LK", "T4", "AU", "6lj", "4YI", "4VH", "6ck", "NT", "0mf", "0CJ", "22a", "6MG", "4xd", "4uT", "7PV", "mH", "0Nz", "1pw", "Cd", "aDI", "5KY", "1Ls", "0I3", "axM", "53U", "4IP", "7lR", "QL", "06v", "1oo", "2Jm", "65K", "5TA", "4jL", "7ON", "6X", "g1", "h0", "9Y", "6Pn", "4eM", "4Ka", "7nc", "2El", "04G", "09w", "d6g", "4S0", "4FQ", "bRN", "a3D", "4i", "0SS", "nc", "0MQ", "42w", "577", "4M2", "4XS", "17T", "dlm", "3KO", "0nM", "4Uc", "74i", "6Nl", "5kn", "v2", "cS", "lR", "y3", "40F", "6Am", "6oA", "4Zb", "0aL", "2To", "ON", "18U", "4WR", "4B3", "586", "bCM", "0BP", "ab", "PZ", "0sh", "4HF", "69m", "6SI", "4fj", "1Me", "2hg", "7N", "0Pt", "4kZ", "7NX", "6pU", "4Ev", "1ny", "0k9", "Rk", "05Q", "4Jw", "5O7", "452", "50r", "1OT", "8O", "qw", "0RE", "4ik", "7Li", "66l", "4GG", "08a", "2IJ", "T8", "AY", "6lf", "4YE", "43a", "6BJ", "ou", "0LG", "0Aw", "bE", "4o8", "4zY", "4Tu", "5Q5", "Li", "8fC", "14s", "Ch", "6nW", "5KU", "41P", "7PZ", "mD", "0Nv", "0CF", "22m", "6MK", "4xh", "4VD", "6cg", "NX", "0mj", "5za", "7OB", "6T", "0Qn", "1oc", "2Ja", "65G", "4Dl", "b7g", "68w", "1w2", "06z", "8UF", "dSN", "5b0", "4gp", "4hq", "5m1", "4e", "8ZG", "1mR", "1x3", "67v", "726", "4Km", "7no", "Sq", "04K", "1NN", "9U", "6Pb", "4eA", "adr", "5kb", "26", "21F", "Ms", "0nA", "4Uo", "74e", "79U", "bbl", "0cq", "1f1", "no", "396", "4vs", "5s3", "6LQ", "4yr", "367", "an", "OB", "0lp", "bmm", "76T", "6oM", "4Zn", "15i", "Br", "2zO", "0Ol", "40J", "6Aa", "6SM", "4fn", "1Ma", "2hc", "2FO", "07d", "4HB", "69i", "64Y", "4Er", "83L", "d5D", "7J", "0Pp", "bQm", "a0g", "456", "50v", "1OP", "8K", "Ro", "05U", "4Js", "5O3", "66h", "4GC", "08e", "2IN", "qs", "0RA", "4io", "7Lm", "43e", "6BN", "oq", "0LC", "0bo", "2WL", "6lb", "4YA", "4Tq", "5Q1", "Lm", "8fG", "0As", "bA", "ael", "cPO", "41T", "ajm", "1K2", "0Nr", "14w", "Cl", "6nS", "5KQ", "5Fa", "6cc", "2XM", "0mn", "0CB", "22i", "6MO", "4xl", "1og", "2Je", "65C", "4Dh", "4jD", "7OF", "6P", "g9", "3l9", "2iy", "5b4", "4gt", "4IX", "68s", "QD", "0rv", "1mV", "1x7", "4S8", "4FY", "4hu", "5m5", "4a", "1Cz", "h8", "9Q", "6Pf", "4eE", "4Ki", "7nk", "Su", "04O", "Mw", "0nE", "4Uk", "74a", "6Nd", "5kf", "22", "21B", "nk", "0MY", "4vw", "5s7", "6mx", "5Hz", "0cu", "1f5", "OF", "0lt", "4WZ", "6by", "6LU", "4yv", "0BX", "aj", "lZ", "0Oh", "40N", "6Ae", "6oI", "4Zj", "0aD", "Bv", "5f", "9Ke", "4ir", "5l2", "66u", "735", "08x", "1y0", "Rr", "05H", "4Jn", "7ol", "6Qa", "4dB", "1OM", "8V", "7W", "0Pm", "4kC", "7NA", "64D", "4Eo", "83Q", "2Kb", "PC", "07y", "b6d", "69t", "5c3", "4fs", "8TE", "dRM", "374", "22t", "599", "4xq", "bln", "77W", "NA", "0ms", "14j", "Cq", "6nN", "5KL", "41I", "7PC", "3km", "0No", "35", "20E", "6Oc", "5ja", "4Tl", "6aO", "Lp", "0oB", "0br", "1g2", "78V", "bco", "43x", "568", "ol", "385", "4Kt", "5N4", "Sh", "04R", "1NW", "9L", "441", "4eX", "4hh", "7Mj", "pt", "0SF", "K9", "2HI", "67o", "4FD", "4IE", "68n", "QY", "0", "1Lf", "2id", "6RJ", "4gi", "4jY", "auh", "6M", "0Qw", "1oz", "2Jx", "5A5", "4Du", "6oT", "4Zw", "0aY", "Bk", "lG", "0Ou", "40S", "6Ax", "6LH", "4yk", "0BE", "aw", "2YJ", "0li", "4WG", "6bd", "6me", "4XF", "0ch", "2VK", "nv", "0MD", "42b", "6CI", "6Ny", "7K9", "1PU", "cF", "Mj", "0nX", "4Uv", "5P6", "66q", "4GZ", "1lU", "1y4", "5b", "0RX", "4iv", "5l6", "6Qe", "4dF", "1OI", "8R", "Rv", "05L", "4Jj", "7oh", "6pH", "4Ek", "1nd", "2Kf", "7S", "0Pi", "4kG", "7NE", "5c7", "4fw", "1Mx", "0H8", "PG", "0su", "5Xz", "69p", "4VY", "4C8", "NE", "0mw", "1Sz", "22p", "6MV", "4xu", "41M", "7PG", "mY", "x8", "14n", "Cu", "6nJ", "5KH", "4Th", "6aK", "Lt", "0oF", "31", "bX", "6Og", "4zD", "4wt", "5r4", "oh", "0LZ", "0bv", "AD", "4L9", "4YX", "1NS", "9H", "445", "51u", "4Kp", "5N0", "Sl", "04V", "09f", "2HM", "67k", "5Va", "4hl", "7Mn", "4x", "0SB", "1Lb", "3yA", "6RN", "4gm", "4IA", "68j", "2GL", "4", "82O", "d4G", "5A1", "4Dq", "bPn", "a1d", "6I", "0Qs", "lC", "0Oq", "40W", "akn", "6oP", "4Zs", "15t", "Bo", "2YN", "0lm", "4WC", "76I", "6LL", "4yo", "0BA", "as", "nr", "8DX", "42f", "6CM", "6ma", "4XB", "0cl", "2VO", "Mn", "8gD", "4Ur", "5P2", "ado", "bAm", "1PQ", "cB", "Rz", "0qH", "4Jf", "7od", "6Qi", "4dJ", "i7", "2jG", "5n", "0RT", "4iz", "7Lx", "4R7", "4GV", "08p", "1y8", "PK", "07q", "4HW", "7mU", "6SX", "52R", "1Mt", "0H4", "sW", "f6", "4kK", "7NI", "64L", "4Eg", "1nh", "2Kj", "14b", "Cy", "6nF", "5KD", "41A", "7PK", "mU", "x4", "0CW", "0V6", "591", "4xy", "4VU", "4C4", "NI", "19R", "0bz", "AH", "4L5", "4YT", "43p", "560", "od", "0LV", "w5", "bT", "6Ok", "4zH", "4Td", "6aG", "Lx", "0oJ", "5xA", "7Mb", "4t", "0SN", "K1", "2HA", "67g", "4FL", "b5G", "aTM", "0e3", "04Z", "8Wf", "9D", "449", "4eP", "4jQ", "7OS", "6E", "255", "1or", "0j2", "65V", "cno", "4IM", "68f", "QQ", "8", "1Ln", "2il", "6RB", "4ga", "afR", "4yc", "0BM", "23f", "OS", "Z2", "4WO", "6bl", "aEN", "c4e", "0aQ", "Bc", "lO", "8Fe", "4tS", "4a2", "4n3", "5ks", "8Id", "cN", "Mb", "0nP", "614", "74t", "6mm", "4XN", "U3", "2VC", "2xo", "0ML", "42j", "6CA", "6Qm", "4dN", "i3", "8Z", "2Do", "05D", "4Jb", "aUS", "4R3", "4GR", "08t", "d7d", "5j", "0RP", "bSM", "a2G", "ayN", "52V", "1Mp", "0H0", "PO", "07u", "4HS", "69x", "64H", "4Ec", "1nl", "2Kn", "sS", "f2", "4kO", "7NM", "41E", "7PO", "mQ", "x0", "14f", "2Ul", "6nB", "aQ1", "4VQ", "4C0", "NM", "19V", "0CS", "0V2", "595", "bBN", "43t", "564", "0Y3", "0LR", "16W", "AL", "4L1", "4YP", "5DA", "6aC", "2Zm", "0oN", "39", "bP", "6Oo", "4zL", "K5", "2HE", "67c", "4FH", "4hd", "7Mf", "4p", "0SJ", "8Wb", "2kY", "4p5", "4eT", "4Kx", "5N8", "Sd", "0pV", "1ov", "0j6", "5A9", "4Dy", "4jU", "7OW", "6A", "1AZ", "1Lj", "2ih", "6RF", "4ge", "4II", "68b", "QU", "D4", "OW", "Z6", "4WK", "6bh", "6LD", "4yg", "0BI", "23b", "lK", "0Oy", "4tW", "4a6", "6oX", "5JZ", "0aU", "Bg", "Mf", "0nT", "4Uz", "74p", "4n7", "5kw", "1PY", "cJ", "nz", "0MH", "42n", "6CE", "6mi", "4XJ", "U7", "2VG", "4MP", "4X1", "UL", "02v", "0XR", "0M3", "a8E", "57U", "4nL", "7KN", "2X", "c1", "1ko", "2Nm", "61K", "5PA", "4Oa", "6zB", "2Al", "00G", "l0", "yQ", "6Tn", "4aM", "58T", "a7D", "0i", "0WS", "84o", "ZM", "4W0", "4BQ", "4I2", "5Lr", "13T", "G", "jc", "0IQ", "46w", "537", "6Jl", "5on", "r2", "gS", "3OO", "0jM", "4Qc", "70i", "6kA", "5NC", "0eL", "2Po", "hR", "8Bx", "44F", "6Em", "abO", "49v", "0FP", "eb", "KN", "8ad", "4SR", "4F3", "3B", "0Tx", "4oV", "4z7", "60Q", "4Az", "0zT", "Yf", "TV", "A7", "4LJ", "6yi", "6WE", "4bf", "0YH", "zz", "1s", "0VI", "4mg", "6XD", "6vh", "4CK", "N6", "2MF", "Vg", "0uU", "6n9", "7ky", "4u6", "5pv", "1KX", "xK", "1UZ", "fI", "4k4", "5nt", "4Py", "5U9", "He", "0kW", "P4", "EU", "6hj", "5Mh", "47m", "6FF", "ky", "0HK", "0GJ", "dx", "6IG", "48l", "4RH", "6gk", "JT", "0if", "0dV", "Gd", "5Z8", "5OY", "4qT", "4d5", "iH", "0Jz", "0XV", "0M7", "5f8", "4cx", "4MT", "4X5", "UH", "02r", "1kk", "Xx", "61O", "5PE", "4nH", "7KJ", "vT", "c5", "l4", "yU", "6Tj", "4aI", "4Oe", "6zF", "Wy", "00C", "1iZ", "ZI", "4W4", "4BU", "4ly", "5i9", "0m", "0WW", "jg", "0IU", "46s", "533", "4I6", "5Lv", "0gy", "C", "3OK", "0jI", "4Qg", "6dD", "6Jh", "5oj", "r6", "gW", "hV", "0Kd", "44B", "6Ei", "6kE", "5NG", "0eH", "Fz", "KJ", "0hx", "4SV", "4F7", "6HY", "49r", "0FT", "ef", "60U", "ckl", "0zP", "Yb", "3F", "206", "4oR", "4z3", "6WA", "4bb", "0YL", "2lo", "TR", "A3", "4LN", "6ym", "62d", "4CO", "N2", "2MB", "1w", "0VM", "4mc", "7Ha", "4u2", "54z", "8Re", "xO", "Vc", "01Y", "b0D", "aQN", "647", "71w", "Ha", "0kS", "8Lg", "fM", "4k0", "5np", "47i", "6FB", "29d", "0HO", "P0", "EQ", "6hn", "5Ml", "4RL", "6go", "JP", "0ib", "0GN", "26e", "6IC", "48h", "45X", "4d1", "iL", "8Cf", "0dR", "0q3", "hYt", "beO", "4nD", "7KF", "2P", "c9", "1kg", "Xt", "61C", "5PI", "4MX", "4X9", "UD", "0vv", "0XZ", "2my", "5f4", "4ct", "4lu", "5i5", "0a", "1Gz", "0yw", "ZE", "4W8", "4BY", "4Oi", "6zJ", "Wu", "00O", "l8", "yY", "6Tf", "4aE", "6Jd", "5of", "62", "25B", "Iw", "0jE", "4Qk", "6dH", "6ix", "5Lz", "0gu", "O", "jk", "0IY", "4rw", "5w7", "5x6", "5mW", "0FX", "ej", "KF", "0ht", "4SZ", "6fy", "6kI", "5NK", "0eD", "Fv", "hZ", "93", "44N", "6Ee", "2BO", "03d", "4LB", "6ya", "6WM", "4bn", "1Ia", "zr", "3J", "0Tp", "bUm", "a4g", "5D2", "4Ar", "87L", "Yn", "Vo", "01U", "4Ns", "5K3", "416", "54v", "1KP", "xC", "us", "0VA", "4mo", "6XL", "62h", "4CC", "0xm", "2MN", "0fo", "2SL", "6hb", "bgr", "47e", "6FN", "kq", "0HC", "0Es", "fA", "aal", "bDn", "4Pq", "5U1", "Hm", "8bG", "10w", "Gl", "5Z0", "5OQ", "45T", "anm", "1O2", "0Jr", "0GB", "dp", "6IO", "48d", "5Ba", "6gc", "3Ll", "0in", "1kc", "Xp", "61G", "5PM", "bTs", "7KB", "2T", "0Un", "8QF", "39T", "5f0", "4cp", "797", "aRm", "1s2", "02z", "0ys", "ZA", "63v", "766", "4lq", "5i1", "0e", "9Nf", "0Zo", "2oL", "6Tb", "4aA", "4Om", "6zN", "Wq", "00K", "Is", "0jA", "4Qo", "6dL", "7ZA", "5ob", "66", "25F", "jo", "9Pd", "4rs", "5w3", "aCn", "bfl", "0gq", "K", "KB", "0hp", "bim", "72T", "5x2", "49z", "327", "en", "3nn", "97", "44J", "6Ea", "6kM", "5NO", "11i", "Fr", "6WI", "4bj", "0YD", "zv", "TZ", "0wh", "4LF", "6ye", "5D6", "4Av", "0zX", "Yj", "3N", "0Tt", "4oZ", "6Zy", "412", "54r", "1KT", "xG", "Vk", "01Q", "4Nw", "5K7", "62l", "4CG", "0xi", "2MJ", "uw", "0VE", "4mk", "6XH", "47a", "6FJ", "ku", "0HG", "P8", "EY", "6hf", "5Md", "4Pu", "5U5", "Hi", "8bC", "0Ew", "fE", "4k8", "5nx", "45P", "4d9", "iD", "0Jv", "0dZ", "Gh", "5Z4", "5OU", "4RD", "6gg", "JX", "0ij", "0GF", "dt", "6IK", "5lI", "4Op", "5J0", "Wl", "00V", "0Zr", "2oQ", "405", "55u", "4ll", "6YO", "0x", "0WB", "0yn", "2LM", "63k", "5Ra", "4MA", "6xb", "2CL", "02g", "0XC", "39I", "6VN", "4cm", "bTn", "a5d", "2I", "0Us", "86O", "Xm", "5E1", "5PP", "6kP", "5NR", "11t", "Fo", "hC", "0Kq", "44W", "aon", "6HL", "49g", "0FA", "es", "3Mo", "0hm", "4SC", "72I", "6ia", "5Lc", "0gl", "V", "jr", "1Ya", "46f", "6GM", "hyV", "bEm", "0Dp", "gB", "In", "8cD", "4Qr", "5T2", "1b", "0VX", "4mv", "5h6", "62q", "4CZ", "0xt", "2MW", "Vv", "01L", "4Nj", "7kh", "6Ue", "54o", "1KI", "xZ", "3S", "0Ti", "4oG", "6Zd", "6tH", "4Ak", "0zE", "Yw", "TG", "0wu", "780", "6yx", "5g7", "4bw", "0YY", "zk", "1Wz", "di", "5y5", "5lT", "4RY", "4G8", "JE", "0iw", "0dG", "Gu", "6jJ", "5OH", "45M", "6Df", "iY", "80", "71", "fX", "6Kg", "5ne", "4Ph", "6eK", "Ht", "0kF", "0fv", "ED", "4H9", "5My", "4st", "5v4", "kh", "0HZ", "0Zv", "yD", "401", "4aX", "4Ot", "5J4", "Wh", "00R", "O9", "ZX", "63o", "4BD", "4lh", "6YK", "tt", "0WF", "0XG", "2md", "6VJ", "4ci", "4ME", "6xf", "UY", "02c", "1kz", "Xi", "5E5", "5PT", "4nY", "aqh", "2M", "0Uw", "hG", "0Ku", "44S", "6Ex", "6kT", "5NV", "0eY", "Fk", "3Mk", "0hi", "4SG", "6fd", "6HH", "49c", "0FE", "ew", "jv", "0ID", "46b", "6GI", "6ie", "5Lg", "0gh", "R", "Ij", "0jX", "4Qv", "5T6", "6Jy", "7O9", "0Dt", "gF", "62u", "775", "0xp", "198", "1f", "9Oe", "4mr", "5h2", "6Ua", "54k", "1KM", "2nO", "Vr", "01H", "4Nn", "7kl", "60D", "4Ao", "0zA", "Ys", "3W", "0Tm", "4oC", "7JA", "5g3", "4bs", "8PE", "zo", "TC", "03y", "784", "aSn", "bhn", "73W", "JA", "0is", "334", "dm", "5y1", "48y", "45I", "6Db", "3om", "84", "0dC", "Gq", "6jN", "5OL", "4Pl", "6eO", "Hp", "0kB", "75", "24E", "6Kc", "5na", "47x", "528", "kl", "8AF", "0fr", "1c2", "aBm", "bgo", "4ld", "6YG", "0p", "0WJ", "O5", "ZT", "63c", "4BH", "4Ox", "5J8", "Wd", "0tV", "0Zz", "yH", "4t5", "4aT", "4nU", "7KW", "2A", "1EZ", "1kv", "Xe", "5E9", "5PX", "4MI", "6xj", "UU", "02o", "0XK", "2mh", "6VF", "4ce", "6HD", "49o", "0FI", "27b", "KW", "0he", "4SK", "6fh", "6kX", "5NZ", "0eU", "Fg", "hK", "0Ky", "4pW", "4e6", "4j7", "5ow", "0Dx", "gJ", "If", "0jT", "4Qz", "6dY", "6ii", "5Lk", "Q7", "DV", "jz", "0IH", "46n", "6GE", "3PN", "01D", "4Nb", "aQS", "6Um", "54g", "m3", "xR", "1j", "0VP", "59W", "a6G", "4V3", "4CR", "85l", "194", "TO", "03u", "4LS", "4Y2", "a9F", "56V", "0YQ", "zc", "wS", "b2", "4oO", "6Zl", "60H", "4Ac", "0zM", "2On", "0dO", "2Ql", "6jB", "aU1", "45E", "6Dn", "iQ", "88", "0GS", "da", "acL", "48u", "4RQ", "4G0", "JM", "94N", "12W", "EL", "4H1", "5Mq", "47t", "524", "29y", "0HR", "79", "fP", "6Ko", "5nm", "aZ0", "6eC", "3NL", "0kN", "O1", "ZP", "63g", "4BL", "58I", "6YC", "0t", "0WN", "8Sf", "yL", "409", "4aP", "b1G", "aPM", "0a3", "00Z", "1kr", "Xa", "61V", "bzN", "4nQ", "7KS", "2E", "215", "0XO", "2ml", "6VB", "4ca", "4MM", "6xn", "UQ", "02k", "KS", "0ha", "4SO", "6fl", "7Xa", "49k", "0FM", "27f", "hO", "8Be", "4pS", "4e2", "aAN", "bdL", "0eQ", "Fc", "Ib", "0jP", "654", "70t", "4j3", "5os", "8Md", "gN", "28g", "0IL", "46j", "6GA", "6im", "5Lo", "Q3", "Z", "6Ui", "54c", "m7", "xV", "Vz", "0uH", "4Nf", "7kd", "4V7", "4CV", "0xx", "190", "1n", "0VT", "4mz", "6XY", "6WX", "56R", "0YU", "zg", "TK", "03q", "4LW", "4Y6", "60L", "4Ag", "0zI", "2Oj", "wW", "b6", "4oK", "6Zh", "45A", "6Dj", "iU", "0Jg", "0dK", "Gy", "6jF", "5OD", "4RU", "4G4", "JI", "1yZ", "0GW", "de", "5y9", "48q", "47p", "520", "kd", "0HV", "0fz", "EH", "4H5", "5Mu", "4Pd", "6eG", "Hx", "0kJ", "s5", "fT", "6Kk", "5ni", "5Y1", "5LP", "13v", "e", "jA", "0Is", "46U", "aml", "6JN", "5oL", "0DC", "gq", "3Om", "0jo", "4QA", "6db", "6kc", "5Na", "0en", "2PM", "hp", "0KB", "44d", "6EO", "abm", "49T", "0Fr", "1C2", "Kl", "8aF", "4Sp", "5V0", "4Mr", "5H2", "Un", "02T", "0Xp", "2mS", "427", "57w", "4nn", "7Kl", "2z", "1Ea", "1kM", "2NO", "61i", "5Pc", "4OC", "7jA", "2AN", "00e", "0ZA", "ys", "6TL", "4ao", "58v", "a7f", "0K", "0Wq", "84M", "Zo", "5G3", "4Bs", "0EY", "fk", "6KT", "5nV", "bjh", "6ex", "HG", "0ku", "0fE", "Ew", "6hH", "5MJ", "47O", "6Fd", "29B", "0Hi", "53", "dZ", "6Ie", "48N", "4Rj", "6gI", "Jv", "0iD", "0dt", "GF", "6jy", "7o9", "4qv", "5t6", "ij", "0JX", "wh", "0TZ", "4ot", "5j4", "4T9", "4AX", "0zv", "YD", "Tt", "03N", "4Lh", "6yK", "6Wg", "4bD", "o9", "zX", "1Q", "0Vk", "4mE", "6Xf", "62B", "4Ci", "0xG", "2Md", "VE", "0uw", "4NY", "aQh", "5e5", "5pT", "1Kz", "xi", "jE", "0Iw", "46Q", "4g8", "5Y5", "5LT", "13r", "a", "IY", "0jk", "4QE", "6df", "6JJ", "5oH", "0DG", "gu", "ht", "0KF", "4ph", "6EK", "6kg", "5Ne", "S9", "FX", "Kh", "0hZ", "4St", "5V4", "4h9", "49P", "0Fv", "eD", "0Xt", "2mW", "423", "4cZ", "4Mv", "5H6", "Uj", "02P", "1kI", "XZ", "61m", "5Pg", "4nj", "7Kh", "vv", "0UD", "0ZE", "yw", "6TH", "4ak", "4OG", "6zd", "2AJ", "00a", "0yY", "Zk", "5G7", "4Bw", "58r", "6Yx", "0O", "0Wu", "bjl", "71U", "HC", "0kq", "316", "fo", "6KP", "5nR", "47K", "7VA", "29F", "0Hm", "0fA", "Es", "6hL", "5MN", "4Rn", "6gM", "Jr", "1ya", "57", "26G", "6Ia", "48J", "45z", "5t2", "in", "8CD", "0dp", "GB", "hYV", "bem", "60w", "757", "0zr", "2OQ", "3d", "9Mg", "4op", "5j0", "6Wc", "56i", "0Yn", "2lM", "Tp", "03J", "4Ll", "6yO", "62F", "4Cm", "0xC", "dwS", "1U", "0Vo", "4mA", "6Xb", "5e1", "54X", "8RG", "xm", "VA", "0us", "b0f", "aQl", "6JF", "5oD", "0DK", "gy", "IU", "0jg", "4QI", "6dj", "5Y9", "5LX", "0gW", "m", "jI", "1YZ", "4rU", "4g4", "4h5", "5mu", "0Fz", "eH", "Kd", "0hV", "4Sx", "5V8", "6kk", "5Ni", "S5", "FT", "hx", "0KJ", "44l", "6EG", "4nf", "7Kd", "2r", "0UH", "M7", "XV", "61a", "5Pk", "4Mz", "6xY", "Uf", "0vT", "0Xx", "39r", "4v7", "4cV", "4lW", "4y6", "0C", "0Wy", "0yU", "Zg", "63P", "5RZ", "4OK", "6zh", "WW", "B6", "0ZI", "2oj", "6TD", "4ag", "0fM", "2Sn", "7xa", "5MB", "47G", "6Fl", "kS", "0Ha", "0EQ", "fc", "aaN", "bDL", "4PS", "4E2", "HO", "8be", "10U", "GN", "4J3", "5Os", "45v", "506", "ib", "0JP", "q3", "dR", "6Im", "48F", "4Rb", "6gA", "3LN", "0iL", "2Bm", "03F", "aF0", "6yC", "6Wo", "4bL", "o1", "zP", "3h", "0TR", "bUO", "a4E", "4T1", "4AP", "87n", "YL", "VM", "01w", "4NQ", "7kS", "hfu", "54T", "1Kr", "xa", "1Y", "0Vc", "4mM", "6Xn", "62J", "4Ca", "0xO", "2Ml", "IQ", "0jc", "4QM", "6dn", "6JB", "a19", "0DO", "25d", "jM", "9PF", "46Y", "4g0", "aCL", "687", "0gS", "i", "3MP", "0hR", "676", "72v", "4h1", "49X", "8Of", "eL", "3nL", "0KN", "44h", "6EC", "6ko", "5Nm", "S1", "FP", "M3", "XR", "61e", "5Po", "4nb", "aqS", "2v", "0UL", "8Qd", "39v", "4v3", "4cR", "b3E", "aRO", "Ub", "02X", "0yQ", "Zc", "63T", "bxL", "4lS", "4y2", "0G", "237", "0ZM", "2on", "7Da", "4ac", "4OO", "6zl", "WS", "B2", "47C", "6Fh", "kW", "0He", "0fI", "2Sj", "6hD", "5MF", "4PW", "4E6", "HK", "0ky", "0EU", "fg", "6KX", "5nZ", "45r", "502", "if", "0JT", "0dx", "GJ", "4J7", "5Ow", "4Rf", "6gE", "Jz", "0iH", "q7", "dV", "6Ii", "48B", "6Wk", "4bH", "o5", "zT", "Tx", "03B", "4Ld", "6yG", "4T5", "4AT", "0zz", "YH", "3l", "0TV", "4ox", "5j8", "5e9", "54P", "1Kv", "xe", "VI", "01s", "4NU", "7kW", "62N", "4Ce", "0xK", "2Mh", "uU", "0Vg", "4mI", "6Xj", "4K0", "5Np", "11V", "FM", "ha", "0KS", "44u", "515", "6Hn", "49E", "48", "eQ", "3MM", "0hO", "4Sa", "6fB", "6iC", "5LA", "0gN", "t", "jP", "0Ib", "46D", "6Go", "hyt", "bEO", "0DR", "0Q3", "IL", "8cf", "4QP", "4D1", "4OR", "4Z3", "WN", "00t", "0ZP", "yb", "hgv", "55W", "4lN", "6Ym", "0Z", "a3", "0yL", "2Lo", "63I", "4Bb", "4Mc", "7ha", "2Cn", "02E", "n2", "2mB", "6Vl", "4cO", "bTL", "a5F", "2k", "0UQ", "86m", "XO", "4U2", "5Pr", "0Gy", "dK", "4i6", "5lv", "5BZ", "6gX", "Jg", "0iU", "R6", "GW", "6jh", "5Oj", "45o", "6DD", "3oK", "0JI", "0EH", "fz", "6KE", "5nG", "4PJ", "6ei", "HV", "0kd", "0fT", "Ef", "6hY", "690", "4sV", "4f7", "kJ", "0Hx", "uH", "0Vz", "4mT", "4x5", "5F8", "4Cx", "0xV", "0m7", "VT", "C5", "4NH", "7kJ", "6UG", "54M", "1Kk", "xx", "3q", "0TK", "4oe", "6ZF", "60b", "4AI", "L4", "YU", "Te", "0wW", "4Ly", "5I9", "4w4", "4bU", "1IZ", "zI", "he", "0KW", "44q", "511", "4K4", "5Nt", "11R", "FI", "Ky", "0hK", "4Se", "6fF", "6Hj", "49A", "p4", "eU", "jT", "0If", "4rH", "6Gk", "6iG", "5LE", "0gJ", "p", "IH", "0jz", "4QT", "4D5", "5z8", "5oY", "0DV", "gd", "0ZT", "yf", "6TY", "4az", "4OV", "4Z7", "WJ", "00p", "0yH", "Zz", "63M", "4Bf", "4lJ", "6Yi", "tV", "a7", "n6", "2mF", "6Vh", "4cK", "4Mg", "6xD", "2Cj", "02A", "1kX", "XK", "4U6", "5Pv", "6N9", "7Ky", "2o", "0UU", "665", "73u", "Jc", "0iQ", "8Ne", "dO", "4i2", "5lr", "45k", "7Ta", "3oO", "0JM", "R2", "GS", "6jl", "5On", "4PN", "6em", "HR", "8bx", "0EL", "24g", "6KA", "5nC", "47Z", "4f3", "kN", "8Ad", "0fP", "Eb", "aBO", "694", "62W", "byO", "0xR", "0m3", "1D", "224", "4mP", "4x1", "6UC", "54I", "1Ko", "2nm", "VP", "C1", "4NL", "7kN", "60f", "4AM", "L0", "YQ", "3u", "0TO", "4oa", "6ZB", "438", "4bQ", "8Pg", "zM", "Ta", "0wS", "b2F", "aSL", "6Hf", "49M", "40", "eY", "Ku", "0hG", "4Si", "6fJ", "4K8", "5Nx", "0ew", "FE", "hi", "8BC", "4pu", "5u5", "5z4", "5oU", "0DZ", "gh", "ID", "0jv", "4QX", "4D9", "6iK", "5LI", "0gF", "Dt", "jX", "0Ij", "46L", "6Gg", "4lF", "6Ye", "0R", "0Wh", "0yD", "Zv", "63A", "4Bj", "4OZ", "6zy", "WF", "0tt", "0ZX", "yj", "5d6", "4av", "4nw", "5k7", "2c", "0UY", "1kT", "XG", "61p", "5Pz", "4Mk", "6xH", "Uw", "02M", "0Xi", "2mJ", "6Vd", "4cG", "0dm", "2QN", "7zA", "5Ob", "45g", "6DL", "is", "0JA", "0Gq", "dC", "acn", "48W", "4Rs", "5W3", "Jo", "94l", "12u", "En", "5X2", "5MS", "47V", "alo", "kB", "0Hp", "1Ua", "fr", "6KM", "5nO", "4PB", "6ea", "3Nn", "0kl", "3Pl", "01f", "bts", "7kB", "6UO", "54E", "1Kc", "xp", "1H", "0Vr", "59u", "a6e", "5F0", "4Cp", "85N", "d3F", "Tm", "03W", "4Lq", "5I1", "434", "56t", "0Ys", "zA", "3y", "0TC", "4om", "6ZN", "60j", "4AA", "0zo", "2OL", "Kq", "0hC", "4Sm", "6fN", "6Hb", "49I", "44", "27D", "hm", "8BG", "44y", "519", "aAl", "bdn", "0es", "FA", "1o2", "0jr", "bko", "70V", "5z0", "5oQ", "305", "gl", "28E", "0In", "46H", "6Gc", "6iO", "5LM", "0gB", "x", "1ia", "Zr", "63E", "4Bn", "4lB", "6Ya", "0V", "0Wl", "8SD", "yn", "5d2", "4ar", "b1e", "aPo", "WB", "00x", "1kP", "XC", "61t", "744", "4ns", "5k3", "2g", "9Ld", "0Xm", "2mN", "7FA", "4cC", "4Mo", "6xL", "Us", "02I", "45c", "6DH", "iw", "0JE", "0di", "2QJ", "6jd", "5Of", "4Rw", "5W7", "Jk", "0iY", "0Gu", "dG", "6Ix", "48S", "47R", "6Fy", "kF", "0Ht", "0fX", "Ej", "5X6", "5MW", "4PF", "6ee", "HZ", "0kh", "0ED", "fv", "6KI", "5nK", "6UK", "54A", "1Kg", "xt", "VX", "C9", "4ND", "7kF", "5F4", "4Ct", "0xZ", "2My", "1L", "0Vv", "4mX", "4x9", "430", "4bY", "0Yw", "zE", "Ti", "03S", "4Lu", "5I5", "60n", "4AE", "L8", "YY", "wu", "0TG", "4oi", "6ZJ" }; #endif redis-8.0.2/src/crc64.c000066400000000000000000000342311501533116600145070ustar00rootroot00000000000000/* Copyright (c) 2014, Matt Stancliff * Copyright (c) 2020, Amazon Web Services * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #include #include "crc64.h" #include "crcspeed.h" #include "redisassert.h" #include "testhelp.h" static uint64_t crc64_table[8][256] = {{0}}; #define POLY UINT64_C(0xad93d23594c935a9) /******************** BEGIN GENERATED PYCRC FUNCTIONS ********************/ /** * Generated on Sun Dec 21 14:14:07 2014, * by pycrc v0.8.2, https://www.tty1.net/pycrc/ * * LICENSE ON GENERATED CODE: * ========================== * As of version 0.6, pycrc is released under the terms of the MIT licence. * The code generated by pycrc is not considered a substantial portion of the * software, therefore the author of pycrc will not claim any copyright on * the generated code. * ========================== * * CRC configuration: * Width = 64 * Poly = 0xad93d23594c935a9 * XorIn = 0xffffffffffffffff * ReflectIn = True * XorOut = 0x0000000000000000 * ReflectOut = True * Algorithm = bit-by-bit-fast * * Modifications after generation (by matt): * - included finalize step in-line with update for single-call generation * - re-worked some inner variable architectures * - adjusted function parameters to match expected prototypes. *****************************************************************************/ /** * Reflect all bits of a \a data word of \a data_len bytes. * * \param data The data word to be reflected. * \param data_len The width of \a data expressed in number of bits. * \return The reflected data. *****************************************************************************/ static inline uint_fast64_t crc_reflect(uint_fast64_t data, size_t data_len) { /* only ever called for data_len == 64 in this codebase * * Borrowed from bit twiddling hacks, original in the public domain. * https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel * Extended to 64 bits, and added byteswap for final 3 steps. * 16-30x 64-bit operations, no comparisons (16 for native byteswap, 30 for pure C) */ assert(data_len <= 64); /* swap odd and even bits */ data = ((data >> 1) & 0x5555555555555555ULL) | ((data & 0x5555555555555555ULL) << 1); /* swap consecutive pairs */ data = ((data >> 2) & 0x3333333333333333ULL) | ((data & 0x3333333333333333ULL) << 2); /* swap nibbles ... */ data = ((data >> 4) & 0x0F0F0F0F0F0F0F0FULL) | ((data & 0x0F0F0F0F0F0F0F0FULL) << 4); #if defined(__GNUC__) || defined(__clang__) data = __builtin_bswap64(data); #else /* swap bytes */ data = ((data >> 8) & 0x00FF00FF00FF00FFULL) | ((data & 0x00FF00FF00FF00FFULL) << 8); /* swap 2-byte long pairs */ data = ( data >> 16 & 0xFFFF0000FFFFULL) | ((data & 0xFFFF0000FFFFULL) << 16); /* swap 4-byte quads */ data = ( data >> 32 & 0xFFFFFFFFULL) | ((data & 0xFFFFFFFFULL) << 32); #endif /* adjust for non-64-bit reversals */ return data >> (64 - data_len); } /** * Update the crc value with new data. * * \param crc The current crc value. * \param data Pointer to a buffer of \a data_len bytes. * \param data_len Number of bytes in the \a data buffer. * \return The updated crc value. ******************************************************************************/ uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len) { const uint8_t *data = in_data; unsigned long long bit; for (uint64_t offset = 0; offset < len; offset++) { uint8_t c = data[offset]; for (uint_fast8_t i = 0x01; i & 0xff; i <<= 1) { bit = crc & 0x8000000000000000; if (c & i) { bit = !bit; } crc <<= 1; if (bit) { crc ^= POLY; } } crc &= 0xffffffffffffffff; } crc = crc & 0xffffffffffffffff; return crc_reflect(crc, 64) ^ 0x0000000000000000; } /******************** END GENERATED PYCRC FUNCTIONS ********************/ /* Initializes the 16KB lookup tables. */ void crc64_init(void) { crcspeed64native_init(_crc64, crc64_table); } /* Compute crc64 */ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) { return crcspeed64native(crc64_table, crc, (void *) s, l); } /* Test main */ #ifdef REDIS_TEST #include static void genBenchmarkRandomData(char *data, int count); static int bench_crc64(unsigned char *data, uint64_t size, long long passes, uint64_t check, char *name, int csv); static void bench_combine(char *label, uint64_t size, uint64_t expect, int csv); long long _ustime(void); #include #include #include #include #include #include #include "zmalloc.h" #include "crccombine.h" long long _ustime(void) { struct timeval tv; long long ust; gettimeofday(&tv, NULL); ust = ((long long)tv.tv_sec)*1000000; ust += tv.tv_usec; return ust; } static int bench_crc64(unsigned char *data, uint64_t size, long long passes, uint64_t check, char *name, int csv) { uint64_t min = size, hash; long long original_start = _ustime(), original_end; for (long long i=passes; i > 0; i--) { hash = crc64(0, data, size); } original_end = _ustime(); min = (original_end - original_start) * 1000 / passes; /* approximate nanoseconds without nstime */ if (csv) { printf("%s,%" PRIu64 ",%" PRIu64 ",%d\n", name, size, (1000 * size) / min, hash == check); } else { printf("test size=%" PRIu64 " algorithm=%s %" PRIu64 " M/sec matches=%d\n", size, name, (1000 * size) / min, hash == check); } return hash != check; } const uint64_t BENCH_RPOLY = UINT64_C(0x95ac9329ac4bc9b5); static void bench_combine(char *label, uint64_t size, uint64_t expect, int csv) { uint64_t min = size, start = expect, thash = expect ^ (expect >> 17); long long original_start = _ustime(), original_end; for (int i=0; i < 1000; i++) { crc64_combine(thash, start, size, BENCH_RPOLY, 64); } original_end = _ustime(); /* ran 1000 times, want ns per, counted us per 1000 ... */ min = original_end - original_start; if (csv) { printf("%s,%" PRIu64 ",%" PRIu64 "\n", label, size, min); } else { printf("%s size=%" PRIu64 " in %" PRIu64 " nsec\n", label, size, min); } } static void genBenchmarkRandomData(char *data, int count) { static uint32_t state = 1234; int i = 0; while (count--) { state = (state*1103515245+12345); data[i++] = '0'+((state>>16)&63); } } #define UNUSED(x) (void)(x) int crc64Test(int argc, char *argv[], int flags) { uint64_t crc64_test_size = 0; int i, lastarg, csv = 0, loop = 0, combine = 0, testAll = 0; again: if ((argc>=4) && (!strcmp(argv[3],"custom"))) { for (i = 4; i < argc; i++) { lastarg = (i == (argc - 1)); if (!strcmp(argv[i], "--help")) { goto usage; } else if (!strcmp(argv[i], "--csv")) { csv = 1; } else if (!strcmp(argv[i], "-l")) { loop = 1; } else if (!strcmp(argv[i], "--crc")) { if (lastarg) goto invalid; crc64_test_size = atoll(argv[++i]); } else if (!strcmp(argv[i], "--combine")) { combine = 1; } else { invalid: printf("Invalid option \"%s\" or option argument missing\n\n", argv[i]); usage: printf( "Usage: crc64 [OPTIONS]\n\n" " --csv Output in CSV format\n" " -l Loop. Run the tests forever\n" " --crc Benchmark crc64 faster options, using a buffer this big, and quit when done.\n" " --combine Benchmark crc64 combine value ranges and timings.\n" ); return 1; } } } else { crc64_test_size = 50000; testAll = 1; if (flags & REDIS_TEST_ACCURATE) crc64_test_size = 5000000; } if ((crc64_test_size == 0 && combine == 0) || testAll) { crc64_init(); printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", (uint64_t)_crc64(0, "123456789", 9)); printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n", (uint64_t)crc64(0, (unsigned char*)"123456789", 9)); char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " "do eiusmod tempor incididunt ut labore et dolore magna " "aliqua. Ut enim ad minim veniam, quis nostrud exercitation " "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis " "aute irure dolor in reprehenderit in voluptate velit esse " "cillum dolore eu fugiat nulla pariatur. Excepteur sint " "occaecat cupidatat non proident, sunt in culpa qui officia " "deserunt mollit anim id est laborum."; printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n", (uint64_t)_crc64(0, li, sizeof(li))); printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n", (uint64_t)crc64(0, (unsigned char*)li, sizeof(li))); if (!testAll) return 0; } int init_this_loop = 1; long long init_start, init_end; do { unsigned char* data = NULL; uint64_t passes = 0; if (crc64_test_size) { data = zmalloc(crc64_test_size); genBenchmarkRandomData((char*)data, crc64_test_size); /* We want to hash about 1 gig of data in total, looped, to get a good * idea of our performance. */ passes = (UINT64_C(0x100000000) / crc64_test_size); passes = passes >= 2 ? passes : 2; passes = passes <= 1000 ? passes : 1000; } crc64_init(); /* warm up the cache */ set_crc64_cutoffs(crc64_test_size+1, crc64_test_size+1); uint64_t expect = crc64(0, data, crc64_test_size); if ((!combine || testAll) && crc64_test_size) { if (csv && init_this_loop) printf("algorithm,buffer,performance,crc64_matches\n"); /* get the single-character version for single-byte Redis behavior */ set_crc64_cutoffs(0, crc64_test_size+1); assert(!bench_crc64(data, crc64_test_size, passes, expect, "crc_1byte", csv)); set_crc64_cutoffs(crc64_test_size+1, crc64_test_size+1); /* run with 8-byte "single" path, crcfaster */ assert(!(bench_crc64(data, crc64_test_size, passes, expect, "crcspeed", csv))); /* run with dual 8-byte paths */ set_crc64_cutoffs(1, crc64_test_size+1); assert(!(bench_crc64(data, crc64_test_size, passes, expect, "crcdual", csv))); /* run with tri 8-byte paths */ set_crc64_cutoffs(1, 1); assert(!(bench_crc64(data, crc64_test_size, passes, expect, "crctri", csv))); /* Be free memory region, be free. */ zfree(data); data = NULL; } uint64_t INIT_SIZE = UINT64_C(0xffffffffffffffff); if (combine || testAll) { if (init_this_loop) { init_start = _ustime(); crc64_combine( UINT64_C(0xdeadbeefdeadbeef), UINT64_C(0xfeebdaedfeebdaed), INIT_SIZE, BENCH_RPOLY, 64); init_end = _ustime(); init_end -= init_start; init_end *= 1000; if (csv) { printf("operation,size,nanoseconds\n"); printf("init_64,%" PRIu64 ",%" PRIu64 "\n", INIT_SIZE, (uint64_t)init_end); } else { printf("init_64 size=%" PRIu64 " in %" PRIu64 " nsec\n", INIT_SIZE, (uint64_t)init_end); } /* use the hash itself as the size (unpredictable) */ bench_combine("hash_as_size_combine", crc64_test_size, expect, csv); /* let's do something big (predictable, so fast) */ bench_combine("largest_combine", INIT_SIZE, expect, csv); } bench_combine("combine", crc64_test_size, expect, csv); } init_this_loop = 0; /* step down by ~1.641 for a range of test sizes */ crc64_test_size -= (crc64_test_size >> 2) + (crc64_test_size >> 3) + (crc64_test_size >> 6); } while (crc64_test_size > 3); if (loop) goto again; return 0; } #endif redis-8.0.2/src/crc64.h000066400000000000000000000003431501533116600145110ustar00rootroot00000000000000#ifndef CRC64_H #define CRC64_H #include void crc64_init(void); uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); #ifdef REDIS_TEST int crc64Test(int argc, char *argv[], int flags); #endif #endif redis-8.0.2/src/crccombine.c000066400000000000000000000163131501533116600156730ustar00rootroot00000000000000#include #include #include #if defined(__i386__) || defined(__X86_64__) #include #endif #include "crccombine.h" /* Copyright (C) 2013 Mark Adler * Copyright (C) 2019-2024 Josiah Carlson * Portions originally from: crc64.c Version 1.4 16 Dec 2013 Mark Adler * Modifications by Josiah Carlson * - Added implementation variations with sample timings for gf_matrix_times*() * - Most folks would be best using gf2_matrix_times_vec or * gf2_matrix_times_vec2, unless some processor does AVX2 fast. * - This is the implementation of the MERGE_CRC macro defined in * crcspeed.c (which calls crc_combine()), and is a specialization of the * generic crc_combine() (and related from the 2013 edition of Mark Adler's * crc64.c)) for the sake of clarity and performance. This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Mark Adler madler@alumni.caltech.edu */ #define STATIC_ASSERT(VVV) do {int test = 1 / (VVV);test++;} while (0) #if !((defined(__i386__) || defined(__X86_64__))) /* This cuts 40% of the time vs bit-by-bit. */ uint64_t gf2_matrix_times_switch(uint64_t *mat, uint64_t vec) { /* * Without using any vector math, this handles 4 bits at a time, * and saves 40+% of the time compared to the bit-by-bit version. Use if you * have no vector compile option available to you. With cache, we see: * E5-2670 ~1-2us to extend ~1 meg 64 bit hash */ uint64_t sum; sum = 0; while (vec) { /* reversing the case order is ~10% slower on Xeon E5-2670 */ switch (vec & 15) { case 15: sum ^= *mat ^ *(mat+1) ^ *(mat+2) ^ *(mat+3); break; case 14: sum ^= *(mat+1) ^ *(mat+2) ^ *(mat+3); break; case 13: sum ^= *mat ^ *(mat+2) ^ *(mat+3); break; case 12: sum ^= *(mat+2) ^ *(mat+3); break; case 11: sum ^= *mat ^ *(mat+1) ^ *(mat+3); break; case 10: sum ^= *(mat+1) ^ *(mat+3); break; case 9: sum ^= *mat ^ *(mat+3); break; case 8: sum ^= *(mat+3); break; case 7: sum ^= *mat ^ *(mat+1) ^ *(mat+2); break; case 6: sum ^= *(mat+1) ^ *(mat+2); break; case 5: sum ^= *mat ^ *(mat+2); break; case 4: sum ^= *(mat+2); break; case 3: sum ^= *mat ^ *(mat+1); break; case 2: sum ^= *(mat+1); break; case 1: sum ^= *mat; break; default: break; } vec >>= 4; mat += 4; } return sum; } #define CRC_MULTIPLY gf2_matrix_times_switch #else /* Warning: here there be dragons involving vector math, and macros to save us from repeating the same information over and over. */ uint64_t gf2_matrix_times_vec2(uint64_t *mat, uint64_t vec) { /* * Uses xmm registers on x86, works basically everywhere fast, doing * cycles of movqda, mov, shr, pand, and, pxor, at least on gcc 8. * Is 9-11x faster than original. * E5-2670 ~29us to extend ~1 meg 64 bit hash * i3-8130U ~22us to extend ~1 meg 64 bit hash */ v2uq sum = {0, 0}, *mv2 = (v2uq*)mat; /* this table allows us to eliminate conditions during gf2_matrix_times_vec2() */ static v2uq masks2[4] = { {0,0}, {-1,0}, {0,-1}, {-1,-1}, }; /* Almost as beautiful as gf2_matrix_times_vec, but only half as many * bits per step, so we need 2 per chunk4 operation. Faster in my tests. */ #define DO_CHUNK4() \ sum ^= (*mv2++) & masks2[vec & 3]; \ vec >>= 2; \ sum ^= (*mv2++) & masks2[vec & 3]; \ vec >>= 2 #define DO_CHUNK16() \ DO_CHUNK4(); \ DO_CHUNK4(); \ DO_CHUNK4(); \ DO_CHUNK4() DO_CHUNK16(); DO_CHUNK16(); DO_CHUNK16(); DO_CHUNK16(); STATIC_ASSERT(sizeof(uint64_t) == 8); STATIC_ASSERT(sizeof(long long unsigned int) == 8); return sum[0] ^ sum[1]; } #undef DO_CHUNK16 #undef DO_CHUNK4 #define CRC_MULTIPLY gf2_matrix_times_vec2 #endif static void gf2_matrix_square(uint64_t *square, uint64_t *mat, uint8_t dim) { unsigned n; for (n = 0; n < dim; n++) square[n] = CRC_MULTIPLY(mat, mat[n]); } /* Turns out our Redis / Jones CRC cycles at this point, so we can support * more than 64 bits of extension if we want. Trivially. */ static uint64_t combine_cache[64][64]; /* Mark Adler has some amazing updates to crc.c in his crcany repository. I * like static caches, and not worrying about finding cycles generally. We are * okay to spend the 32k of memory here, leaving the algorithm unchanged from * as it was a decade ago, and be happy that it costs <200 microseconds to * init, and that subsequent calls to the combine function take under 100 * nanoseconds. We also note that the crcany/crc.c code applies to any CRC, and * we are currently targeting one: Jones CRC64. */ void init_combine_cache(uint64_t poly, uint8_t dim) { unsigned n, cache_num = 0; combine_cache[1][0] = poly; int prev = 1; uint64_t row = 1; for (n = 1; n < dim; n++) { combine_cache[1][n] = row; row <<= 1; } gf2_matrix_square(combine_cache[0], combine_cache[1], dim); gf2_matrix_square(combine_cache[1], combine_cache[0], dim); /* do/while to overwrite the first two layers, they are not used, but are * re-generated in the last two layers for the Redis polynomial */ do { gf2_matrix_square(combine_cache[cache_num], combine_cache[cache_num + prev], dim); prev = -1; } while (++cache_num < 64); } /* Return the CRC-64 of two sequential blocks, where crc1 is the CRC-64 of the * first block, crc2 is the CRC-64 of the second block, and len2 is the length * of the second block. * * If you want reflections on your CRCs; do them outside before / after. * WARNING: if you enable USE_STATIC_COMBINE_CACHE to make this fast, you MUST * ALWAYS USE THE SAME POLYNOMIAL, otherwise you will get the wrong results. * You MAY bzero() the even/odd static arrays, which will induce a re-cache on * next call as a work-around, but ... maybe just parameterize the cached * models at that point like Mark Adler does in modern crcany/crc.c . */ uint64_t crc64_combine(uint64_t crc1, uint64_t crc2, uintmax_t len2, uint64_t poly, uint8_t dim) { /* degenerate case */ if (len2 == 0) return crc1; unsigned cache_num = 0; if (combine_cache[0][0] == 0) { init_combine_cache(poly, dim); } /* apply len2 zeros to crc1 (first square will put the operator for one zero byte, eight zero bits, in even) */ do { /* apply zeros operator for this bit of len2 */ if (len2 & 1) crc1 = CRC_MULTIPLY(combine_cache[cache_num], crc1); len2 >>= 1; cache_num = (cache_num + 1) & 63; /* if no more bits set, then done */ } while (len2 != 0); /* return combined crc */ crc1 ^= crc2; return crc1; } #undef CRC_MULTIPLY redis-8.0.2/src/crccombine.h000066400000000000000000000005011501533116600156700ustar00rootroot00000000000000 #include /* mask types */ typedef unsigned long long v2uq __attribute__ ((vector_size (16))); uint64_t gf2_matrix_times_vec2(uint64_t *mat, uint64_t vec); void init_combine_cache(uint64_t poly, uint8_t dim); uint64_t crc64_combine(uint64_t crc1, uint64_t crc2, uintmax_t len2, uint64_t poly, uint8_t dim); redis-8.0.2/src/crcspeed.c000066400000000000000000000324671501533116600153670ustar00rootroot00000000000000/* * Copyright (C) 2013 Mark Adler * Copyright (C) 2019-2024 Josiah Carlson * Originally by: crc64.c Version 1.4 16 Dec 2013 Mark Adler * Modifications by Matt Stancliff : * - removed CRC64-specific behavior * - added generation of lookup tables by parameters * - removed inversion of CRC input/result * - removed automatic initialization in favor of explicit initialization * Modifications by Josiah Carlson * - Added case/vector/AVX/+ versions of crc combine function; see crccombine.c * - added optional static cache * - Modified to use 1 thread to: * - Partition large crc blobs into 2-3 segments * - Process the 2-3 segments in parallel * - Merge the resulting crcs * -> Resulting in 10-90% performance boost for data > 1 meg * - macro-ized to reduce copy/pasta This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Mark Adler madler@alumni.caltech.edu */ #include "crcspeed.h" #include "crccombine.h" #define CRC64_LEN_MASK UINT64_C(0x7ffffffffffffff8) #define CRC64_REVERSED_POLY UINT64_C(0x95ac9329ac4bc9b5) /* Fill in a CRC constants table. */ void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) { uint64_t crc; /* generate CRCs for all single byte sequences */ for (int n = 0; n < 256; n++) { unsigned char v = n; table[0][n] = crcfn(0, &v, 1); } /* generate nested CRC table for future slice-by-8/16/24+ lookup */ for (int n = 0; n < 256; n++) { crc = table[0][n]; for (int k = 1; k < 8; k++) { crc = table[0][crc & 0xff] ^ (crc >> 8); table[k][n] = crc; } } #if USE_STATIC_COMBINE_CACHE /* initialize combine cache for CRC stapling for slice-by 16/24+ */ init_combine_cache(CRC64_REVERSED_POLY, 64); #endif } void crcspeed16little_init(crcfn16 crcfn, uint16_t table[8][256]) { uint16_t crc; /* generate CRCs for all single byte sequences */ for (int n = 0; n < 256; n++) { table[0][n] = crcfn(0, &n, 1); } /* generate nested CRC table for future slice-by-8 lookup */ for (int n = 0; n < 256; n++) { crc = table[0][n]; for (int k = 1; k < 8; k++) { crc = table[0][(crc >> 8) & 0xff] ^ (crc << 8); table[k][n] = crc; } } } /* Reverse the bytes in a 64-bit word. */ static inline uint64_t rev8(uint64_t a) { #if defined(__GNUC__) || defined(__clang__) return __builtin_bswap64(a); #else uint64_t m; m = UINT64_C(0xff00ff00ff00ff); a = ((a >> 8) & m) | (a & m) << 8; m = UINT64_C(0xffff0000ffff); a = ((a >> 16) & m) | (a & m) << 16; return a >> 32 | a << 32; #endif } /* This function is called once to initialize the CRC table for use on a big-endian architecture. */ void crcspeed64big_init(crcfn64 fn, uint64_t big_table[8][256]) { /* Create the little endian table then reverse all the entries. */ crcspeed64little_init(fn, big_table); for (int k = 0; k < 8; k++) { for (int n = 0; n < 256; n++) { big_table[k][n] = rev8(big_table[k][n]); } } } void crcspeed16big_init(crcfn16 fn, uint16_t big_table[8][256]) { /* Create the little endian table then reverse all the entries. */ crcspeed16little_init(fn, big_table); for (int k = 0; k < 8; k++) { for (int n = 0; n < 256; n++) { big_table[k][n] = rev8(big_table[k][n]); } } } /* Note: doing all of our crc/next modifications *before* the crc table * references is an absolute speedup on all CPUs tested. So... keep these * macros separate. */ #define DO_8_1(crc, next) \ crc ^= *(uint64_t *)next; \ next += 8 #define DO_8_2(crc) \ crc = little_table[7][(uint8_t)crc] ^ \ little_table[6][(uint8_t)(crc >> 8)] ^ \ little_table[5][(uint8_t)(crc >> 16)] ^ \ little_table[4][(uint8_t)(crc >> 24)] ^ \ little_table[3][(uint8_t)(crc >> 32)] ^ \ little_table[2][(uint8_t)(crc >> 40)] ^ \ little_table[1][(uint8_t)(crc >> 48)] ^ \ little_table[0][crc >> 56] #define CRC64_SPLIT(div) \ olen = len; \ next2 = next1 + ((len / div) & CRC64_LEN_MASK); \ len = (next2 - next1) #define MERGE_CRC(crcn) \ crc1 = crc64_combine(crc1, crcn, next2 - next1, CRC64_REVERSED_POLY, 64) #define MERGE_END(last, DIV) \ len = olen - ((next2 - next1) * DIV); \ next1 = last /* Variables so we can change for benchmarking; these seem to be fairly * reasonable for Intel CPUs made since 2010. Please adjust as necessary if * or when your CPU has more load / execute units. We've written benchmark code * to help you tune your platform, see crc64Test. */ #if defined(__i386__) || defined(__X86_64__) static size_t CRC64_TRI_CUTOFF = (2*1024); static size_t CRC64_DUAL_CUTOFF = (128); #else static size_t CRC64_TRI_CUTOFF = (16*1024); static size_t CRC64_DUAL_CUTOFF = (1024); #endif void set_crc64_cutoffs(size_t dual_cutoff, size_t tri_cutoff) { CRC64_DUAL_CUTOFF = dual_cutoff; CRC64_TRI_CUTOFF = tri_cutoff; } /* Calculate a non-inverted CRC multiple bytes at a time on a little-endian * architecture. If you need inverted CRC, invert *before* calling and invert * *after* calling. * 64 bit crc = process 8/16/24 bytes at once; */ uint64_t crcspeed64little(uint64_t little_table[8][256], uint64_t crc1, void *buf, size_t len) { unsigned char *next1 = buf; if (CRC64_DUAL_CUTOFF < 1) { goto final; } /* process individual bytes until we reach an 8-byte aligned pointer */ while (len && ((uintptr_t)next1 & 7) != 0) { crc1 = little_table[0][(crc1 ^ *next1++) & 0xff] ^ (crc1 >> 8); len--; } if (len > CRC64_TRI_CUTOFF) { /* 24 bytes per loop, doing 3 parallel 8 byte chunks at a time */ unsigned char *next2, *next3; uint64_t olen, crc2=0, crc3=0; CRC64_SPLIT(3); /* len is now the length of the first segment, the 3rd segment possibly * having extra bytes to clean up at the end */ next3 = next2 + len; while (len >= 8) { len -= 8; DO_8_1(crc1, next1); DO_8_1(crc2, next2); DO_8_1(crc3, next3); DO_8_2(crc1); DO_8_2(crc2); DO_8_2(crc3); } /* merge the 3 crcs */ MERGE_CRC(crc2); MERGE_CRC(crc3); MERGE_END(next3, 3); } else if (len > CRC64_DUAL_CUTOFF) { /* 16 bytes per loop, doing 2 parallel 8 byte chunks at a time */ unsigned char *next2; uint64_t olen, crc2=0; CRC64_SPLIT(2); /* len is now the length of the first segment, the 2nd segment possibly * having extra bytes to clean up at the end */ while (len >= 8) { len -= 8; DO_8_1(crc1, next1); DO_8_1(crc2, next2); DO_8_2(crc1); DO_8_2(crc2); } /* merge the 2 crcs */ MERGE_CRC(crc2); MERGE_END(next2, 2); } /* We fall through here to handle our = 8) { len -= 8; DO_8_1(crc1, next1); DO_8_2(crc1); } final: /* process remaining bytes (can't be larger than 8) */ while (len) { crc1 = little_table[0][(crc1 ^ *next1++) & 0xff] ^ (crc1 >> 8); len--; } return crc1; } /* clean up our namespace */ #undef DO_8_1 #undef DO_8_2 #undef CRC64_SPLIT #undef MERGE_CRC #undef MERGE_END #undef CRC64_REVERSED_POLY #undef CRC64_LEN_MASK /* note: similar perf advantages can be had for long strings in crc16 using all * of the same optimizations as above; though this is unnecessary. crc16 is * normally used to shard keys; not hash / verify data, so is used on shorter * data that doesn't warrant such changes. */ uint16_t crcspeed16little(uint16_t little_table[8][256], uint16_t crc, void *buf, size_t len) { unsigned char *next = buf; /* process individual bytes until we reach an 8-byte aligned pointer */ while (len && ((uintptr_t)next & 7) != 0) { crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8); len--; } /* fast middle processing, 8 bytes (aligned!) per loop */ while (len >= 8) { uint64_t n = *(uint64_t *)next; crc = little_table[7][(n & 0xff) ^ ((crc >> 8) & 0xff)] ^ little_table[6][((n >> 8) & 0xff) ^ (crc & 0xff)] ^ little_table[5][(n >> 16) & 0xff] ^ little_table[4][(n >> 24) & 0xff] ^ little_table[3][(n >> 32) & 0xff] ^ little_table[2][(n >> 40) & 0xff] ^ little_table[1][(n >> 48) & 0xff] ^ little_table[0][n >> 56]; next += 8; len -= 8; } /* process remaining bytes (can't be larger than 8) */ while (len) { crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8); len--; } return crc; } /* Calculate a non-inverted CRC eight bytes at a time on a big-endian * architecture. */ uint64_t crcspeed64big(uint64_t big_table[8][256], uint64_t crc, void *buf, size_t len) { unsigned char *next = buf; crc = rev8(crc); while (len && ((uintptr_t)next & 7) != 0) { crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8); len--; } /* note: alignment + 2/3-way processing can probably be handled here nearly the same as above, using our updated DO_8_2 macro. Not included in these changes, as other authors, I don't have big-endian to test with. */ while (len >= 8) { crc ^= *(uint64_t *)next; crc = big_table[0][crc & 0xff] ^ big_table[1][(crc >> 8) & 0xff] ^ big_table[2][(crc >> 16) & 0xff] ^ big_table[3][(crc >> 24) & 0xff] ^ big_table[4][(crc >> 32) & 0xff] ^ big_table[5][(crc >> 40) & 0xff] ^ big_table[6][(crc >> 48) & 0xff] ^ big_table[7][crc >> 56]; next += 8; len -= 8; } while (len) { crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8); len--; } return rev8(crc); } /* WARNING: Completely untested on big endian architecture. Possibly broken. */ uint16_t crcspeed16big(uint16_t big_table[8][256], uint16_t crc_in, void *buf, size_t len) { unsigned char *next = buf; uint64_t crc = crc_in; crc = rev8(crc); while (len && ((uintptr_t)next & 7) != 0) { crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8); len--; } while (len >= 8) { uint64_t n = *(uint64_t *)next; crc = big_table[0][(n & 0xff) ^ ((crc >> (56 - 8)) & 0xff)] ^ big_table[1][((n >> 8) & 0xff) ^ (crc & 0xff)] ^ big_table[2][(n >> 16) & 0xff] ^ big_table[3][(n >> 24) & 0xff] ^ big_table[4][(n >> 32) & 0xff] ^ big_table[5][(n >> 40) & 0xff] ^ big_table[6][(n >> 48) & 0xff] ^ big_table[7][n >> 56]; next += 8; len -= 8; } while (len) { crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8); len--; } return rev8(crc); } /* Return the CRC of buf[0..len-1] with initial crc, processing eight bytes at a time using passed-in lookup table. This selects one of two routines depending on the endianness of the architecture. */ uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf, size_t len) { uint64_t n = 1; return *(char *)&n ? crcspeed64little(table, crc, buf, len) : crcspeed64big(table, crc, buf, len); } uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf, size_t len) { uint64_t n = 1; return *(char *)&n ? crcspeed16little(table, crc, buf, len) : crcspeed16big(table, crc, buf, len); } /* Initialize CRC lookup table in architecture-dependent manner. */ void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]) { uint64_t n = 1; *(char *)&n ? crcspeed64little_init(fn, table) : crcspeed64big_init(fn, table); } void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]) { uint64_t n = 1; *(char *)&n ? crcspeed16little_init(fn, table) : crcspeed16big_init(fn, table); } redis-8.0.2/src/crcspeed.h000066400000000000000000000056151501533116600153670ustar00rootroot00000000000000/* Copyright (c) 2014, Matt Stancliff * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * Neither the name of Redis nor the names of its contributors may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 OWNER 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. */ #ifndef CRCSPEED_H #define CRCSPEED_H #include #include typedef uint64_t (*crcfn64)(uint64_t, const void *, const uint64_t); typedef uint16_t (*crcfn16)(uint16_t, const void *, const uint64_t); void set_crc64_cutoffs(size_t dual_cutoff, size_t tri_cutoff); /* CRC-64 */ void crcspeed64little_init(crcfn64 fn, uint64_t table[8][256]); void crcspeed64big_init(crcfn64 fn, uint64_t table[8][256]); void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]); uint64_t crcspeed64little(uint64_t table[8][256], uint64_t crc, void *buf, size_t len); uint64_t crcspeed64big(uint64_t table[8][256], uint64_t crc, void *buf, size_t len); uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf, size_t len); /* CRC-16 */ void crcspeed16little_init(crcfn16 fn, uint16_t table[8][256]); void crcspeed16big_init(crcfn16 fn, uint16_t table[8][256]); void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]); uint16_t crcspeed16little(uint16_t table[8][256], uint16_t crc, void *buf, size_t len); uint16_t crcspeed16big(uint16_t table[8][256], uint16_t crc, void *buf, size_t len); uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf, size_t len); #endif redis-8.0.2/src/db.c000066400000000000000000003466251501533116600141700ustar00rootroot00000000000000/* * Copyright (c) 2009-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 "server.h" #include "cluster.h" #include "atomicvar.h" #include "latency.h" #include "script.h" #include "functions.h" #include #include #include "bio.h" /*----------------------------------------------------------------------------- * C-level DB API *----------------------------------------------------------------------------*/ static_assert(MAX_KEYSIZES_TYPES == OBJ_TYPE_BASIC_MAX, "Must be equal"); /* Flags for expireIfNeeded */ #define EXPIRE_FORCE_DELETE_EXPIRED 1 #define EXPIRE_AVOID_DELETE_EXPIRED 2 #define EXPIRE_ALLOW_ACCESS_EXPIRED 4 /* Return values for expireIfNeeded */ typedef enum { KEY_VALID = 0, /* Could be volatile and not yet expired, non-volatile, or even non-existing key. */ KEY_EXPIRED, /* Logically expired but not yet deleted. */ KEY_DELETED /* The key was deleted now. */ } keyStatus; static inline keyStatus expireIfNeededWithSlot(redisDb *db, robj *key, int flags, const int keySlot); keyStatus expireIfNeeded(redisDb *db, robj *key, int flags); int keyIsExpired(redisDb *db, robj *key); static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de); static inline dictEntry *dbFindWithKeySlot(redisDb *db, void *key, int keySlot); static inline dictEntry *dbFindExpiresWithKeySlot(redisDb *db, void *key, int keySlot); /* Update LFU when an object is accessed. * Firstly, decrement the counter if the decrement time is reached. * Then logarithmically increment the counter, and update the access time. */ void updateLFU(robj *val) { unsigned long counter = LFUDecrAndReturn(val); counter = LFULogIncr(counter); val->lru = (LFUGetTimeInMinutes()<<8) | counter; } /* * Update histogram of keys-sizes * * It is used to track the distribution of key sizes in the dataset. It is updated * every time key's length is modified. Available to user via INFO command. * * The histogram is a base-2 logarithmic histogram, with 64 bins. The i'th bin * represents the number of keys with a size in the range 2^i and 2^(i+1) * exclusive. oldLen/newLen must be smaller than 2^48, and if their value * equals -1, it means that the key is being created/deleted, respectively. Each * data type has its own histogram and it is per database (In addition, there is * histogram per slot for future cluster use). * * Example mapping of key lengths to bins: * [1,2)->1 [2,4)->2 [4,8)->3 [8,16)->4 ... * * Since strings can be zero length, the histogram also tracks: * [0,1)->0 */ void updateKeysizesHist(redisDb *db, int didx, uint32_t type, int64_t oldLen, int64_t newLen) { if(unlikely(type >= OBJ_TYPE_BASIC_MAX)) return; kvstoreDictMetadata *dictMeta = kvstoreGetDictMetadata(db->keys, didx); kvstoreMetadata *kvstoreMeta = kvstoreGetMetadata(db->keys); if (oldLen > 0) { int old_bin = log2ceil(oldLen) + 1; debugServerAssertWithInfo(server.current_client, NULL, old_bin < MAX_KEYSIZES_BINS); /* If following a key deletion it is last one in slot's dict, then * slot's dict might get released as well. Verify if metadata is not NULL. */ if(dictMeta) dictMeta->keysizes_hist[type][old_bin]--; kvstoreMeta->keysizes_hist[type][old_bin]--; } else { /* here, oldLen can be either 0 or -1 */ if (oldLen == 0) { if (dictMeta) dictMeta->keysizes_hist[type][0]--; kvstoreMeta->keysizes_hist[type][0]--; } } if (newLen > 0) { int new_bin = log2ceil(newLen) + 1; debugServerAssertWithInfo(server.current_client, NULL, new_bin < MAX_KEYSIZES_BINS); /* If following a key deletion it is last one in slot's dict, then * slot's dict might get released as well. Verify if metadata is not NULL. */ if(dictMeta) dictMeta->keysizes_hist[type][new_bin]++; kvstoreMeta->keysizes_hist[type][new_bin]++; } else { /* here, newLen can be either 0 or -1 */ if (newLen == 0) { if (dictMeta) dictMeta->keysizes_hist[type][0]++; kvstoreMeta->keysizes_hist[type][0]++; } } } /* Lookup a key for read or write operations, or return NULL if the key is not * found in the specified DB. This function implements the functionality of * lookupKeyRead(), lookupKeyWrite() and their ...WithFlags() variants. * * 'deref' is an optional output dictEntry reference argument, to get the * associated dictEntry* of the key in case the key is found. * * Side-effects of calling this function: * * 1. A key gets expired if it reached it's TTL. * 2. The key's last access time is updated. * 3. The global keys hits/misses stats are updated (reported in INFO). * 4. If keyspace notifications are enabled, a "keymiss" notification is fired. * * Flags change the behavior of this command: * * LOOKUP_NONE (or zero): No special flags are passed. * LOOKUP_NOTOUCH: Don't alter the last access time of the key. * LOOKUP_NONOTIFY: Don't trigger keyspace event on key miss. * LOOKUP_NOSTATS: Don't increment key hits/misses counters. * LOOKUP_WRITE: Prepare the key for writing (delete expired keys even on * replicas, use separate keyspace stats and events (TODO)). * LOOKUP_NOEXPIRE: Perform expiration check, but avoid deleting the key, * so that we don't have to propagate the deletion. * * Note: this function also returns NULL if the key is logically expired but * still existing, in case this is a replica and the LOOKUP_WRITE is not set. * Even if the key expiry is master-driven, we can correctly report a key is * expired on replicas even if the master is lagging expiring our key via DELs * in the replication link. */ robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) { const int key_slot = getKeySlot(key->ptr); dictEntry *de = dbFindWithKeySlot(db, key->ptr, key_slot); robj *val = NULL; if (de) { val = dictGetVal(de); /* Forcing deletion of expired keys on a replica makes the replica * inconsistent with the master. We forbid it on readonly replicas, but * we have to allow it on writable replicas to make write commands * behave consistently. * * It's possible that the WRITE flag is set even during a readonly * command, since the command may trigger events that cause modules to * perform additional writes. */ int is_ro_replica = server.masterhost && server.repl_slave_ro; int expire_flags = 0; if (flags & LOOKUP_WRITE && !is_ro_replica) expire_flags |= EXPIRE_FORCE_DELETE_EXPIRED; if (flags & LOOKUP_NOEXPIRE) expire_flags |= EXPIRE_AVOID_DELETE_EXPIRED; if (flags & LOOKUP_ACCESS_EXPIRED) expire_flags |= EXPIRE_ALLOW_ACCESS_EXPIRED; if (expireIfNeededWithSlot(db, key, expire_flags, key_slot) != KEY_VALID) { /* The key is no longer valid. */ val = NULL; } } if (val) { /* Update the access time for the ageing algorithm. * Don't do it if we have a saving child, as this will trigger * a copy on write madness. */ if (server.current_client && server.current_client->flags & CLIENT_NO_TOUCH && server.executing_client->cmd->proc != touchCommand) flags |= LOOKUP_NOTOUCH; if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { updateLFU(val); } else { val->lru = LRU_CLOCK(); } } if (!(flags & (LOOKUP_NOSTATS | LOOKUP_WRITE))) server.stat_keyspace_hits++; /* TODO: Use separate hits stats for WRITE */ } else { if (!(flags & (LOOKUP_NONOTIFY | LOOKUP_WRITE))) notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id); if (!(flags & (LOOKUP_NOSTATS | LOOKUP_WRITE))) server.stat_keyspace_misses++; /* TODO: Use separate misses stats and notify event for WRITE */ } if (val && deref) *deref = de; return val; } /* Lookup a key for read operations, or return NULL if the key is not found * in the specified DB. * * This API should not be used when we write to the key after obtaining * the object linked to the key, but only for read only operations. * * This function is equivalent to lookupKey(). The point of using this function * rather than lookupKey() directly is to indicate that the purpose is to read * the key. */ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { serverAssert(!(flags & LOOKUP_WRITE)); return lookupKey(db, key, flags, NULL); } /* Like lookupKeyReadWithFlags(), but does not use any flag, which is the * common case. */ robj *lookupKeyRead(redisDb *db, robj *key) { return lookupKeyReadWithFlags(db,key,LOOKUP_NONE); } /* Lookup a key for write operations, and as a side effect, if needed, expires * the key if its TTL is reached. It's equivalent to lookupKey() with the * LOOKUP_WRITE flag added. * * Returns the linked value object if the key exists or NULL if the key * does not exist in the specified DB. */ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) { return lookupKey(db, key, flags | LOOKUP_WRITE, NULL); } robj *lookupKeyWrite(redisDb *db, robj *key) { return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE); } /* Like lookupKeyWrite(), but accepts an optional dictEntry input, * which can be used if we already have one, thus saving the dbFind call. */ robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref) { return lookupKey(db, key, LOOKUP_NONE | LOOKUP_WRITE, deref); } robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) { robj *o = lookupKeyRead(c->db, key); if (!o) addReplyOrErrorObject(c, reply); return o; } robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) { robj *o = lookupKeyWrite(c->db, key); if (!o) addReplyOrErrorObject(c, reply); return o; } /* Add the key to the DB. It's up to the caller to increment the reference * counter of the value if needed. * * If the update_if_existing argument is false, the program is aborted * if the key already exists, otherwise, it can fall back to dbOverwrite. */ static dictEntry *dbAddInternal(redisDb *db, robj *key, robj *val, int update_if_existing) { dictEntry *existing; int slot = getKeySlot(key->ptr); dictEntry *de = kvstoreDictAddRaw(db->keys, slot, key->ptr, &existing); if (update_if_existing && existing) { dbSetValue(db, key, val, 1, existing); return existing; } serverAssertWithInfo(NULL, key, de != NULL); kvstoreDictSetKey(db->keys, slot, de, sdsdup(key->ptr)); initObjectLRUOrLFU(val); kvstoreDictSetVal(db->keys, slot, de, val); signalKeyAsReady(db, key, val->type); notifyKeyspaceEvent(NOTIFY_NEW,"new",key,db->id); updateKeysizesHist(db, slot, val->type, -1, getObjectLength(val)); /* add hist */ return de; } dictEntry *dbAdd(redisDb *db, robj *key, robj *val) { return dbAddInternal(db, key, val, 0); } /* Returns key's hash slot when cluster mode is enabled, or 0 when disabled. * The only difference between this function and getKeySlot, is that it's not using cached key slot from the current_client * and always calculates CRC hash. * This is useful when slot needs to be calculated for a key that user didn't request for, such as in case of eviction. */ int calculateKeySlot(sds key) { return server.cluster_enabled ? keyHashSlot(key, (int) sdslen(key)) : 0; } /* Return slot-specific dictionary for key based on key's hash slot when cluster mode is enabled, else 0.*/ int getKeySlot(sds key) { /* This is performance optimization that uses pre-set slot id from the current command, * in order to avoid calculation of the key hash. * This optimization is only used when current_client flag `CLIENT_EXECUTING_COMMAND` is set. * It only gets set during the execution of command under `call` method. Other flows requesting * the key slot would fallback to calculateKeySlot. */ if (server.current_client && server.current_client->slot >= 0 && server.current_client->flags & CLIENT_EXECUTING_COMMAND) { debugServerAssertWithInfo(server.current_client, NULL, calculateKeySlot(key)==server.current_client->slot); return server.current_client->slot; } return calculateKeySlot(key); } /* This is a special version of dbAdd() that is used only when loading * keys from the RDB file: the key is passed as an SDS string that is * retained by the function (and not freed by the caller). * * Moreover this function will not abort if the key is already busy, to * give more control to the caller, nor will signal the key as ready * since it is not useful in this context. * * The function returns 1 if the key was added to the database, taking * ownership of the SDS string, otherwise 0 is returned, and is up to the * caller to free the SDS string. */ int dbAddRDBLoad(redisDb *db, sds key, robj *val) { int slot = getKeySlot(key); dictEntry *de = kvstoreDictAddRaw(db->keys, slot, key, NULL); if (de == NULL) return 0; updateKeysizesHist(db, slot, val->type, -1, getObjectLength(val)); /* add hist */ initObjectLRUOrLFU(val); kvstoreDictSetVal(db->keys, slot, de, val); return 1; } /* Overwrite an existing key with a new value. Incrementing the reference * count of the new value is up to the caller. * This function does not modify the expire time of the existing key. * * The 'overwrite' flag is an indication whether this is done as part of a * complete replacement of their key, which can be thought as a deletion and * replacement (in which case we need to emit deletion signals), or just an * update of a value of an existing key (when false). * * The dictEntry input is optional, can be used if we already have one. * * The program is aborted if the key was not already present. */ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de) { int slot = getKeySlot(key->ptr); if (!de) de = kvstoreDictFind(db->keys, slot, key->ptr); serverAssertWithInfo(NULL,key,de != NULL); robj *old = dictGetVal(de); /* Remove old key from keysizes histogram */ updateKeysizesHist(db, slot, old->type, getObjectLength(old), -1); /* remove hist */ val->lru = old->lru; if (overwrite) { /* RM_StringDMA may call dbUnshareStringValue which may free val, so we * need to incr to retain old */ incrRefCount(old); /* Although the key is not really deleted from the database, we regard * overwrite as two steps of unlink+add, so we still need to call the unlink * callback of the module. */ moduleNotifyKeyUnlink(key,old,db->id,DB_FLAG_KEY_OVERWRITE); /* We want to try to unblock any module clients or clients using a blocking XREADGROUP */ signalDeletedKeyAsReady(db,key,old->type); decrRefCount(old); /* Because of RM_StringDMA, old may be changed, so we need get old again */ old = dictGetVal(de); } kvstoreDictSetVal(db->keys, slot, de, val); /* Add new key to keysizes histogram */ updateKeysizesHist(db, slot, val->type, -1, getObjectLength(val)); /* if hash with HFEs, take care to remove from global HFE DS */ if (old->type == OBJ_HASH) hashTypeRemoveFromExpires(&db->hexpires, old); if (server.lazyfree_lazy_server_del) { freeObjAsync(key,old,db->id); } else { decrRefCount(old); } } /* Replace an existing key with a new value, we just replace value and don't * emit any events */ void dbReplaceValue(redisDb *db, robj *key, robj *val) { dbSetValue(db, key, val, 0, NULL); } /* Replace an existing key with a new value, we just replace value and don't * emit any events. * The dictEntry input is optional, can be used if we already have one. */ void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de) { dbSetValue(db, key, val, 0, de); } /* High level Set operation. This function can be used in order to set * a key, whatever it was existing or not, to a new object. * * 1) The ref count of the value object is incremented. * 2) clients WATCHing for the destination key notified. * 3) The expire time of the key is reset (the key is made persistent), * unless 'SETKEY_KEEPTTL' is enabled in flags. * 4) The key lookup can take place outside this interface outcome will be * delivered with 'SETKEY_ALREADY_EXIST' or 'SETKEY_DOESNT_EXIST' * * All the new keys in the database should be created via this interface. * The client 'c' argument may be set to NULL if the operation is performed * in a context where there is no clear client performing the operation. */ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) { setKeyWithDictEntry(c,db,key,val,flags,NULL); } /* Like setKey(), but accepts an optional dictEntry input, * which can be used if we already have one, thus saving the dictFind call. */ void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de) { int keyfound = 0; if (flags & SETKEY_ALREADY_EXIST) keyfound = 1; else if (flags & SETKEY_ADD_OR_UPDATE) keyfound = -1; else if (!(flags & SETKEY_DOESNT_EXIST)) keyfound = (lookupKeyWrite(db,key) != NULL); if (!keyfound) { dbAdd(db,key,val); } else if (keyfound<0) { dbAddInternal(db,key,val,1); } else { dbSetValue(db,key,val,1,de); } incrRefCount(val); if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key); if (!(flags & SETKEY_NO_SIGNAL)) signalModifiedKey(c,db,key); } /* Return a random key, in form of a Redis object. * If there are no keys, NULL is returned. * * The function makes sure to return keys not already expired. */ robj *dbRandomKey(redisDb *db) { dictEntry *de; int maxtries = 100; int allvolatile = kvstoreSize(db->keys) == kvstoreSize(db->expires); while(1) { sds key; robj *keyobj; int randomSlot = kvstoreGetFairRandomDictIndex(db->keys); de = kvstoreDictGetFairRandomKey(db->keys, randomSlot); if (de == NULL) return NULL; key = dictGetKey(de); keyobj = createStringObject(key,sdslen(key)); if (allvolatile && (server.masterhost || isPausedActions(PAUSE_ACTION_EXPIRE)) && --maxtries == 0) { /* If the DB is composed only of keys with an expire set, * it could happen that all the keys are already logically * expired in the slave, so the function cannot stop because * expireIfNeeded() is false, nor it can stop because * dictGetFairRandomKey() returns NULL (there are keys to return). * To prevent the infinite loop we do some tries, but if there * are the conditions for an infinite loop, eventually we * return a key name that may be already expired. */ return keyobj; } if (expireIfNeededWithSlot(db,keyobj,0,randomSlot) != KEY_VALID) { decrRefCount(keyobj); continue; /* search for another key. This expired. */ } return keyobj; } } /* Helper for sync and async delete. */ int dbGenericDelete(redisDb *db, robj *key, int async, int flags) { dictEntry **plink; int table; int slot = getKeySlot(key->ptr); dictEntry *de = kvstoreDictTwoPhaseUnlinkFind(db->keys, slot, key->ptr, &plink, &table); if (de) { robj *val = dictGetVal(de); /* remove key from histogram */ updateKeysizesHist(db, slot, val->type, getObjectLength(val), -1); /* If hash object with expiry on fields, remove it from HFE DS of DB */ if (val->type == OBJ_HASH) hashTypeRemoveFromExpires(&db->hexpires, val); /* RM_StringDMA may call dbUnshareStringValue which may free val, so we * need to incr to retain val */ incrRefCount(val); /* Tells the module that the key has been unlinked from the database. */ moduleNotifyKeyUnlink(key,val,db->id,flags); /* We want to try to unblock any module clients or clients using a blocking XREADGROUP */ signalDeletedKeyAsReady(db,key,val->type); /* We should call decr before freeObjAsync. If not, the refcount may be * greater than 1, so freeObjAsync doesn't work */ decrRefCount(val); if (async) { /* Because of dbUnshareStringValue, the val in de may change. */ freeObjAsync(key, dictGetVal(de), db->id); kvstoreDictSetVal(db->keys, slot, de, NULL); } /* Deleting an entry from the expires dict will not free the sds of * the key, because it is shared with the main dictionary. */ kvstoreDictDelete(db->expires, slot, key->ptr); kvstoreDictTwoPhaseUnlinkFree(db->keys, slot, de, plink, table); return 1; } else { return 0; } } /* Delete a key, value, and associated expiration entry if any, from the DB */ int dbSyncDelete(redisDb *db, robj *key) { return dbGenericDelete(db, key, 0, DB_FLAG_KEY_DELETED); } /* Delete a key, value, and associated expiration entry if any, from the DB. If * the value consists of many allocations, it may be freed asynchronously. */ int dbAsyncDelete(redisDb *db, robj *key) { return dbGenericDelete(db, key, 1, DB_FLAG_KEY_DELETED); } /* This is a wrapper whose behavior depends on the Redis lazy free * configuration. Deletes the key synchronously or asynchronously. */ int dbDelete(redisDb *db, robj *key) { return dbGenericDelete(db, key, server.lazyfree_lazy_server_del, DB_FLAG_KEY_DELETED); } /* Prepare the string object stored at 'key' to be modified destructively * to implement commands like SETBIT or APPEND. * * An object is usually ready to be modified unless one of the two conditions * are true: * * 1) The object 'o' is shared (refcount > 1), we don't want to affect * other users. * 2) The object encoding is not "RAW". * * If the object is found in one of the above conditions (or both) by the * function, an unshared / not-encoded copy of the string object is stored * at 'key' in the specified 'db'. Otherwise the object 'o' itself is * returned. * * USAGE: * * The object 'o' is what the caller already obtained by looking up 'key' * in 'db', the usage pattern looks like this: * * o = lookupKeyWrite(db,key); * if (checkType(c,o,OBJ_STRING)) return; * o = dbUnshareStringValue(db,key,o); * * At this point the caller is ready to modify the object, for example * using an sdscat() call to append some data, or anything else. */ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) { return dbUnshareStringValueWithDictEntry(db,key,o,NULL); } /* Like dbUnshareStringValue(), but accepts a optional dictEntry, * which can be used if we already have one, thus saving the dbFind call. */ robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de) { serverAssert(o->type == OBJ_STRING); if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) { robj *decoded = getDecodedObject(o); o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr)); decrRefCount(decoded); dbReplaceValueWithDictEntry(db,key,o,de); } return o; } /* Remove all keys from the database(s) structure. The dbarray argument * may not be the server main DBs (could be a temporary DB). * * The dbnum can be -1 if all the DBs should be emptied, or the specified * DB index if we want to empty only a single database. * The function returns the number of keys removed from the database(s). */ long long emptyDbStructure(redisDb *dbarray, int dbnum, int async, void(callback)(dict*)) { long long removed = 0; int startdb, enddb; if (dbnum == -1) { startdb = 0; enddb = server.dbnum-1; } else { startdb = enddb = dbnum; } for (int j = startdb; j <= enddb; j++) { removed += kvstoreSize(dbarray[j].keys); if (async) { emptyDbAsync(&dbarray[j]); } else { /* Destroy global HFE DS before deleting the hashes since ebuckets * DS is embedded in the stored objects. */ ebDestroy(&dbarray[j].hexpires, &hashExpireBucketsType, NULL); kvstoreEmpty(dbarray[j].keys, callback); kvstoreEmpty(dbarray[j].expires, callback); } /* Because all keys of database are removed, reset average ttl. */ dbarray[j].avg_ttl = 0; dbarray[j].expires_cursor = 0; } return removed; } /* Remove all data (keys and functions) from all the databases in a * Redis server. If callback is given the function is called from * time to time to signal that work is in progress. * * The dbnum can be -1 if all the DBs should be flushed, or the specified * DB number if we want to flush only a single Redis database number. * * Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or * EMPTYDB_ASYNC if we want the memory to be freed in a different thread * and the function to return ASAP. EMPTYDB_NOFUNCTIONS can also be set * to specify that we do not want to delete the functions. * * On success the function returns the number of keys removed from the * database(s). Otherwise -1 is returned in the specific case the * DB number is out of range, and errno is set to EINVAL. */ long long emptyData(int dbnum, int flags, void(callback)(dict*)) { int async = (flags & EMPTYDB_ASYNC); int with_functions = !(flags & EMPTYDB_NOFUNCTIONS); RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum}; long long removed = 0; if (dbnum < -1 || dbnum >= server.dbnum) { errno = EINVAL; return -1; } /* Fire the flushdb modules event. */ moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB, REDISMODULE_SUBEVENT_FLUSHDB_START, &fi); /* Make sure the WATCHed keys are affected by the FLUSH* commands. * Note that we need to call the function while the keys are still * there. */ signalFlushedDb(dbnum, async); /* Empty redis database structure. */ removed = emptyDbStructure(server.db, dbnum, async, callback); if (dbnum == -1) flushSlaveKeysWithExpireList(); if (with_functions) { serverAssert(dbnum == -1); functionsLibCtxClearCurrent(async); } /* Also fire the end event. Note that this event will fire almost * immediately after the start event if the flush is asynchronous. */ moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB, REDISMODULE_SUBEVENT_FLUSHDB_END, &fi); return removed; } /* Initialize temporary db on replica for use during diskless replication. */ redisDb *initTempDb(void) { int slot_count_bits = 0; int flags = KVSTORE_ALLOCATE_DICTS_ON_DEMAND; if (server.cluster_enabled) { slot_count_bits = CLUSTER_SLOT_MASK_BITS; flags |= KVSTORE_FREE_EMPTY_DICTS; } redisDb *tempDb = zcalloc(sizeof(redisDb)*server.dbnum); for (int i=0; i= server.dbnum) return C_ERR; c->db = &server.db[id]; return C_OK; } long long dbTotalServerKeyCount(void) { long long total = 0; int j; for (j = 0; j < server.dbnum; j++) { total += kvstoreSize(server.db[j].keys); } return total; } /*----------------------------------------------------------------------------- * Hooks for key space changes. * * Every time a key in the database is modified the function * signalModifiedKey() is called. * * Every time a DB is flushed the function signalFlushDb() is called. *----------------------------------------------------------------------------*/ /* Note that the 'c' argument may be NULL if the key was modified out of * a context of a client. */ void signalModifiedKey(client *c, redisDb *db, robj *key) { touchWatchedKey(db,key); trackingInvalidateKey(c,key,1); } void signalFlushedDb(int dbid, int async) { int startdb, enddb; if (dbid == -1) { startdb = 0; enddb = server.dbnum-1; } else { startdb = enddb = dbid; } for (int j = startdb; j <= enddb; j++) { scanDatabaseForDeletedKeys(&server.db[j], NULL); touchAllWatchedKeysInDb(&server.db[j], NULL); } trackingInvalidateKeysOnFlush(async); /* Changes in this method may take place in swapMainDbWithTempDb as well, * where we execute similar calls, but with subtle differences as it's * not simply flushing db. */ } /*----------------------------------------------------------------------------- * Type agnostic commands operating on the key space *----------------------------------------------------------------------------*/ /* Return the set of flags to use for the emptyData() call for FLUSHALL * and FLUSHDB commands. * * sync: flushes the database in an sync manner. * async: flushes the database in an async manner. * no option: determine sync or async according to the value of lazyfree-lazy-user-flush. * * On success C_OK is returned and the flags are stored in *flags, otherwise * C_ERR is returned and the function sends an error to the client. */ int getFlushCommandFlags(client *c, int *flags) { /* Parse the optional ASYNC option. */ if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"sync")) { *flags = EMPTYDB_NO_FLAGS; } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"async")) { *flags = EMPTYDB_ASYNC; } else if (c->argc == 1) { *flags = server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS; } else { addReplyErrorObject(c,shared.syntaxerr); return C_ERR; } return C_OK; } /* Flushes the whole server data set. */ void flushAllDataAndResetRDB(int flags) { server.dirty += emptyData(-1,flags,NULL); if (server.child_type == CHILD_TYPE_RDB) killRDBChild(); if (server.saveparamslen > 0) { rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); rdbSave(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE); } #if defined(USE_JEMALLOC) /* jemalloc 5 doesn't release pages back to the OS when there's no traffic. * for large databases, flushdb blocks for long anyway, so a bit more won't * harm and this way the flush and purge will be synchronous. */ if (!(flags & EMPTYDB_ASYNC)) { /* Only clear the current thread cache. * Ignore the return call since this will fail if the tcache is disabled. */ je_mallctl("thread.tcache.flush", NULL, NULL, NULL, 0); jemalloc_purge(); } #endif } /* CB function on blocking ASYNC FLUSH completion * * Utilized by commands SFLUSH, FLUSHALL and FLUSHDB. */ void flushallSyncBgDone(uint64_t client_id, void *sflush) { SlotsFlush *slotsFlush = sflush; client *c = lookupClientByID(client_id); /* Verify that client still exists and being blocked. */ if (!(c && c->flags & CLIENT_BLOCKED)) { zfree(sflush); return; } /* Update current_client (Called functions might rely on it) */ client *old_client = server.current_client; server.current_client = c; /* Don't update blocked_us since command was processed in bg by lazy_free thread */ updateStatsOnUnblock(c, 0 /*blocked_us*/, elapsedUs(c->bstate.lazyfreeStartTime), 0); /* Only SFLUSH command pass pointer to `SlotsFlush` */ if (slotsFlush) replySlotsFlushAndFree(c, slotsFlush); else addReply(c, shared.ok); /* mark client as unblocked */ unblockClient(c, 1); if (c->flags & CLIENT_PENDING_COMMAND) { c->flags &= ~CLIENT_PENDING_COMMAND; /* The FLUSH command won't be reprocessed, FLUSH command is finished, but * we still need to complete its full processing flow, including updating * the replication offset. */ commandProcessed(c); } /* On flush completion, update the client's memory */ updateClientMemUsageAndBucket(c); /* restore current_client */ server.current_client = old_client; } /* Common flush command implementation for FLUSHALL and FLUSHDB. * * Return 1 indicates that flush SYNC is actually running in bg as blocking ASYNC * Return 0 otherwise * * sflush - provided only by SFLUSH command, otherwise NULL. Will be used on * completion to reply with the slots flush result. Ownership is passed * to the completion job in case of `blocking_async`. */ int flushCommandCommon(client *c, int type, int flags, SlotsFlush *sflush) { int blocking_async = 0; /* Flush SYNC option to run as blocking ASYNC */ /* in case of SYNC, check if we can optimize and run it in bg as blocking ASYNC */ if ((!(flags & EMPTYDB_ASYNC)) && (!(c->flags & CLIENT_AVOID_BLOCKING_ASYNC_FLUSH))) { /* Run as ASYNC */ flags |= EMPTYDB_ASYNC; blocking_async = 1; } if (type == FLUSH_TYPE_ALL) flushAllDataAndResetRDB(flags | EMPTYDB_NOFUNCTIONS); else server.dirty += emptyData(c->db->id,flags | EMPTYDB_NOFUNCTIONS,NULL); /* Without the forceCommandPropagation, when DB(s) was already empty, * FLUSHALL\FLUSHDB will not be replicated nor put into the AOF. */ forceCommandPropagation(c, PROPAGATE_REPL | PROPAGATE_AOF); /* if blocking ASYNC, block client and add completion job request to BIO lazyfree * worker's queue. To be called and reply with OK only after all preceding pending * lazyfree jobs in queue were processed */ if (blocking_async) { /* measure bg job till completion as elapsed time of flush command */ elapsedStart(&c->bstate.lazyfreeStartTime); c->bstate.timeout = 0; /* We still need to perform cleanup operations for the command, including * updating the replication offset, so mark this command as pending to * avoid command from being reset during unblock. */ c->flags |= CLIENT_PENDING_COMMAND; blockClient(c,BLOCKED_LAZYFREE); bioCreateCompRq(BIO_WORKER_LAZY_FREE, flushallSyncBgDone, c->id, sflush); } #if defined(USE_JEMALLOC) /* jemalloc 5 doesn't release pages back to the OS when there's no traffic. * for large databases, flushdb blocks for long anyway, so a bit more won't * harm and this way the flush and purge will be synchronous. * * Take care purge only FLUSHDB for sync flow. FLUSHALL sync flow already * applied at flushAllDataAndResetRDB. Async flow will apply only later on */ if ((type != FLUSH_TYPE_ALL) && (!(flags & EMPTYDB_ASYNC))) { /* Only clear the current thread cache. * Ignore the return call since this will fail if the tcache is disabled. */ je_mallctl("thread.tcache.flush", NULL, NULL, NULL, 0); jemalloc_purge(); } #endif return blocking_async; } /* FLUSHALL [SYNC|ASYNC] * * Flushes the whole server data set. */ void flushallCommand(client *c) { int flags; if (getFlushCommandFlags(c,&flags) == C_ERR) return; /* If FLUSH SYNC isn't running as blocking async, then reply */ if (flushCommandCommon(c, FLUSH_TYPE_ALL, flags, NULL) == 0) addReply(c, shared.ok); } /* FLUSHDB [SYNC|ASYNC] * * Flushes the currently SELECTed Redis DB. */ void flushdbCommand(client *c) { int flags; if (getFlushCommandFlags(c,&flags) == C_ERR) return; /* If FLUSH SYNC isn't running as blocking async, then reply */ if (flushCommandCommon(c, FLUSH_TYPE_DB,flags, NULL) == 0) addReply(c, shared.ok); } /* This command implements DEL and UNLINK. */ void delGenericCommand(client *c, int lazy) { int numdel = 0, j; for (j = 1; j < c->argc; j++) { if (expireIfNeeded(c->db,c->argv[j],0) == KEY_DELETED) continue; int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) : dbSyncDelete(c->db,c->argv[j]); if (deleted) { signalModifiedKey(c,c->db,c->argv[j]); notifyKeyspaceEvent(NOTIFY_GENERIC, "del",c->argv[j],c->db->id); server.dirty++; numdel++; } } addReplyLongLong(c,numdel); } void delCommand(client *c) { delGenericCommand(c,server.lazyfree_lazy_user_del); } void unlinkCommand(client *c) { delGenericCommand(c,1); } /* EXISTS key1 key2 ... key_N. * Return value is the number of keys existing. */ void existsCommand(client *c) { long long count = 0; int j; for (j = 1; j < c->argc; j++) { if (lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH)) count++; } addReplyLongLong(c,count); } void selectCommand(client *c) { int id; if (getIntFromObjectOrReply(c, c->argv[1], &id, NULL) != C_OK) return; if (server.cluster_enabled && id != 0) { addReplyError(c,"SELECT is not allowed in cluster mode"); return; } if (id != 0) { server.stat_cluster_incompatible_ops++; } if (selectDb(c,id) == C_ERR) { addReplyError(c,"DB index is out of range"); } else { addReply(c,shared.ok); } } void randomkeyCommand(client *c) { robj *key; if ((key = dbRandomKey(c->db)) == NULL) { addReplyNull(c); return; } addReplyBulk(c,key); decrRefCount(key); } void keysCommand(client *c) { dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys, pslot = -1; unsigned long numkeys = 0; void *replylen = addReplyDeferredLen(c); allkeys = (pattern[0] == '*' && plen == 1); if (server.cluster_enabled && !allkeys) { pslot = patternHashSlot(pattern, plen); } kvstoreDictIterator *kvs_di = NULL; kvstoreIterator *kvs_it = NULL; if (pslot != -1) { if (!kvstoreDictSize(c->db->keys, pslot)) { /* Requested slot is empty */ setDeferredArrayLen(c,replylen,0); return; } kvs_di = kvstoreGetDictSafeIterator(c->db->keys, pslot); } else { kvs_it = kvstoreIteratorInit(c->db->keys); } robj keyobj; while ((de = kvs_di ? kvstoreDictIteratorNext(kvs_di) : kvstoreIteratorNext(kvs_it)) != NULL) { sds key = dictGetKey(de); if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { initStaticStringObject(keyobj, key); if (!keyIsExpired(c->db, &keyobj)) { addReplyBulkCBuffer(c, key, sdslen(key)); numkeys++; } } if (c->flags & CLIENT_CLOSE_ASAP) break; } if (kvs_di) kvstoreReleaseDictIterator(kvs_di); if (kvs_it) kvstoreIteratorRelease(kvs_it); setDeferredArrayLen(c,replylen,numkeys); } /* Data used by the dict scan callback. */ typedef struct { list *keys; /* elements that collect from dict */ robj *o; /* o must be a hash/set/zset object, NULL means current db */ long long type; /* the particular type when scan the db */ sds pattern; /* pattern string, NULL means no pattern */ long sampled; /* cumulative number of keys sampled */ int no_values; /* set to 1 means to return keys only */ size_t (*strlen)(char *s); /* (o->type == OBJ_HASH) ? hfieldlen : sdslen */ } scanData; /* Helper function to compare key type in scan commands */ int objectTypeCompare(robj *o, long long target) { if (o->type != OBJ_MODULE) { if (o->type != target) return 0; else return 1; } /* module type compare */ long long mt = (long long)REDISMODULE_TYPE_SIGN(((moduleValue *)o->ptr)->type->id); if (target != -mt) return 0; else return 1; } /* This callback is used by scanGenericCommand in order to collect elements * returned by the dictionary iterator into a list. */ void scanCallback(void *privdata, const dictEntry *de) { scanData *data = (scanData *)privdata; list *keys = data->keys; robj *o = data->o; sds val = NULL; void *key = NULL; /* if OBJ_HASH then key is of type `hfield`. Otherwise, `sds` */ data->sampled++; /* o and typename can not have values at the same time. */ serverAssert(!((data->type != LLONG_MAX) && o)); /* Filter an element if it isn't the type we want. */ /* TODO: uncomment in redis 8.0 if (!o && data->type != LLONG_MAX) { robj *rval = dictGetVal(de); if (!objectTypeCompare(rval, data->type)) return; }*/ /* Filter element if it does not match the pattern. */ void *keyStr = dictGetKey(de); if (data->pattern) { if (!stringmatchlen(data->pattern, sdslen(data->pattern), keyStr, data->strlen(keyStr), 0)) { return; } } if (o == NULL) { key = keyStr; } else if (o->type == OBJ_SET) { key = keyStr; } else if (o->type == OBJ_HASH) { key = keyStr; val = dictGetVal(de); /* If field is expired, then ignore */ if (hfieldIsExpired(key)) return; } else if (o->type == OBJ_ZSET) { char buf[MAX_LONG_DOUBLE_CHARS]; int len = ld2string(buf, sizeof(buf), *(double *)dictGetVal(de), LD_STR_AUTO); key = sdsdup(keyStr); val = sdsnewlen(buf, len); } else { serverPanic("Type not handled in SCAN callback."); } listAddNodeTail(keys, key); if (val && !data->no_values) listAddNodeTail(keys, val); } /* Try to parse a SCAN cursor stored at object 'o': * if the cursor is valid, store it as unsigned integer into *cursor and * returns C_OK. Otherwise return C_ERR and send an error to the * client. */ int parseScanCursorOrReply(client *c, robj *o, unsigned long long *cursor) { if (!string2ull(o->ptr, cursor)) { addReplyError(c, "invalid cursor"); return C_ERR; } return C_OK; } char *obj_type_name[OBJ_TYPE_MAX] = { "string", "list", "set", "zset", "hash", NULL, /* module type is special */ "stream" }; /* Helper function to get type from a string in scan commands */ long long getObjectTypeByName(char *name) { for (long long i = 0; i < OBJ_TYPE_MAX; i++) { if (obj_type_name[i] && !strcasecmp(name, obj_type_name[i])) { return i; } } moduleType *mt = moduleTypeLookupModuleByNameIgnoreCase(name); if (mt != NULL) return -(REDISMODULE_TYPE_SIGN(mt->id)); return LLONG_MAX; } char *getObjectTypeName(robj *o) { if (o == NULL) { return "none"; } serverAssert(o->type >= 0 && o->type < OBJ_TYPE_MAX); if (o->type == OBJ_MODULE) { moduleValue *mv = o->ptr; return mv->type->name; } else { return obj_type_name[o->type]; } } /* This command implements SCAN, HSCAN and SSCAN commands. * If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise * if 'o' is NULL the command will operate on the dictionary associated with * the current database. * * When 'o' is not NULL the function assumes that the first argument in * the client arguments vector is a key so it skips it before iterating * in order to parse options. * * In the case of a Hash object the function returns both the field and value * of every element on the Hash. */ void scanGenericCommand(client *c, robj *o, unsigned long long cursor) { int isKeysHfield = 0; int i, j; listNode *node; long count = 10; sds pat = NULL; sds typename = NULL; long long type = LLONG_MAX; int patlen = 0, use_pattern = 0, no_values = 0; dict *ht; /* Object must be NULL (to iterate keys names), or the type of the object * must be Set, Sorted Set, or Hash. */ serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH || o->type == OBJ_ZSET); /* Set i to the first option argument. The previous one is the cursor. */ i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */ /* Step 1: Parse options. */ while (i < c->argc) { j = c->argc - i; if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) { if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL) != C_OK) { return; } if (count < 1) { addReplyErrorObject(c,shared.syntaxerr); return; } i += 2; } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) { pat = c->argv[i+1]->ptr; patlen = sdslen(pat); /* The pattern always matches if it is exactly "*", so it is * equivalent to disabling it. */ use_pattern = !(patlen == 1 && pat[0] == '*'); i += 2; } else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) { /* SCAN for a particular type only applies to the db dict */ typename = c->argv[i+1]->ptr; type = getObjectTypeByName(typename); if (type == LLONG_MAX) { /* TODO: uncomment in redis 8.0 addReplyErrorFormat(c, "unknown type name '%s'", typename); return; */ } i+= 2; } else if (!strcasecmp(c->argv[i]->ptr, "novalues")) { if (!o || o->type != OBJ_HASH) { addReplyError(c, "NOVALUES option can only be used in HSCAN"); return; } no_values = 1; i++; } else { addReplyErrorObject(c,shared.syntaxerr); return; } } /* Step 2: Iterate the collection. * * Note that if the object is encoded with a listpack, intset, or any other * representation that is not a hash table, we are sure that it is also * composed of a small number of elements. So to avoid taking state we * just return everything inside the object in a single call, setting the * cursor to zero to signal the end of the iteration. */ /* Handle the case of a hash table. */ ht = NULL; if (o == NULL) { ht = NULL; } else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) { ht = o->ptr; } else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) { isKeysHfield = 1; ht = o->ptr; } else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = o->ptr; ht = zs->dict; } list *keys = listCreate(); /* Set a free callback for the contents of the collected keys list. * For the main keyspace dict, and when we scan a key that's dict encoded * (we have 'ht'), we don't need to define free method because the strings * in the list are just a shallow copy from the pointer in the dictEntry. * When scanning a key with other encodings (e.g. listpack), we need to * free the temporary strings we add to that list. * The exception to the above is ZSET, where we do allocate temporary * strings even when scanning a dict. */ if (o && (!ht || o->type == OBJ_ZSET)) { listSetFreeMethod(keys, sdsfreegeneric); } /* For main dictionary scan or data structure using hashtable. */ if (!o || ht) { /* We set the max number of iterations to ten times the specified * COUNT, so if the hash table is in a pathological state (very * sparsely populated) we avoid to block too much time at the cost * of returning no or very few elements. */ long maxiterations = count*10; /* We pass scanData which have three pointers to the callback: * 1. data.keys: the list to which it will add new elements; * 2. data.o: the object containing the dictionary so that * it is possible to fetch more data in a type-dependent way; * 3. data.type: the specified type scan in the db, LLONG_MAX means * type matching is no needed; * 4. data.pattern: the pattern string; * 5. data.sampled: the maxiteration limit is there in case we're * working on an empty dict, one with a lot of empty buckets, and * for the buckets are not empty, we need to limit the spampled number * to prevent a long hang time caused by filtering too many keys; * 6. data.no_values: to control whether values will be returned or * only keys are returned. */ scanData data = { .keys = keys, .o = o, .type = type, .pattern = use_pattern ? pat : NULL, .sampled = 0, .no_values = no_values, .strlen = (isKeysHfield) ? hfieldlen : sdslen, }; /* A pattern may restrict all matching keys to one cluster slot. */ int onlydidx = -1; if (o == NULL && use_pattern && server.cluster_enabled) { onlydidx = patternHashSlot(pat, patlen); } do { /* In cluster mode there is a separate dictionary for each slot. * If cursor is empty, we should try exploring next non-empty slot. */ if (o == NULL) { cursor = kvstoreScan(c->db->keys, cursor, onlydidx, scanCallback, NULL, &data); } else { cursor = dictScan(ht, cursor, scanCallback, &data); } } while (cursor && maxiterations-- && data.sampled < count); } else if (o->type == OBJ_SET) { unsigned long array_reply_len = 0; void *replylen = NULL; listRelease(keys); char *str; char buf[LONG_STR_SIZE]; size_t len; int64_t llele; /* Reply to the client. */ addReplyArrayLen(c, 2); /* Cursor is always 0 given we iterate over all set */ addReplyBulkLongLong(c,0); /* If there is no pattern the length is the entire set size, otherwise we defer the reply size */ if (use_pattern) replylen = addReplyDeferredLen(c); else { array_reply_len = setTypeSize(o); addReplyArrayLen(c, array_reply_len); } setTypeIterator *si = setTypeInitIterator(o); unsigned long cur_length = 0; while (setTypeNext(si, &str, &len, &llele) != -1) { if (str == NULL) { len = ll2string(buf, sizeof(buf), llele); } char *key = str ? str : buf; if (use_pattern && !stringmatchlen(pat, patlen, key, len, 0)) { continue; } addReplyBulkCBuffer(c, key, len); cur_length++; } setTypeReleaseIterator(si); if (use_pattern) setDeferredArrayLen(c,replylen,cur_length); else serverAssert(cur_length == array_reply_len); /* fail on corrupt data */ return; } else if ((o->type == OBJ_HASH || o->type == OBJ_ZSET) && o->encoding == OBJ_ENCODING_LISTPACK) { unsigned char *p = lpFirst(o->ptr); unsigned char *str; int64_t len; unsigned long array_reply_len = 0; unsigned char intbuf[LP_INTBUF_SIZE]; void *replylen = NULL; listRelease(keys); /* Reply to the client. */ addReplyArrayLen(c, 2); /* Cursor is always 0 given we iterate over all set */ addReplyBulkLongLong(c,0); /* If there is no pattern the length is the entire set size, otherwise we defer the reply size */ if (use_pattern) replylen = addReplyDeferredLen(c); else { array_reply_len = o->type == OBJ_HASH ? hashTypeLength(o, 0) : zsetLength(o); if (!no_values) { array_reply_len *= 2; } addReplyArrayLen(c, array_reply_len); } unsigned long cur_length = 0; while(p) { str = lpGet(p, &len, intbuf); /* point to the value */ p = lpNext(o->ptr, p); if (use_pattern && !stringmatchlen(pat, patlen, (char *)str, len, 0)) { /* jump to the next key/val pair */ p = lpNext(o->ptr, p); continue; } /* add key object */ addReplyBulkCBuffer(c, str, len); cur_length++; /* add value object */ if (!no_values) { str = lpGet(p, &len, intbuf); addReplyBulkCBuffer(c, str, len); cur_length++; } p = lpNext(o->ptr, p); } if (use_pattern) setDeferredArrayLen(c,replylen,cur_length); else serverAssert(cur_length == array_reply_len); /* fail on corrupt data */ return; } else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_LISTPACK_EX) { int64_t len; long long expire_at; unsigned char *lp = hashTypeListpackGetLp(o); unsigned char *p = lpFirst(lp); unsigned char *str, *val; unsigned char intbuf[LP_INTBUF_SIZE]; void *replylen = NULL; listRelease(keys); /* Reply to the client. */ addReplyArrayLen(c, 2); /* Cursor is always 0 given we iterate over all set */ addReplyBulkLongLong(c,0); /* In the case of OBJ_ENCODING_LISTPACK_EX we always defer the reply size given some fields might be expired */ replylen = addReplyDeferredLen(c); unsigned long cur_length = 0; while (p) { str = lpGet(p, &len, intbuf); p = lpNext(lp, p); val = p; /* Keep pointer to value */ p = lpNext(lp, p); serverAssert(p && lpGetIntegerValue(p, &expire_at)); if (hashTypeIsExpired(o, expire_at) || (use_pattern && !stringmatchlen(pat, patlen, (char *)str, len, 0))) { /* jump to the next key/val pair */ p = lpNext(lp, p); continue; } /* add key object */ addReplyBulkCBuffer(c, str, len); cur_length++; /* add value object */ if (!no_values) { str = lpGet(val, &len, intbuf); addReplyBulkCBuffer(c, str, len); cur_length++; } p = lpNext(lp, p); } setDeferredArrayLen(c,replylen,cur_length); return; } else { serverPanic("Not handled encoding in SCAN."); } /* Step 3: Filter the expired keys */ if (o == NULL && listLength(keys)) { robj kobj; listIter li; listNode *ln; listRewind(keys, &li); while ((ln = listNext(&li))) { sds key = listNodeValue(ln); initStaticStringObject(kobj, key); /* Filter an element if it isn't the type we want. */ /* TODO: remove this in redis 8.0 */ if (typename) { robj* typecheck = lookupKeyReadWithFlags(c->db, &kobj, LOOKUP_NOTOUCH|LOOKUP_NONOTIFY); if (!typecheck || !objectTypeCompare(typecheck, type)) { listDelNode(keys, ln); } continue; } if (expireIfNeeded(c->db, &kobj, 0) != KEY_VALID) { listDelNode(keys, ln); } } } /* Step 4: Reply to the client. */ addReplyArrayLen(c, 2); addReplyBulkLongLong(c,cursor); unsigned long long idx = 0; addReplyArrayLen(c, listLength(keys)); while ((node = listFirst(keys)) != NULL) { void *key = listNodeValue(node); /* For HSCAN, list will contain keys value pairs unless no_values arg * was given. We should call mstrlen for the keys only. */ int hfieldkey = isKeysHfield && (no_values || (idx++ % 2 == 0)); addReplyBulkCBuffer(c, key, hfieldkey ? mstrlen(key) : sdslen(key)); listDelNode(keys, node); } listRelease(keys); } /* The SCAN command completely relies on scanGenericCommand. */ void scanCommand(client *c) { unsigned long long cursor; if (parseScanCursorOrReply(c,c->argv[1],&cursor) == C_ERR) return; scanGenericCommand(c,NULL,cursor); } void dbsizeCommand(client *c) { addReplyLongLong(c,kvstoreSize(c->db->keys)); } void lastsaveCommand(client *c) { addReplyLongLong(c,server.lastsave); } void typeCommand(client *c) { robj *o; o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH); addReplyStatus(c, getObjectTypeName(o)); } void shutdownCommand(client *c) { int flags = SHUTDOWN_NOFLAGS; int abort = 0; for (int i = 1; i < c->argc; i++) { if (!strcasecmp(c->argv[i]->ptr,"nosave")) { flags |= SHUTDOWN_NOSAVE; } else if (!strcasecmp(c->argv[i]->ptr,"save")) { flags |= SHUTDOWN_SAVE; } else if (!strcasecmp(c->argv[i]->ptr, "now")) { flags |= SHUTDOWN_NOW; } else if (!strcasecmp(c->argv[i]->ptr, "force")) { flags |= SHUTDOWN_FORCE; } else if (!strcasecmp(c->argv[i]->ptr, "abort")) { abort = 1; } else { addReplyErrorObject(c,shared.syntaxerr); return; } } if ((abort && flags != SHUTDOWN_NOFLAGS) || (flags & SHUTDOWN_NOSAVE && flags & SHUTDOWN_SAVE)) { /* Illegal combo. */ addReplyErrorObject(c,shared.syntaxerr); return; } if (abort) { if (abortShutdown() == C_OK) addReply(c, shared.ok); else addReplyError(c, "No shutdown in progress."); return; } if (!(flags & SHUTDOWN_NOW) && c->flags & CLIENT_DENY_BLOCKING) { addReplyError(c, "SHUTDOWN without NOW or ABORT isn't allowed for DENY BLOCKING client"); return; } if (!(flags & SHUTDOWN_NOSAVE) && isInsideYieldingLongCommand()) { /* Script timed out. Shutdown allowed only with the NOSAVE flag. See * also processCommand where these errors are returned. */ if (server.busy_module_yield_flags && server.busy_module_yield_reply) { addReplyErrorFormat(c, "-BUSY %s", server.busy_module_yield_reply); } else if (server.busy_module_yield_flags) { addReplyErrorObject(c, shared.slowmoduleerr); } else if (scriptIsEval()) { addReplyErrorObject(c, shared.slowevalerr); } else { addReplyErrorObject(c, shared.slowscripterr); } return; } blockClientShutdown(c); if (prepareForShutdown(flags) == C_OK) exit(0); /* If we're here, then shutdown is ongoing (the client is still blocked) or * failed (the client has received an error). */ } void renameGenericCommand(client *c, int nx) { robj *o; long long expire; int samekey = 0; uint64_t minHashExpireTime = EB_EXPIRE_TIME_INVALID; /* When source and dest key is the same, no operation is performed, * if the key exists, however we still return an error on unexisting key. */ if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1; if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) return; if (samekey) { addReply(c,nx ? shared.czero : shared.ok); return; } incrRefCount(o); expire = getExpire(c->db,c->argv[1]); if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { if (nx) { decrRefCount(o); addReply(c,shared.czero); return; } /* Overwrite: delete the old key before creating the new one * with the same name. */ dbDelete(c->db,c->argv[2]); } dictEntry *de = dbAdd(c->db, c->argv[2], o); if (expire != -1) setExpire(c,c->db,c->argv[2],expire); /* If hash with expiration on fields then remove it from global HFE DS and * keep next expiration time. Otherwise, dbDelete() will remove it from the * global HFE DS and we will lose the expiration time. */ if (o->type == OBJ_HASH) minHashExpireTime = hashTypeRemoveFromExpires(&c->db->hexpires, o); dbDelete(c->db,c->argv[1]); /* If hash with HFEs, register in db->hexpires */ if (minHashExpireTime != EB_EXPIRE_TIME_INVALID) hashTypeAddToExpires(c->db, dictGetKey(de), o, minHashExpireTime); signalModifiedKey(c,c->db,c->argv[1]); signalModifiedKey(c,c->db,c->argv[2]); notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from", c->argv[1],c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_to", c->argv[2],c->db->id); server.dirty++; addReply(c,nx ? shared.cone : shared.ok); } void renameCommand(client *c) { renameGenericCommand(c,0); } void renamenxCommand(client *c) { renameGenericCommand(c,1); } void moveCommand(client *c) { robj *o; redisDb *src, *dst; int srcid, dbid; long long expire; uint64_t hashExpireTime = EB_EXPIRE_TIME_INVALID; if (server.cluster_enabled) { addReplyError(c,"MOVE is not allowed in cluster mode"); return; } /* Obtain source and target DB pointers */ src = c->db; srcid = c->db->id; if (getIntFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) return; if (selectDb(c,dbid) == C_ERR) { addReplyError(c,"DB index is out of range"); return; } dst = c->db; selectDb(c,srcid); /* Back to the source DB */ /* If the user is moving using as target the same * DB as the source DB it is probably an error. */ if (src == dst) { addReplyErrorObject(c,shared.sameobjecterr); return; } /* Record incompatible operations in cluster mode */ server.stat_cluster_incompatible_ops++; /* Check if the element exists and get a reference */ o = lookupKeyWrite(c->db,c->argv[1]); if (!o) { addReply(c,shared.czero); return; } expire = getExpire(c->db,c->argv[1]); /* Return zero if the key already exists in the target DB */ if (lookupKeyWrite(dst,c->argv[1]) != NULL) { addReply(c,shared.czero); return; } dictEntry *dstDictEntry = dbAdd(dst,c->argv[1],o); if (expire != -1) setExpire(c,dst,c->argv[1],expire); /* If hash with expiration on fields, remove it from global HFE DS and keep * aside registered expiration time. Must be before deletion of the object. * hexpires (ebuckets) embed in stored items its structure. */ if (o->type == OBJ_HASH) hashExpireTime = hashTypeRemoveFromExpires(&src->hexpires, o); incrRefCount(o); /* OK! key moved, free the entry in the source DB */ dbDelete(src,c->argv[1]); /* If object of type hash with expiration on fields. Taken care to add the * hash to hexpires of `dst` only after dbDelete(). */ if (hashExpireTime != EB_EXPIRE_TIME_INVALID) hashTypeAddToExpires(dst, dictGetKey(dstDictEntry), o, hashExpireTime); signalModifiedKey(c,src,c->argv[1]); signalModifiedKey(c,dst,c->argv[1]); notifyKeyspaceEvent(NOTIFY_GENERIC, "move_from",c->argv[1],src->id); notifyKeyspaceEvent(NOTIFY_GENERIC, "move_to",c->argv[1],dst->id); server.dirty++; addReply(c,shared.cone); } void copyCommand(client *c) { robj *o; redisDb *src, *dst; int srcid, dbid; long long expire; int j, replace = 0, delete = 0; /* Obtain source and target DB pointers * Default target DB is the same as the source DB * Parse the REPLACE option and targetDB option. */ src = c->db; dst = c->db; srcid = c->db->id; dbid = c->db->id; for (j = 3; j < c->argc; j++) { int additional = c->argc - j - 1; if (!strcasecmp(c->argv[j]->ptr,"replace")) { replace = 1; } else if (!strcasecmp(c->argv[j]->ptr, "db") && additional >= 1) { if (getIntFromObjectOrReply(c, c->argv[j+1], &dbid, NULL) != C_OK) return; if (selectDb(c, dbid) == C_ERR) { addReplyError(c,"DB index is out of range"); return; } dst = c->db; selectDb(c,srcid); /* Back to the source DB */ j++; /* Consume additional arg. */ } else { addReplyErrorObject(c,shared.syntaxerr); return; } } if ((server.cluster_enabled == 1) && (srcid != 0 || dbid != 0)) { addReplyError(c,"Copying to another database is not allowed in cluster mode"); return; } /* If the user select the same DB as * the source DB and using newkey as the same key * it is probably an error. */ robj *key = c->argv[1]; robj *newkey = c->argv[2]; if (src == dst && (sdscmp(key->ptr, newkey->ptr) == 0)) { addReplyErrorObject(c,shared.sameobjecterr); return; } if (srcid != 0 || dbid != 0) { server.stat_cluster_incompatible_ops++; } /* Check if the element exists and get a reference */ o = lookupKeyRead(c->db, key); if (!o) { addReply(c,shared.czero); return; } expire = getExpire(c->db,key); /* Return zero if the key already exists in the target DB. * If REPLACE option is selected, delete newkey from targetDB. */ if (lookupKeyWrite(dst,newkey) != NULL) { if (replace) { delete = 1; } else { addReply(c,shared.czero); return; } } /* Duplicate object according to object's type. */ robj *newobj; uint64_t minHashExpire = EB_EXPIRE_TIME_INVALID; /* HFE feature */ switch(o->type) { case OBJ_STRING: newobj = dupStringObject(o); break; case OBJ_LIST: newobj = listTypeDup(o); break; case OBJ_SET: newobj = setTypeDup(o); break; case OBJ_ZSET: newobj = zsetDup(o); break; case OBJ_HASH: newobj = hashTypeDup(o, newkey->ptr, &minHashExpire); break; case OBJ_STREAM: newobj = streamDup(o); break; case OBJ_MODULE: newobj = moduleTypeDupOrReply(c, key, newkey, dst->id, o); if (!newobj) return; break; default: addReplyError(c, "unknown type object"); return; } if (delete) { dbDelete(dst,newkey); } dictEntry *deCopy = dbAdd(dst,newkey,newobj); /* if key with expiration then set it */ if (expire != -1) setExpire(c, dst, newkey, expire); /* If minExpiredField was set, then the object is hash with expiration * on fields and need to register it in global HFE DS */ if (minHashExpire != EB_EXPIRE_TIME_INVALID) hashTypeAddToExpires(dst, dictGetKey(deCopy), newobj, minHashExpire); /* OK! key copied */ signalModifiedKey(c,dst,c->argv[2]); notifyKeyspaceEvent(NOTIFY_GENERIC,"copy_to",c->argv[2],dst->id); server.dirty++; addReply(c,shared.cone); } /* Helper function for dbSwapDatabases(): scans the list of keys that have * one or more blocked clients for B[LR]POP or other blocking commands * and signal the keys as ready if they are of the right type. See the comment * where the function is used for more info. */ void scanDatabaseForReadyKeys(redisDb *db) { dictEntry *de; dictIterator *di = dictGetSafeIterator(db->blocking_keys); while((de = dictNext(di)) != NULL) { robj *key = dictGetKey(de); dictEntry *kde = dbFind(db, key->ptr); if (kde) { robj *value = dictGetVal(kde); signalKeyAsReady(db, key, value->type); } } dictReleaseIterator(di); } /* Since we are unblocking XREADGROUP clients in the event the * key was deleted/overwritten we must do the same in case the * database was flushed/swapped. */ void scanDatabaseForDeletedKeys(redisDb *emptied, redisDb *replaced_with) { dictEntry *de; dictIterator *di = dictGetSafeIterator(emptied->blocking_keys); while((de = dictNext(di)) != NULL) { robj *key = dictGetKey(de); int existed = 0, exists = 0; int original_type = -1, curr_type = -1; dictEntry *kde = dbFind(emptied, key->ptr); if (kde) { robj *value = dictGetVal(kde); original_type = value->type; existed = 1; } if (replaced_with) { kde = dbFind(replaced_with, key->ptr); if (kde) { robj *value = dictGetVal(kde); curr_type = value->type; exists = 1; } } /* We want to try to unblock any client using a blocking XREADGROUP */ if ((existed && !exists) || original_type != curr_type) signalDeletedKeyAsReady(emptied, key, original_type); } dictReleaseIterator(di); } /* Swap two databases at runtime so that all clients will magically see * the new database even if already connected. Note that the client * structure c->db points to a given DB, so we need to be smarter and * swap the underlying referenced structures, otherwise we would need * to fix all the references to the Redis DB structure. * * Returns C_ERR if at least one of the DB ids are out of range, otherwise * C_OK is returned. */ int dbSwapDatabases(int id1, int id2) { if (id1 < 0 || id1 >= server.dbnum || id2 < 0 || id2 >= server.dbnum) return C_ERR; if (id1 == id2) return C_OK; redisDb aux = server.db[id1]; redisDb *db1 = &server.db[id1], *db2 = &server.db[id2]; /* Swapdb should make transaction fail if there is any * client watching keys */ touchAllWatchedKeysInDb(db1, db2); touchAllWatchedKeysInDb(db2, db1); /* Try to unblock any XREADGROUP clients if the key no longer exists. */ scanDatabaseForDeletedKeys(db1, db2); scanDatabaseForDeletedKeys(db2, db1); /* Swap hash tables. Note that we don't swap blocking_keys, * ready_keys and watched_keys, since we want clients to * remain in the same DB they were. */ db1->keys = db2->keys; db1->expires = db2->expires; db1->hexpires = db2->hexpires; db1->avg_ttl = db2->avg_ttl; db1->expires_cursor = db2->expires_cursor; db2->keys = aux.keys; db2->expires = aux.expires; db2->hexpires = aux.hexpires; db2->avg_ttl = aux.avg_ttl; db2->expires_cursor = aux.expires_cursor; /* Now we need to handle clients blocked on lists: as an effect * of swapping the two DBs, a client that was waiting for list * X in a given DB, may now actually be unblocked if X happens * to exist in the new version of the DB, after the swap. * * However normally we only do this check for efficiency reasons * in dbAdd() when a list is created. So here we need to rescan * the list of clients blocked on lists and signal lists as ready * if needed. */ scanDatabaseForReadyKeys(db1); scanDatabaseForReadyKeys(db2); return C_OK; } /* Logically, this discards (flushes) the old main database, and apply the newly loaded * database (temp) as the main (active) database, the actual freeing of old database * (which will now be placed in the temp one) is done later. */ void swapMainDbWithTempDb(redisDb *tempDb) { for (int i=0; ikeys = newdb->keys; activedb->expires = newdb->expires; activedb->hexpires = newdb->hexpires; activedb->avg_ttl = newdb->avg_ttl; activedb->expires_cursor = newdb->expires_cursor; newdb->keys = aux.keys; newdb->expires = aux.expires; newdb->hexpires = aux.hexpires; newdb->avg_ttl = aux.avg_ttl; newdb->expires_cursor = aux.expires_cursor; /* Now we need to handle clients blocked on lists: as an effect * of swapping the two DBs, a client that was waiting for list * X in a given DB, may now actually be unblocked if X happens * to exist in the new version of the DB, after the swap. * * However normally we only do this check for efficiency reasons * in dbAdd() when a list is created. So here we need to rescan * the list of clients blocked on lists and signal lists as ready * if needed. */ scanDatabaseForReadyKeys(activedb); } trackingInvalidateKeysOnFlush(1); flushSlaveKeysWithExpireList(); } /* SWAPDB db1 db2 */ void swapdbCommand(client *c) { int id1, id2; /* Not allowed in cluster mode: we have just DB 0 there. */ if (server.cluster_enabled) { addReplyError(c,"SWAPDB is not allowed in cluster mode"); return; } /* Get the two DBs indexes. */ if (getIntFromObjectOrReply(c, c->argv[1], &id1, "invalid first DB index") != C_OK) return; if (getIntFromObjectOrReply(c, c->argv[2], &id2, "invalid second DB index") != C_OK) return; /* Swap... */ if (dbSwapDatabases(id1,id2) == C_ERR) { addReplyError(c,"DB index is out of range"); return; } else { RedisModuleSwapDbInfo si = {REDISMODULE_SWAPDBINFO_VERSION,id1,id2}; moduleFireServerEvent(REDISMODULE_EVENT_SWAPDB,0,&si); server.dirty++; server.stat_cluster_incompatible_ops++; addReply(c,shared.ok); } } /*----------------------------------------------------------------------------- * Expires API *----------------------------------------------------------------------------*/ int removeExpire(redisDb *db, robj *key) { return kvstoreDictDelete(db->expires, getKeySlot(key->ptr), key->ptr) == DICT_OK; } /* Set an expire to the specified key. If the expire is set in the context * of an user calling a command 'c' is the client, otherwise 'c' is set * to NULL. The 'when' parameter is the absolute unix time in milliseconds * after which the key will no longer be considered valid. */ void setExpire(client *c, redisDb *db, robj *key, long long when) { setExpireWithDictEntry(c,db,key,when,NULL); } /* Like setExpire(), but accepts an optional dictEntry input, * which can be used if we already have one, thus saving the kvstoreDictFind call. */ void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde) { dictEntry *de, *existing; /* Reuse the sds from the main dict in the expire dict */ int slot = getKeySlot(key->ptr); if (!kde) kde = kvstoreDictFind(db->keys, slot, key->ptr); serverAssertWithInfo(NULL,key,kde != NULL); de = kvstoreDictAddRaw(db->expires, slot, dictGetKey(kde), &existing); if (existing) { dictSetSignedIntegerVal(existing, when); } else { dictSetSignedIntegerVal(de, when); } int writable_slave = server.masterhost && server.repl_slave_ro == 0; if (c && writable_slave && !(c->flags & CLIENT_MASTER)) rememberSlaveKeyWithExpire(db,key); } /* Return the expire time of the specified key, or -1 if no expire * is associated with this key (i.e. the key is non volatile) */ static inline long long getExpireWithSlot(redisDb *db, robj *key, int keySlot) { dictEntry *de; if ((de = dbFindExpiresWithKeySlot(db, key->ptr, keySlot)) == NULL) return -1; return dictGetSignedIntegerVal(de); } /* Return the expire time of the specified key, or -1 if no expire * is associated with this key (i.e. the key is non volatile) */ long long getExpire(redisDb *db, robj *key) { dictEntry *de; if ((de = dbFindExpires(db, key->ptr)) == NULL) return -1; return dictGetSignedIntegerVal(de); } /* Delete the specified expired or evicted key and propagate to replicas. * Currently notify_type can only be NOTIFY_EXPIRED or NOTIFY_EVICTED, * and it affects other aspects like the latency monitor event name and, * which config to look for lazy free, stats var to increment, and so on. * * key_mem_freed is an out parameter which contains the estimated * amount of memory freed due to the trimming (may be NULL) */ static void deleteKeyAndPropagate(redisDb *db, robj *keyobj, int notify_type, long long *key_mem_freed) { mstime_t latency; int del_flag = notify_type == NOTIFY_EXPIRED ? DB_FLAG_KEY_EXPIRED : DB_FLAG_KEY_EVICTED; int lazy_flag = notify_type == NOTIFY_EXPIRED ? server.lazyfree_lazy_expire : server.lazyfree_lazy_eviction; char *latency_name = notify_type == NOTIFY_EXPIRED ? "expire-del" : "evict-del"; char *notify_name = notify_type == NOTIFY_EXPIRED ? "expired" : "evicted"; /* The key needs to be converted from static to heap before deleted */ int static_key = keyobj->refcount == OBJ_STATIC_REFCOUNT; if (static_key) { keyobj = createStringObject(keyobj->ptr, sdslen(keyobj->ptr)); } serverLog(LL_DEBUG,"key %s %s: deleting it", (char*)keyobj->ptr, notify_type == NOTIFY_EXPIRED ? "expired" : "evicted"); /* We compute the amount of memory freed by db*Delete() alone. * It is possible that actually the memory needed to propagate * the DEL in AOF and replication link is greater than the one * we are freeing removing the key, but we can't account for * that otherwise we would never exit the loop. * * Same for CSC invalidation messages generated by signalModifiedKey. * * AOF and Output buffer memory will be freed eventually so * we only care about memory used by the key space. * * The code here used to first propagate and then record delta * using only zmalloc_used_memory but in CRDT we can't do that * so we use freeMemoryGetNotCountedMemory to avoid counting * AOF and slave buffers */ if (key_mem_freed) *key_mem_freed = (long long) zmalloc_used_memory() - freeMemoryGetNotCountedMemory(); latencyStartMonitor(latency); dbGenericDelete(db, keyobj, lazy_flag, del_flag); latencyEndMonitor(latency); latencyAddSampleIfNeeded(latency_name, latency); if (key_mem_freed) *key_mem_freed -= (long long) zmalloc_used_memory() - freeMemoryGetNotCountedMemory(); notifyKeyspaceEvent(notify_type, notify_name,keyobj, db->id); signalModifiedKey(NULL, db, keyobj); propagateDeletion(db, keyobj, lazy_flag); if (notify_type == NOTIFY_EXPIRED) server.stat_expiredkeys++; else server.stat_evictedkeys++; if (static_key) decrRefCount(keyobj); } /* Delete the specified expired key and propagate. */ void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj) { deleteKeyAndPropagate(db, keyobj, NOTIFY_EXPIRED, NULL); } /* Delete the specified evicted key and propagate. */ void deleteEvictedKeyAndPropagate(redisDb *db, robj *keyobj, long long *key_mem_freed) { deleteKeyAndPropagate(db, keyobj, NOTIFY_EVICTED, key_mem_freed); } /* Propagate an implicit key deletion into replicas and the AOF file. * When a key was deleted in the master by eviction, expiration or a similar * mechanism a DEL/UNLINK operation for this key is sent * to all the replicas and the AOF file if enabled. * * This way the key deletion is centralized in one place, and since both * AOF and the replication link guarantee operation ordering, everything * will be consistent even if we allow write operations against deleted * keys. * * This function may be called from: * 1. Within call(): Example: Lazy-expire on key access. * In this case the caller doesn't have to do anything * because call() handles server.also_propagate(); or * 2. Outside of call(): Example: Active-expire, eviction, slot ownership changed. * In this the caller must remember to call * postExecutionUnitOperations, preferably just after a * single deletion batch, so that DEL/UNLINK will NOT be wrapped * in MULTI/EXEC */ void propagateDeletion(redisDb *db, robj *key, int lazy) { robj *argv[2]; argv[0] = lazy ? shared.unlink : shared.del; argv[1] = key; incrRefCount(argv[0]); incrRefCount(argv[1]); /* If the master decided to delete a key we must propagate it to replicas no matter what. * Even if module executed a command without asking for propagation. */ int prev_replication_allowed = server.replication_allowed; server.replication_allowed = 1; alsoPropagate(db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL); server.replication_allowed = prev_replication_allowed; decrRefCount(argv[0]); decrRefCount(argv[1]); } /* Internal Check if the key is expired based upon mstime_t. */ static inline int keyIsExpiredInternal(mstime_t when) { /* Don't expire anything while loading. It will be done later. */ if (server.loading) return 0; if (when < 0) return 0; /* No expire for this key */ const mstime_t now = commandTimeSnapshot(); /* The key expired if the current (virtual or real) time is greater * than the expire time of the key. */ return now > when; } /* Check if the key is expired. */ static inline int keyIsExpiredWithSlot(redisDb *db, robj *key, int keySlot) { return keyIsExpiredInternal(getExpireWithSlot(db,key,keySlot)); } /* Check if the key is expired. */ int keyIsExpired(redisDb *db, robj *key) { return keyIsExpiredInternal(getExpire(db,key)); } /* This function is called when we are going to perform some operation * in a given key, but such key may be already logically expired even if * it still exists in the database. The main way this function is called * is via lookupKey*() family of functions. * * The behavior of the function depends on the replication role of the * instance, because by default replicas do not delete expired keys. They * wait for DELs from the master for consistency matters. However even * replicas will try to have a coherent return value for the function, * so that read commands executed in the replica side will be able to * behave like if the key is expired even if still present (because the * master has yet to propagate the DEL). * * In masters as a side effect of finding a key which is expired, such * key will be evicted from the database. Also this may trigger the * propagation of a DEL/UNLINK command in AOF / replication stream. * * On replicas, this function does not delete expired keys by default, but * it still returns KEY_EXPIRED if the key is logically expired. To force deletion * of logically expired keys even on replicas, use the EXPIRE_FORCE_DELETE_EXPIRED * flag. Note though that if the current client is executing * replicated commands from the master, keys are never considered expired. * * On the other hand, if you just want expiration check, but need to avoid * the actual key deletion and propagation of the deletion, use the * EXPIRE_AVOID_DELETE_EXPIRED flag. If also needed to read expired key (that * hasn't being deleted yet) then use EXPIRE_ALLOW_ACCESS_EXPIRED. * * The return value of the function is KEY_VALID if the key is still valid. * The function returns KEY_EXPIRED if the key is expired BUT not deleted, * or returns KEY_DELETED if the key is expired and deleted. */ keyStatus expireIfNeeded(redisDb *db, robj *key, int flags) { return expireIfNeededWithSlot(db,key,flags,getKeySlot(key->ptr)); } static inline keyStatus expireIfNeededWithSlot(redisDb *db, robj *key, int flags, const int keySlot) { if ((server.allow_access_expired) || (flags & EXPIRE_ALLOW_ACCESS_EXPIRED) || (!keyIsExpiredWithSlot(db,key,keySlot))) return KEY_VALID; /* If we are running in the context of a replica, instead of * evicting the expired key from the database, we return ASAP: * the replica key expiration is controlled by the master that will * send us synthesized DEL operations for expired keys. The * exception is when write operations are performed on writable * replicas. * * Still we try to return the right information to the caller, * that is, KEY_VALID if we think the key should still be valid, * KEY_EXPIRED if we think the key is expired but don't want to delete it at this time. * * When replicating commands from the master, keys are never considered * expired. */ if (server.masterhost != NULL) { if (server.current_client && (server.current_client->flags & CLIENT_MASTER)) return KEY_VALID; if (!(flags & EXPIRE_FORCE_DELETE_EXPIRED)) return KEY_EXPIRED; } /* In some cases we're explicitly instructed to return an indication of a * missing key without actually deleting it, even on masters. */ if (flags & EXPIRE_AVOID_DELETE_EXPIRED) return KEY_EXPIRED; /* If 'expire' action is paused, for whatever reason, then don't expire any key. * Typically, at the end of the pause we will properly expire the key OR we * will have failed over and the new primary will send us the expire. */ if (isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)) return KEY_EXPIRED; /* Delete the key */ deleteExpiredKeyAndPropagate(db,key); return KEY_DELETED; } /* CB passed to kvstoreExpand. * The purpose is to skip expansion of unused dicts in cluster mode (all * dicts not mapped to *my* slots) */ static int dbExpandSkipSlot(int slot) { return !clusterNodeCoversSlot(getMyClusterNode(), slot); } /* * This functions increases size of the main/expires db to match desired number. * In cluster mode resizes all individual dictionaries for slots that this node owns. * * Based on the parameter `try_expand`, appropriate dict expand API is invoked. * if try_expand is set to 1, `dictTryExpand` is used else `dictExpand`. * The return code is either `DICT_OK`/`DICT_ERR` for both the API(s). * `DICT_OK` response is for successful expansion. However ,`DICT_ERR` response signifies failure in allocation in * `dictTryExpand` call and in case of `dictExpand` call it signifies no expansion was performed. */ static int dbExpandGeneric(kvstore *kvs, uint64_t db_size, int try_expand) { int ret; if (server.cluster_enabled) { /* We don't know exact number of keys that would fall into each slot, but we can * approximate it, assuming even distribution, divide it by the number of slots. */ int slots = getMyShardSlotCount(); if (slots == 0) return C_OK; db_size = db_size / slots; ret = kvstoreExpand(kvs, db_size, try_expand, dbExpandSkipSlot); } else { ret = kvstoreExpand(kvs, db_size, try_expand, NULL); } return ret? C_OK : C_ERR; } int dbExpand(redisDb *db, uint64_t db_size, int try_expand) { return dbExpandGeneric(db->keys, db_size, try_expand); } int dbExpandExpires(redisDb *db, uint64_t db_size, int try_expand) { return dbExpandGeneric(db->expires, db_size, try_expand); } static inline dictEntry *dbFindGenericWithKeySlot(kvstore *kvs, void *key, int keySlot) { return kvstoreDictFind(kvs, keySlot, key); } static dictEntry *dbFindGeneric(kvstore *kvs, void *key) { return kvstoreDictFind(kvs, getKeySlot(key), key); } dictEntry *dbFind(redisDb *db, void *key) { return dbFindGeneric(db->keys, key); } static inline dictEntry *dbFindWithKeySlot(redisDb *db, void *key, int keySlot) { return dbFindGenericWithKeySlot(db->keys, key, keySlot); } static inline dictEntry *dbFindExpiresWithKeySlot(redisDb *db, void *key, int keySlot) { return dbFindGenericWithKeySlot(db->expires, key, keySlot); } dictEntry *dbFindExpires(redisDb *db, void *key) { return dbFindGeneric(db->expires, key); } unsigned long long dbSize(redisDb *db) { return kvstoreSize(db->keys); } unsigned long long dbScan(redisDb *db, unsigned long long cursor, dictScanFunction *scan_cb, void *privdata) { return kvstoreScan(db->keys, cursor, -1, scan_cb, NULL, privdata); } /* ----------------------------------------------------------------------------- * API to get key arguments from commands * ---------------------------------------------------------------------------*/ /* Prepare the getKeysResult struct to hold numkeys, either by using the * pre-allocated keysbuf or by allocating a new array on the heap. * * This function must be called at least once before starting to populate * the result, and can be called repeatedly to enlarge the result array. */ keyReference *getKeysPrepareResult(getKeysResult *result, int numkeys) { /* GETKEYS_RESULT_INIT initializes keys to NULL, point it to the pre-allocated stack * buffer here. */ if (!result->keys) { serverAssert(!result->numkeys); result->keys = result->keysbuf; } /* Resize if necessary */ if (numkeys > result->size) { if (result->keys != result->keysbuf) { /* We're not using a static buffer, just (re)alloc */ result->keys = zrealloc(result->keys, numkeys * sizeof(keyReference)); } else { /* We are using a static buffer, copy its contents */ result->keys = zmalloc(numkeys * sizeof(keyReference)); if (result->numkeys) memcpy(result->keys, result->keysbuf, result->numkeys * sizeof(keyReference)); } result->size = numkeys; } return result->keys; } /* Returns a bitmask with all the flags found in any of the key specs of the command. * The 'inv' argument means we'll return a mask with all flags that are missing in at least one spec. */ int64_t getAllKeySpecsFlags(struct redisCommand *cmd, int inv) { int64_t flags = 0; for (int j = 0; j < cmd->key_specs_num; j++) { keySpec *spec = cmd->key_specs + j; flags |= inv? ~spec->flags : spec->flags; } return flags; } /* Fetch the keys based of the provided key specs. Returns the number of keys found, or -1 on error. * There are several flags that can be used to modify how this function finds keys in a command. * * GET_KEYSPEC_INCLUDE_NOT_KEYS: Return 'fake' keys as if they were keys. * GET_KEYSPEC_RETURN_PARTIAL: Skips invalid and incomplete keyspecs but returns the keys * found in other valid keyspecs. */ int getKeysUsingKeySpecs(struct redisCommand *cmd, robj **argv, int argc, int search_flags, getKeysResult *result) { long j, i, last, first, step; keyReference *keys; serverAssert(result->numkeys == 0); /* caller should initialize or reset it */ for (j = 0; j < cmd->key_specs_num; j++) { keySpec *spec = cmd->key_specs + j; serverAssert(spec->begin_search_type != KSPEC_BS_INVALID); /* Skip specs that represent 'fake' keys */ if ((spec->flags & CMD_KEY_NOT_KEY) && !(search_flags & GET_KEYSPEC_INCLUDE_NOT_KEYS)) { continue; } first = 0; if (spec->begin_search_type == KSPEC_BS_INDEX) { first = spec->bs.index.pos; } else if (spec->begin_search_type == KSPEC_BS_KEYWORD) { int start_index = spec->bs.keyword.startfrom > 0 ? spec->bs.keyword.startfrom : argc+spec->bs.keyword.startfrom; int end_index = spec->bs.keyword.startfrom > 0 ? argc-1: 1; for (i = start_index; i != end_index; i = start_index <= end_index ? i + 1 : i - 1) { if (i >= argc || i < 1) break; if (!strcasecmp((char*)argv[i]->ptr,spec->bs.keyword.keyword)) { first = i+1; break; } } /* keyword not found */ if (!first) { continue; } } else { /* unknown spec */ goto invalid_spec; } if (spec->find_keys_type == KSPEC_FK_RANGE) { step = spec->fk.range.keystep; if (spec->fk.range.lastkey >= 0) { last = first + spec->fk.range.lastkey; } else { if (!spec->fk.range.limit) { last = argc + spec->fk.range.lastkey; } else { serverAssert(spec->fk.range.lastkey == -1); last = first + ((argc-first)/spec->fk.range.limit + spec->fk.range.lastkey); } } } else if (spec->find_keys_type == KSPEC_FK_KEYNUM) { step = spec->fk.keynum.keystep; long long numkeys; if (spec->fk.keynum.keynumidx >= argc) goto invalid_spec; sds keynum_str = argv[first + spec->fk.keynum.keynumidx]->ptr; if (!string2ll(keynum_str,sdslen(keynum_str),&numkeys) || numkeys < 0) { /* Unable to parse the numkeys argument or it was invalid */ goto invalid_spec; } first += spec->fk.keynum.firstkey; last = first + (long)numkeys-1; } else { /* unknown spec */ goto invalid_spec; } /* First or last is out of bounds, which indicates a syntax error */ if (last >= argc || last < first || first >= argc) { goto invalid_spec; } int count = ((last - first)+1); keys = getKeysPrepareResult(result, result->numkeys + count); for (i = first; i <= last; i += step) { if (i >= argc || i < first) { /* Modules commands, and standard commands with a not fixed number * of arguments (negative arity parameter) do not have dispatch * time arity checks, so we need to handle the case where the user * passed an invalid number of arguments here. In this case we * return no keys and expect the command implementation to report * an arity or syntax error. */ if (cmd->flags & CMD_MODULE || cmd->arity < 0) { continue; } else { serverPanic("Redis built-in command declared keys positions not matching the arity requirements."); } } keys[result->numkeys].pos = i; keys[result->numkeys].flags = spec->flags; result->numkeys++; } /* Handle incomplete specs (only after we added the current spec * to `keys`, just in case GET_KEYSPEC_RETURN_PARTIAL was given) */ if (spec->flags & CMD_KEY_INCOMPLETE) { goto invalid_spec; } /* Done with this spec */ continue; invalid_spec: if (search_flags & GET_KEYSPEC_RETURN_PARTIAL) { continue; } else { result->numkeys = 0; return -1; } } return result->numkeys; } /* Return all the arguments that are keys in the command passed via argc / argv. * This function will eventually replace getKeysFromCommand. * * The command returns the positions of all the key arguments inside the array, * so the actual return value is a heap allocated array of integers. The * length of the array is returned by reference into *numkeys. * * Along with the position, this command also returns the flags that are * associated with how Redis will access the key. * * 'cmd' must be point to the corresponding entry into the redisCommand * table, according to the command name in argv[0]. */ int getKeysFromCommandWithSpecs(struct redisCommand *cmd, robj **argv, int argc, int search_flags, getKeysResult *result) { /* The command has at least one key-spec not marked as NOT_KEY */ int has_keyspec = (getAllKeySpecsFlags(cmd, 1) & CMD_KEY_NOT_KEY); /* The command has at least one key-spec marked as VARIABLE_FLAGS */ int has_varflags = (getAllKeySpecsFlags(cmd, 0) & CMD_KEY_VARIABLE_FLAGS); /* We prefer key-specs if there are any, and their flags are reliable. */ if (has_keyspec && !has_varflags) { int ret = getKeysUsingKeySpecs(cmd,argv,argc,search_flags,result); if (ret >= 0) return ret; /* If the specs returned with an error (probably an INVALID or INCOMPLETE spec), * fallback to the callback method. */ } /* Resort to getkeys callback methods. */ if (cmd->flags & CMD_MODULE_GETKEYS) return moduleGetCommandKeysViaAPI(cmd,argv,argc,result); /* We use native getkeys as a last resort, since not all these native getkeys provide * flags properly (only the ones that correspond to INVALID, INCOMPLETE or VARIABLE_FLAGS do.*/ if (cmd->getkeys_proc) return cmd->getkeys_proc(cmd,argv,argc,result); return 0; } /* This function returns a sanity check if the command may have keys. */ int doesCommandHaveKeys(struct redisCommand *cmd) { return cmd->getkeys_proc || /* has getkeys_proc (non modules) */ (cmd->flags & CMD_MODULE_GETKEYS) || /* module with GETKEYS */ (getAllKeySpecsFlags(cmd, 1) & CMD_KEY_NOT_KEY); /* has at least one key-spec not marked as NOT_KEY */ } /* A simplified channel spec table that contains all of the redis commands * and which channels they have and how they are accessed. */ typedef struct ChannelSpecs { redisCommandProc *proc; /* Command procedure to match against */ uint64_t flags; /* CMD_CHANNEL_* flags for this command */ int start; /* The initial position of the first channel */ int count; /* The number of channels, or -1 if all remaining * arguments are channels. */ } ChannelSpecs; ChannelSpecs commands_with_channels[] = { {subscribeCommand, CMD_CHANNEL_SUBSCRIBE, 1, -1}, {ssubscribeCommand, CMD_CHANNEL_SUBSCRIBE, 1, -1}, {unsubscribeCommand, CMD_CHANNEL_UNSUBSCRIBE, 1, -1}, {sunsubscribeCommand, CMD_CHANNEL_UNSUBSCRIBE, 1, -1}, {psubscribeCommand, CMD_CHANNEL_PATTERN | CMD_CHANNEL_SUBSCRIBE, 1, -1}, {punsubscribeCommand, CMD_CHANNEL_PATTERN | CMD_CHANNEL_UNSUBSCRIBE, 1, -1}, {publishCommand, CMD_CHANNEL_PUBLISH, 1, 1}, {spublishCommand, CMD_CHANNEL_PUBLISH, 1, 1}, {NULL,0} /* Terminator. */ }; /* Returns 1 if the command may access any channels matched by the flags * argument. */ int doesCommandHaveChannelsWithFlags(struct redisCommand *cmd, int flags) { /* If a module declares get channels, we are just going to assume * has channels. This API is allowed to return false positives. */ if (cmd->flags & CMD_MODULE_GETCHANNELS) { return 1; } for (ChannelSpecs *spec = commands_with_channels; spec->proc != NULL; spec += 1) { if (cmd->proc == spec->proc) { return !!(spec->flags & flags); } } return 0; } /* Return all the arguments that are channels in the command passed via argc / argv. * This function behaves similar to getKeysFromCommandWithSpecs, but with channels * instead of keys. * * The command returns the positions of all the channel arguments inside the array, * so the actual return value is a heap allocated array of integers. The * length of the array is returned by reference into *numkeys. * * Along with the position, this command also returns the flags that are * associated with how Redis will access the channel. * * 'cmd' must be point to the corresponding entry into the redisCommand * table, according to the command name in argv[0]. */ int getChannelsFromCommand(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { keyReference *keys; /* If a module declares get channels, use that. */ if (cmd->flags & CMD_MODULE_GETCHANNELS) { return moduleGetCommandChannelsViaAPI(cmd, argv, argc, result); } /* Otherwise check the channel spec table */ for (ChannelSpecs *spec = commands_with_channels; spec != NULL; spec += 1) { if (cmd->proc == spec->proc) { int start = spec->start; int stop = (spec->count == -1) ? argc : start + spec->count; if (stop > argc) stop = argc; int count = 0; keys = getKeysPrepareResult(result, stop - start); for (int i = start; i < stop; i++ ) { keys[count].pos = i; keys[count++].flags = spec->flags; } result->numkeys = count; return count; } } return 0; } /* The base case is to use the keys position as given in the command table * (firstkey, lastkey, step). * This function works only on command with the legacy_range_key_spec, * all other commands should be handled by getkeys_proc. * * If the commands keyspec is incomplete, no keys will be returned, and the provided * keys function should be called instead. * * NOTE: This function does not guarantee populating the flags for * the keys, in order to get flags you should use getKeysUsingKeySpecs. */ int getKeysUsingLegacyRangeSpec(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { int j, i = 0, last, first, step; keyReference *keys; UNUSED(argv); if (cmd->legacy_range_key_spec.begin_search_type == KSPEC_BS_INVALID) { result->numkeys = 0; return 0; } first = cmd->legacy_range_key_spec.bs.index.pos; last = cmd->legacy_range_key_spec.fk.range.lastkey; if (last >= 0) last += first; step = cmd->legacy_range_key_spec.fk.range.keystep; if (last < 0) last = argc+last; int count = ((last - first)+1); keys = getKeysPrepareResult(result, count); for (j = first; j <= last; j += step) { if (j >= argc || j < first) { /* Modules commands, and standard commands with a not fixed number * of arguments (negative arity parameter) do not have dispatch * time arity checks, so we need to handle the case where the user * passed an invalid number of arguments here. In this case we * return no keys and expect the command implementation to report * an arity or syntax error. */ if (cmd->flags & CMD_MODULE || cmd->arity < 0) { result->numkeys = 0; return 0; } else { serverPanic("Redis built-in command declared keys positions not matching the arity requirements."); } } keys[i].pos = j; /* Flags are omitted from legacy key specs */ keys[i++].flags = 0; } result->numkeys = i; return i; } /* Return all the arguments that are keys in the command passed via argc / argv. * * The command returns the positions of all the key arguments inside the array, * so the actual return value is a heap allocated array of integers. The * length of the array is returned by reference into *numkeys. * * 'cmd' must be point to the corresponding entry into the redisCommand * table, according to the command name in argv[0]. * * This function uses the command table if a command-specific helper function * is not required, otherwise it calls the command-specific function. */ int getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { if (cmd->flags & CMD_MODULE_GETKEYS) { return moduleGetCommandKeysViaAPI(cmd,argv,argc,result); } else if (cmd->getkeys_proc) { return cmd->getkeys_proc(cmd,argv,argc,result); } else { return getKeysUsingLegacyRangeSpec(cmd,argv,argc,result); } } /* Free the result of getKeysFromCommand. */ void getKeysFreeResult(getKeysResult *result) { if (result && result->keys != result->keysbuf) zfree(result->keys); } /* Helper function to extract keys from following commands: * COMMAND [destkey] [...] [...] ... * * eg: * ZUNION ... * ZUNIONSTORE ... * * 'storeKeyOfs': destkey index, 0 means destkey not exists. * 'keyCountOfs': num-keys index. * 'firstKeyOfs': firstkey index. * 'keyStep': the interval of each key, usually this value is 1. * * The commands using this function have a fully defined keyspec, so returning flags isn't needed. */ int genericGetKeys(int storeKeyOfs, int keyCountOfs, int firstKeyOfs, int keyStep, robj **argv, int argc, getKeysResult *result) { int i, num; keyReference *keys; num = atoi(argv[keyCountOfs]->ptr); /* Sanity check. Don't return any key if the command is going to * reply with syntax error. (no input keys). */ if (num < 1 || num > (argc - firstKeyOfs)/keyStep) { result->numkeys = 0; return 0; } int numkeys = storeKeyOfs ? num + 1 : num; keys = getKeysPrepareResult(result, numkeys); result->numkeys = numkeys; /* Add all key positions for argv[firstKeyOfs...n] to keys[] */ for (i = 0; i < num; i++) { keys[i].pos = firstKeyOfs+(i*keyStep); keys[i].flags = 0; } if (storeKeyOfs) { keys[num].pos = storeKeyOfs; keys[num].flags = 0; } return result->numkeys; } int sintercardGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 1, 2, 1, argv, argc, result); } int zunionInterDiffStoreGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(1, 2, 3, 1, argv, argc, result); } int zunionInterDiffGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 1, 2, 1, argv, argc, result); } int evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 2, 3, 1, argv, argc, result); } int functionGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 2, 3, 1, argv, argc, result); } int lmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 1, 2, 1, argv, argc, result); } int blmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 2, 3, 1, argv, argc, result); } int zmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 1, 2, 1, argv, argc, result); } int bzmpopGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { UNUSED(cmd); return genericGetKeys(0, 2, 3, 1, argv, argc, result); } /* Helper function to extract keys from the SORT RO command. * * SORT * * The second argument of SORT is always a key, however an arbitrary number of * keys may be accessed while doing the sort (the BY and GET args), so the * key-spec declares incomplete keys which is why we have to provide a concrete * implementation to fetch the keys. * * This command declares incomplete keys, so the flags are correctly set for this function */ int sortROGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { keyReference *keys; UNUSED(cmd); UNUSED(argv); UNUSED(argc); keys = getKeysPrepareResult(result, 1); keys[0].pos = 1; /* is always present. */ keys[0].flags = CMD_KEY_RO | CMD_KEY_ACCESS; result->numkeys = 1; return result->numkeys; } /* Helper function to extract keys from the SORT command. * * SORT ... STORE ... * * The first argument of SORT is always a key, however a list of options * follow in SQL-alike style. Here we parse just the minimum in order to * correctly identify keys in the "STORE" option. * * This command declares incomplete keys, so the flags are correctly set for this function */ int sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { int i, j, num, found_store = 0; keyReference *keys; UNUSED(cmd); num = 0; keys = getKeysPrepareResult(result, 2); /* Alloc 2 places for the worst case. */ keys[num].pos = 1; /* is always present. */ keys[num++].flags = CMD_KEY_RO | CMD_KEY_ACCESS; /* Search for STORE option. By default we consider options to don't * have arguments, so if we find an unknown option name we scan the * next. However there are options with 1 or 2 arguments, so we * provide a list here in order to skip the right number of args. */ struct { char *name; int skip; } skiplist[] = { {"limit", 2}, {"get", 1}, {"by", 1}, {NULL, 0} /* End of elements. */ }; for (i = 2; i < argc; i++) { for (j = 0; skiplist[j].name != NULL; j++) { if (!strcasecmp(argv[i]->ptr,skiplist[j].name)) { i += skiplist[j].skip; break; } else if (!strcasecmp(argv[i]->ptr,"store") && i+1 < argc) { /* Note: we don't increment "num" here and continue the loop * to be sure to process the *last* "STORE" option if multiple * ones are provided. This is same behavior as SORT. */ found_store = 1; keys[num].pos = i+1; /* */ keys[num].flags = CMD_KEY_OW | CMD_KEY_UPDATE; break; } } } result->numkeys = num + found_store; return result->numkeys; } /* This command declares incomplete keys, so the flags are correctly set for this function */ int migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { int i, j, num, first; keyReference *keys; UNUSED(cmd); /* Assume the obvious form. */ first = 3; num = 1; /* But check for the extended one with the KEYS option. */ struct { char* name; int skip; } skip_keywords[] = { {"copy", 0}, {"replace", 0}, {"auth", 1}, {"auth2", 2}, {NULL, 0} }; if (argc > 6) { for (i = 6; i < argc; i++) { if (!strcasecmp(argv[i]->ptr, "keys")) { if (sdslen(argv[3]->ptr) > 0) { /* This is a syntax error. So ignore the keys and leave * the syntax error to be handled by migrateCommand. */ num = 0; } else { first = i + 1; num = argc - first; } break; } for (j = 0; skip_keywords[j].name != NULL; j++) { if (!strcasecmp(argv[i]->ptr, skip_keywords[j].name)) { i += skip_keywords[j].skip; break; } } } } keys = getKeysPrepareResult(result, num); for (i = 0; i < num; i++) { keys[i].pos = first+i; keys[i].flags = CMD_KEY_RW | CMD_KEY_ACCESS | CMD_KEY_DELETE; } result->numkeys = num; return num; } /* Helper function to extract keys from following commands: * GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC] * [COUNT count] [STORE key|STOREDIST key] * GEORADIUSBYMEMBER key member radius unit ... options ... * * This command has a fully defined keyspec, so returning flags isn't needed. */ int georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { int i, num; keyReference *keys; UNUSED(cmd); /* Check for the presence of the stored key in the command */ int stored_key = -1; for (i = 5; i < argc; i++) { char *arg = argv[i]->ptr; /* For the case when user specifies both "store" and "storedist" options, the * second key specified would override the first key. This behavior is kept * the same as in georadiusCommand method. */ if ((!strcasecmp(arg, "store") || !strcasecmp(arg, "storedist")) && ((i+1) < argc)) { stored_key = i+1; i++; } } num = 1 + (stored_key == -1 ? 0 : 1); /* Keys in the command come from two places: * argv[1] = key, * argv[5...n] = stored key if present */ keys = getKeysPrepareResult(result, num); /* Add all key positions to keys[] */ keys[0].pos = 1; keys[0].flags = 0; if(num > 1) { keys[1].pos = stored_key; keys[1].flags = 0; } result->numkeys = num; return num; } /* XREAD [BLOCK ] [COUNT ] [GROUP ] * STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N * * This command has a fully defined keyspec, so returning flags isn't needed. */ int xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { int i, num = 0; keyReference *keys; UNUSED(cmd); /* We need to parse the options of the command in order to seek the first * "STREAMS" string which is actually the option. This is needed because * "STREAMS" could also be the name of the consumer group and even the * name of the stream key. */ int streams_pos = -1; for (i = 1; i < argc; i++) { char *arg = argv[i]->ptr; if (!strcasecmp(arg, "block")) { i++; /* Skip option argument. */ } else if (!strcasecmp(arg, "count")) { i++; /* Skip option argument. */ } else if (!strcasecmp(arg, "group")) { i += 2; /* Skip option argument. */ } else if (!strcasecmp(arg, "noack")) { /* Nothing to do. */ } else if (!strcasecmp(arg, "streams")) { streams_pos = i; break; } else { break; /* Syntax error. */ } } if (streams_pos != -1) num = argc - streams_pos - 1; /* Syntax error. */ if (streams_pos == -1 || num == 0 || num % 2 != 0) { result->numkeys = 0; return 0; } num /= 2; /* We have half the keys as there are arguments because there are also the IDs, one per key. */ keys = getKeysPrepareResult(result, num); for (i = streams_pos+1; i < argc-num; i++) { keys[i-streams_pos-1].pos = i; keys[i-streams_pos-1].flags = 0; } result->numkeys = num; return num; } /* Helper function to extract keys from the SET command, which may have * a read flag if the GET argument is passed in. */ int setGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { keyReference *keys; UNUSED(cmd); keys = getKeysPrepareResult(result, 1); keys[0].pos = 1; /* We always know the position */ result->numkeys = 1; for (int i = 3; i < argc; i++) { char *arg = argv[i]->ptr; if ((arg[0] == 'g' || arg[0] == 'G') && (arg[1] == 'e' || arg[1] == 'E') && (arg[2] == 't' || arg[2] == 'T') && arg[3] == '\0') { keys[0].flags = CMD_KEY_RW | CMD_KEY_ACCESS | CMD_KEY_UPDATE; return 1; } } keys[0].flags = CMD_KEY_OW | CMD_KEY_UPDATE; return 1; } /* Helper function to extract keys from the BITFIELD command, which may be * read-only if the BITFIELD GET subcommand is used. */ int bitfieldGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { keyReference *keys; int readonly = 1; UNUSED(cmd); keys = getKeysPrepareResult(result, 1); keys[0].pos = 1; /* We always know the position */ result->numkeys = 1; for (int i = 2; i < argc; i++) { int remargs = argc - i - 1; /* Remaining args other than current. */ char *arg = argv[i]->ptr; if (!strcasecmp(arg, "get") && remargs >= 2) { i += 2; } else if ((!strcasecmp(arg, "set") || !strcasecmp(arg, "incrby")) && remargs >= 3) { readonly = 0; i += 3; break; } else if (!strcasecmp(arg, "overflow") && remargs >= 1) { i += 1; } else { readonly = 0; /* Syntax error. safer to assume non-RO. */ break; } } if (readonly) { keys[0].flags = CMD_KEY_RO | CMD_KEY_ACCESS; } else { keys[0].flags = CMD_KEY_RW | CMD_KEY_ACCESS | CMD_KEY_UPDATE; } return 1; } redis-8.0.2/src/debug.c000066400000000000000000003257511501533116600146660ustar00rootroot00000000000000/* * Copyright (c) 2009-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). * * Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information. */ #include "server.h" #include "util.h" #include "sha1.h" /* SHA1 is used for DEBUG DIGEST */ #include "crc64.h" #include "bio.h" #include "quicklist.h" #include "fpconv_dtoa.h" #include "fast_float_strtod.h" #include "cluster.h" #include "threads_mngr.h" #include "script.h" #include #include #include #include #include #include #ifdef HAVE_BACKTRACE #include #ifndef __OpenBSD__ #include #else typedef ucontext_t sigcontext_t; #endif #endif /* HAVE_BACKTRACE */ #ifdef __CYGWIN__ #ifndef SA_ONSTACK #define SA_ONSTACK 0x08000000 #endif #endif #if defined(__APPLE__) && defined(__arm64__) #include #endif /* Globals */ static int bug_report_start = 0; /* True if bug report header was already logged. */ static pthread_mutex_t bug_report_start_mutex = PTHREAD_MUTEX_INITIALIZER; /* Mutex for a case when two threads crash at the same time. */ static pthread_mutex_t signal_handler_lock; static pthread_mutexattr_t signal_handler_lock_attr; static volatile int signal_handler_lock_initialized = 0; /* Forward declarations */ int bugReportStart(void); void printCrashReport(void); void bugReportEnd(int killViaSignal, int sig); void logStackTrace(void *eip, int uplevel, int current_thread); void sigalrmSignalHandler(int sig, siginfo_t *info, void *secret); /* ================================= Debugging ============================== */ /* Compute the sha1 of string at 's' with 'len' bytes long. * The SHA1 is then xored against the string pointed by digest. * Since xor is commutative, this operation is used in order to * "add" digests relative to unordered elements. * * So digest(a,b,c,d) will be the same of digest(b,a,c,d) */ void xorDigest(unsigned char *digest, const void *ptr, size_t len) { SHA1_CTX ctx; unsigned char hash[20]; int j; SHA1Init(&ctx); SHA1Update(&ctx,ptr,len); SHA1Final(hash,&ctx); for (j = 0; j < 20; j++) digest[j] ^= hash[j]; } void xorStringObjectDigest(unsigned char *digest, robj *o) { o = getDecodedObject(o); xorDigest(digest,o->ptr,sdslen(o->ptr)); decrRefCount(o); } /* This function instead of just computing the SHA1 and xoring it * against digest, also perform the digest of "digest" itself and * replace the old value with the new one. * * So the final digest will be: * * digest = SHA1(digest xor SHA1(data)) * * This function is used every time we want to preserve the order so * that digest(a,b,c,d) will be different than digest(b,c,d,a) * * Also note that mixdigest("foo") followed by mixdigest("bar") * will lead to a different digest compared to "fo", "obar". */ void mixDigest(unsigned char *digest, const void *ptr, size_t len) { SHA1_CTX ctx; xorDigest(digest,ptr,len); SHA1Init(&ctx); SHA1Update(&ctx,digest,20); SHA1Final(digest,&ctx); } void mixStringObjectDigest(unsigned char *digest, robj *o) { o = getDecodedObject(o); mixDigest(digest,o->ptr,sdslen(o->ptr)); decrRefCount(o); } /* This function computes the digest of a data structure stored in the * object 'o'. It is the core of the DEBUG DIGEST command: when taking the * digest of a whole dataset, we take the digest of the key and the value * pair, and xor all those together. * * Note that this function does not reset the initial 'digest' passed, it * will continue mixing this object digest to anything that was already * present. */ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) { uint32_t aux = htonl(o->type); mixDigest(digest,&aux,sizeof(aux)); long long expiretime = getExpire(db,keyobj); char buf[128]; /* Save the key and associated value */ if (o->type == OBJ_STRING) { mixStringObjectDigest(digest,o); } else if (o->type == OBJ_LIST) { listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { robj *eleobj = listTypeGet(&entry); mixStringObjectDigest(digest,eleobj); decrRefCount(eleobj); } listTypeReleaseIterator(li); } else if (o->type == OBJ_SET) { setTypeIterator *si = setTypeInitIterator(o); sds sdsele; while((sdsele = setTypeNextObject(si)) != NULL) { xorDigest(digest,sdsele,sdslen(sdsele)); sdsfree(sdsele); } setTypeReleaseIterator(si); } else if (o->type == OBJ_ZSET) { unsigned char eledigest[20]; if (o->encoding == OBJ_ENCODING_LISTPACK) { unsigned char *zl = o->ptr; unsigned char *eptr, *sptr; unsigned char *vstr; unsigned int vlen; long long vll; double score; eptr = lpSeek(zl,0); serverAssert(eptr != NULL); sptr = lpNext(zl,eptr); serverAssert(sptr != NULL); while (eptr != NULL) { vstr = lpGetValue(eptr,&vlen,&vll); score = zzlGetScore(sptr); memset(eledigest,0,20); if (vstr != NULL) { mixDigest(eledigest,vstr,vlen); } else { ll2string(buf,sizeof(buf),vll); mixDigest(eledigest,buf,strlen(buf)); } const int len = fpconv_dtoa(score, buf); buf[len] = '\0'; mixDigest(eledigest,buf,strlen(buf)); xorDigest(digest,eledigest,20); zzlNext(zl,&eptr,&sptr); } } else if (o->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = o->ptr; dictIterator *di = dictGetIterator(zs->dict); dictEntry *de; while((de = dictNext(di)) != NULL) { sds sdsele = dictGetKey(de); double *score = dictGetVal(de); const int len = fpconv_dtoa(*score, buf); buf[len] = '\0'; memset(eledigest,0,20); mixDigest(eledigest,sdsele,sdslen(sdsele)); mixDigest(eledigest,buf,strlen(buf)); xorDigest(digest,eledigest,20); } dictReleaseIterator(di); } else { serverPanic("Unknown sorted set encoding"); } } else if (o->type == OBJ_HASH) { hashTypeIterator *hi = hashTypeInitIterator(o); while (hashTypeNext(hi, 0) != C_ERR) { unsigned char eledigest[20]; sds sdsele; /* field */ memset(eledigest,0,20); sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY); mixDigest(eledigest,sdsele,sdslen(sdsele)); sdsfree(sdsele); /* val */ sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE); mixDigest(eledigest,sdsele,sdslen(sdsele)); sdsfree(sdsele); /* hash-field expiration (HFE) */ if (hi->expire_time != EB_EXPIRE_TIME_INVALID) xorDigest(eledigest,"!!hexpire!!",11); xorDigest(digest,eledigest,20); } hashTypeReleaseIterator(hi); } else if (o->type == OBJ_STREAM) { streamIterator si; streamIteratorStart(&si,o->ptr,NULL,NULL,0); streamID id; int64_t numfields; while(streamIteratorGetID(&si,&id,&numfields)) { sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq); mixDigest(digest,itemid,sdslen(itemid)); sdsfree(itemid); while(numfields--) { unsigned char *field, *value; int64_t field_len, value_len; streamIteratorGetField(&si,&field,&value, &field_len,&value_len); mixDigest(digest,field,field_len); mixDigest(digest,value,value_len); } } streamIteratorStop(&si); } else if (o->type == OBJ_MODULE) { RedisModuleDigest md = {{0},{0},keyobj,db->id}; moduleValue *mv = o->ptr; moduleType *mt = mv->type; moduleInitDigestContext(md); if (mt->digest) { mt->digest(&md,mv->value); xorDigest(digest,md.x,sizeof(md.x)); } } else { serverPanic("Unknown object type"); } /* If the key has an expire, add it to the mix */ if (expiretime != -1) xorDigest(digest,"!!expire!!",10); } /* Compute the dataset digest. Since keys, sets elements, hashes elements * are not ordered, we use a trick: every aggregate digest is the xor * of the digests of their elements. This way the order will not change * the result. For list instead we use a feedback entering the output digest * as input in order to ensure that a different ordered list will result in * a different digest. */ void computeDatasetDigest(unsigned char *final) { unsigned char digest[20]; dictEntry *de; int j; uint32_t aux; memset(final,0,20); /* Start with a clean result */ for (j = 0; j < server.dbnum; j++) { redisDb *db = server.db+j; if (kvstoreSize(db->keys) == 0) continue; kvstoreIterator *kvs_it = kvstoreIteratorInit(db->keys); /* hash the DB id, so the same dataset moved in a different DB will lead to a different digest */ aux = htonl(j); mixDigest(final,&aux,sizeof(aux)); /* Iterate this DB writing every entry */ while((de = kvstoreIteratorNext(kvs_it)) != NULL) { sds key; robj *keyobj, *o; memset(digest,0,20); /* This key-val digest */ key = dictGetKey(de); keyobj = createStringObject(key,sdslen(key)); mixDigest(digest,key,sdslen(key)); o = dictGetVal(de); xorObjectDigest(db,keyobj,digest,o); /* We can finally xor the key-val digest to the final digest */ xorDigest(final,digest,20); decrRefCount(keyobj); } kvstoreIteratorRelease(kvs_it); } } #ifdef USE_JEMALLOC void mallctl_int(client *c, robj **argv, int argc) { int ret; /* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */ int64_t old = 0, val; if (argc > 1) { long long ll; if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK) return; val = ll; } size_t sz = sizeof(old); while (sz > 0) { size_t zz = sz; if ((ret=je_mallctl(argv[0]->ptr, &old, &zz, argc > 1? &val: NULL, argc > 1?sz: 0))) { if (ret == EPERM && argc > 1) { /* if this option is write only, try just writing to it. */ if (!(ret=je_mallctl(argv[0]->ptr, NULL, 0, &val, sz))) { addReply(c, shared.ok); return; } } if (ret==EINVAL) { /* size might be wrong, try a smaller one */ sz /= 2; #if BYTE_ORDER == BIG_ENDIAN val <<= 8*sz; #endif continue; } addReplyErrorFormat(c,"%s", strerror(ret)); return; } else { #if BYTE_ORDER == BIG_ENDIAN old >>= 64 - 8*sz; #endif addReplyLongLong(c, old); return; } } addReplyErrorFormat(c,"%s", strerror(EINVAL)); } void mallctl_string(client *c, robj **argv, int argc) { int rret, wret; char *old; size_t sz = sizeof(old); /* for strings, it seems we need to first get the old value, before overriding it. */ if ((rret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) { /* return error unless this option is write only. */ if (!(rret == EPERM && argc > 1)) { addReplyErrorFormat(c,"%s", strerror(rret)); return; } } if(argc > 1) { char *val = argv[1]->ptr; char **valref = &val; if ((!strcmp(val,"VOID"))) valref = NULL, sz = 0; wret = je_mallctl(argv[0]->ptr, NULL, 0, valref, sz); } if (!rret) addReplyBulkCString(c, old); else if (wret) addReplyErrorFormat(c,"%s", strerror(wret)); else addReply(c, shared.ok); } #endif void debugCommand(client *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { const char *help[] = { "AOF-FLUSH-SLEEP ", " Server will sleep before flushing the AOF, this is used for testing.", "ASSERT", " Crash by assertion failed.", "CHANGE-REPL-ID", " Change the replication IDs of the instance.", " Dangerous: should be used only for testing the replication subsystem.", "CONFIG-REWRITE-FORCE-ALL", " Like CONFIG REWRITE but writes all configuration options, including", " keywords not listed in original configuration file or default values.", "CRASH-AND-RECOVER []", " Hard crash and restart after a delay (default 0).", "DIGEST", " Output a hex signature representing the current DB content.", "INTERNAL_SECRET", " Return the cluster internal secret (hashed with crc16) or error if not in cluster mode.", "DIGEST-VALUE [ ...]", " Output a hex signature of the values of all the specified keys.", "ERROR ", " Return a Redis protocol error with as message. Useful for clients", " unit tests to simulate Redis errors.", "LEAK ", " Create a memory leak of the input string.", "LOG ", " Write to the server log.", "HTSTATS [full]", " Return hash table statistics of the specified Redis database.", "HTSTATS-KEY [full]", " Like HTSTATS but for the hash table stored at 's value.", "LOADAOF", " Flush the AOF buffers on disk and reload the AOF in memory.", "REPLICATE ", " Replicates the provided string to replicas, allowing data divergence.", #ifdef USE_JEMALLOC "MALLCTL []", " Get or set a malloc tuning integer.", "MALLCTL-STR []", " Get or set a malloc tuning string.", #endif "OBJECT ", " Show low level info about `key` and associated value.", "DROP-CLUSTER-PACKET-FILTER ", " Drop all packets that match the filtered type. Set to -1 allow all packets.", "OOM", " Crash the server simulating an out-of-memory error.", "PANIC", " Crash the server simulating a panic.", "POPULATE [] []", " Create string keys named key:. If is specified then", " it is used instead of the 'key' prefix. These are not propagated to", " replicas. Cluster slots are not respected so keys not belonging to the", " current node can be created in cluster mode.", "PROTOCOL ", " Reply with a test value of the specified type. can be: string,", " integer, double, bignum, null, array, set, map, attrib, push, verbatim,", " true, false.", "RELOAD [option ...]", " Save the RDB on disk and reload it back to memory. Valid